reorganized files and methods for session actor in preparation for custom spectator implementation

This commit is contained in:
Fate-JH 2024-04-09 02:41:14 -04:00
parent 9319f7e7bd
commit cab41ac0b8
16 changed files with 1816 additions and 1722 deletions

View file

@ -1,38 +1,20 @@
// Copyright (c) 2016, 2020 PSForever
// Copyright (c) 2016, 2020, 2024 PSForever
package net.psforever.actors.session
import akka.actor.typed.receptionist.Receptionist
import akka.actor.typed.scaladsl.adapter._
import akka.actor.{Actor, MDCContextAware, typed}
import akka.actor.{Actor, Cancellable, MDCContextAware, typed}
import net.psforever.actors.session.support.NormalUser
import org.joda.time.LocalDateTime
import org.log4s.MDC
import scala.collection.mutable
//
import net.psforever.actors.net.MiddlewareActor
import net.psforever.actors.session.support.SessionData
import net.psforever.objects._
import net.psforever.objects.avatar._
import net.psforever.objects.definition._
import net.psforever.objects.guid._
import net.psforever.objects.serverobject.containable.Containable
import net.psforever.objects.serverobject.deploy.Deployment
import net.psforever.objects.serverobject.mount.Mountable
import net.psforever.objects.serverobject.terminals._
import net.psforever.objects.serverobject.CommonMessages
import net.psforever.objects.zones._
import net.psforever.packet._
import net.psforever.packet.game._
import net.psforever.services.CavernRotationService.SendCavernRotationUpdates
import net.psforever.services.ServiceManager.{Lookup, LookupResult}
import net.psforever.services.account.{PlayerToken, ReceiveAccountData}
import net.psforever.services.avatar.AvatarServiceResponse
import net.psforever.services.galaxy.GalaxyServiceResponse
import net.psforever.services.local.LocalServiceResponse
import net.psforever.services.teamwork.SquadServiceResponse
import net.psforever.services.vehicle.VehicleServiceResponse
import net.psforever.services.{CavernRotationService, ServiceManager, InterstellarClusterService => ICS}
import net.psforever.types._
import net.psforever.util.Config
import net.psforever.objects.{Default, Player}
import net.psforever.objects.avatar.Avatar
import net.psforever.objects.definition.BasicDefinition
import net.psforever.packet.PlanetSidePacket
import net.psforever.packet.game.{FriendsResponse, KeepAliveMessage}
import net.psforever.types.Vector3
import scala.collection.mutable
object SessionActor {
sealed trait Command
@ -86,28 +68,6 @@ object SessionActor {
final case object CharSaved extends Command
private[session] case object CharSavedMsg extends Command
/**
* The message that progresses some form of user-driven activity with a certain eventual outcome
* and potential feedback per cycle.
* @param delta how much the progress value changes each tick, which will be treated as a percentage;
* must be a positive value
* @param completionAction a finalizing action performed once the progress reaches 100(%)
* @param tickAction an action that is performed for each increase of progress
* @param tickTime how long between each `tickAction` (ms);
* defaults to 250 milliseconds
*/
private[session] final case class ProgressEvent(
delta: Float,
completionAction: () => Unit,
tickAction: Float => Boolean,
tickTime: Long = 250L
)
private[session] final case class AvatarAwardMessageBundle(
bundle: Iterable[Iterable[PlanetSideGamePacket]],
delay: Long
)
}
class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], connectionId: String, sessionId: Long)
@ -115,493 +75,46 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
with MDCContextAware {
MDC("connectionId") = connectionId
private var clientKeepAlive: Cancellable = Default.Cancellable
private[this] val buffer: mutable.ListBuffer[Any] = new mutable.ListBuffer[Any]()
private[this] val sessionFuncs = new SessionData(middlewareActor, context)
ServiceManager.serviceManager ! Lookup("accountIntermediary")
ServiceManager.serviceManager ! Lookup("accountPersistence")
ServiceManager.serviceManager ! Lookup("galaxy")
ServiceManager.serviceManager ! Lookup("squad")
ServiceManager.receptionist ! Receptionist.Find(ICS.InterstellarClusterServiceKey, context.self)
private[this] val logic = new NormalUser(middlewareActor, context)
override def postStop(): Unit = {
//normally, the player avatar persists a minute or so after disconnect; we are subject to the SessionReaper
//TODO put any temporary values back into the avatar
sessionFuncs.stop()
clientKeepAlive.cancel()
logic.stop()
}
def receive: Receive = startup
def startup: Receive = {
case msg if !sessionFuncs.assignEventBus(msg) =>
private def startup: Receive = {
case msg if !logic.assignEventBus(msg) =>
buffer.addOne(msg)
case _ if sessionFuncs.whenAllEventBusesLoaded() =>
case _ if logic.whenAllEventBusesLoaded() =>
context.become(inTheGame)
startHeartbeat()
buffer.foreach { self.tell(_, self) } //we forget the original sender, shouldn't be doing callbacks at this point
buffer.clear()
case _ => ()
}
def inTheGame: Receive = {
/* really common messages (very frequently, every life) */
case packet: PlanetSideGamePacket =>
handleGamePkt(packet)
case AvatarServiceResponse(toChannel, guid, reply) =>
sessionFuncs.avatarResponse.handle(toChannel, guid, reply)
case GalaxyServiceResponse(_, reply) =>
sessionFuncs.galaxyResponseHanders.handle(reply)
case LocalServiceResponse(toChannel, guid, reply) =>
sessionFuncs.localResponse.handle(toChannel, guid, reply)
case Mountable.MountMessages(tplayer, reply) =>
sessionFuncs.mountResponse.handle(tplayer, reply)
case SquadServiceResponse(_, excluded, response) =>
sessionFuncs.squad.handle(response, excluded)
case Terminal.TerminalMessage(tplayer, msg, order) =>
sessionFuncs.terminals.handle(tplayer, msg, order)
case VehicleServiceResponse(toChannel, guid, reply) =>
sessionFuncs.vehicleResponseOperations.handle(toChannel, guid, reply)
case SessionActor.PokeClient() =>
sessionFuncs.sendResponse(KeepAliveMessage())
case SessionActor.SendResponse(packet) =>
sessionFuncs.sendResponse(packet)
case SessionActor.CharSaved =>
sessionFuncs.renewCharSavedTimer(
Config.app.game.savedMsg.interruptedByAction.fixed,
Config.app.game.savedMsg.interruptedByAction.variable
)
case SessionActor.CharSavedMsg =>
sessionFuncs.displayCharSavedMsgThenRenewTimer(
Config.app.game.savedMsg.renewal.fixed,
Config.app.game.savedMsg.renewal.variable
)
/* common messages (maybe once every respawn) */
case ICS.SpawnPointResponse(response) =>
sessionFuncs.zoning.handleSpawnPointResponse(response)
case SessionActor.NewPlayerLoaded(tplayer) =>
sessionFuncs.zoning.spawn.handleNewPlayerLoaded(tplayer)
case SessionActor.PlayerLoaded(tplayer) =>
sessionFuncs.zoning.spawn.handlePlayerLoaded(tplayer)
case Zone.Population.PlayerHasLeft(zone, None) =>
log.debug(s"PlayerHasLeft: ${sessionFuncs.player.Name} does not have a body on ${zone.id}")
case Zone.Population.PlayerHasLeft(zone, Some(tplayer)) =>
if (tplayer.isAlive) {
log.info(s"${tplayer.Name} has left zone ${zone.id}")
}
case Zone.Population.PlayerCanNotSpawn(zone, tplayer) =>
log.warning(s"${tplayer.Name} can not spawn in zone ${zone.id}; why?")
case Zone.Population.PlayerAlreadySpawned(zone, tplayer) =>
log.warning(s"${tplayer.Name} is already spawned on zone ${zone.id}; is this a clerical error?")
case Zone.Vehicle.CanNotSpawn(zone, vehicle, reason) =>
log.warning(
s"${sessionFuncs.player.Name}'s ${vehicle.Definition.Name} can not spawn in ${zone.id} because $reason"
)
case Zone.Vehicle.CanNotDespawn(zone, vehicle, reason) =>
log.warning(
s"${sessionFuncs.player.Name}'s ${vehicle.Definition.Name} can not deconstruct in ${zone.id} because $reason"
)
case ICS.ZoneResponse(Some(zone)) =>
sessionFuncs.zoning.handleZoneResponse(zone)
/* uncommon messages (once a session) */
case ICS.ZonesResponse(zones) =>
sessionFuncs.zoning.handleZonesResponse(zones)
case SessionActor.SetAvatar(avatar) =>
sessionFuncs.handleSetAvatar(avatar)
case PlayerToken.LoginInfo(name, Zone.Nowhere, _) =>
sessionFuncs.zoning.spawn.handleLoginInfoNowhere(name, sender())
case PlayerToken.LoginInfo(name, inZone, optionalSavedData) =>
sessionFuncs.zoning.spawn.handleLoginInfoSomewhere(name, inZone, optionalSavedData, sender())
case PlayerToken.RestoreInfo(playerName, inZone, pos) =>
sessionFuncs.zoning.spawn.handleLoginInfoRestore(playerName, inZone, pos, sender())
case PlayerToken.CanNotLogin(playerName, reason) =>
sessionFuncs.zoning.spawn.handleLoginCanNot(playerName, reason)
case ReceiveAccountData(account) =>
sessionFuncs.handleReceiveAccountData(account)
case AvatarActor.AvatarResponse(avatar) =>
sessionFuncs.handleAvatarResponse(avatar)
case AvatarActor.AvatarLoginResponse(avatar) =>
sessionFuncs.zoning.spawn.avatarLoginResponse(avatar)
case SessionActor.SetCurrentAvatar(tplayer, max_attempts, attempt) =>
sessionFuncs.zoning.spawn.ReadyToSetCurrentAvatar(tplayer, max_attempts, attempt)
case SessionActor.SetConnectionState(state) =>
sessionFuncs.connectionState = state
case SessionActor.AvatarLoadingSync(state) =>
sessionFuncs.zoning.spawn.handleAvatarLoadingSync(state)
/* uncommon messages (utility, or once in a while) */
case SessionActor.AvatarAwardMessageBundle(pkts, delay) =>
sessionFuncs.zoning.spawn.performAvatarAwardMessageDelivery(pkts, delay)
case CommonMessages.Progress(rate, finishedAction, stepAction) =>
sessionFuncs.setupProgressChange(rate, finishedAction, stepAction)
case SessionActor.ProgressEvent(delta, finishedAction, stepAction, tick) =>
sessionFuncs.handleProgressChange(delta, finishedAction, stepAction, tick)
case CavernRotationService.CavernRotationServiceKey.Listing(listings) =>
listings.head ! SendCavernRotationUpdates(context.self)
case LookupResult("propertyOverrideManager", endpoint) =>
sessionFuncs.zoning.propertyOverrideManagerLoadOverrides(endpoint)
case SessionActor.UpdateIgnoredPlayers(msg) =>
sessionFuncs.handleUpdateIgnoredPlayers(msg)
case SessionActor.UseCooldownRenewed(definition, _) =>
sessionFuncs.handleUseCooldownRenew(definition)
case Deployment.CanDeploy(obj, state) =>
sessionFuncs.vehicles.handleCanDeploy(obj, state)
case Deployment.CanUndeploy(obj, state) =>
sessionFuncs.vehicles.handleCanUndeploy(obj, state)
case Deployment.CanNotChangeDeployment(obj, state, reason) =>
sessionFuncs.vehicles.handleCanNotChangeDeployment(obj, state, reason)
/* rare messages */
case ProximityUnit.StopAction(term, _) =>
sessionFuncs.terminals.LocalStopUsingProximityUnit(term)
case SessionActor.Suicide() =>
sessionFuncs.suicide(sessionFuncs.player)
case SessionActor.Recall() =>
sessionFuncs.zoning.handleRecall()
case SessionActor.InstantAction() =>
sessionFuncs.zoning.handleInstantAction()
case SessionActor.Quit() =>
sessionFuncs.zoning.handleQuit()
case ICS.DroppodLaunchDenial(errorCode, _) =>
sessionFuncs.zoning.handleDroppodLaunchDenial(errorCode)
case ICS.DroppodLaunchConfirmation(zone, position) =>
sessionFuncs.zoning.LoadZoneLaunchDroppod(zone, position)
case SessionActor.PlayerFailedToLoad(tplayer) =>
sessionFuncs.failWithError(s"${tplayer.Name} failed to load anywhere")
/* csr only */
case SessionActor.SetSpeed(speed) =>
sessionFuncs.handleSetSpeed(speed)
case SessionActor.SetFlying(isFlying) =>
sessionFuncs.handleSetFlying(isFlying)
case SessionActor.SetSpectator(isSpectator) =>
sessionFuncs.handleSetSpectator(isSpectator)
case SessionActor.Kick(player, time) =>
sessionFuncs.handleKick(player, time)
case SessionActor.SetZone(zoneId, position) =>
sessionFuncs.zoning.handleSetZone(zoneId, position)
case SessionActor.SetPosition(position) =>
sessionFuncs.zoning.spawn.handleSetPosition(position)
case SessionActor.SetSilenced(silenced) =>
sessionFuncs.handleSilenced(silenced)
/* catch these messages */
case _: ProximityUnit.Action => ;
case _: Zone.Vehicle.HasSpawned => ;
case _: Zone.Vehicle.HasDespawned => ;
case Zone.Deployable.IsDismissed(obj: TurretDeployable) => //only if target deployable was never fully introduced
TaskWorkflow.execute(GUIDTask.unregisterDeployableTurret(sessionFuncs.continent.GUID, obj))
case Zone.Deployable.IsDismissed(obj) => //only if target deployable was never fully introduced
TaskWorkflow.execute(GUIDTask.unregisterObject(sessionFuncs.continent.GUID, obj))
case msg: Containable.ItemPutInSlot =>
log.debug(s"ItemPutInSlot: $msg")
case msg: Containable.CanNotPutItemInSlot =>
log.debug(s"CanNotPutItemInSlot: $msg")
case default =>
log.warning(s"Invalid packet class received: $default from ${sender()}")
private def startHeartbeat(): Unit = {
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global
clientKeepAlive.cancel()
clientKeepAlive = context.system.scheduler.scheduleWithFixedDelay(
initialDelay = 0.seconds,
delay = 500.milliseconds,
context.self,
SessionActor.PokeClient()
)
}
private def handleGamePkt: PlanetSideGamePacket => Unit = {
case packet: ConnectToWorldRequestMessage =>
sessionFuncs.handleConnectToWorldRequest(packet)
private def inTheGame: Receive = {
/* used for the game's heartbeat*/
case SessionActor.PokeClient() =>
middlewareActor ! MiddlewareActor.Send(KeepAliveMessage())
case packet: MountVehicleCargoMsg =>
sessionFuncs.vehicles.handleMountVehicleCargo(packet)
case packet: DismountVehicleCargoMsg =>
sessionFuncs.vehicles.handleDismountVehicleCargo(packet)
case packet: CharacterCreateRequestMessage =>
sessionFuncs.handleCharacterCreateRequest(packet)
case packet: CharacterRequestMessage =>
sessionFuncs.handleCharacterRequest(packet)
case _: KeepAliveMessage =>
sessionFuncs.keepAliveFunc()
case packet: BeginZoningMessage =>
sessionFuncs.zoning.handleBeginZoning(packet)
case packet: PlayerStateMessageUpstream =>
sessionFuncs.handlePlayerStateUpstream(packet)
case packet: ChildObjectStateMessage =>
sessionFuncs.vehicles.handleChildObjectState(packet)
case packet: VehicleStateMessage =>
sessionFuncs.vehicles.handleVehicleState(packet)
case packet: VehicleSubStateMessage =>
sessionFuncs.vehicles.handleVehicleSubState(packet)
case packet: FrameVehicleStateMessage =>
sessionFuncs.vehicles.handleFrameVehicleState(packet)
case packet: ProjectileStateMessage =>
sessionFuncs.shooting.handleProjectileState(packet)
case packet: LongRangeProjectileInfoMessage =>
sessionFuncs.shooting.handleLongRangeProjectileState(packet)
case packet: ReleaseAvatarRequestMessage =>
sessionFuncs.zoning.spawn.handleReleaseAvatarRequest(packet)
case packet: SpawnRequestMessage =>
sessionFuncs.zoning.spawn.handleSpawnRequest(packet)
case packet: ChatMsg =>
sessionFuncs.handleChat(packet)
case packet: SetChatFilterMessage =>
sessionFuncs.handleChatFilter(packet)
case packet: VoiceHostRequest =>
sessionFuncs.handleVoiceHostRequest(packet)
case packet: VoiceHostInfo =>
sessionFuncs.handleVoiceHostInfo(packet)
case packet: ChangeAmmoMessage =>
sessionFuncs.shooting.handleChangeAmmo(packet)
case packet: ChangeFireModeMessage =>
sessionFuncs.shooting.handleChangeFireMode(packet)
case packet: ChangeFireStateMessage_Start =>
sessionFuncs.shooting.handleChangeFireStateStart(packet)
case packet: ChangeFireStateMessage_Stop =>
sessionFuncs.shooting.handleChangeFireStateStop(packet)
case packet: EmoteMsg =>
sessionFuncs.handleEmote(packet)
case packet: DropItemMessage =>
sessionFuncs.handleDropItem(packet)
case packet: PickupItemMessage =>
sessionFuncs.handlePickupItem(packet)
case packet: ReloadMessage =>
sessionFuncs.shooting.handleReload(packet)
case packet: ObjectHeldMessage =>
sessionFuncs.handleObjectHeld(packet)
case packet: AvatarJumpMessage =>
sessionFuncs.handleAvatarJump(packet)
case packet: ZipLineMessage =>
sessionFuncs.handleZipLine(packet)
case packet: RequestDestroyMessage =>
sessionFuncs.handleRequestDestroy(packet)
case packet: MoveItemMessage =>
sessionFuncs.handleMoveItem(packet)
case packet: LootItemMessage =>
sessionFuncs.handleLootItem(packet)
case packet: AvatarImplantMessage =>
sessionFuncs.handleAvatarImplant(packet)
case packet: UseItemMessage =>
sessionFuncs.handleUseItem(packet)
case packet: UnuseItemMessage =>
sessionFuncs.handleUnuseItem(packet)
case packet: ProximityTerminalUseMessage =>
sessionFuncs.terminals.handleProximityTerminalUse(packet)
case packet: DeployObjectMessage =>
sessionFuncs.handleDeployObject(packet)
case packet: GenericObjectActionMessage =>
sessionFuncs.handleGenericObjectAction(packet)
case packet: GenericObjectActionAtPositionMessage =>
sessionFuncs.handleGenericObjectActionAtPosition(packet)
case packet: GenericObjectStateMsg =>
sessionFuncs.handleGenericObjectState(packet)
case packet: GenericActionMessage =>
sessionFuncs.handleGenericAction(packet)
case packet: ItemTransactionMessage =>
sessionFuncs.terminals.handleItemTransaction(packet)
case packet: FavoritesRequest =>
sessionFuncs.handleFavoritesRequest(packet)
case packet: WeaponDelayFireMessage =>
sessionFuncs.shooting.handleWeaponDelayFire(packet)
case packet: WeaponDryFireMessage =>
sessionFuncs.shooting.handleWeaponDryFire(packet)
case packet: WeaponFireMessage =>
sessionFuncs.shooting.handleWeaponFire(packet)
case packet: WeaponLazeTargetPositionMessage =>
sessionFuncs.shooting.handleWeaponLazeTargetPosition(packet)
case packet: HitMessage =>
sessionFuncs.shooting.handleDirectHit(packet)
case packet: SplashHitMessage =>
sessionFuncs.shooting.handleSplashHit(packet)
case packet: LashMessage =>
sessionFuncs.shooting.handleLashHit(packet)
case packet: AIDamage =>
sessionFuncs.shooting.handleAIDamage(packet)
case packet: AvatarFirstTimeEventMessage =>
sessionFuncs.handleAvatarFirstTimeEvent(packet)
case packet: WarpgateRequest =>
sessionFuncs.zoning.handleWarpgateRequest(packet)
case packet: MountVehicleMsg =>
sessionFuncs.vehicles.handleMountVehicle(packet)
case packet: DismountVehicleMsg =>
sessionFuncs.vehicles.handleDismountVehicle(packet)
case packet: DeployRequestMessage =>
sessionFuncs.vehicles.handleDeployRequest(packet)
case packet: AvatarGrenadeStateMessage =>
sessionFuncs.shooting.handleAvatarGrenadeState(packet)
case packet: SquadDefinitionActionMessage =>
sessionFuncs.squad.handleSquadDefinitionAction(packet)
case packet: SquadMembershipRequest =>
sessionFuncs.squad.handleSquadMemberRequest(packet)
case packet: SquadWaypointRequest =>
sessionFuncs.squad.handleSquadWaypointRequest(packet)
case packet: GenericCollisionMsg =>
sessionFuncs.handleGenericCollision(packet)
case packet: BugReportMessage =>
sessionFuncs.handleBugReport(packet)
case packet: BindPlayerMessage =>
sessionFuncs.handleBindPlayer(packet)
case packet: PlanetsideAttributeMessage =>
sessionFuncs.handlePlanetsideAttribute(packet)
case packet: FacilityBenefitShieldChargeRequestMessage =>
sessionFuncs.handleFacilityBenefitShieldChargeRequest(packet)
case packet: BattleplanMessage =>
sessionFuncs.handleBattleplan(packet)
case packet: CreateShortcutMessage =>
sessionFuncs.handleCreateShortcut(packet)
case packet: ChangeShortcutBankMessage =>
sessionFuncs.handleChangeShortcutBank(packet)
case packet: FriendsRequest =>
sessionFuncs.handleFriendRequest(packet)
case packet: DroppodLaunchRequestMessage =>
sessionFuncs.zoning.handleDroppodLaunchRequest(packet)
case packet: InvalidTerrainMessage =>
sessionFuncs.handleInvalidTerrain(packet)
case packet: ActionCancelMessage =>
sessionFuncs.handleActionCancel(packet)
case packet: TradeMessage =>
sessionFuncs.handleTrade(packet)
case packet: DisplayedAwardMessage =>
sessionFuncs.handleDisplayedAward(packet)
case packet: ObjectDetectedMessage =>
sessionFuncs.handleObjectDetected(packet)
case packet: TargetingImplantRequest =>
sessionFuncs.handleTargetingImplantRequest(packet)
case packet: HitHint =>
sessionFuncs.handleHitHint(packet)
case _: OutfitRequest => ()
case pkt =>
log.warning(s"Unhandled GamePacket $pkt")
case packet =>
logic.parse(sender())(packet)
}
}

View file

@ -23,23 +23,25 @@ trait CommonSessionInterfacingFunctionality {
protected def context: ActorContext
protected def sessionData: SessionData
protected def sessionLogic: SessionLogic
protected def session: Session = sessionData.session
protected def session: Session = sessionLogic.session
protected def session_=(newsession: Session): Unit = sessionData.session_=(newsession)
protected def session_=(newsession: Session): Unit = sessionLogic.session_=(newsession)
protected def account: Account = sessionData.account
protected def account: Account = sessionLogic.account
protected def continent: Zone = sessionData.continent
protected def continent: Zone = sessionLogic.continent
protected def player: Player = sessionData.player
protected def player: Player = sessionLogic.player
protected def avatar: Avatar = sessionData.avatar
protected def avatar: Avatar = sessionLogic.avatar
protected def log: Logger = sessionData.log
protected def log: Logger = sessionLogic.log
protected def sendResponse(pkt: PlanetSideGamePacket): Unit = sessionData.sendResponse(pkt)
protected def sendResponse(pkt: PlanetSideGamePacket): Unit = sessionLogic.sendResponse(pkt)
protected[session] def stop(): Unit = { /* to override */ }
protected[support] def actionsToCancel(): Unit = { /* to override */ }
protected[support] def stop(): Unit = { /* to override */ }
}

View file

@ -0,0 +1,496 @@
// Copyright (c) 2024 PSForever
package net.psforever.actors.session.support
import akka.actor.Actor.Receive
import akka.actor.{ActorContext, ActorRef, typed}
import net.psforever.actors.net.MiddlewareActor
import net.psforever.actors.session.{AvatarActor, SessionActor}
import net.psforever.objects.TurretDeployable
import net.psforever.objects.guid.{GUIDTask, TaskWorkflow}
import net.psforever.objects.serverobject.CommonMessages
import net.psforever.objects.serverobject.containable.Containable
import net.psforever.objects.serverobject.deploy.Deployment
import net.psforever.objects.serverobject.mount.Mountable
import net.psforever.objects.serverobject.terminals.{ProximityUnit, Terminal}
import net.psforever.objects.zones.Zone
import net.psforever.packet.PlanetSideGamePacket
import net.psforever.packet.game.{AIDamage, ActionCancelMessage, AvatarFirstTimeEventMessage, AvatarGrenadeStateMessage, AvatarImplantMessage, AvatarJumpMessage, BattleplanMessage, BeginZoningMessage, BindPlayerMessage, BugReportMessage, ChangeAmmoMessage, ChangeFireModeMessage, ChangeFireStateMessage_Start, ChangeFireStateMessage_Stop, ChangeShortcutBankMessage, CharacterCreateRequestMessage, CharacterRequestMessage, ChatMsg, ChildObjectStateMessage, ConnectToWorldRequestMessage, CreateShortcutMessage, DeployObjectMessage, DeployRequestMessage, DismountVehicleCargoMsg, DismountVehicleMsg, DisplayedAwardMessage, DropItemMessage, DroppodLaunchRequestMessage, EmoteMsg, FacilityBenefitShieldChargeRequestMessage, FavoritesRequest, FrameVehicleStateMessage, FriendsRequest, GenericActionMessage, GenericCollisionMsg, GenericObjectActionAtPositionMessage, GenericObjectActionMessage, GenericObjectStateMsg, HitHint, HitMessage, InvalidTerrainMessage, ItemTransactionMessage, KeepAliveMessage, LashMessage, LongRangeProjectileInfoMessage, LootItemMessage, MountVehicleCargoMsg, MountVehicleMsg, MoveItemMessage, ObjectDetectedMessage, ObjectHeldMessage, OutfitRequest, PickupItemMessage, PlanetsideAttributeMessage, PlayerStateMessageUpstream, ProjectileStateMessage, ProximityTerminalUseMessage, ReleaseAvatarRequestMessage, ReloadMessage, RequestDestroyMessage, SetChatFilterMessage, SpawnRequestMessage, SplashHitMessage, SquadDefinitionActionMessage, SquadMembershipRequest, SquadWaypointRequest, TargetingImplantRequest, TradeMessage, UnuseItemMessage, UseItemMessage, VehicleStateMessage, VehicleSubStateMessage, VoiceHostInfo, VoiceHostRequest, WarpgateRequest, WeaponDelayFireMessage, WeaponDryFireMessage, WeaponFireMessage, WeaponLazeTargetPositionMessage, ZipLineMessage}
import net.psforever.services.{InterstellarClusterService => ICS}
import net.psforever.services.CavernRotationService
import net.psforever.services.CavernRotationService.SendCavernRotationUpdates
import net.psforever.services.ServiceManager.LookupResult
import net.psforever.services.account.{PlayerToken, ReceiveAccountData}
import net.psforever.services.avatar.AvatarServiceResponse
import net.psforever.services.galaxy.GalaxyServiceResponse
import net.psforever.services.local.LocalServiceResponse
import net.psforever.services.teamwork.SquadServiceResponse
import net.psforever.services.vehicle.VehicleServiceResponse
import net.psforever.util.Config
class NormalUser(
val middlewareActor: typed.ActorRef[MiddlewareActor.Command],
implicit val context: ActorContext
) extends SessionLogic {
def parse(sender: ActorRef): Receive = {
/* really common messages (very frequently, every life) */
case packet: PlanetSideGamePacket =>
handleGamePkt(packet)
case AvatarServiceResponse(toChannel, guid, reply) =>
avatarResponse.handle(toChannel, guid, reply)
case GalaxyServiceResponse(_, reply) =>
galaxyResponseHandlers.handle(reply)
case LocalServiceResponse(toChannel, guid, reply) =>
localResponse.handle(toChannel, guid, reply)
case Mountable.MountMessages(tplayer, reply) =>
mountResponse.handle(tplayer, reply)
case SquadServiceResponse(_, excluded, response) =>
squad.handle(response, excluded)
case Terminal.TerminalMessage(tplayer, msg, order) =>
terminals.handle(tplayer, msg, order)
case VehicleServiceResponse(toChannel, guid, reply) =>
vehicleResponseOperations.handle(toChannel, guid, reply)
case SessionActor.PokeClient() =>
sendResponse(KeepAliveMessage())
case SessionActor.SendResponse(packet) =>
sendResponse(packet)
case SessionActor.CharSaved =>
general.renewCharSavedTimer(
Config.app.game.savedMsg.interruptedByAction.fixed,
Config.app.game.savedMsg.interruptedByAction.variable
)
case SessionActor.CharSavedMsg =>
general.displayCharSavedMsgThenRenewTimer(
Config.app.game.savedMsg.renewal.fixed,
Config.app.game.savedMsg.renewal.variable
)
/* common messages (maybe once every respawn) */
case ICS.SpawnPointResponse(response) =>
zoning.handleSpawnPointResponse(response)
case SessionActor.NewPlayerLoaded(tplayer) =>
zoning.spawn.handleNewPlayerLoaded(tplayer)
case SessionActor.PlayerLoaded(tplayer) =>
zoning.spawn.handlePlayerLoaded(tplayer)
case Zone.Population.PlayerHasLeft(zone, None) =>
log.debug(s"PlayerHasLeft: ${player.Name} does not have a body on ${zone.id}")
case Zone.Population.PlayerHasLeft(zone, Some(tplayer)) =>
if (tplayer.isAlive) {
log.info(s"${tplayer.Name} has left zone ${zone.id}")
}
case Zone.Population.PlayerCanNotSpawn(zone, tplayer) =>
log.warn(s"${tplayer.Name} can not spawn in zone ${zone.id}; why?")
case Zone.Population.PlayerAlreadySpawned(zone, tplayer) =>
log.warn(s"${tplayer.Name} is already spawned on zone ${zone.id}; is this a clerical error?")
case Zone.Vehicle.CanNotSpawn(zone, vehicle, reason) =>
log.warn(
s"${player.Name}'s ${vehicle.Definition.Name} can not spawn in ${zone.id} because $reason"
)
case Zone.Vehicle.CanNotDespawn(zone, vehicle, reason) =>
log.warn(
s"${player.Name}'s ${vehicle.Definition.Name} can not deconstruct in ${zone.id} because $reason"
)
case ICS.ZoneResponse(Some(zone)) =>
zoning.handleZoneResponse(zone)
/* uncommon messages (once a session) */
case ICS.ZonesResponse(zones) =>
zoning.handleZonesResponse(zones)
case SessionActor.SetAvatar(avatar) =>
general.handleSetAvatar(avatar)
case PlayerToken.LoginInfo(name, Zone.Nowhere, _) =>
zoning.spawn.handleLoginInfoNowhere(name, sender)
case PlayerToken.LoginInfo(name, inZone, optionalSavedData) =>
zoning.spawn.handleLoginInfoSomewhere(name, inZone, optionalSavedData, sender)
case PlayerToken.RestoreInfo(playerName, inZone, pos) =>
zoning.spawn.handleLoginInfoRestore(playerName, inZone, pos, sender)
case PlayerToken.CanNotLogin(playerName, reason) =>
zoning.spawn.handleLoginCanNot(playerName, reason)
case ReceiveAccountData(account) =>
general.handleReceiveAccountData(account)
case AvatarActor.AvatarResponse(avatar) =>
general.handleAvatarResponse(avatar)
case AvatarActor.AvatarLoginResponse(avatar) =>
zoning.spawn.avatarLoginResponse(avatar)
case SessionActor.SetCurrentAvatar(tplayer, max_attempts, attempt) =>
zoning.spawn.ReadyToSetCurrentAvatar(tplayer, max_attempts, attempt)
case SessionActor.SetConnectionState(state) =>
connectionState = state
case SessionActor.AvatarLoadingSync(state) =>
zoning.spawn.handleAvatarLoadingSync(state)
/* uncommon messages (utility, or once in a while) */
case ZoningOperations.AvatarAwardMessageBundle(pkts, delay) =>
zoning.spawn.performAvatarAwardMessageDelivery(pkts, delay)
case CommonMessages.ProgressEvent(delta, finishedAction, stepAction, tick) =>
general.handleProgressChange(delta, finishedAction, stepAction, tick)
case CommonMessages.Progress(rate, finishedAction, stepAction) =>
general.setupProgressChange(rate, finishedAction, stepAction)
case CavernRotationService.CavernRotationServiceKey.Listing(listings) =>
listings.head ! SendCavernRotationUpdates(context.self)
case LookupResult("propertyOverrideManager", endpoint) =>
zoning.propertyOverrideManagerLoadOverrides(endpoint)
case SessionActor.UpdateIgnoredPlayers(msg) =>
galaxyResponseHandlers.handleUpdateIgnoredPlayers(msg)
case SessionActor.UseCooldownRenewed(definition, _) =>
general.handleUseCooldownRenew(definition)
case Deployment.CanDeploy(obj, state) =>
vehicles.handleCanDeploy(obj, state)
case Deployment.CanUndeploy(obj, state) =>
vehicles.handleCanUndeploy(obj, state)
case Deployment.CanNotChangeDeployment(obj, state, reason) =>
vehicles.handleCanNotChangeDeployment(obj, state, reason)
/* rare messages */
case ProximityUnit.StopAction(term, _) =>
terminals.LocalStopUsingProximityUnit(term)
case SessionActor.Suicide() =>
general.suicide(player)
case SessionActor.Recall() =>
zoning.handleRecall()
case SessionActor.InstantAction() =>
zoning.handleInstantAction()
case SessionActor.Quit() =>
zoning.handleQuit()
case ICS.DroppodLaunchDenial(errorCode, _) =>
zoning.handleDroppodLaunchDenial(errorCode)
case ICS.DroppodLaunchConfirmation(zone, position) =>
zoning.LoadZoneLaunchDroppod(zone, position)
case SessionActor.PlayerFailedToLoad(tplayer) =>
failWithError(s"${tplayer.Name} failed to load anywhere")
/* csr only */
case SessionActor.SetSpeed(speed) =>
general.handleSetSpeed(speed)
case SessionActor.SetFlying(isFlying) =>
general.handleSetFlying(isFlying)
case SessionActor.SetSpectator(isSpectator) =>
general.handleSetSpectator(isSpectator)
case SessionActor.Kick(player, time) =>
general.handleKick(player, time)
case SessionActor.SetZone(zoneId, position) =>
zoning.handleSetZone(zoneId, position)
case SessionActor.SetPosition(position) =>
zoning.spawn.handleSetPosition(position)
case SessionActor.SetSilenced(silenced) =>
general.handleSilenced(silenced)
/* catch these messages */
case _: ProximityUnit.Action => ;
case _: Zone.Vehicle.HasSpawned => ;
case _: Zone.Vehicle.HasDespawned => ;
case Zone.Deployable.IsDismissed(obj: TurretDeployable) => //only if target deployable was never fully introduced
TaskWorkflow.execute(GUIDTask.unregisterDeployableTurret(continent.GUID, obj))
case Zone.Deployable.IsDismissed(obj) => //only if target deployable was never fully introduced
TaskWorkflow.execute(GUIDTask.unregisterObject(continent.GUID, obj))
case msg: Containable.ItemPutInSlot =>
log.debug(s"ItemPutInSlot: $msg")
case msg: Containable.CanNotPutItemInSlot =>
log.debug(s"CanNotPutItemInSlot: $msg")
case default =>
log.warn(s"Invalid packet class received: $default from $sender")
}
private def handleGamePkt: PlanetSideGamePacket => Unit = {
case packet: ConnectToWorldRequestMessage =>
general.handleConnectToWorldRequest(packet)
case packet: MountVehicleCargoMsg =>
vehicles.handleMountVehicleCargo(packet)
case packet: DismountVehicleCargoMsg =>
vehicles.handleDismountVehicleCargo(packet)
case packet: CharacterCreateRequestMessage =>
general.handleCharacterCreateRequest(packet)
case packet: CharacterRequestMessage =>
general.handleCharacterRequest(packet)
case _: KeepAliveMessage =>
keepAliveFunc()
case packet: BeginZoningMessage =>
zoning.handleBeginZoning(packet)
case packet: PlayerStateMessageUpstream =>
general.handlePlayerStateUpstream(packet)
case packet: ChildObjectStateMessage =>
vehicles.handleChildObjectState(packet)
case packet: VehicleStateMessage =>
vehicles.handleVehicleState(packet)
case packet: VehicleSubStateMessage =>
vehicles.handleVehicleSubState(packet)
case packet: FrameVehicleStateMessage =>
vehicles.handleFrameVehicleState(packet)
case packet: ProjectileStateMessage =>
shooting.handleProjectileState(packet)
case packet: LongRangeProjectileInfoMessage =>
shooting.handleLongRangeProjectileState(packet)
case packet: ReleaseAvatarRequestMessage =>
zoning.spawn.handleReleaseAvatarRequest(packet)
case packet: SpawnRequestMessage =>
zoning.spawn.handleSpawnRequest(packet)
case packet: ChatMsg =>
general.handleChat(packet)
case packet: SetChatFilterMessage =>
general.handleChatFilter(packet)
case packet: VoiceHostRequest =>
general.handleVoiceHostRequest(packet)
case packet: VoiceHostInfo =>
general.handleVoiceHostInfo(packet)
case packet: ChangeAmmoMessage =>
shooting.handleChangeAmmo(packet)
case packet: ChangeFireModeMessage =>
shooting.handleChangeFireMode(packet)
case packet: ChangeFireStateMessage_Start =>
shooting.handleChangeFireStateStart(packet)
case packet: ChangeFireStateMessage_Stop =>
shooting.handleChangeFireStateStop(packet)
case packet: EmoteMsg =>
general.handleEmote(packet)
case packet: DropItemMessage =>
general.handleDropItem(packet)
case packet: PickupItemMessage =>
general.handlePickupItem(packet)
case packet: ReloadMessage =>
shooting.handleReload(packet)
case packet: ObjectHeldMessage =>
general.handleObjectHeld(packet)
case packet: AvatarJumpMessage =>
general.handleAvatarJump(packet)
case packet: ZipLineMessage =>
general.handleZipLine(packet)
case packet: RequestDestroyMessage =>
general.handleRequestDestroy(packet)
case packet: MoveItemMessage =>
general.handleMoveItem(packet)
case packet: LootItemMessage =>
general.handleLootItem(packet)
case packet: AvatarImplantMessage =>
general.handleAvatarImplant(packet)
case packet: UseItemMessage =>
general.handleUseItem(packet)
case packet: UnuseItemMessage =>
general.handleUnuseItem(packet)
case packet: ProximityTerminalUseMessage =>
terminals.handleProximityTerminalUse(packet)
case packet: DeployObjectMessage =>
general.handleDeployObject(packet)
case packet: GenericObjectActionMessage =>
general.handleGenericObjectAction(packet)
case packet: GenericObjectActionAtPositionMessage =>
general.handleGenericObjectActionAtPosition(packet)
case packet: GenericObjectStateMsg =>
general.handleGenericObjectState(packet)
case packet: GenericActionMessage =>
general.handleGenericAction(packet)
case packet: ItemTransactionMessage =>
terminals.handleItemTransaction(packet)
case packet: FavoritesRequest =>
general.handleFavoritesRequest(packet)
case packet: WeaponDelayFireMessage =>
shooting.handleWeaponDelayFire(packet)
case packet: WeaponDryFireMessage =>
shooting.handleWeaponDryFire(packet)
case packet: WeaponFireMessage =>
shooting.handleWeaponFire(packet)
case packet: WeaponLazeTargetPositionMessage =>
shooting.handleWeaponLazeTargetPosition(packet)
case packet: HitMessage =>
shooting.handleDirectHit(packet)
case packet: SplashHitMessage =>
shooting.handleSplashHit(packet)
case packet: LashMessage =>
shooting.handleLashHit(packet)
case packet: AIDamage =>
shooting.handleAIDamage(packet)
case packet: AvatarFirstTimeEventMessage =>
general.handleAvatarFirstTimeEvent(packet)
case packet: WarpgateRequest =>
zoning.handleWarpgateRequest(packet)
case packet: MountVehicleMsg =>
vehicles.handleMountVehicle(packet)
case packet: DismountVehicleMsg =>
vehicles.handleDismountVehicle(packet)
case packet: DeployRequestMessage =>
vehicles.handleDeployRequest(packet)
case packet: AvatarGrenadeStateMessage =>
shooting.handleAvatarGrenadeState(packet)
case packet: SquadDefinitionActionMessage =>
squad.handleSquadDefinitionAction(packet)
case packet: SquadMembershipRequest =>
squad.handleSquadMemberRequest(packet)
case packet: SquadWaypointRequest =>
squad.handleSquadWaypointRequest(packet)
case packet: GenericCollisionMsg =>
general.handleGenericCollision(packet)
case packet: BugReportMessage =>
general.handleBugReport(packet)
case packet: BindPlayerMessage =>
general.handleBindPlayer(packet)
case packet: PlanetsideAttributeMessage =>
general.handlePlanetsideAttribute(packet)
case packet: FacilityBenefitShieldChargeRequestMessage =>
general.handleFacilityBenefitShieldChargeRequest(packet)
case packet: BattleplanMessage =>
general.handleBattleplan(packet)
case packet: CreateShortcutMessage =>
general.handleCreateShortcut(packet)
case packet: ChangeShortcutBankMessage =>
general.handleChangeShortcutBank(packet)
case packet: FriendsRequest =>
general.handleFriendRequest(packet)
case packet: DroppodLaunchRequestMessage =>
zoning.handleDroppodLaunchRequest(packet)
case packet: InvalidTerrainMessage =>
general.handleInvalidTerrain(packet)
case packet: ActionCancelMessage =>
general.handleActionCancel(packet)
case packet: TradeMessage =>
general.handleTrade(packet)
case packet: DisplayedAwardMessage =>
general.handleDisplayedAward(packet)
case packet: ObjectDetectedMessage =>
general.handleObjectDetected(packet)
case packet: TargetingImplantRequest =>
general.handleTargetingImplantRequest(packet)
case packet: HitHint =>
general.handleHitHint(packet)
case _: OutfitRequest => ()
case pkt =>
log.warn(s"Unhandled GamePacket $pkt")
}
}

View file

@ -3,6 +3,7 @@ package net.psforever.actors.session.support
import akka.actor.typed.scaladsl.adapter._
import akka.actor.{ActorContext, typed}
import net.psforever.objects.sourcing.{PlayerSource, SourceEntry}
import net.psforever.packet.game.objectcreate.ConstructorData
import net.psforever.services.Service
import net.psforever.objects.zones.exp
@ -29,7 +30,7 @@ import net.psforever.util.Config
import net.psforever.zones.Zones
class SessionAvatarHandlers(
val sessionData: SessionData,
val sessionLogic: SessionLogic,
avatarActor: typed.ActorRef[AvatarActor.Command],
chatActor: typed.ActorRef[ChatActor.Command],
implicit val context: ActorContext
@ -86,7 +87,7 @@ class SessionAvatarHandlers(
val inDrawableRange = currentDistance <= maxRange
val now = System.currentTimeMillis() //ms
if (
sessionData.zoning.zoningStatus != Zoning.Status.Deconstructing &&
sessionLogic.zoning.zoningStatus != Zoning.Status.Deconstructing &&
!isNotRendered && inDrawableRange
) {
//conditions where visibility is assured
@ -95,7 +96,7 @@ class SessionAvatarHandlers(
lazy val targetDelay = {
val populationOver = math.max(
0,
sessionData.localSector.livePlayerList.size - drawConfig.populationThreshold
sessionLogic.localSector.livePlayerList.size - drawConfig.populationThreshold
)
val distanceAdjustment = math.pow(populationOver / drawConfig.populationStep * drawConfig.rangeStep, 2) //sq.m
val adjustedDistance = currentDistance + distanceAdjustment //sq.m
@ -110,7 +111,7 @@ class SessionAvatarHandlers(
(!lastMsg.contains(pstateToSave) &&
(canSeeReallyFar ||
currentDistance < drawConfig.rangeMin * drawConfig.rangeMin ||
sessionData.canSeeReallyFar ||
sessionLogic.general.canSeeReallyFar ||
durationSince > targetDelay
)
)
@ -164,11 +165,11 @@ class SessionAvatarHandlers(
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)
continent.GUID(sessionLogic.terminals.usingMedicalTerminal).collect {
case term: Terminal with ProximityUnit => sessionLogic.terminals.StopUsingProximityUnit(term)
}
if (sessionData.zoning.zoningStatus == Zoning.Status.Deconstructing) {
sessionData.stopDeconstructing()
if (sessionLogic.zoning.zoningStatus == Zoning.Status.Deconstructing) {
sessionLogic.zoning.spawn.stopDeconstructing()
}
case AvatarResponse.ObjectHeld(slot, _)
@ -221,32 +222,28 @@ class SessionAvatarHandlers(
case AvatarResponse.HitHint(sourceGuid) if player.isAlive =>
sendResponse(HitHint(sourceGuid, guid))
sessionData.zoning.CancelZoningProcessWithDescriptiveReason("cancel_dmg")
case AvatarResponse.DestroyDisplay(killer, victim, method, unk)
if killer.CharId == avatar.id && killer.Faction != victim.Faction =>
sendResponse(sessionData.destroyDisplayMessage(killer, victim, method, unk))
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_dmg")
case AvatarResponse.Destroy(victim, killer, weapon, pos) =>
// guid = victim // killer = killer
sendResponse(DestroyMessage(victim, killer, weapon, pos))
case AvatarResponse.DestroyDisplay(killer, victim, method, unk) =>
sendResponse(sessionData.destroyDisplayMessage(killer, victim, method, unk))
sendResponse(destroyDisplayMessage(killer, victim, method, unk))
case AvatarResponse.TerminalOrderResult(terminalGuid, action, result)
if result && (action == TransactionType.Buy || action == TransactionType.Loadout) =>
sendResponse(ItemTransactionResultMessage(terminalGuid, action, result))
sessionData.terminals.lastTerminalOrderFulfillment = true
sessionLogic.terminals.lastTerminalOrderFulfillment = true
AvatarActor.savePlayerData(player)
sessionData.renewCharSavedTimer(
sessionLogic.general.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
sessionLogic.terminals.lastTerminalOrderFulfillment = true
case AvatarResponse.ChangeExosuit(
target,
@ -358,7 +355,7 @@ class SessionAvatarHandlers(
slot = 0
))
}
sessionData.applyPurchaseTimersBeforePackingLoadout(player, player, holsters ++ inventory)
sessionLogic.general.applyPurchaseTimersBeforePackingLoadout(player, player, holsters ++ inventory)
DropLeftovers(player)(drops)
case AvatarResponse.ChangeLoadout(target, armor, exosuit, subtype, slot, _, oldHolsters, _, _, _, _) =>
@ -389,10 +386,10 @@ class SessionAvatarHandlers(
sendResponse(ObjectDeleteMessage(kguid, unk1=0))
case AvatarResponse.KitNotUsed(_, "") =>
sessionData.kitToBeUsed = None
sessionLogic.general.kitToBeUsed = None
case AvatarResponse.KitNotUsed(_, msg) =>
sessionData.kitToBeUsed = None
sessionLogic.general.kitToBeUsed = None
sendResponse(ChatMsg(ChatMessageType.UNK_225, msg))
case AvatarResponse.UpdateKillsDeathsAssists(_, kda) =>
@ -439,40 +436,40 @@ class SessionAvatarHandlers(
adversarial.map {_.Name }.orElse { Some(s"a ${reason.getClass.getSimpleName}") }
}.getOrElse { s"an unfortunate circumstance (probably ${player.Sex.pronounObject} own fault)" }
log.info(s"${player.Name} has died, killed by $cause")
if (sessionData.shooting.shotsWhileDead > 0) {
if (sessionLogic.shooting.shotsWhileDead > 0) {
log.warn(
s"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 ${sessionLogic.shooting.shotsWhileDead} rounds while character was dead on server"
)
sessionData.shooting.shotsWhileDead = 0
sessionLogic.shooting.shotsWhileDead = 0
}
sessionData.zoning.CancelZoningProcessWithDescriptiveReason(msg = "cancel")
sessionData.renewCharSavedTimer(fixedLen = 1800L, varLen = 0L)
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason(msg = "cancel")
sessionLogic.general.renewCharSavedTimer(fixedLen = 1800L, varLen = 0L)
//player state changes
AvatarActor.updateToolDischargeFor(avatar)
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
sessionLogic.general.dropSpecialSlotItem()
sessionLogic.general.toggleMaxSpecialState(enable = false)
sessionLogic.keepAliveFunc = sessionLogic.zoning.NormalKeepAlive
sessionLogic.zoning.zoningStatus = Zoning.Status.None
sessionLogic.zoning.spawn.deadState = DeadState.Dead
continent.GUID(mount).collect { case obj: Vehicle =>
sessionData.vehicles.ConditionalDriverVehicleControl(obj)
sessionData.unaccessContainer(obj)
sessionLogic.vehicles.ConditionalDriverVehicleControl(obj)
sessionLogic.general.unaccessContainer(obj)
}
sessionData.playerActionsToCancel()
sessionData.terminals.CancelAllProximityUnits()
sessionLogic.actionsToCancel()
sessionLogic.terminals.CancelAllProximityUnits()
AvatarActor.savePlayerLocation(player)
sessionData.zoning.spawn.shiftPosition = Some(player.Position)
sessionLogic.zoning.spawn.shiftPosition = Some(player.Position)
//respawn
val respawnTimer = 300.seconds
sessionData.zoning.spawn.reviveTimer.cancel()
sessionLogic.zoning.spawn.reviveTimer.cancel()
if (player.death_by == 0) {
sessionData.zoning.spawn.reviveTimer = context.system.scheduler.scheduleOnce(respawnTimer) {
sessionData.cluster ! ICS.GetRandomSpawnPoint(
sessionLogic.zoning.spawn.reviveTimer = context.system.scheduler.scheduleOnce(respawnTimer) {
sessionLogic.cluster ! ICS.GetRandomSpawnPoint(
Zones.sanctuaryZoneNumber(player.Faction),
player.Faction,
Seq(SpawnGroup.Sanctuary),
@ -480,16 +477,16 @@ class SessionAvatarHandlers(
)
}
} else {
sessionData.zoning.spawn.HandleReleaseAvatar(player, continent)
sessionLogic.zoning.spawn.HandleReleaseAvatar(player, continent)
}
case AvatarResponse.Release(tplayer) if isNotSameTarget =>
sessionData.zoning.spawn.DepictPlayerAsCorpse(tplayer)
sessionLogic.zoning.spawn.DepictPlayerAsCorpse(tplayer)
case AvatarResponse.Revive(revivalTargetGuid) if resolvedPlayerGuid == revivalTargetGuid =>
log.info(s"No time for rest, ${player.Name}. Back on your feet!")
sessionData.zoning.spawn.reviveTimer.cancel()
sessionData.zoning.spawn.deadState = DeadState.Alive
sessionLogic.zoning.spawn.reviveTimer.cancel()
sessionLogic.zoning.spawn.deadState = DeadState.Alive
player.Revive
val health = player.Health
sendResponse(PlanetsideAttributeMessage(revivalTargetGuid, attribute_type=0, health))
@ -517,7 +514,7 @@ class SessionAvatarHandlers(
case AvatarResponse.EnvironmentalDamage(_, _, _) =>
//TODO damage marker?
sessionData.zoning.CancelZoningProcessWithDescriptiveReason("cancel_dmg")
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_dmg")
case AvatarResponse.DropItem(pkt) if isNotSameTarget =>
sendResponse(pkt)
@ -530,7 +527,7 @@ class SessionAvatarHandlers(
sendResponse(SetEmpireMessage(objectGuid, faction))
case AvatarResponse.DropSpecialItem() =>
sessionData.dropSpecialSlotItem()
sessionLogic.general.dropSpecialSlotItem()
case AvatarResponse.OxygenState(player, vehicle) =>
sendResponse(OxygenStateMessage(
@ -612,7 +609,7 @@ class SessionAvatarHandlers(
//TODO squad services deactivated, participation trophy rewards for now - 11-20-2023
//must be in a squad to earn experience
val charId = player.CharId
val squadUI = sessionData.squad.squadUI
val squadUI = sessionLogic.squad.squadUI
val participation = continent
.Building(buildingId)
.map { building =>
@ -672,6 +669,45 @@ class SessionAvatarHandlers(
Some(modifiedExp)
}
}
/**
* Properly format a `DestroyDisplayMessage` packet
* given sufficient information about a target (victim) and an actor (killer).
* For the packet, the `charId` field is important for determining distinction between players.
* @param killer the killer's entry
* @param victim the victim's entry
* @param method the manner of death
* @param unk na;
* defaults to 121, the object id of `avatar`
* @return a `DestroyDisplayMessage` packet that is properly formatted
*/
def destroyDisplayMessage(
killer: SourceEntry,
victim: SourceEntry,
method: Int,
unk: Int = 121
): DestroyDisplayMessage = {
val killerSeated = killer match {
case obj: PlayerSource => obj.Seated
case _ => false
}
val victimSeated = victim match {
case obj: PlayerSource => obj.Seated
case _ => false
}
new DestroyDisplayMessage(
killer.Name,
killer.CharId,
killer.Faction,
killerSeated,
unk,
method,
victim.Name,
victim.CharId,
victim.Faction,
victimSeated
)
}
}
object SessionAvatarHandlers {

View file

@ -2,21 +2,34 @@
package net.psforever.actors.session.support
import akka.actor.{ActorContext, ActorRef, typed}
import scala.concurrent.duration._
import net.psforever.packet.PlanetSideGamePacket
import net.psforever.packet.game.FriendsResponse
import net.psforever.services.galaxy.{GalaxyAction, GalaxyServiceMessage}
//
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.packet.game.{BroadcastWarpgateUpdateMessage, HotSpotInfo => PacketHotSpotInfo, HotSpotUpdateMessage, ZoneInfoMessage, ZonePopulationUpdateMessage}
import net.psforever.services.galaxy.GalaxyResponse
import net.psforever.types.{MemberAction, PlanetSideEmpire}
class SessionGalaxyHandlers(
val sessionData: SessionData,
val sessionLogic: SessionLogic,
avatarActor: typed.ActorRef[AvatarActor.Command],
galaxyService: ActorRef,
implicit val context: ActorContext
) extends CommonSessionInterfacingFunctionality {
/* packets */
def handleUpdateIgnoredPlayers: PlanetSideGamePacket => Unit = {
case msg: FriendsResponse =>
sendResponse(msg)
msg.friends.foreach { f =>
galaxyService ! GalaxyServiceMessage(GalaxyAction.LogStatusChange(f.name))
}
case _ => ()
}
/* response handlers */
def handle(reply: GalaxyResponse.Response): Unit = {
reply match {
case GalaxyResponse.HotSpotUpdate(zone_index, priority, hot_spot_info) =>
@ -45,7 +58,7 @@ class SessionGalaxyHandlers(
sendResponse(msg)
case GalaxyResponse.TransferPassenger(temp_channel, vehicle, _, manifest) =>
sessionData.zoning.handleTransferPassenger(temp_channel, vehicle, manifest)
sessionLogic.zoning.handleTransferPassenger(temp_channel, vehicle, manifest)
case GalaxyResponse.LockedZoneUpdate(zone, time) =>
sendResponse(ZoneInfoMessage(zone.Number, empire_status=false, lock_time=time))
@ -58,7 +71,7 @@ class SessionGalaxyHandlers(
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) }) =>
case GalaxyResponse.LogStatusChange(name) if avatar.people.friend.exists(_.name.equals(name)) =>
avatarActor ! AvatarActor.MemberListRequest(MemberAction.UpdateFriend, name)
case GalaxyResponse.SendResponse(msg) =>

View file

@ -5,14 +5,13 @@ import akka.actor.ActorContext
import net.psforever.objects.ce.Deployable
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}
class SessionLocalHandlers(
val sessionData: SessionData,
val sessionLogic: SessionLogic,
implicit val context: ActorContext
) extends CommonSessionInterfacingFunctionality {
/**
@ -33,7 +32,7 @@ class SessionLocalHandlers(
sendResponse(DeployableObjectsInfoMessage(behavior, deployInfo))
case LocalResponse.DeployableUIFor(item) =>
sessionData.updateDeployableUIElements(avatar.deployables.UpdateUIElement(item))
sessionLogic.general.updateDeployableUIElements(avatar.deployables.UpdateUIElement(item))
case LocalResponse.Detonate(dguid, _: BoomerDeployable) =>
sendResponse(TriggerEffectMessage(dguid, "detonate_boomer"))
@ -112,10 +111,10 @@ class SessionLocalHandlers(
sendResponse(HackMessage(unk1=0, targetGuid, guid, progress=0, unk1, HackState.HackCleared, unk2))
case LocalResponse.HackObject(targetGuid, unk1, unk2) =>
HackObject(targetGuid, unk1, unk2)
sessionLogic.general.hackObject(targetGuid, unk1, unk2)
case LocalResponse.PlanetsideAttribute(targetGuid, attributeType, attributeValue) =>
SendPlanetsideAttributeMessage(targetGuid, attributeType, attributeValue)
sessionLogic.general.sendPlanetsideAttributeMessage(targetGuid, attributeType, attributeValue)
case LocalResponse.GenericObjectAction(targetGuid, actionNumber) =>
sendResponse(GenericObjectActionMessage(targetGuid, actionNumber))
@ -142,8 +141,8 @@ class SessionLocalHandlers(
sendResponse(TriggerSoundMessage(TriggeredSound.LLUDeconstruct, position, unk=20, volume=0.8000001f))
sendResponse(ObjectDeleteMessage(lluGuid, unk1=0))
// If the player was holding the LLU, remove it from their tracked special item slot
sessionData.specialItemSlotGuid.collect { case guid if guid == lluGuid =>
sessionData.specialItemSlotGuid = None
sessionLogic.general.specialItemSlotGuid.collect { case guid if guid == lluGuid =>
sessionLogic.general.specialItemSlotGuid = None
player.Carrying = None
}
@ -155,13 +154,13 @@ class SessionLocalHandlers(
case LocalResponse.ProximityTerminalEffect(objectGuid, false) =>
sendResponse(ProximityTerminalUseMessage(Service.defaultPlayerGUID, objectGuid, unk=false))
sessionData.terminals.ForgetAllProximityTerminals(objectGuid)
sessionLogic.terminals.ForgetAllProximityTerminals(objectGuid)
case LocalResponse.RouterTelepadMessage(msg) =>
sendResponse(ChatMsg(ChatMessageType.UNK_229, wideContents=false, recipient="", msg, note=None))
case LocalResponse.RouterTelepadTransport(passengerGuid, srcGuid, destGuid) =>
sessionData.useRouterTelepadEffect(passengerGuid, srcGuid, destGuid)
sessionLogic.general.useRouterTelepadEffect(passengerGuid, srcGuid, destGuid)
case LocalResponse.SendResponse(msg) =>
sendResponse(msg)
@ -190,7 +189,7 @@ class SessionLocalHandlers(
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, systemPlan) =>
sessionData.toggleTeleportSystem(router, systemPlan)
sessionLogic.general.toggleTeleportSystem(router, systemPlan)
case LocalResponse.TriggerEffect(targetGuid, effect, effectInfo, triggerLocation) =>
sendResponse(TriggerEffectMessage(targetGuid, effect, effectInfo, triggerLocation))
@ -236,28 +235,4 @@ class SessionLocalHandlers(
sendResponse(PlanetsideAttributeMessage(guid, 29, 1)) //make deployable vanish
sendResponse(ObjectDeleteMessage(guid, deletionType))
}
/**
* na
* @param targetGuid na
* @param unk1 na
* @param unk2 na
*/
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 targetGuid The target of the attribute
* @param attributeType The attribute number
* @param attributeValue The attribute value
*/
def SendPlanetsideAttributeMessage(
targetGuid: PlanetSideGUID,
attributeType: PlanetsideAttributeEnum,
attributeValue: Long
): Unit = {
sendResponse(PlanetsideAttributeMessage(targetGuid, attributeType, attributeValue))
}
}

View file

@ -0,0 +1,573 @@
// Copyright (c) 2023 PSForever
package net.psforever.actors.session.support
import akka.actor.Actor.Receive
import akka.actor.typed.receptionist.Receptionist
import akka.actor.typed.scaladsl.adapter._
import akka.actor.{ActorContext, ActorRef, typed}
import net.psforever.objects.zones.blockmap.{SectorGroup, SectorPopulation}
import net.psforever.services.ServiceManager
import net.psforever.services.ServiceManager.Lookup
import scala.collection.mutable
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._
//
import net.psforever.actors.net.MiddlewareActor
import net.psforever.actors.session.{AvatarActor, ChatActor}
import net.psforever.actors.zone.ZoneActor
import net.psforever.objects._
import net.psforever.objects.avatar._
import net.psforever.objects.ce._
import net.psforever.objects.equipment._
import net.psforever.objects.inventory.{Container, InventoryItem}
import net.psforever.objects.serverobject.mount.Mountable
import net.psforever.objects.serverobject.structures.Amenity
import net.psforever.objects.vehicles._
import net.psforever.objects.vital._
import net.psforever.objects.vital.interaction.DamageInteraction
import net.psforever.objects.zones._
import net.psforever.objects.zones.blockmap.{BlockMap, BlockMapEntity}
import net.psforever.packet._
import net.psforever.packet.game._
import net.psforever.services.account.AccountPersistenceService
import net.psforever.services.ServiceManager.LookupResult
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
import net.psforever.services.{Service, InterstellarClusterService => ICS}
import net.psforever.types._
import net.psforever.util.Config
object SessionLogic {
//noinspection ScalaUnusedSymbol
private def NoTurnCounterYet(guid: PlanetSideGUID): Unit = { }
private def updateOldRefsMap(inventory: net.psforever.objects.inventory.GridInventory): IterableOnce[(PlanetSideGUID, String)] = {
inventory.Items.flatMap {
case InventoryItem(o, _) => updateOldRefsMap(o)
}
}
private def updateOldRefsMap(item: PlanetSideGameObject): IterableOnce[(PlanetSideGUID, String)] = {
item match {
case t: Tool =>
t.AmmoSlots.map { slot =>
val box = slot.Box
box.GUID -> box.Definition.Name
} :+ (t.GUID -> t.Definition.Name)
case _ =>
Seq(item.GUID -> item.Definition.Name)
}
}
}
trait SessionLogic {
def middlewareActor: typed.ActorRef[MiddlewareActor.Command]
implicit def context: ActorContext
/**
* Hardwire an implicit `sender` to be the same as `context.self` of the `SessionActor` actor class
* for which this support class was initialized.
* Allows for proper use for `ActorRef.tell` or an actor's `!` in the support class,
* one where the result is always directed back to the same `SessionActor` instance.
* If there is a different packet "sender" that has to be respected by a given method,
* pass that `ActorRef` into the method as a parameter.
* @see `ActorRef.!(Any)(ActorRef)`
* @see `ActorRef.tell(Any)(ActorRef)`
*/
private[this] implicit val sender: ActorRef = context.self
private val avatarActor: typed.ActorRef[AvatarActor.Command] = context.spawnAnonymous(AvatarActor(context.self))
private val chatActor: typed.ActorRef[ChatActor.Command] = context.spawnAnonymous(ChatActor(context.self, avatarActor))
private[support] val log = org.log4s.getLogger
private[support] var theSession: Session = Session()
private[support] var accountIntermediary: ActorRef = Default.Actor
private[support] var accountPersistence: ActorRef = Default.Actor
private[support] var galaxyService: ActorRef = Default.Actor
private[support] var squadService: ActorRef = Default.Actor
private[support] var cluster: typed.ActorRef[ICS.Command] = Default.typed.Actor
private[session] var connectionState: Int = 25
private[support] var persistFunc: () => Unit = noPersistence
private[support] var persist: () => Unit = updatePersistenceOnly
private[session] var keepAliveFunc: () => Unit = keepAlivePersistenceInitial
private[support] var turnCounterFunc: PlanetSideGUID => Unit = SessionLogic.NoTurnCounterYet
private[support] val oldRefsMap: mutable.HashMap[PlanetSideGUID, String] = new mutable.HashMap[PlanetSideGUID, String]()
private var contextSafeEntity: PlanetSideGUID = PlanetSideGUID(0)
val general: GeneralOperations =
new GeneralOperations(sessionLogic=this, avatarActor, chatActor, context)
val shooting: WeaponAndProjectileOperations =
new WeaponAndProjectileOperations(sessionLogic=this, avatarActor, chatActor, context)
val vehicles: VehicleOperations =
new VehicleOperations(sessionLogic=this, avatarActor, context)
val avatarResponse: SessionAvatarHandlers =
new SessionAvatarHandlers(sessionLogic=this, avatarActor, chatActor, context)
val localResponse: SessionLocalHandlers =
new SessionLocalHandlers(sessionLogic=this, context)
val mountResponse: SessionMountHandlers =
new SessionMountHandlers(sessionLogic=this, avatarActor, context)
val terminals: SessionTerminalHandlers =
new SessionTerminalHandlers(sessionLogic=this, avatarActor, context)
private var vehicleResponseOpt: Option[SessionVehicleHandlers] = None
private var galaxyResponseOpt: Option[SessionGalaxyHandlers] = None
private var squadResponseOpt: Option[SessionSquadHandlers] = None
private var zoningOpt: Option[ZoningOperations] = None
def vehicleResponseOperations: SessionVehicleHandlers = vehicleResponseOpt.orNull
def galaxyResponseHandlers: SessionGalaxyHandlers = galaxyResponseOpt.orNull
def squad: SessionSquadHandlers = squadResponseOpt.orNull
def zoning: ZoningOperations = zoningOpt.orNull
ServiceManager.serviceManager ! Lookup("accountIntermediary")
ServiceManager.serviceManager ! Lookup("accountPersistence")
ServiceManager.serviceManager ! Lookup("galaxy")
ServiceManager.serviceManager ! Lookup("squad")
ServiceManager.receptionist ! Receptionist.Find(ICS.InterstellarClusterServiceKey, context.self)
/**
* updated when an upstream packet arrives;
* allow to be a little stale for a short while
*/
private[support] var localSector: SectorPopulation = SectorGroup(Nil)
def session: Session = theSession
def session_=(session: Session): Unit = {
chatActor ! ChatActor.SetSession(session)
avatarActor ! AvatarActor.SetSession(session)
theSession = session
}
def account: Account = theSession.account
def continent: Zone = theSession.zone
def player: Player = theSession.player
def avatar: Avatar = theSession.avatar
/* setup functions */
def assignEventBus(msg: Any): Boolean = {
msg match {
case LookupResult("accountIntermediary", endpoint) =>
accountIntermediary = endpoint
true
case LookupResult("accountPersistence", endpoint) =>
accountPersistence = endpoint
true
case LookupResult("galaxy", endpoint) =>
galaxyService = endpoint
buildDependentOperationsForGalaxy(endpoint)
buildDependentOperations(endpoint, cluster)
true
case LookupResult("squad", endpoint) =>
squadService = endpoint
buildDependentOperationsForSquad(endpoint)
true
case ICS.InterstellarClusterServiceKey.Listing(listings) =>
cluster = listings.head
buildDependentOperations(galaxyService, cluster)
true
case _ =>
false
}
}
def buildDependentOperationsForGalaxy(galaxyActor: ActorRef): Unit = {
if (vehicleResponseOpt.isEmpty && galaxyActor != Default.Actor) {
galaxyResponseOpt = Some(new SessionGalaxyHandlers(sessionLogic=this, avatarActor, galaxyActor, context))
vehicleResponseOpt = Some(new SessionVehicleHandlers(sessionLogic=this, avatarActor, galaxyActor, context))
}
}
def buildDependentOperations(galaxyActor: ActorRef, clusterActor: typed.ActorRef[ICS.Command]): Unit = {
if (zoningOpt.isEmpty && galaxyActor != Default.Actor && clusterActor != Default.typed.Actor) {
zoningOpt = Some(new ZoningOperations(sessionLogic=this, avatarActor, galaxyActor, clusterActor, context))
}
}
def buildDependentOperationsForSquad(squadActor: ActorRef): Unit = {
if (squadResponseOpt.isEmpty && squadActor != Default.Actor) {
squadResponseOpt = Some(new SessionSquadHandlers(sessionLogic=this, avatarActor, chatActor, squadActor, context))
}
}
def whenAllEventBusesLoaded(): Boolean = {
accountIntermediary != Default.Actor &&
accountPersistence != Default.Actor &&
vehicleResponseOpt.nonEmpty &&
galaxyResponseOpt.nonEmpty &&
squadResponseOpt.nonEmpty &&
zoningOpt.nonEmpty
}
/* message processing */
def parse(sender: ActorRef): Receive
/* support functions */
def validObject(id: Int): Option[PlanetSideGameObject] = validObject(Some(PlanetSideGUID(id)), decorator = "")
def validObject(id: Int, decorator: String): Option[PlanetSideGameObject] = validObject(Some(PlanetSideGUID(id)), decorator)
def validObject(id: PlanetSideGUID): Option[PlanetSideGameObject] = validObject(Some(id), decorator = "")
def validObject(id: PlanetSideGUID, decorator: String): Option[PlanetSideGameObject] = validObject(Some(id), decorator)
def validObject(id: Option[PlanetSideGUID]): Option[PlanetSideGameObject] = validObject(id, decorator = "")
def validObject(id: Option[PlanetSideGUID], decorator: String): Option[PlanetSideGameObject] = {
val elevatedDecorator = if (decorator.nonEmpty) decorator else "ValidObject"
id match {
case Some(guid) =>
val hint = oldRefsMap.getOrElse(guid, "thing")
continent.GUID(guid) match {
case Some(_: LocalProjectile) =>
shooting.FindProjectileEntry(guid)
case Some(_: LocalLockerItem) =>
player.avatar.locker.Inventory.hasItem(guid) match {
case out @ Some(_) =>
contextSafeEntity = guid
out
case None if contextSafeEntity == guid =>
//safeguard
None
case None =>
//delete stale entity reference from client
log.warn(
s"$elevatedDecorator: ${player.Name} is looking for an invalid GUID $guid, believing it a $hint in ${player.Sex.possessive} locker"
)
sendResponse(ObjectDeleteMessage(guid, 0))
None
}
case Some(obj) if obj.HasGUID && obj.GUID != guid =>
log.error(
s"$elevatedDecorator: ${player.Name} found a ${obj.Definition.Name} that isn't the $hint ${player.Sex.pronounSubject} thought it was in zone ${continent.id}"
)
log.debug(
s"$elevatedDecorator: potentially fatal error in ${continent.id} - requested $hint with $guid, got ${obj.Definition.Name} with ${obj.GUID}; mismatch"
)
None
case out @ Some(obj) if obj.HasGUID =>
out
case None if !id.contains(PlanetSideGUID(0)) =>
//delete stale entity reference from client
//deleting guid=0 will cause BAD things to happen
log.error(s"$elevatedDecorator: ${player.Name} has an invalid reference to $hint with GUID $guid in zone ${continent.id}")
sendResponse(ObjectDeleteMessage(guid, 0))
None
case None if contextSafeEntity == guid =>
//safeguard
None
case _ =>
None
}
case None =>
None
}
}
/**
* Update this player avatar for persistence.
* Set to `persist` initially.
*/
def updatePersistenceOnly(): Unit = {
persistFunc()
}
/**
* Do not update this player avatar for persistence.
* Set to `persistFunc` initially.
*/
def noPersistence(): Unit = { }
/**
* Check two locations for a controlled piece of equipment that is associated with the `player`.<br>
* <br>
* The first location is dependent on whether the avatar is in a vehicle.
* Some vehicle seats may have a "controlled weapon" which counts as the first location to be checked.
* The second location is dependent on whether the avatar has a raised hand.
* That is only possible if the player has something in their hand at the moment, hence the second location.
* Players do have a concept called a "last drawn slot" (hand) but that former location is not eligible.<br>
* <br>
* Along with any discovered item, a containing object such that the statement:<br>
* `container.Find(object) = Some(slot)`<br>
* ... will return a proper result.
* For a mount controlled weapon, the vehicle is returned.
* For the player's hand, the player is returned.
* @return a `Tuple` of the returned values;
* the first value is a `Container` object;
* the second value is an `Equipment` object in the former
*/
def findContainedEquipment(): (Option[PlanetSideGameObject with Container], Set[Equipment]) = {
continent.GUID(player.VehicleSeated) match {
case Some(vehicle: Mountable with MountableWeapons with Container) =>
vehicle.PassengerInSeat(player) match {
case Some(seatNum) =>
(Some(vehicle), vehicle.WeaponControlledFromSeat(seatNum))
case None =>
(None, Set.empty)
}
case _ =>
player.Slot(player.DrawnSlot).Equipment match {
case Some(a) =>
(Some(player), Set(a))
case _ =>
(None, Set.empty)
}
}
}
/**
* Check two locations for a controlled piece of equipment that is associated with the `player`
* and has the specified global unique identifier number.
*/
def findContainedEquipment(
guid: PlanetSideGUID
): (Option[PlanetSideGameObject with Container], Set[Equipment]) = {
val (o, equipment) = findContainedEquipment()
equipment.find { _.GUID == guid } match {
case Some(equip) => (o, Set(equip))
case None => (None, Set.empty)
}
}
/**
* Runs `FindContainedEquipment` but ignores the `Container` object output.
* @return an `Equipment` object
*/
def findEquipment(): Set[Equipment] = findContainedEquipment()._2
/**
* Runs `FindContainedEquipment` but ignores the `Container` object output
* and only discovers `Equipment` with the specified global unique identifier number.
* @return an `Equipment` object
*/
def findEquipment(guid: PlanetSideGUID): Option[Equipment] = findEquipment().find { _.GUID == guid }
/**
* An event has occurred that would cause the player character to stop certain stateful activities.
* These activities include shooting, the weapon being drawn, hacking, accessing (a container), flying, and running.
* Other players in the same zone must be made aware that the player has stopped as well.<br>
* <br>
* Things whose configuration should not be changed:<br>
* - if the player is seated<br>
* - if the player is anchored<br>
* This is not a complete list but, for the purpose of enforcement, some pointers will be documented here.
*/
def actionsToCancel(): Unit = {
general.actionsToCancel()
shooting.actionsToCancel()
terminals.actionsToCancel()
if (session.flying) {
session = session.copy(flying = false)
chatActor ! ChatActor.Message(ChatMsg(ChatMessageType.CMT_FLY, wideContents=false, "", "off", None))
}
if (session.speed > 1) {
session = session.copy(speed = 1)
chatActor ! ChatActor.Message(ChatMsg(ChatMessageType.CMT_SPEED, wideContents=false, "", "1.000", None))
}
}
/**
* Calculate the amount of damage to be dealt to an active `target`
* using the information reconstructed from a `ResolvedProjectile`
* and affect the `target` in a synchronized manner.
* The active `target` and the target of the `DamageResult` do not have be the same.
* While the "tell" for being able to sustain damage is an entity of type `Vitality`,
* only specific `Vitality` entity types are being screened for sustaining damage.
* @see `DamageResistanceModel`
* @see `Vitality`
* @param target a valid game object that is known to the server
* @param data a projectile that will affect the target
*/
def handleDealingDamage(target: PlanetSideGameObject with Vitality, data: DamageInteraction): Unit = {
val func = data.calculate()
target match {
case obj: Player if obj.CanDamage && obj.Actor != Default.Actor =>
if (obj.CharId != player.CharId) {
log.info(s"${player.Name} is attacking ${obj.Name}")
} else {
log.info(s"${player.Name} hurt ${player.Sex.pronounObject}self")
}
// auto kick players damaging spectators
if (obj.spectator && obj != player) {
administrativeKick(player)
} else {
obj.Actor ! Vitality.Damage(func)
}
case obj: Vehicle if obj.CanDamage =>
val name = player.Name
val ownerName = obj.OwnerName.getOrElse("someone")
if (ownerName.equals(name)) {
log.info(s"$name is damaging ${player.Sex.possessive} own ${obj.Definition.Name}")
} else {
log.info(s"$name is attacking $ownerName's ${obj.Definition.Name}")
}
obj.Actor ! Vitality.Damage(func)
case obj: Amenity if obj.CanDamage =>
obj.Actor ! Vitality.Damage(func)
case obj: Deployable if obj.CanDamage =>
val name = player.Name
val ownerName = obj.OwnerName.getOrElse("someone")
if (ownerName.equals(name)) {
log.info(s"$name is damaging ${player.Sex.possessive} own ${obj.Definition.Name}")
} else {
log.info(s"$name is attacking $ownerName's ${obj.Definition.Name}")
}
obj.Actor ! Vitality.Damage(func)
case _ => ()
}
}
/**
* The atypical response to receiving a `KeepAliveMessage` packet from the client.<br>
* <br>
* `KeepAliveMessage` packets are the primary vehicle for persistence due to client reporting
* in the case where the player's avatar is riding in a vehicle in a mount with no weapon to control.
* @see `KeepAliveMessage`
* @see `keepAliveFunc`
* @see `turnCounterFunc`
* @see `persist`
*/
def keepAlivePersistence(): Unit = {
zoning.spawn.interimUngunnedVehicle = None
persist()
if (player.HasGUID) {
turnCounterFunc(player.GUID)
} else {
turnCounterFunc(PlanetSideGUID(0))
}
}
/**
* A really atypical response to receiving a `KeepAliveMessage` packet from the client
* that applies only during the character select portion and part of the first zone load activity.
*/
def keepAlivePersistenceInitial(): Unit = {
persist()
if (player != null && player.HasGUID) {
keepAliveFunc = keepAlivePersistence
}
}
def updateBlockMap(target: BlockMapEntity, newCoords: Vector3): Unit = {
target.blockMapEntry.foreach { entry =>
val sectorIndices = BlockMap.findSectorIndices(continent.blockMap, newCoords, entry.rangeX, entry.rangeY).toSet
if (sectorIndices.equals(entry.sectors)) {
target.updateBlockMapEntry(newCoords) //soft update
localSector = continent.blockMap.sector(sectorIndices, Config.app.game.playerDraw.rangeMax.toFloat)
} else {
continent.actor ! ZoneActor.UpdateBlockMap(target, newCoords) //hard update
}
}
}
def updateLocalBlockMap(pos: Vector3): Unit = {
localSector = continent.blockMap.sector(pos, Config.app.game.playerDraw.rangeMax.toFloat)
}
def updateOldRefsMap(): Unit = {
if(player.HasGUID) {
oldRefsMap.addAll(
(continent.GUID(player.VehicleSeated) match {
case Some(v : Vehicle) =>
v.Weapons.toList.collect {
case (_, slot : EquipmentSlot) if slot.Equipment.nonEmpty => SessionLogic.updateOldRefsMap(slot.Equipment.get)
}.flatten ++
SessionLogic.updateOldRefsMap(v.Inventory)
case _ =>
Map.empty[PlanetSideGUID, String]
}) ++
(general.accessedContainer match {
case Some(cont) => SessionLogic.updateOldRefsMap(cont.Inventory)
case None => Map.empty[PlanetSideGUID, String]
}) ++
player.Holsters().toList.collect {
case slot if slot.Equipment.nonEmpty => SessionLogic.updateOldRefsMap(slot.Equipment.get)
}.flatten ++
SessionLogic.updateOldRefsMap(player.Inventory) ++
SessionLogic.updateOldRefsMap(player.avatar.locker.Inventory)
)
}
}
def administrativeKick(tplayer: Player): Unit = {
log.warn(s"${tplayer.Name} has been kicked by ${player.Name}")
tplayer.death_by = -1
accountPersistence ! AccountPersistenceService.Kick(tplayer.Name)
//get out of that vehicle
vehicles.GetMountableAndSeat(None, tplayer, continent) match {
case (Some(obj), Some(seatNum)) =>
tplayer.VehicleSeated = None
obj.Seats(seatNum).unmount(tplayer)
continent.VehicleEvents ! VehicleServiceMessage(
continent.id,
VehicleAction.KickPassenger(tplayer.GUID, seatNum, unk2=false, obj.GUID)
)
case _ => ()
}
}
def kickedByAdministration(): Unit = {
sendResponse(DisconnectMessage("@kick_w"))
context.system.scheduler.scheduleOnce(
delay = 300.milliseconds,
middlewareActor.toClassic,
MiddlewareActor.Teardown()
)
}
def immediateDisconnect(): Unit = {
if (avatar != null) {
accountPersistence ! AccountPersistenceService.Logout(avatar.name)
}
middlewareActor ! MiddlewareActor.Teardown()
}
def failWithError(error: String): Unit = {
log.error(error)
middlewareActor ! MiddlewareActor.Teardown()
}
def sendResponse(packet: PlanetSidePacket): Unit = {
middlewareActor ! MiddlewareActor.Send(packet)
}
def stop(): Unit = {
context.stop(avatarActor)
context.stop(chatActor)
general.stop()
shooting.stop()
vehicles.stop()
avatarResponse.stop()
localResponse.stop()
mountResponse.stop()
terminals.stop()
vehicleResponseOpt.foreach(_.stop())
galaxyResponseOpt.foreach(_.stop())
squadResponseOpt.foreach(_.stop())
zoningOpt.foreach(_.stop())
continent.AvatarEvents ! Service.Leave()
continent.LocalEvents ! Service.Leave()
continent.VehicleEvents ! Service.Leave()
galaxyService ! Service.Leave()
if (avatar != null && squadService != Default.Actor) {
squadService ! Service.Leave()
}
}
}

View file

@ -2,9 +2,12 @@
package net.psforever.actors.session.support
import akka.actor.{ActorContext, typed}
import net.psforever.objects.Tool
import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.serverobject.environment.interaction.ResetAllEnvironmentInteractions
import net.psforever.objects.vehicles.MountableWeapons
import net.psforever.objects.vital.InGameHistory
import net.psforever.packet.game.InventoryStateMessage
import scala.concurrent.duration._
//
@ -24,7 +27,7 @@ import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
import net.psforever.types.{BailType, ChatMessageType, PlanetSideGUID, Vector3}
class SessionMountHandlers(
val sessionData: SessionData,
val sessionLogic: SessionLogic,
avatarActor: typed.ActorRef[AvatarActor.Command],
implicit val context: ActorContext
) extends CommonSessionInterfacingFunctionality {
@ -37,82 +40,82 @@ class SessionMountHandlers(
def handle(tplayer: Player, reply: Mountable.Exchange): Unit = {
reply match {
case Mountable.CanMount(obj: ImplantTerminalMech, seatNumber, _) =>
sessionData.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
log.info(s"${player.Name} mounts an implant terminal")
sessionData.terminals.CancelAllProximityUnits()
sessionLogic.terminals.CancelAllProximityUnits()
MountingAction(tplayer, obj, seatNumber)
sessionData.keepAliveFunc = sessionData.keepAlivePersistence
sessionLogic.keepAliveFunc = sessionLogic.keepAlivePersistence
case Mountable.CanMount(obj: Vehicle, seatNumber, _)
if obj.Definition == GlobalDefinitions.orbital_shuttle =>
sessionData.zoning.CancelZoningProcessWithDescriptiveReason("cancel_mount")
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_mount")
log.info(s"${player.Name} mounts the orbital shuttle")
sessionData.terminals.CancelAllProximityUnits()
sessionLogic.terminals.CancelAllProximityUnits()
MountingAction(tplayer, obj, seatNumber)
sessionData.keepAliveFunc = sessionData.keepAlivePersistence
sessionLogic.keepAliveFunc = sessionLogic.keepAlivePersistence
case Mountable.CanMount(obj: Vehicle, seatNumber, _)
if obj.Definition == GlobalDefinitions.ant =>
sessionData.zoning.CancelZoningProcessWithDescriptiveReason("cancel_mount")
sessionLogic.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()
sessionLogic.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=45, obj.NtuCapacitorScaled))
sendResponse(GenericObjectActionMessage(obj_guid, code=11))
sessionData.accessContainer(obj)
sessionLogic.general.accessContainer(obj)
tplayer.Actor ! ResetAllEnvironmentInteractions
MountingAction(tplayer, obj, seatNumber)
case Mountable.CanMount(obj: Vehicle, seatNumber, _)
if obj.Definition == GlobalDefinitions.quadstealth =>
sessionData.zoning.CancelZoningProcessWithDescriptiveReason("cancel_mount")
sessionLogic.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()
sessionLogic.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)
sessionLogic.general.accessContainer(obj)
tplayer.Actor ! ResetAllEnvironmentInteractions
MountingAction(tplayer, obj, seatNumber)
case Mountable.CanMount(obj: Vehicle, seatNumber, _)
if seatNumber == 0 && obj.Definition.MaxCapacitor > 0 =>
sessionData.zoning.CancelZoningProcessWithDescriptiveReason("cancel_mount")
sessionLogic.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()
sessionLogic.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)
sessionLogic.general.accessContainer(obj)
updateWeaponAtSeatPosition(obj, seatNumber)
tplayer.Actor ! ResetAllEnvironmentInteractions
MountingAction(tplayer, obj, seatNumber)
case Mountable.CanMount(obj: Vehicle, seatNumber, _)
if seatNumber == 0 =>
sessionData.zoning.CancelZoningProcessWithDescriptiveReason("cancel_mount")
sessionLogic.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()
sessionLogic.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)
sessionLogic.general.accessContainer(obj)
updateWeaponAtSeatPosition(obj, seatNumber)
tplayer.Actor ! ResetAllEnvironmentInteractions
MountingAction(tplayer, obj, seatNumber)
case Mountable.CanMount(obj: Vehicle, seatNumber, _)
if obj.Definition.MaxCapacitor > 0 =>
sessionData.zoning.CancelZoningProcessWithDescriptiveReason("cancel_mount")
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_mount")
log.info(s"${player.Name} mounts ${
obj.SeatPermissionGroup(seatNumber) match {
case Some(seatType) => s"a $seatType seat (#$seatNumber)"
@ -120,18 +123,18 @@ class SessionMountHandlers(
}
} of the ${obj.Definition.Name}")
val obj_guid: PlanetSideGUID = obj.GUID
sessionData.terminals.CancelAllProximityUnits()
sessionLogic.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
sessionLogic.general.accessContainer(obj)
updateWeaponAtSeatPosition(obj, seatNumber)
sessionLogic.keepAliveFunc = sessionLogic.keepAlivePersistence
tplayer.Actor ! ResetAllEnvironmentInteractions
MountingAction(tplayer, obj, seatNumber)
case Mountable.CanMount(obj: Vehicle, seatNumber, _) =>
sessionData.zoning.CancelZoningProcessWithDescriptiveReason("cancel_mount")
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_mount")
log.info(s"${player.Name} mounts the ${
obj.SeatPermissionGroup(seatNumber) match {
case Some(seatType) => s"a $seatType seat (#$seatNumber)"
@ -139,44 +142,44 @@ class SessionMountHandlers(
}
} of the ${obj.Definition.Name}")
val obj_guid: PlanetSideGUID = obj.GUID
sessionData.terminals.CancelAllProximityUnits()
sessionLogic.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
sessionLogic.general.accessContainer(obj)
updateWeaponAtSeatPosition(obj, seatNumber)
sessionLogic.keepAliveFunc = sessionLogic.keepAlivePersistence
tplayer.Actor ! ResetAllEnvironmentInteractions
MountingAction(tplayer, obj, seatNumber)
case Mountable.CanMount(obj: FacilityTurret, seatNumber, _)
if obj.Definition == GlobalDefinitions.vanu_sentry_turret =>
sessionData.zoning.CancelZoningProcessWithDescriptiveReason("cancel_mount")
sessionLogic.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)
updateWeaponAtSeatPosition(obj, seatNumber)
MountingAction(tplayer, obj, seatNumber)
case Mountable.CanMount(obj: FacilityTurret, seatNumber, _)
if !obj.isUpgrading || System.currentTimeMillis() - getTurretUpgradeTime >= 1500L =>
obj.setMiddleOfUpgrade(false)
sessionData.zoning.CancelZoningProcessWithDescriptiveReason("cancel_mount")
sessionLogic.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)
updateWeaponAtSeatPosition(obj, seatNumber)
MountingAction(tplayer, obj, seatNumber)
case Mountable.CanMount(obj: FacilityTurret, _, _) =>
sessionData.zoning.CancelZoningProcessWithDescriptiveReason("cancel_mount")
sessionLogic.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 FactionAffinity with WeaponTurret with InGameHistory, seatNumber, _) =>
sessionData.zoning.CancelZoningProcessWithDescriptiveReason("cancel_mount")
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_mount")
log.info(s"${player.Name} mounts the ${obj.Definition.asInstanceOf[BasicDefinition].Name}")
sendResponse(PlanetsideAttributeMessage(obj.GUID, attribute_type=0, obj.Health))
sessionData.updateWeaponAtSeatPosition(obj, seatNumber)
updateWeaponAtSeatPosition(obj, seatNumber)
MountingAction(tplayer, obj, seatNumber)
case Mountable.CanMount(obj: Mountable, _, _) =>
@ -199,7 +202,7 @@ class SessionMountHandlers(
continent.id,
LocalAction.SendResponse(ObjectDetachMessage(sguid, pguid, pos, roll=0, pitch=0, zang))
)
sessionData.keepAliveFunc = sessionData.zoning.NormalKeepAlive
sessionLogic.keepAliveFunc = sessionLogic.zoning.NormalKeepAlive
case Mountable.CanDismount(obj: Vehicle, seatNum, _)
if obj.Definition == GlobalDefinitions.orbital_shuttle =>
@ -229,12 +232,12 @@ class SessionMountHandlers(
continent.id,
VehicleAction.SendResponse(pguid, GenericObjectActionMessage(pguid, code=9)) //conceal the player
)
sessionData.keepAliveFunc = sessionData.zoning.NormalKeepAlive
sessionLogic.keepAliveFunc = sessionLogic.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)
sessionLogic.general.unaccessContainer(obj)
DismountAction(tplayer, obj, seatNum)
obj.Actor ! Vehicle.Deconstruct()
@ -248,8 +251,8 @@ class SessionMountHandlers(
case None => "seat"
}
}")
sessionData.vehicles.ConditionalDriverVehicleControl(obj)
sessionData.unaccessContainer(obj)
sessionLogic.vehicles.ConditionalDriverVehicleControl(obj)
sessionLogic.general.unaccessContainer(obj)
DismountVehicleAction(tplayer, obj, seatNum)
case Mountable.CanDismount(obj: Vehicle, seat_num, _) =>
@ -291,7 +294,7 @@ class SessionMountHandlers(
def MountingAction(tplayer: Player, obj: PlanetSideGameObject with FactionAffinity with InGameHistory, seatNum: Int): Unit = {
val playerGuid: PlanetSideGUID = tplayer.GUID
val objGuid: PlanetSideGUID = obj.GUID
sessionData.playerActionsToCancel()
sessionLogic.actionsToCancel()
avatarActor ! AvatarActor.DeactivateActiveImplants()
avatarActor ! AvatarActor.SuspendStaminaRegeneration(3.seconds)
sendResponse(ObjectAttachMessage(objGuid, playerGuid, seatNum))
@ -313,8 +316,8 @@ class SessionMountHandlers(
obj match {
case v: Vehicle
if seatNum == 0 && Vector3.MagnitudeSquared(v.Velocity.getOrElse(Vector3.Zero)) > 0f =>
sessionData.vehicles.serverVehicleControlVelocity.collect { _ =>
sessionData.vehicles.ServerVehicleOverrideStop(v)
sessionLogic.vehicles.serverVehicleControlVelocity.collect { _ =>
sessionLogic.vehicles.ServerVehicleOverrideStop(v)
}
v.Velocity = Vector3.Zero
continent.VehicleEvents ! VehicleServiceMessage(
@ -347,7 +350,7 @@ class SessionMountHandlers(
def DismountAction(tplayer: Player, obj: PlanetSideGameObject with FactionAffinity with InGameHistory, seatNum: Int): Unit = {
val playerGuid: PlanetSideGUID = tplayer.GUID
tplayer.ContributionFrom(obj)
sessionData.keepAliveFunc = sessionData.zoning.NormalKeepAlive
sessionLogic.keepAliveFunc = sessionLogic.zoning.NormalKeepAlive
val bailType = if (tplayer.BailProtection) {
BailType.Bailed
} else {
@ -359,4 +362,22 @@ class SessionMountHandlers(
VehicleAction.DismountVehicle(playerGuid, bailType, unk2 = false)
)
}
/**
* From a mount, find the weapon controlled from it, and update the ammunition counts for that weapon's magazines.
* @param objWithSeat the object that owns seats (and weaponry)
* @param seatNum the mount
*/
def updateWeaponAtSeatPosition(objWithSeat: MountableWeapons, seatNum: Int): Unit = {
objWithSeat.WeaponControlledFromSeat(seatNum) foreach {
case weapon: Tool =>
//update mounted weapon belonging to mount
weapon.AmmoSlots.foreach(slot => {
//update the magazine(s) in the weapon, specifically
val magazine = slot.Box
sendResponse(InventoryStateMessage(magazine.GUID, weapon.GUID, magazine.Capacity.toLong))
})
case _ => () //no weapons to update
}
}
}

View file

@ -27,7 +27,7 @@ object SessionSquadHandlers {
}
class SessionSquadHandlers(
val sessionData: SessionData,
val sessionLogic: SessionLogic,
avatarActor: typed.ActorRef[AvatarActor.Command],
chatActor: typed.ActorRef[ChatActor.Command],
squadService: ActorRef,

View file

@ -2,6 +2,7 @@
package net.psforever.actors.session.support
import akka.actor.{ActorContext, typed}
import net.psforever.objects.guid.GUIDTask
import net.psforever.objects.sourcing.AmenitySource
import net.psforever.objects.vital.TerminalUsedActivity
@ -21,7 +22,7 @@ import net.psforever.packet.game.{ItemTransactionMessage, ItemTransactionResultM
import net.psforever.types.{PlanetSideGUID, TransactionType, Vector3}
class SessionTerminalHandlers(
val sessionData: SessionData,
val sessionLogic: SessionLogic,
avatarActor: typed.ActorRef[AvatarActor.Command],
implicit val context: ActorContext
) extends CommonSessionInterfacingFunctionality {
@ -37,7 +38,7 @@ class SessionTerminalHandlers(
val msg: String = if (itemName.nonEmpty) s" of $itemName" else ""
log.info(s"${player.Name} is submitting an order - a $transactionType from a ${term.Definition.Name}$msg")
lastTerminalOrderFulfillment = false
sessionData.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
sessionLogic.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}")
@ -203,7 +204,7 @@ class SessionTerminalHandlers(
Future(true)
}
},
List(sessionData.registerVehicle(vehicle))
List(registerVehicle(vehicle))
)
}
@ -293,7 +294,7 @@ class SessionTerminalHandlers(
/**
* Cease all current interactions with proximity-based units.
* Pair with `PlayerActionsToCancel`, except when logging out (stopping).
* Pair with `actionsToCancel`, except when logging out (stopping).
* This operations may invoke callback messages.
* @see `postStop`
*/
@ -303,7 +304,7 @@ class SessionTerminalHandlers(
/**
* Cease all current interactions with proximity-based units.
* Pair with `PlayerActionsToCancel`, except when logging out (stopping).
* Pair with `actionsToCancel`, except when logging out (stopping).
* This operations may invoke callback messages.
* @param guid globally unique identifier for a proximity terminal
* @see `postStop`
@ -326,4 +327,28 @@ class SessionTerminalHandlers(
usingMedicalTerminal = None
}
}
/**
* Construct tasking that adds a completed and registered vehicle into the scene.
* Use this function to renew the globally unique identifiers on a vehicle that has already been added to the scene once.
* @param vehicle the `Vehicle` object
* @see `RegisterVehicleFromSpawnPad`
* @return a `TaskBundle` message
*/
private[session] def registerVehicle(vehicle: Vehicle): TaskBundle = {
TaskBundle(
new StraightforwardTask() {
private val localVehicle = vehicle
override def description(): String = s"register a ${localVehicle.Definition.Name}"
def action(): Future[Any] = Future(true)
},
List(GUIDTask.registerVehicle(continent.GUID, vehicle))
)
}
override protected[support] def actionsToCancel(): Unit = {
lastTerminalOrderFulfillment = true
}
}

View file

@ -17,7 +17,7 @@ import net.psforever.types.{BailType, ChatMessageType, PlanetSideGUID, Vector3}
import scala.concurrent.duration._
class SessionVehicleHandlers(
val sessionData: SessionData,
val sessionLogic: SessionLogic,
avatarActor: typed.ActorRef[AvatarActor.Command],
galaxyService: ActorRef,
implicit val context: ActorContext
@ -55,7 +55,7 @@ class SessionVehicleHandlers(
player.Position = pos
player.Orientation = orient
player.Velocity = vel
sessionData.updateLocalBlockMap(pos)
sessionLogic.updateLocalBlockMap(pos)
case VehicleResponse.VehicleState(
vehicleGuid,
@ -165,7 +165,7 @@ class SessionVehicleHandlers(
sendResponse(DismountVehicleMsg(guid, BailType.Kicked, wasKickedByDriver))
val typeOfRide = continent.GUID(vehicleGuid) match {
case Some(obj: Vehicle) =>
sessionData.unaccessContainer(obj)
sessionLogic.general.unaccessContainer(obj)
s"the ${obj.Definition.Name}'s seat by ${obj.OwnerName.getOrElse("the pilot")}"
case _ =>
s"${player.Sex.possessive} ride"
@ -217,22 +217,22 @@ class SessionVehicleHandlers(
sendResponse(ObjectDeleteMessage(itemGuid, unk1=0))
case VehicleResponse.UpdateAmsSpawnPoint(list) =>
sessionData.zoning.spawn.amsSpawnPoints = list.filter(tube => tube.Faction == player.Faction)
sessionData.zoning.spawn.DrawCurrentAmsSpawnPoint()
sessionLogic.zoning.spawn.amsSpawnPoints = list.filter(tube => tube.Faction == player.Faction)
sessionLogic.zoning.spawn.DrawCurrentAmsSpawnPoint()
case VehicleResponse.TransferPassengerChannel(oldChannel, tempChannel, vehicle, vehicleToDelete) if isNotSameTarget =>
sessionData.zoning.interstellarFerry = Some(vehicle)
sessionData.zoning.interstellarFerryTopLevelGUID = Some(vehicleToDelete)
sessionLogic.zoning.interstellarFerry = Some(vehicle)
sessionLogic.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 && speed > 0 =>
if player.VehicleSeated.nonEmpty && sessionLogic.zoning.spawn.deadState == DeadState.Alive && speed > 0 =>
val strafe = 1 + Vehicles.CargoOrientation(vehicle)
val reverseSpeed = if (strafe > 1) { 0 } else { speed }
//strafe or reverse, not both
sessionData.vehicles.ServerVehicleOverrideWithPacket(
sessionLogic.vehicles.ServerVehicleOverrideWithPacket(
vehicle,
ServerVehicleOverrideMsg(
lock_accelerator=true,
@ -253,8 +253,8 @@ class SessionVehicleHandlers(
)
case VehicleResponse.KickCargo(cargo, _, _)
if player.VehicleSeated.nonEmpty && sessionData.zoning.spawn.deadState == DeadState.Alive =>
sessionData.vehicles.TotalDriverVehicleControl(cargo)
if player.VehicleSeated.nonEmpty && sessionLogic.zoning.spawn.deadState == DeadState.Alive =>
sessionLogic.vehicles.TotalDriverVehicleControl(cargo)
case VehicleResponse.StartPlayerSeatedInVehicle(vehicle, _)
if player.VisibleSlots.contains(player.DrawnSlot) =>
@ -266,7 +266,7 @@ class SessionVehicleHandlers(
case VehicleResponse.PlayerSeatedInVehicle(vehicle, _) =>
Vehicles.ReloadAccessPermissions(vehicle, player.Name)
sessionData.vehicles.ServerVehicleOverrideWithPacket(
sessionLogic.vehicles.ServerVehicleOverrideWithPacket(
vehicle,
ServerVehicleOverrideMsg(
lock_accelerator=true,
@ -279,11 +279,11 @@ class SessionVehicleHandlers(
unk8=Some(0)
)
)
sessionData.vehicles.serverVehicleControlVelocity = Some(0)
sessionLogic.vehicles.serverVehicleControlVelocity = Some(0)
case VehicleResponse.ServerVehicleOverrideStart(vehicle, _) =>
val vdef = vehicle.Definition
sessionData.vehicles.ServerVehicleOverrideWithPacket(
sessionLogic.vehicles.ServerVehicleOverrideWithPacket(
vehicle,
ServerVehicleOverrideMsg(
lock_accelerator=true,
@ -298,7 +298,7 @@ class SessionVehicleHandlers(
)
case VehicleResponse.ServerVehicleOverrideEnd(vehicle, _) =>
sessionData.vehicles.ServerVehicleOverrideStop(vehicle)
sessionLogic.vehicles.ServerVehicleOverrideStop(vehicle)
case VehicleResponse.PeriodicReminder(VehicleSpawnPad.Reminders.Blocked, data) =>
sendResponse(ChatMsg(
@ -328,7 +328,7 @@ class SessionVehicleHandlers(
sendResponse(ObjectDeleteMessage(eguid, unk1=0))
TaskWorkflow.execute(GUIDTask.unregisterEquipment(continent.GUID, obj))
}
sessionData.applyPurchaseTimersBeforePackingLoadout(player, vehicle, addedWeapons ++ newInventory)
sessionLogic.general.applyPurchaseTimersBeforePackingLoadout(player, vehicle, addedWeapons ++ newInventory)
//jammer or unjamm new weapons based on vehicle status
val vehicleJammered = vehicle.Jammed
addedWeapons
@ -342,7 +342,7 @@ class SessionVehicleHandlers(
}
case VehicleResponse.ChangeLoadout(target, oldWeapons, _, oldInventory, _)
if sessionData.accessedContainer.map { _.GUID }.contains(target) =>
if sessionLogic.general.accessedContainer.map(_.GUID).contains(target) =>
//TODO when vehicle weapons can be changed without visual glitches, rewrite this
continent.GUID(target).collect { case vehicle: Vehicle =>
//external participant: observe changes to equipment
@ -371,7 +371,7 @@ class SessionVehicleHandlers(
(oldWeapons ++ oldInventory).foreach {
case (_, eguid) => sendResponse(ObjectDeleteMessage(eguid, unk1=0))
}
sessionData.updateWeaponAtSeatPosition(vehicle, seatNum)
sessionLogic.mountResponse.updateWeaponAtSeatPosition(vehicle, seatNum)
case None =>
//observer: observe changes to external equipment
oldWeapons.foreach { case (_, eguid) => sendResponse(ObjectDeleteMessage(eguid, unk1=0)) }
@ -380,9 +380,9 @@ class SessionVehicleHandlers(
private def startPlayerSeatedInVehicle(vehicle: Vehicle): Unit = {
val vehicle_guid = vehicle.GUID
sessionData.playerActionsToCancel()
sessionData.terminals.CancelAllProximityUnits()
sessionData.vehicles.serverVehicleControlVelocity = Some(0)
sessionLogic.actionsToCancel()
sessionLogic.terminals.CancelAllProximityUnits()
sessionLogic.vehicles.serverVehicleControlVelocity = Some(0)
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 {

View file

@ -15,7 +15,7 @@ import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
import net.psforever.types.{BailType, DriveState, Vector3}
class VehicleOperations(
val sessionData: SessionData,
val sessionLogic: SessionLogic,
avatarActor: typed.ActorRef[AvatarActor.Command],
implicit val context: ActorContext
) extends CommonSessionInterfacingFunctionality {
@ -40,11 +40,11 @@ class VehicleOperations(
GetVehicleAndSeat() match {
case (Some(obj), Some(0)) =>
//we're driving the vehicle
sessionData.persist()
sessionData.turnCounterFunc(player.GUID)
sessionData.fallHeightTracker(pos.z)
sessionLogic.persist()
sessionLogic.turnCounterFunc(player.GUID)
sessionLogic.general.fallHeightTracker(pos.z)
if (obj.MountedIn.isEmpty) {
sessionData.updateBlockMap(obj, pos)
sessionLogic.updateBlockMap(obj, pos)
}
player.Position = pos //convenient
if (obj.WeaponControlledFromSeat(0).isEmpty) {
@ -87,7 +87,7 @@ class VehicleOperations(
obj.Cloaked
)
)
sessionData.squad.updateSquad()
sessionLogic.squad.updateSquad()
obj.zoneInteractions()
case (None, _) =>
//log.error(s"VehicleState: no vehicle $vehicle_guid found in zone")
@ -99,7 +99,7 @@ class VehicleOperations(
case _ => ;
}
if (player.death_by == -1) {
sessionData.kickedByAdministration()
sessionLogic.kickedByAdministration()
}
}
@ -123,11 +123,11 @@ class VehicleOperations(
GetVehicleAndSeat() match {
case (Some(obj), Some(0)) =>
//we're driving the vehicle
sessionData.persist()
sessionData.turnCounterFunc(player.GUID)
sessionLogic.persist()
sessionLogic.turnCounterFunc(player.GUID)
val (position, angle, velocity, notMountedState) = continent.GUID(obj.MountedIn) match {
case Some(v: Vehicle) =>
sessionData.updateBlockMap(obj, pos)
sessionLogic.updateBlockMap(obj, pos)
(pos, v.Orientation - Vector3.z(value = 90f) * Vehicles.CargoOrientation(obj).toFloat, v.Velocity, false)
case _ =>
(pos, ang, vel, true)
@ -186,7 +186,7 @@ class VehicleOperations(
unkA
)
)
sessionData.squad.updateSquad()
sessionLogic.squad.updateSquad()
case (None, _) =>
//log.error(s"VehicleState: no vehicle $vehicle_guid found in zone")
//TODO placing a "not driving" warning here may trigger as we are disembarking the vehicle
@ -197,13 +197,13 @@ class VehicleOperations(
case _ => ;
}
if (player.death_by == -1) {
sessionData.kickedByAdministration()
sessionLogic.kickedByAdministration()
}
}
def handleChildObjectState(pkt: ChildObjectStateMessage): Unit = {
val ChildObjectStateMessage(object_guid, pitch, yaw) = pkt
val (o, tools) = sessionData.shooting.FindContainedWeapon
val (o, tools) = sessionLogic.shooting.FindContainedWeapon
//is COSM our primary upstream packet?
(o match {
case Some(mount: Mountable) => (o, mount.PassengerInSeat(player))
@ -211,8 +211,8 @@ class VehicleOperations(
}) match {
case (None, None) | (_, None) | (Some(_: Vehicle), Some(0)) => ;
case _ =>
sessionData.persist()
sessionData.turnCounterFunc(player.GUID)
sessionLogic.persist()
sessionLogic.turnCounterFunc(player.GUID)
}
//the majority of the following check retrieves information to determine if we are in control of the child
tools.find { _.GUID == object_guid } match {
@ -231,19 +231,19 @@ class VehicleOperations(
}
//TODO status condition of "playing getting out of vehicle to allow for late packets without warning
if (player.death_by == -1) {
sessionData.kickedByAdministration()
sessionLogic.kickedByAdministration()
}
}
def handleVehicleSubState(pkt: VehicleSubStateMessage): Unit = {
val VehicleSubStateMessage(vehicle_guid, _, pos, ang, vel, unk1, _) = pkt
sessionData.validObject(vehicle_guid, decorator = "VehicleSubState") match {
sessionLogic.validObject(vehicle_guid, decorator = "VehicleSubState") match {
case Some(obj: Vehicle) =>
import net.psforever.login.WorldSession.boolToInt
obj.Position = pos
obj.Orientation = ang
obj.Velocity = vel
sessionData.updateBlockMap(obj, pos)
sessionLogic.updateBlockMap(obj, pos)
obj.zoneInteractions()
continent.VehicleEvents ! VehicleServiceMessage(
continent.id,
@ -268,7 +268,7 @@ class VehicleOperations(
def handleMountVehicle(pkt: MountVehicleMsg): Unit = {
val MountVehicleMsg(_, mountable_guid, entry_point) = pkt
sessionData.validObject(mountable_guid, decorator = "MountVehicle").collect {
sessionLogic.validObject(mountable_guid, decorator = "MountVehicle").collect {
case obj: Mountable =>
obj.Actor ! Mountable.TryMount(player, entry_point)
case _ =>
@ -283,7 +283,7 @@ class VehicleOperations(
//common warning for this section
if (player.GUID == player_guid) {
//normally disembarking from a mount
(sessionData.zoning.interstellarFerry.orElse(continent.GUID(player.VehicleSeated)) match {
(sessionLogic.zoning.interstellarFerry.orElse(continent.GUID(player.VehicleSeated)) match {
case out @ Some(obj: Vehicle) =>
continent.GUID(obj.MountedIn) match {
case Some(_: Vehicle) => None //cargo vehicle
@ -300,7 +300,7 @@ class VehicleOperations(
case Some(seat_num) =>
obj.Actor ! Mountable.TryDismount(player, seat_num, bailType)
//short-circuit the temporary channel for transferring between zones, the player is no longer doing that
sessionData.zoning.interstellarFerry = None
sessionLogic.zoning.interstellarFerry = None
// Deconstruct the vehicle if the driver has bailed out and the vehicle is capable of flight
//todo: implement auto landing procedure if the pilot bails but passengers are still present instead of deconstructing the vehicle
//todo: continue flight path until aircraft crashes if no passengers present (or no passenger seats), then deconstruct.
@ -327,8 +327,8 @@ class VehicleOperations(
case Some(obj_guid) =>
(
(
sessionData.validObject(obj_guid, decorator = "DismountVehicle/Vehicle"),
sessionData.validObject(player_guid, decorator = "DismountVehicle/Player")
sessionLogic.validObject(obj_guid, decorator = "DismountVehicle/Vehicle"),
sessionLogic.validObject(player_guid, decorator = "DismountVehicle/Player")
) match {
case (vehicle @ Some(obj: Vehicle), tplayer) =>
if (obj.MountedIn.isEmpty) (vehicle, tplayer) else (None, None)
@ -508,7 +508,7 @@ class VehicleOperations(
* `(None, None)`, otherwise (even if the vehicle can be determined)
*/
def GetKnownVehicleAndSeat(): (Option[Vehicle], Option[Int]) =
GetMountableAndSeat(sessionData.zoning.interstellarFerry, player, continent) match {
GetMountableAndSeat(sessionLogic.zoning.interstellarFerry, player, continent) match {
case (Some(v: Vehicle), Some(seat)) => (Some(v), Some(seat))
case _ => (None, None)
}

View file

@ -15,7 +15,7 @@ import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
import scala.concurrent.duration._
//
import net.psforever.actors.session.{AvatarActor, ChatActor, SessionActor}
import net.psforever.actors.session.{AvatarActor, ChatActor}
import net.psforever.login.WorldSession.{CountAmmunition, CountGrenades, FindAmmoBoxThatUses, FindEquipmentStock, FindToolThatUses, PutEquipmentInInventoryOrDrop, PutNewEquipmentInInventoryOrDrop, RemoveOldEquipmentFromInventory}
import net.psforever.objects.avatar.scoring.EquipmentStat
import net.psforever.objects.ballistics.{Projectile, ProjectileQuality}
@ -42,10 +42,10 @@ import net.psforever.types.{ExoSuitType, PlanetSideGUID, Vector3}
import net.psforever.util.Config
private[support] class WeaponAndProjectileOperations(
val sessionData: SessionData,
avatarActor: typed.ActorRef[AvatarActor.Command],
chatActor: typed.ActorRef[ChatActor.Command],
implicit val context: ActorContext
val sessionLogic: SessionLogic,
avatarActor: typed.ActorRef[AvatarActor.Command],
chatActor: typed.ActorRef[ChatActor.Command],
implicit val context: ActorContext
) extends CommonSessionInterfacingFunctionality {
var shooting: mutable.Set[PlanetSideGUID] = mutable.Set.empty //ChangeFireStateMessage_Start
var prefire: mutable.Set[PlanetSideGUID] = mutable.Set.empty //if WeaponFireMessage precedes ChangeFireStateMessage_Start
@ -111,8 +111,8 @@ private[support] class WeaponAndProjectileOperations(
val WeaponLazeTargetPositionMessage(_, _, _) = pkt
//do not need to handle the progress bar animation/state on the server
//laze waypoint is requested by client upon completion (see SquadWaypointRequest)
val purpose = if (sessionData.squad.squad_supplement_id > 0) {
s" for ${player.Sex.possessive} squad (#${sessionData.squad.squad_supplement_id -1})"
val purpose = if (sessionLogic.squad.squad_supplement_id > 0) {
s" for ${player.Sex.possessive} squad (#${sessionLogic.squad.squad_supplement_id -1})"
} else {
" ..."
}
@ -128,7 +128,7 @@ private[support] class WeaponAndProjectileOperations(
def handleChangeFireStateStart(pkt: ChangeFireStateMessage_Start)(implicit context: ActorContext): Unit = {
val ChangeFireStateMessage_Start(item_guid) = pkt
if (shooting.isEmpty) {
sessionData.findEquipment(item_guid) match {
sessionLogic.findEquipment(item_guid) match {
case Some(tool: Tool) if player.VehicleSeated.isEmpty =>
fireStateStartWhenPlayer(tool, item_guid)
case Some(tool: Tool) =>
@ -151,7 +151,7 @@ private[support] class WeaponAndProjectileOperations(
prefire -= item_guid
shootingStop += item_guid -> now
shooting -= item_guid
sessionData.findEquipment(item_guid) match {
sessionLogic.findEquipment(item_guid) match {
case Some(tool: Tool) if player.VehicleSeated.isEmpty =>
fireStateStopWhenPlayer(tool, item_guid)
case Some(tool: Tool) =>
@ -169,8 +169,8 @@ private[support] class WeaponAndProjectileOperations(
case _ =>
log.warn(s"ChangeFireState_Stop: can not find $item_guid")
}
sessionData.progressBarUpdate.cancel()
sessionData.progressBarValue = None
sessionLogic.general.progressBarUpdate.cancel()
sessionLogic.general.progressBarValue = None
}
def handleReload(pkt: ReloadMessage): Unit = {
@ -187,7 +187,7 @@ private[support] class WeaponAndProjectileOperations(
def handleChangeAmmo(pkt: ChangeAmmoMessage): Unit = {
val ChangeAmmoMessage(item_guid, _) = pkt
val (thing, equipment) = sessionData.findContainedEquipment()
val (thing, equipment) = sessionLogic.findContainedEquipment()
if (equipment.isEmpty) {
log.warn(s"ChangeAmmo: either can not find $item_guid or the object found was not Equipment")
} else {
@ -216,7 +216,7 @@ private[support] class WeaponAndProjectileOperations(
def handleChangeFireMode(pkt: ChangeFireModeMessage): Unit = {
val ChangeFireModeMessage(item_guid, _/*fire_mode*/) = pkt
sessionData.findEquipment(item_guid) match {
sessionLogic.findEquipment(item_guid) match {
case Some(obj: PlanetSideGameObject with FireModeSwitch[_]) =>
val originalModeIndex = obj.FireModeIndex
if (obj match {
@ -306,7 +306,7 @@ private[support] class WeaponAndProjectileOperations(
(hit_info match {
case Some(hitInfo) =>
val hitPos = hitInfo.hit_pos
sessionData.validObject(hitInfo.hitobject_guid, decorator = "Hit/hitInfo") match {
sessionLogic.validObject(hitInfo.hitobject_guid, decorator = "Hit/hitInfo") match {
case _ if projectile.profile == GlobalDefinitions.flail_projectile =>
val radius = projectile.profile.DamageRadius * projectile.profile.DamageRadius
val targets = Zone.findAllTargets(continent, player, hitPos, projectile.profile)
@ -340,7 +340,7 @@ private[support] class WeaponAndProjectileOperations(
) =>
ResolveProjectileInteraction(proj, DamageResolution.Hit, target, hitPos).collect { resprojectile =>
addShotsLanded(resprojectile.cause.attribution, shots = 1)
sessionData.handleDealingDamage(target, resprojectile)
sessionLogic.handleDealingDamage(target, resprojectile)
}
case _ => ()
}
@ -371,23 +371,23 @@ private[support] class WeaponAndProjectileOperations(
(DamageResolution.Splash, DamageResolution.Splash)
}
//direct_victim_uid
sessionData.validObject(direct_victim_uid, decorator = "SplashHit/direct_victim") match {
sessionLogic.validObject(direct_victim_uid, decorator = "SplashHit/direct_victim") match {
case Some(target: PlanetSideGameObject with FactionAffinity with Vitality) =>
CheckForHitPositionDiscrepancy(projectile_guid, explosion_pos, target)
ResolveProjectileInteraction(projectile, resolution1, target, target.Position).collect { resprojectile =>
addShotsLanded(resprojectile.cause.attribution, shots = 1)
sessionData.handleDealingDamage(target, resprojectile)
sessionLogic.handleDealingDamage(target, resprojectile)
}
case _ => ()
}
//other victims
targets.foreach(elem => {
sessionData.validObject(elem.uid, decorator = "SplashHit/other_victims") match {
sessionLogic.validObject(elem.uid, decorator = "SplashHit/other_victims") match {
case Some(target: PlanetSideGameObject with FactionAffinity with Vitality) =>
CheckForHitPositionDiscrepancy(projectile_guid, explosion_pos, target)
ResolveProjectileInteraction(projectile, resolution2, target, explosion_pos).collect { resprojectile =>
addShotsLanded(resprojectile.cause.attribution, shots = 1)
sessionData.handleDealingDamage(target, resprojectile)
sessionLogic.handleDealingDamage(target, resprojectile)
}
case _ => ()
}
@ -421,13 +421,13 @@ private[support] class WeaponAndProjectileOperations(
def handleLashHit(pkt: LashMessage): Unit = {
val LashMessage(_, _, victim_guid, projectile_guid, hit_pos, _) = pkt
sessionData.validObject(victim_guid, decorator = "Lash") match {
sessionLogic.validObject(victim_guid, decorator = "Lash") match {
case Some(target: PlanetSideGameObject with FactionAffinity with Vitality) =>
CheckForHitPositionDiscrepancy(projectile_guid, hit_pos, target)
ResolveProjectileInteraction(projectile_guid, DamageResolution.Lash, target, hit_pos).foreach {
resprojectile =>
addShotsLanded(resprojectile.cause.attribution, shots = 1)
sessionData.handleDealingDamage(target, resprojectile)
sessionLogic.handleDealingDamage(target, resprojectile)
}
case _ => ()
}
@ -454,7 +454,7 @@ private[support] class WeaponAndProjectileOperations(
None
}).collect {
case target: AutomatedTurret.Target =>
sessionData.validObject(attackerGuid, decorator = "AIDamage/AutomatedTurret")
sessionLogic.validObject(attackerGuid, decorator = "AIDamage/AutomatedTurret")
.collect {
case turret: AutomatedTurret if turret.Target.isEmpty =>
turret.Actor ! AutomatedTurretBehavior.ConfirmShot(target)
@ -468,10 +468,10 @@ private[support] class WeaponAndProjectileOperations(
}
.orElse {
//occasionally, something that is not technically a turret's natural target may be attacked
sessionData.validObject(targetGuid, decorator = "AIDamage/Target")
sessionLogic.validObject(targetGuid, decorator = "AIDamage/Target")
.collect {
case target: PlanetSideServerObject with FactionAffinity with Vitality =>
sessionData.validObject(attackerGuid, decorator = "AIDamage/Attacker")
sessionLogic.validObject(attackerGuid, decorator = "AIDamage/Attacker")
.collect {
case turret: AutomatedTurret if turret.Target.nonEmpty =>
//the turret must be shooting at something (else) first
@ -587,11 +587,11 @@ private[support] class WeaponAndProjectileOperations(
projectileGUID: PlanetSideGUID
): (Option[PlanetSideGameObject with Container], Option[Tool]) = {
if (player.ZoningRequest != Zoning.Method.None) {
sessionData.zoning.CancelZoningProcessWithDescriptiveReason("cancel_fire")
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_fire")
}
if (player.isShielded) {
// Cancel NC MAX shield if it's active
sessionData.toggleMaxSpecialState(enable = false)
sessionLogic.general.toggleMaxSpecialState(enable = false)
}
val (o, tools) = FindContainedWeapon
val (_, enabledTools) = FindEnabledWeaponsToHandleWeaponFireAccountability(o, tools)
@ -893,7 +893,7 @@ private[support] class WeaponAndProjectileOperations(
case Some(_) =>
stowFunc(previousBox)
case None =>
sessionData.normalItemDrop(player, continent)(previousBox)
sessionLogic.general.normalItemDrop(player, continent)(previousBox)
}
AmmoBox.Split(previousBox) match {
case Nil | List(_) => () //done (the former case is technically not possible)
@ -919,7 +919,7 @@ private[support] class WeaponAndProjectileOperations(
*/
def FindDetectedProjectileTargets(targets: Iterable[PlanetSideGUID]): Iterable[String] = {
targets
.map { sessionData.validObject(_, decorator="FindDetectedProjectileTargets") }
.map { sessionLogic.validObject(_, decorator="FindDetectedProjectileTargets") }
.flatMap {
case Some(obj: Vehicle) if !obj.Cloaked =>
//TODO hint: vehicleService ! VehicleServiceMessage(s"${obj.Actor}", VehicleAction.ProjectileAutoLockAwareness(mode))
@ -1163,7 +1163,7 @@ private[support] class WeaponAndProjectileOperations(
* the second value is an `Tool` object in the former
*/
def FindContainedWeapon: (Option[PlanetSideGameObject with Container], Set[Tool]) = {
sessionData.findContainedEquipment() match {
sessionLogic.findContainedEquipment() match {
case (container, equipment) =>
(container, equipment collect { case t: Tool => t })
case _ =>
@ -1207,11 +1207,11 @@ private[support] class WeaponAndProjectileOperations(
//charge ammunition drain
tool.FireMode match {
case mode: ChargeFireModeDefinition =>
sessionData.progressBarValue = Some(0f)
sessionData.progressBarUpdate = context.system.scheduler.scheduleOnce(
sessionLogic.general.progressBarValue = Some(0f)
sessionLogic.general.progressBarUpdate = context.system.scheduler.scheduleOnce(
(mode.Time + mode.DrainInterval) milliseconds,
context.self,
SessionActor.ProgressEvent(1f, () => {}, Tools.ChargeFireMode(player, tool), mode.DrainInterval)
CommonMessages.ProgressEvent(1f, () => {}, Tools.ChargeFireMode(player, tool), mode.DrainInterval)
)
case _ => ()
}
@ -1225,7 +1225,7 @@ private[support] class WeaponAndProjectileOperations(
}
private def fireStateStartMountedMessages(itemGuid: PlanetSideGUID): Unit = {
sessionData.findContainedEquipment()._1.collect {
sessionLogic.findContainedEquipment()._1.collect {
case turret: FacilityTurret if continent.map.cavern =>
turret.Actor ! VanuSentry.ChangeFireStart
}
@ -1291,7 +1291,7 @@ private[support] class WeaponAndProjectileOperations(
}
private def fireStateStopMountedMessages(itemGuid: PlanetSideGUID): Unit = {
sessionData.findContainedEquipment()._1.collect {
sessionLogic.findContainedEquipment()._1.collect {
case turret: FacilityTurret if continent.map.cavern =>
turret.Actor ! VanuSentry.ChangeFireStop
}
@ -1498,7 +1498,7 @@ private[support] class WeaponAndProjectileOperations(
val hitPos = target.Position + Vector3.z(value = 1f)
ResolveProjectileInteraction(proj, DamageResolution.Hit, target, hitPos).collect { resprojectile =>
addShotsLanded(resprojectile.cause.attribution, shots = 1)
sessionData.handleDealingDamage(target, resprojectile)
sessionLogic.handleDealingDamage(target, resprojectile)
}
}
}
@ -1585,7 +1585,21 @@ private[support] class WeaponAndProjectileOperations(
}.filter(p => Vector3.DistanceSquared(start, p) <= a)
}
override protected[session] def stop(): Unit = {
override protected[support] def actionsToCancel(): Unit = {
shootingStart.clear()
shootingStop.clear()
(prefire ++ shooting).foreach { guid =>
sendResponse(ChangeFireStateMessage_Stop(guid))
continent.AvatarEvents ! AvatarServiceMessage(
continent.id,
AvatarAction.ChangeFireState_Stop(player.GUID, guid)
)
}
prefire.clear()
shooting.clear()
}
override protected[support] def stop(): Unit = {
if (player != null && player.HasGUID) {
(prefire ++ shooting).foreach { guid =>
//do I need to do this? (maybe)

View file

@ -73,6 +73,11 @@ import net.psforever.util.{Config, DefinitionUtil}
import net.psforever.zones.Zones
object ZoningOperations {
private[support] final case class AvatarAwardMessageBundle(
bundle: Iterable[Iterable[PlanetSideGamePacket]],
delay: Long
)
private final val zoningCountdownMessages: Seq[Int] = Seq(5, 10, 20)
def reportProgressionSystem(sessionActor: ActorRef): Unit = {
@ -101,7 +106,7 @@ object ZoningOperations {
}
class ZoningOperations(
val sessionData: SessionData,
val sessionLogic: SessionLogic,
avatarActor: typed.ActorRef[AvatarActor.Command],
galaxyService: ActorRef,
cluster: typed.ActorRef[ICS.Command],
@ -147,7 +152,7 @@ class ZoningOperations(
CancelZoningProcessWithDescriptiveReason("cancel_use")
if (spawn.deadState != DeadState.RespawnTime) {
continent.Buildings.values.find(_.GUID == building_guid) match {
case Some(wg: WarpGate) if wg.Active && (sessionData.vehicles.GetKnownVehicleAndSeat() match {
case Some(wg: WarpGate) if wg.Active && (sessionLogic.vehicles.GetKnownVehicleAndSeat() match {
case (Some(vehicle), _) =>
wg.Definition.VehicleAllowance && !wg.Definition.NoWarp.contains(vehicle.Definition)
case _ =>
@ -202,7 +207,7 @@ class ZoningOperations(
continent.VehicleEvents ! Service.Join(name)
continent.VehicleEvents ! Service.Join(continentId)
continent.VehicleEvents ! Service.Join(factionChannel)
if (sessionData.connectionState != 100) configZone(continent)
if (sessionLogic.connectionState != 100) configZone(continent)
sendResponse(TimeOfDayMessage(1191182336))
//custom
sendResponse(ReplicationStreamMessage(5, Some(6), Vector.empty)) //clear squad list
@ -487,7 +492,7 @@ class ZoningOperations(
//the router won't work if it doesn't completely deploy
sendResponse(DeployRequestMessage(player.GUID, obj.GUID, DriveState.Deploying, 0, unk3=false, Vector3.Zero))
sendResponse(DeployRequestMessage(player.GUID, obj.GUID, DriveState.Deployed, 0, unk3=false, Vector3.Zero))
sessionData.toggleTeleportSystem(obj, TelepadLike.AppraiseTeleportationSystem(obj, continent))
sessionLogic.general.toggleTeleportSystem(obj, TelepadLike.AppraiseTeleportationSystem(obj, continent))
}
ServiceManager.serviceManager
.ask(Lookup("hart"))(Timeout(2 seconds))
@ -656,8 +661,8 @@ class ZoningOperations(
//the following subscriptions last until character switch/logout
galaxyService ! Service.Join("galaxy") //for galaxy-wide messages
galaxyService ! Service.Join(s"${avatar.faction}") //for hotspots, etc.
sessionData.squadService ! Service.Join(s"${avatar.faction}") //channel will be player.Faction
sessionData.squadService ! Service.Join(s"${avatar.id}") //channel will be player.CharId (in order to work with packets)
sessionLogic.squadService ! Service.Join(s"${avatar.faction}") //channel will be player.Faction
sessionLogic.squadService ! Service.Join(s"${avatar.id}") //channel will be player.CharId (in order to work with packets)
player.Zone match {
case Zone.Nowhere =>
RandomSanctuarySpawnPosition(player)
@ -669,7 +674,7 @@ class ZoningOperations(
session = session.copy(zone = zone)
//the only zone-level event system subscription necessary before BeginZoningMessage (for persistence purposes)
zone.AvatarEvents ! Service.Join(player.Name)
sessionData.persist()
sessionLogic.persist()
oldZone.AvatarEvents ! Service.Leave()
oldZone.LocalEvents ! Service.Leave()
oldZone.VehicleEvents ! Service.Leave()
@ -699,7 +704,7 @@ class ZoningOperations(
loadConfZone = true
val oldZone = session.zone
session = session.copy(zone = foundZone)
sessionData.persist()
sessionLogic.persist()
oldZone.AvatarEvents ! Service.Leave()
oldZone.LocalEvents ! Service.Leave()
oldZone.VehicleEvents ! Service.Leave()
@ -709,9 +714,9 @@ class ZoningOperations(
player.avatar = avatar
interstellarFerry match {
case Some(vehicle) if vehicle.PassengerInSeat(player).contains(0) =>
TaskWorkflow.execute(sessionData.registerDrivenVehicle(vehicle, player))
TaskWorkflow.execute(registerDrivenVehicle(vehicle, player))
case _ =>
TaskWorkflow.execute(sessionData.registerNewAvatar(player))
TaskWorkflow.execute(registerNewAvatar(player))
}
}
@ -749,9 +754,9 @@ class ZoningOperations(
}
val previousZoningType = ztype
CancelZoningProcess()
sessionData.playerActionsToCancel()
sessionData.terminals.CancelAllProximityUnits()
sessionData.dropSpecialSlotItem()
sessionLogic.actionsToCancel()
sessionLogic.terminals.CancelAllProximityUnits()
sessionLogic.general.dropSpecialSlotItem()
continent.Population ! Zone.Population.Release(avatar)
spawn.resolveZoningSpawnPointLoad(response, previousZoningType)
}
@ -859,13 +864,13 @@ class ZoningOperations(
zoningStatus = Zoning.Status.Request
beginZoningCountdown(() => {
log.info(s"Good-bye, ${player.Name}")
sessionData.immediateDisconnect()
sessionLogic.immediateDisconnect()
})
}
def handleSetZone(zoneId: String, position: Vector3): Unit = {
if (sessionData.vehicles.serverVehicleControlVelocity.isEmpty) {
sessionData.playerActionsToCancel()
if (sessionLogic.vehicles.serverVehicleControlVelocity.isEmpty) {
sessionLogic.actionsToCancel()
continent.GUID(player.VehicleSeated) match {
case Some(vehicle: Vehicle) if vehicle.MountedIn.isEmpty =>
vehicle.PassengerInSeat(player) match {
@ -1143,13 +1148,13 @@ class ZoningOperations(
//sync hack state
amenity.Definition match {
case GlobalDefinitions.capture_terminal =>
sessionData.sendPlanetsideAttributeMessage(
sessionLogic.general.sendPlanetsideAttributeMessage(
amenity.GUID,
PlanetsideAttributeEnum.ControlConsoleHackUpdate,
HackCaptureActor.GetHackUpdateAttributeValue(amenity.asInstanceOf[CaptureTerminal], isResecured = false)
)
case _ =>
sessionData.hackObject(amenity.GUID, 1114636288L, 8L) //generic hackable object
sessionLogic.general.hackObject(amenity.GUID, 1114636288L, 8L) //generic hackable object
}
// sync capture flags
@ -1216,7 +1221,7 @@ class ZoningOperations(
if (!zoneReload && zoneId.equals(continent.id)) {
if (player.isBackpack) { // important! test the actor-wide player ref, not the parameter
// respawning from unregistered player
TaskWorkflow.execute(sessionData.registerAvatar(targetPlayer))
TaskWorkflow.execute(registerAvatar(targetPlayer))
} else {
// move existing player; this is the one case where the original GUID is retained by the player
context.self ! SessionActor.PlayerLoaded(targetPlayer)
@ -1336,7 +1341,7 @@ class ZoningOperations(
ICS.FindZone(_.id == zoneId, context.self)
))
} else {
sessionData.unaccessContainer(vehicle)
sessionLogic.general.unaccessContainer(vehicle)
LoadZoneCommonTransferActivity()
player.VehicleSeated = vehicle.GUID
player.Continent = zoneId //forward-set the continent id to perform a test
@ -1354,7 +1359,7 @@ class ZoningOperations(
//unregister vehicle and driver whole + GiveWorld
continent.Transport ! Zone.Vehicle.Despawn(vehicle)
TaskWorkflow.execute(taskThenZoneChange(
sessionData.unregisterDrivenVehicle(vehicle, player),
unregisterDrivenVehicle(vehicle, player),
ICS.FindZone(_.id == zoneId, context.self)
))
}
@ -1480,9 +1485,9 @@ class ZoningOperations(
// allow AMS, ANT and Router to remain deployed when owner leaves the zone
vehicle.Definition match {
case GlobalDefinitions.ams | GlobalDefinitions.ant | GlobalDefinitions.router
=> sessionData.vehicles.ConditionalDriverVehicleControl(vehicle)
=> sessionLogic.vehicles.ConditionalDriverVehicleControl(vehicle)
case _ => sessionData.vehicles.TotalDriverVehicleControl(vehicle)
case _ => sessionLogic.vehicles.TotalDriverVehicleControl(vehicle)
}
// remove owner
@ -1492,12 +1497,12 @@ class ZoningOperations(
}
avatarActor ! AvatarActor.SetVehicle(None)
}
sessionData.removeBoomerTriggersFromInventory().foreach(obj => {
spawn.removeBoomerTriggersFromInventory().foreach(obj => {
TaskWorkflow.execute(GUIDTask.unregisterObject(continent.GUID, obj))
})
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
sessionLogic.squad.squadSetup = sessionLogic.squad.ZoneChangeSquadSetup
}
/**
@ -1514,7 +1519,7 @@ class ZoningOperations(
if (currentZone == Zones.sanctuaryZoneNumber(tplayer.Faction)) {
log.error(s"RequestSanctuaryZoneSpawn: ${player.Name} is already in faction sanctuary zone.")
sendResponse(DisconnectMessage("RequestSanctuaryZoneSpawn: player is already in sanctuary."))
sessionData.immediateDisconnect()
sessionLogic.immediateDisconnect()
} else {
continent.GUID(player.VehicleSeated) match {
case Some(obj: Vehicle) if !obj.Destroyed =>
@ -1547,8 +1552,8 @@ class ZoningOperations(
def LoadZoneLaunchDroppod(zone: Zone, spawnPosition: Vector3): Unit = {
log.info(s"${player.Name} is launching to ${zone.id} in ${player.Sex.possessive} droppod")
CancelZoningProcess()
sessionData.playerActionsToCancel()
sessionData.terminals.CancelAllProximityUnits()
sessionLogic.actionsToCancel()
sessionLogic.terminals.CancelAllProximityUnits()
//droppod action
val droppod = Vehicle(GlobalDefinitions.droppod)
droppod.GUID = PlanetSideGUID(0) //droppod is not registered, we must jury-rig this
@ -1596,6 +1601,88 @@ class ZoningOperations(
}
}
/**
* Construct tasking that registers all aspects of a `Player` avatar
* as if that player was already introduced and is just being renewed.
* `Players` are complex objects that contain a variety of other register-able objects and each of these objects much be handled.
* @param tplayer the avatar `Player`
* @return a `TaskBundle` message
*/
private[session] def registerAvatar(tplayer: Player): TaskBundle = {
TaskBundle(
new StraightforwardTask() {
private val localPlayer = tplayer
private val localAnnounce = context.self
override def description(): String = s"register player avatar ${localPlayer.Name}"
def action(): Future[Any] = {
localAnnounce ! SessionActor.PlayerLoaded(localPlayer)
Future(true)
}
},
List(GUIDTask.registerPlayer(continent.GUID, tplayer))
)
}
/**
* Construct tasking that registers all aspects of a `Player` avatar
* as if that player is only just being introduced.
* `Players` are complex objects that contain a variety of other register-able objects and each of these objects much be handled.
* @param tplayer the avatar `Player`
* @return a `TaskBundle` message
*/
private[session] def registerNewAvatar(tplayer: Player): TaskBundle = {
TaskBundle(
new StraightforwardTask() {
private val localPlayer = tplayer
private val localAnnounce = context.self
override def description(): String = s"register new player avatar ${localPlayer.Name}"
def action(): Future[Any] = {
localAnnounce ! SessionActor.NewPlayerLoaded(localPlayer)
Future(true)
}
},
List(GUIDTask.registerAvatar(continent.GUID, tplayer))
)
}
private[session] def registerDrivenVehicle(vehicle: Vehicle, driver: Player): TaskBundle = {
TaskBundle(
new StraightforwardTask() {
private val localVehicle = vehicle
private val localDriver = driver
private val localAnnounce = context.self
override def description(): String = s"register a ${localVehicle.Definition.Name} driven by ${localDriver.Name}"
def action(): Future[Any] = {
localDriver.VehicleSeated = localVehicle.GUID
Vehicles.Own(localVehicle, localDriver)
localAnnounce ! SessionActor.NewPlayerLoaded(localDriver)
Future(true)
}
},
List(GUIDTask.registerAvatar(continent.GUID, driver), GUIDTask.registerVehicle(continent.GUID, vehicle))
)
}
private[session] def unregisterDrivenVehicle(vehicle: Vehicle, driver: Player): TaskBundle = {
TaskBundle(
new StraightforwardTask() {
private val localVehicle = vehicle
private val localDriver = driver
override def description(): String = s"unregister a ${localVehicle.Definition.Name} driven by ${localDriver.Name}"
def action(): Future[Any] = Future(true)
},
List(GUIDTask.unregisterAvatar(continent.GUID, driver), GUIDTask.unregisterVehicle(continent.GUID, vehicle))
)
}
/**
* Use this function to facilitate registering a droppod for a globally unique identifier
* in the event that the user has instigated an instant action event to a destination within the current zone.<br>
@ -1730,7 +1817,7 @@ class ZoningOperations(
def handleLoginInfoNowhere(name: String, from: ActorRef): Unit = {
log.info(s"LoginInfo: player $name is considered a fresh character")
sessionData.persistFunc = UpdatePersistence(from)
sessionLogic.persistFunc = UpdatePersistence(from)
deadState = DeadState.RespawnTime
val tplayer = new Player(avatar)
session = session.copy(player = tplayer)
@ -1742,7 +1829,7 @@ class ZoningOperations(
def handleLoginInfoSomewhere(name: String, inZone: Zone, optionalSavedData: Option[Savedplayer], from: ActorRef): Unit = {
log.info(s"LoginInfo: player $name is considered a fresh character")
sessionData.persistFunc = UpdatePersistence(from)
sessionLogic.persistFunc = UpdatePersistence(from)
deadState = DeadState.RespawnTime
session = session.copy(player = new Player(avatar))
player.Zone = inZone
@ -1836,7 +1923,7 @@ class ZoningOperations(
def handleLoginInfoRestore(name: String, inZone: Zone, pos: Vector3, from: ActorRef): Unit = {
log.info(s"RestoreInfo: player $name is already logged in zone ${inZone.id}; rejoining that character")
sessionData.persistFunc = UpdatePersistence(from)
sessionLogic.persistFunc = UpdatePersistence(from)
//tell the old WorldSessionActor to kill itself by using its own subscriptions against itself
inZone.AvatarEvents ! AvatarServiceMessage(name, AvatarAction.TeardownConnection())
spawn.switchAvatarStatisticsFieldToRefreshAfterRespawn()
@ -1847,14 +1934,14 @@ class ZoningOperations(
) match {
case (_, Some(p)) if p.death_by == -1 =>
//player is not allowed
sessionData.kickedByAdministration()
sessionLogic.kickedByAdministration()
case (Some(a), Some(p)) if p.isAlive =>
//rejoin current avatar/player
log.info(s"RestoreInfo: player $name is alive")
deadState = DeadState.Alive
session = session.copy(player = p, avatar = a)
sessionData.persist()
sessionLogic.persist()
setupAvatarFunc = AvatarRejoin
dropMedicalApplicators(p)
avatarActor ! AvatarActor.ReplaceAvatar(a)
@ -1865,7 +1952,7 @@ class ZoningOperations(
log.info(s"RestoreInfo: player $name is dead")
deadState = DeadState.Dead
session = session.copy(player = p, avatar = a)
sessionData.persist()
sessionLogic.persist()
dropMedicalApplicators(p)
HandleReleaseAvatar(p, inZone)
avatarActor ! AvatarActor.ReplaceAvatar(a)
@ -1900,7 +1987,7 @@ class ZoningOperations(
def handleLoginCanNot(name: String, reason: PlayerToken.DeniedLoginReason.Value): Unit = {
log.warn(s"LoginInfo: $name is denied login for reason - $reason")
reason match {
case PlayerToken.DeniedLoginReason.Kicked => sessionData.kickedByAdministration()
case PlayerToken.DeniedLoginReason.Kicked => sessionLogic.kickedByAdministration()
case _ => sendResponse(DisconnectMessage("You will be logged out."))
}
}
@ -1939,9 +2026,9 @@ class ZoningOperations(
}
val previousZoningType = ztype
CancelZoningProcess()
sessionData.playerActionsToCancel()
sessionData.terminals.CancelAllProximityUnits()
sessionData.dropSpecialSlotItem()
sessionLogic.actionsToCancel()
sessionLogic.terminals.CancelAllProximityUnits()
sessionLogic.general.dropSpecialSlotItem()
continent.Population ! Zone.Population.Release(avatar)
resolveZoningSpawnPointLoad(response, previousZoningType)
}
@ -1954,8 +2041,8 @@ class ZoningOperations(
val map = zone.map
val mapName = map.name
log.info(s"${tplayer.Name} has spawned into $id")
sessionData.oldRefsMap.clear()
sessionData.persist = UpdatePersistenceAndRefs
sessionLogic.oldRefsMap.clear()
sessionLogic.persist = UpdatePersistenceAndRefs
tplayer.avatar = avatar
session = session.copy(player = tplayer)
avatarActor ! AvatarActor.CreateImplants()
@ -1967,7 +2054,7 @@ class ZoningOperations(
//important! the LoadMapMessage must be processed by the client before the avatar is created
setupAvatarFunc()
//interimUngunnedVehicle should have been setup by setupAvatarFunc, if it is applicable
sessionData.turnCounterFunc = interimUngunnedVehicle match {
sessionLogic.turnCounterFunc = interimUngunnedVehicle match {
case Some(_) =>
TurnCounterDuringInterimWhileInPassengerSeat
case None if zoningType == Zoning.Method.Login || zoningType == Zoning.Method.Reset =>
@ -1975,14 +2062,14 @@ class ZoningOperations(
case None =>
TurnCounterDuringInterim
}
sessionData.keepAliveFunc = NormalKeepAlive
sessionLogic.keepAliveFunc = NormalKeepAlive
if (zoningStatus == Zoning.Status.Deconstructing) {
sessionData.stopDeconstructing()
stopDeconstructing()
}
sessionData.avatarResponse.lastSeenStreamMessage.clear()
sessionLogic.avatarResponse.lastSeenStreamMessage.clear()
upstreamMessageCount = 0
setAvatar = false
sessionData.persist()
sessionLogic.persist()
} else {
//look for different spawn point in same zone
cluster ! ICS.GetNearbySpawnPoint(
@ -2004,20 +2091,20 @@ class ZoningOperations(
//try this spawn point
setupAvatarFunc()
//interimUngunnedVehicle should have been setup by setupAvatarFunc, if it is applicable
sessionData.turnCounterFunc = interimUngunnedVehicle match {
sessionLogic.turnCounterFunc = interimUngunnedVehicle match {
case Some(_) =>
TurnCounterDuringInterimWhileInPassengerSeat
case None =>
TurnCounterDuringInterim
}
sessionData.keepAliveFunc = NormalKeepAlive
sessionLogic.keepAliveFunc = NormalKeepAlive
if (zoningStatus == Zoning.Status.Deconstructing) {
sessionData.stopDeconstructing()
stopDeconstructing()
}
sessionData.avatarResponse.lastSeenStreamMessage.clear()
sessionLogic.avatarResponse.lastSeenStreamMessage.clear()
upstreamMessageCount = 0
setAvatar = false
sessionData.persist()
sessionLogic.persist()
} else {
//look for different spawn point in same zone
cluster ! ICS.GetNearbySpawnPoint(
@ -2087,7 +2174,7 @@ class ZoningOperations(
* @param zone na
*/
def HandleReleaseAvatar(tplayer: Player, zone: Zone): Unit = {
sessionData.keepAliveFunc = sessionData.keepAlivePersistence
sessionLogic.keepAliveFunc = sessionLogic.keepAlivePersistence
tplayer.Release
tplayer.VehicleSeated match {
case None =>
@ -2101,8 +2188,8 @@ class ZoningOperations(
}
def handleSetPosition(position: Vector3): Unit = {
if (sessionData.vehicles.serverVehicleControlVelocity.isEmpty) {
sessionData.playerActionsToCancel()
if (sessionLogic.vehicles.serverVehicleControlVelocity.isEmpty) {
sessionLogic.actionsToCancel()
continent.GUID(player.VehicleSeated) match {
case Some(vehicle: Vehicle) if vehicle.MountedIn.isEmpty =>
vehicle.PassengerInSeat(player) match {
@ -2147,7 +2234,7 @@ class ZoningOperations(
player.Armor = armor
}
player.death_by = math.min(player.death_by, 0)
sessionData.vehicles.GetKnownVehicleAndSeat() match {
sessionLogic.vehicles.GetKnownVehicleAndSeat() match {
case (Some(vehicle: Vehicle), Some(seat: Int)) =>
//if the vehicle is the cargo of another vehicle in this zone
val carrierInfo = continent.GUID(vehicle.MountedIn) match {
@ -2254,7 +2341,7 @@ class ZoningOperations(
* Neither the player avatar nor the vehicle should be reconstructed before the next zone load operation
* to avoid damaging the critical setup of this function.
* @see `AccessContainer`
* @see `UpdateWeaponAtSeatPosition`
* @see `SessionMountHandlers.updateWeaponAtSeatPosition`
* @param tplayer the player avatar seated in the vehicle's mount
* @param vehicle the vehicle the player is riding
* @param seat the mount index
@ -2271,8 +2358,8 @@ class ZoningOperations(
sendResponse(ObjectCreateDetailedMessage(pdef.ObjectId, pguid, pdata))
if (seat == 0 || vehicle.WeaponControlledFromSeat(seat).nonEmpty) {
sendResponse(ObjectAttachMessage(vguid, pguid, seat))
sessionData.accessContainer(vehicle)
sessionData.updateWeaponAtSeatPosition(vehicle, seat)
sessionLogic.general.accessContainer(vehicle)
sessionLogic.mountResponse.updateWeaponAtSeatPosition(vehicle, seat)
} else {
interimUngunnedVehicle = Some(vguid)
interimUngunnedVehicleSeat = Some(seat)
@ -2317,7 +2404,7 @@ class ZoningOperations(
* to avoid damaging the critical setup of this function.
*/
def AvatarRejoin(): Unit = {
sessionData.vehicles.GetKnownVehicleAndSeat() match {
sessionLogic.vehicles.GetKnownVehicleAndSeat() match {
case (Some(vehicle: Vehicle), Some(seat: Int)) =>
//vehicle and driver/passenger
val vdef = vehicle.Definition
@ -2336,8 +2423,8 @@ class ZoningOperations(
log.debug(s"AvatarRejoin: ${player.Name} - $pguid -> $pdata")
if (seat == 0 || vehicle.WeaponControlledFromSeat(seat).nonEmpty) {
sendResponse(ObjectAttachMessage(vguid, pguid, seat))
sessionData.accessContainer(vehicle)
sessionData.updateWeaponAtSeatPosition(vehicle, seat)
sessionLogic.general.accessContainer(vehicle)
sessionLogic.mountResponse.updateWeaponAtSeatPosition(vehicle, seat)
} else {
interimUngunnedVehicle = Some(vguid)
interimUngunnedVehicleSeat = Some(seat)
@ -2406,10 +2493,37 @@ class ZoningOperations(
case Some(_) | None => ;
}
})
sessionData.removeBoomerTriggersFromInventory().foreach(trigger => { sessionData.normalItemDrop(obj, continent)(trigger) })
removeBoomerTriggersFromInventory().foreach(trigger => { sessionLogic.general.normalItemDrop(obj, continent)(trigger) })
}
}
/**
* Search through the player's holsters and their inventory space
* and remove all `BoomerTrigger` objects, both functionally and visually.
* @return all discovered `BoomTrigger` objects
*/
def removeBoomerTriggersFromInventory(): List[BoomerTrigger] = {
val events = continent.AvatarEvents
val zoneId = continent.id
(player.Inventory.Items ++ player.HolsterItems())
.collect { case InventoryItem(obj: BoomerTrigger, index) =>
player.Slot(index).Equipment = None
continent.GUID(obj.Companion) match {
case Some(mine: BoomerDeployable) => mine.Actor ! Deployable.Ownership(None)
case _ => ()
}
if (player.VisibleSlots.contains(index)) {
events ! AvatarServiceMessage(
zoneId,
AvatarAction.ObjectDelete(Service.defaultPlayerGUID, obj.GUID)
)
} else {
sendResponse(ObjectDeleteMessage(obj.GUID, 0))
}
obj
}
}
/**
* Creates a player that has the characteristics of a corpse
* so long as the player has items in their knapsack or their holsters.
@ -2762,10 +2876,10 @@ class ZoningOperations(
SessionActor.SetCurrentAvatar(player, max_attempts, attempt + max_attempts / 3)
)
} else {
sessionData.keepAliveFunc = sessionData.vehicles.GetMountableAndSeat(None, player, continent) match {
sessionLogic.keepAliveFunc = sessionLogic.vehicles.GetMountableAndSeat(None, player, continent) match {
case (Some(v: Vehicle), Some(seatNumber))
if seatNumber > 0 && v.WeaponControlledFromSeat(seatNumber).isEmpty =>
sessionData.keepAlivePersistence
sessionLogic.keepAlivePersistence
case _ =>
NormalKeepAlive
}
@ -2792,7 +2906,7 @@ class ZoningOperations(
log.trace(s"HandleSetCurrentAvatar - ${tplayer.Name}")
session = session.copy(player = tplayer)
val guid = tplayer.GUID
sessionData.updateDeployableUIElements(Deployables.InitializeDeployableUIElements(avatar))
sessionLogic.general.updateDeployableUIElements(Deployables.InitializeDeployableUIElements(avatar))
sendResponse(PlanetsideAttributeMessage(PlanetSideGUID(0), 75, 0))
sendResponse(SetCurrentAvatarMessage(guid, 0, 0))
sendResponse(ChatMsg(ChatMessageType.CMT_EXPANSIONS, wideContents=true, "", "1 on", None)) //CC on //TODO once per respawn?
@ -2851,7 +2965,7 @@ class ZoningOperations(
sendResponse(PlanetsideStringAttributeMessage(guid, 0, "Outfit Name"))
//squad stuff (loadouts, assignment)
sessionData.squad.squadSetup()
sessionLogic.squad.squadSetup()
//MapObjectStateBlockMessage and ObjectCreateMessage?
//TacticsMessage?
//change the owner on our deployables (re-draw the icons for our deployables too)
@ -2875,7 +2989,7 @@ class ZoningOperations(
case _ =>
avatarActor ! AvatarActor.SetVehicle(None)
}
sessionData.vehicles.GetVehicleAndSeat() match {
sessionLogic.vehicles.GetVehicleAndSeat() match {
case (Some(vehicle), _) if vehicle.Definition == GlobalDefinitions.droppod =>
//we're falling
sendResponse(
@ -2923,7 +3037,7 @@ class ZoningOperations(
case _ => ;
}
interstellarFerryTopLevelGUID = None
if (loadConfZone && sessionData.connectionState == 100) {
if (loadConfZone && sessionLogic.connectionState == 100) {
configZone(continent)
loadConfZone = false
}
@ -2934,7 +3048,7 @@ class ZoningOperations(
player.Actor ! Player.Die()
} else {
AvatarActor.savePlayerData(player)
sessionData.displayCharSavedMsgThenRenewTimer(
sessionLogic.general.displayCharSavedMsgThenRenewTimer(
Config.app.game.savedMsg.short.fixed,
Config.app.game.savedMsg.short.variable
)
@ -3167,7 +3281,7 @@ class ZoningOperations(
def TurnCounterDuringInterim(guid: PlanetSideGUID): Unit = {
upstreamMessageCount = 0
if (player != null && player.HasGUID && player.GUID == guid && player.Zone == continent) {
sessionData.turnCounterFunc = NormalTurnCounter
sessionLogic.turnCounterFunc = NormalTurnCounter
}
}
@ -3201,15 +3315,15 @@ class ZoningOperations(
case (Some(vehicle: Vehicle), Some(vguid), Some(seat)) =>
//sit down
sendResponse(ObjectAttachMessage(vguid, pguid, seat))
sessionData.accessContainer(vehicle)
sessionData.keepAliveFunc = sessionData.keepAlivePersistence
sessionLogic.general.accessContainer(vehicle)
sessionLogic.keepAliveFunc = sessionLogic.keepAlivePersistence
case _ => ;
//we can't find a vehicle? and we're still here? that's bad
player.VehicleSeated = None
}
interimUngunnedVehicle = None
interimUngunnedVehicleSeat = None
sessionData.turnCounterFunc = NormalTurnCounter
sessionLogic.turnCounterFunc = NormalTurnCounter
}
}
/**
@ -3226,7 +3340,7 @@ class ZoningOperations(
loginChatMessage.foreach { msg => sendResponse(ChatMsg(zoningChatMessageType, wideContents=false, "", msg, None)) }
loginChatMessage.clear()
CancelZoningProcess()
sessionData.turnCounterFunc = NormalTurnCounter
sessionLogic.turnCounterFunc = NormalTurnCounter
}
/**
@ -3403,7 +3517,7 @@ class ZoningOperations(
context.system.scheduler.scheduleOnce(
delay.milliseconds,
context.self,
SessionActor.AvatarAwardMessageBundle(xs, delay)
ZoningOperations.AvatarAwardMessageBundle(xs, delay)
)
}
}
@ -3413,8 +3527,8 @@ class ZoningOperations(
* Set to `persist` when (new) player is loaded.
*/
def UpdatePersistenceAndRefs(): Unit = {
sessionData.persistFunc()
sessionData.updateOldRefsMap()
sessionLogic.persistFunc()
sessionLogic.updateOldRefsMap()
}
/**
@ -3425,6 +3539,34 @@ class ZoningOperations(
def UpdatePersistence(persistRef: ActorRef)(): Unit = {
persistRef ! AccountPersistenceService.Update(player.Name, continent, player.Position)
}
def startDeconstructing(obj: SpawnTube): Unit = {
log.info(s"${player.Name} is deconstructing at the ${obj.Owner.Definition.Name}'s spawns")
avatar.implants.collect {
case Some(implant) if implant.active && !implant.definition.Passive =>
avatarActor ! AvatarActor.DeactivateImplant(implant.definition.implantType)
}
if (player.ExoSuit != ExoSuitType.MAX) {
player.Actor ! PlayerControl.ObjectHeld(Player.HandsDownSlot, updateMyHolsterArm = true)
}
nextSpawnPoint = Some(obj) //set fallback
zoningStatus = Zoning.Status.Deconstructing
player.allowInteraction = false
if (player.death_by == 0) {
player.death_by = 1
}
GoToDeploymentMap()
}
def stopDeconstructing(): Unit = {
zoningStatus = Zoning.Status.None
player.death_by = math.min(player.death_by, 0)
player.allowInteraction = true
nextSpawnPoint.foreach { tube =>
sendResponse(PlayerStateShiftMessage(ShiftState(0, tube.Position, tube.Orientation.z)))
nextSpawnPoint = None
}
}
}
override protected[session] def stop(): Unit = {

View file

@ -11,6 +11,23 @@ object CommonMessages {
final case class Hack(player: Player, obj: PlanetSideServerObject with Hackable, data: Option[Any] = None)
final case class ClearHack()
/**
* The message that progresses some form of user-driven activity with a certain eventual outcome
* and potential feedback per cycle.
* @param delta how much the progress value changes each tick, which will be treated as a percentage;
* must be a positive value
* @param completionAction a finalizing action performed once the progress reaches 100(%)
* @param tickAction an action that is performed for each increase of progress
* @param tickTime how long between each `tickAction` (ms);
* defaults to 250 milliseconds
*/
final case class ProgressEvent(
delta: Float,
completionAction: () => Unit,
tickAction: Float => Boolean,
tickTime: Long = 250L
)
/**
* The message that initializes a process -
* some form of user-driven activity with a certain eventual outcome and potential feedback per cycle.