mirror of
https://github.com/psforever/PSF-LoginServer.git
synced 2026-01-19 18:44:45 +00:00
split existing code between data and functions, and entry points; parsing and logic management is now handled within the current game mode, which should reduce the need to ask explicits
This commit is contained in:
parent
cab41ac0b8
commit
9645bd79d4
|
|
@ -2,11 +2,13 @@
|
|||
package net.psforever.actors.session
|
||||
|
||||
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.normal.NormalMode
|
||||
import net.psforever.actors.session.support.{ModeLogic, PlayerMode, SessionData}
|
||||
import net.psforever.objects.{Default, Player}
|
||||
import net.psforever.objects.avatar.Avatar
|
||||
import net.psforever.objects.definition.BasicDefinition
|
||||
|
|
@ -14,8 +16,6 @@ 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
|
||||
|
||||
|
|
@ -68,29 +68,34 @@ object SessionActor {
|
|||
final case object CharSaved extends Command
|
||||
|
||||
private[session] case object CharSavedMsg extends Command
|
||||
|
||||
final case class SetMode(mode: PlayerMode) extends Command
|
||||
}
|
||||
|
||||
class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], connectionId: String, sessionId: Long)
|
||||
extends Actor
|
||||
extends Actor
|
||||
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 logic = new NormalUser(middlewareActor, context)
|
||||
private[this] val data = new SessionData(middlewareActor, context)
|
||||
private[this] var mode: PlayerMode = NormalMode
|
||||
private[this] var logic: ModeLogic = _
|
||||
|
||||
override def postStop(): Unit = {
|
||||
clientKeepAlive.cancel()
|
||||
logic.stop()
|
||||
data.stop()
|
||||
}
|
||||
|
||||
def receive: Receive = startup
|
||||
|
||||
private def startup: Receive = {
|
||||
case msg if !logic.assignEventBus(msg) =>
|
||||
case msg if !data.assignEventBus(msg) =>
|
||||
buffer.addOne(msg)
|
||||
case _ if logic.whenAllEventBusesLoaded() =>
|
||||
case _ if data.whenAllEventBusesLoaded() =>
|
||||
context.become(inTheGame)
|
||||
logic = mode.setup(data)
|
||||
startHeartbeat()
|
||||
buffer.foreach { self.tell(_, self) } //we forget the original sender, shouldn't be doing callbacks at this point
|
||||
buffer.clear()
|
||||
|
|
@ -110,10 +115,14 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
|||
}
|
||||
|
||||
private def inTheGame: Receive = {
|
||||
/* used for the game's heartbeat*/
|
||||
/* used for the game's heartbeat */
|
||||
case SessionActor.PokeClient() =>
|
||||
middlewareActor ! MiddlewareActor.Send(KeepAliveMessage())
|
||||
|
||||
case SessionActor.SetMode(newMode) =>
|
||||
mode = newMode
|
||||
logic = mode.setup(data)
|
||||
|
||||
case packet =>
|
||||
logic.parse(sender())(packet)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,569 @@
|
|||
// Copyright (c) 2024 PSForever
|
||||
package net.psforever.actors.session.normal
|
||||
|
||||
import akka.actor.{ActorContext, typed}
|
||||
import net.psforever.actors.session.support.AvatarHandlerFunctions
|
||||
|
||||
import scala.concurrent.duration._
|
||||
//
|
||||
import net.psforever.actors.session.AvatarActor
|
||||
import net.psforever.actors.session.support.{SessionAvatarHandlers, SessionData}
|
||||
import net.psforever.login.WorldSession.{DropEquipmentFromInventory, DropLeftovers, HoldNewEquipmentUp}
|
||||
import net.psforever.objects.{GlobalDefinitions, Player, Tool, Vehicle}
|
||||
import net.psforever.objects.guid.{GUIDTask, TaskWorkflow}
|
||||
import net.psforever.objects.inventory.InventoryItem
|
||||
import net.psforever.objects.serverobject.pad.VehicleSpawnPad
|
||||
import net.psforever.objects.serverobject.terminals.{ProximityUnit, Terminal}
|
||||
import net.psforever.objects.vital.etc.ExplodingEntityReason
|
||||
import net.psforever.objects.zones.Zoning
|
||||
import net.psforever.packet.game.objectcreate.ObjectCreateMessageParent
|
||||
import net.psforever.packet.game.{ArmorChangedMessage, AvatarDeadStateMessage, ChangeAmmoMessage, ChangeFireModeMessage, ChangeFireStateMessage_Start, ChangeFireStateMessage_Stop, ChatMsg, DeadState, DestroyMessage, DrowningTarget, GenericActionMessage, GenericObjectActionMessage, HitHint, ItemTransactionResultMessage, ObjectCreateDetailedMessage, ObjectCreateMessage, ObjectDeleteMessage, ObjectHeldMessage, OxygenStateMessage, PlanetsideAttributeMessage, PlayerStateMessage, ProjectileStateMessage, ReloadMessage, SetEmpireMessage, UseItemMessage, WeaponDryFireMessage}
|
||||
import net.psforever.services.avatar.{AvatarAction, AvatarResponse, AvatarServiceMessage}
|
||||
import net.psforever.services.Service
|
||||
import net.psforever.types.{ChatMessageType, PlanetSideGUID, TransactionType, Vector3}
|
||||
import net.psforever.util.Config
|
||||
|
||||
class AvatarHandlerLogic(val ops: SessionAvatarHandlers) extends AvatarHandlerFunctions {
|
||||
def sessionLogic: SessionData = ops.sessionLogic
|
||||
|
||||
implicit val context: ActorContext = ops.context
|
||||
|
||||
private val avatarActor: typed.ActorRef[AvatarActor.Command] = ops.avatarActor
|
||||
|
||||
/**
|
||||
* na
|
||||
* @param toChannel na
|
||||
* @param guid na
|
||||
* @param reply na
|
||||
*/
|
||||
def handle(toChannel: String, guid: PlanetSideGUID, reply: AvatarResponse.Response): Unit = {
|
||||
val resolvedPlayerGuid = if (player != null && player.HasGUID) {
|
||||
player.GUID
|
||||
} else {
|
||||
Service.defaultPlayerGUID
|
||||
}
|
||||
val isNotSameTarget = resolvedPlayerGuid != guid
|
||||
val isSameTarget = !isNotSameTarget
|
||||
reply match {
|
||||
/* special messages */
|
||||
case AvatarResponse.TeardownConnection() =>
|
||||
log.trace(s"ending ${player.Name}'s old session by event system request (relog)")
|
||||
context.stop(context.self)
|
||||
|
||||
/* really common messages (very frequently, every life) */
|
||||
case pstate @ AvatarResponse.PlayerState(
|
||||
pos,
|
||||
vel,
|
||||
yaw,
|
||||
pitch,
|
||||
yawUpper,
|
||||
_,
|
||||
isCrouching,
|
||||
isJumping,
|
||||
jumpThrust,
|
||||
isCloaking,
|
||||
isNotRendered,
|
||||
canSeeReallyFar
|
||||
) if isNotSameTarget =>
|
||||
val pstateToSave = pstate.copy(timestamp = 0)
|
||||
val (lastMsg, lastTime, lastPosition, wasVisible, wasShooting) = ops.lastSeenStreamMessage.get(guid.guid) match {
|
||||
case Some(SessionAvatarHandlers.LastUpstream(Some(msg), visible, shooting, time)) => (Some(msg), time, msg.pos, visible, shooting)
|
||||
case _ => (None, 0L, Vector3.Zero, false, None)
|
||||
}
|
||||
val drawConfig = Config.app.game.playerDraw //m
|
||||
val maxRange = drawConfig.rangeMax * drawConfig.rangeMax //sq.m
|
||||
val ourPosition = player.Position //xyz
|
||||
val currentDistance = Vector3.DistanceSquared(ourPosition, pos) //sq.m
|
||||
val inDrawableRange = currentDistance <= maxRange
|
||||
val now = System.currentTimeMillis() //ms
|
||||
if (
|
||||
sessionLogic.zoning.zoningStatus != Zoning.Status.Deconstructing &&
|
||||
!isNotRendered && inDrawableRange
|
||||
) {
|
||||
//conditions where visibility is assured
|
||||
val durationSince = now - lastTime //ms
|
||||
lazy val previouslyInDrawableRange = Vector3.DistanceSquared(ourPosition, lastPosition) <= maxRange
|
||||
lazy val targetDelay = {
|
||||
val populationOver = math.max(
|
||||
0,
|
||||
sessionLogic.localSector.livePlayerList.size - drawConfig.populationThreshold
|
||||
)
|
||||
val distanceAdjustment = math.pow(populationOver / drawConfig.populationStep * drawConfig.rangeStep, 2) //sq.m
|
||||
val adjustedDistance = currentDistance + distanceAdjustment //sq.m
|
||||
drawConfig.ranges.lastIndexWhere { dist => adjustedDistance > dist * dist } match {
|
||||
case -1 => 1
|
||||
case index => drawConfig.delays(index)
|
||||
}
|
||||
} //ms
|
||||
if (!wasVisible ||
|
||||
!previouslyInDrawableRange ||
|
||||
durationSince > drawConfig.delayMax ||
|
||||
(!lastMsg.contains(pstateToSave) &&
|
||||
(canSeeReallyFar ||
|
||||
currentDistance < drawConfig.rangeMin * drawConfig.rangeMin ||
|
||||
sessionLogic.general.canSeeReallyFar ||
|
||||
durationSince > targetDelay
|
||||
)
|
||||
)
|
||||
) {
|
||||
//must draw
|
||||
sendResponse(
|
||||
PlayerStateMessage(
|
||||
guid,
|
||||
pos,
|
||||
vel,
|
||||
yaw,
|
||||
pitch,
|
||||
yawUpper,
|
||||
timestamp = 0, //is this okay?
|
||||
isCrouching,
|
||||
isJumping,
|
||||
jumpThrust,
|
||||
isCloaking
|
||||
)
|
||||
)
|
||||
ops.lastSeenStreamMessage.put(guid.guid, SessionAvatarHandlers.LastUpstream(Some(pstateToSave), visible=true, wasShooting, now))
|
||||
} else {
|
||||
//is visible, but skip reinforcement
|
||||
ops.lastSeenStreamMessage.put(guid.guid, SessionAvatarHandlers.LastUpstream(Some(pstateToSave), visible=true, wasShooting, lastTime))
|
||||
}
|
||||
} else {
|
||||
//conditions where the target is not currently visible
|
||||
if (wasVisible) {
|
||||
//the target was JUST PREVIOUSLY visible; one last draw to move target beyond a renderable distance
|
||||
val lat = (1 + ops.hidingPlayerRandomizer.nextInt(continent.map.scale.height.toInt)).toFloat
|
||||
sendResponse(
|
||||
PlayerStateMessage(
|
||||
guid,
|
||||
Vector3(1f, lat, 1f),
|
||||
vel=None,
|
||||
facingYaw=0f,
|
||||
facingPitch=0f,
|
||||
facingYawUpper=0f,
|
||||
timestamp=0, //is this okay?
|
||||
is_cloaked = isCloaking
|
||||
)
|
||||
)
|
||||
ops.lastSeenStreamMessage.put(guid.guid, SessionAvatarHandlers.LastUpstream(Some(pstateToSave), visible=false, wasShooting, now))
|
||||
} else {
|
||||
//skip drawing altogether
|
||||
ops.lastSeenStreamMessage.put(guid.guid, SessionAvatarHandlers.LastUpstream(Some(pstateToSave), visible=false, wasShooting, lastTime))
|
||||
}
|
||||
}
|
||||
|
||||
case AvatarResponse.ObjectHeld(slot, _)
|
||||
if isSameTarget && player.VisibleSlots.contains(slot) =>
|
||||
sendResponse(ObjectHeldMessage(guid, slot, unk1=true))
|
||||
//Stop using proximity terminals if player unholsters a weapon
|
||||
continent.GUID(sessionLogic.terminals.usingMedicalTerminal).collect {
|
||||
case term: Terminal with ProximityUnit => sessionLogic.terminals.StopUsingProximityUnit(term)
|
||||
}
|
||||
if (sessionLogic.zoning.zoningStatus == Zoning.Status.Deconstructing) {
|
||||
sessionLogic.zoning.spawn.stopDeconstructing()
|
||||
}
|
||||
|
||||
case AvatarResponse.ObjectHeld(slot, _)
|
||||
if isSameTarget && slot > -1 =>
|
||||
sendResponse(ObjectHeldMessage(guid, slot, unk1=true))
|
||||
|
||||
case AvatarResponse.ObjectHeld(_, _)
|
||||
if isSameTarget => ()
|
||||
|
||||
case AvatarResponse.ObjectHeld(_, previousSlot) =>
|
||||
sendResponse(ObjectHeldMessage(guid, previousSlot, unk1=false))
|
||||
|
||||
case AvatarResponse.ChangeFireState_Start(weaponGuid)
|
||||
if isNotSameTarget && ops.lastSeenStreamMessage.get(guid.guid).exists { _.visible } =>
|
||||
sendResponse(ChangeFireStateMessage_Start(weaponGuid))
|
||||
val entry = ops.lastSeenStreamMessage(guid.guid)
|
||||
ops.lastSeenStreamMessage.put(guid.guid, entry.copy(shooting = Some(weaponGuid)))
|
||||
|
||||
case AvatarResponse.ChangeFireState_Start(weaponGuid)
|
||||
if isNotSameTarget =>
|
||||
sendResponse(ChangeFireStateMessage_Start(weaponGuid))
|
||||
|
||||
case AvatarResponse.ChangeFireState_Stop(weaponGuid)
|
||||
if isNotSameTarget && ops.lastSeenStreamMessage.get(guid.guid).exists { msg => msg.visible || msg.shooting.nonEmpty } =>
|
||||
sendResponse(ChangeFireStateMessage_Stop(weaponGuid))
|
||||
val entry = ops.lastSeenStreamMessage(guid.guid)
|
||||
ops.lastSeenStreamMessage.put(guid.guid, entry.copy(shooting = None))
|
||||
|
||||
case AvatarResponse.ChangeFireState_Stop(weaponGuid)
|
||||
if isNotSameTarget =>
|
||||
sendResponse(ChangeFireStateMessage_Stop(weaponGuid))
|
||||
|
||||
case AvatarResponse.LoadPlayer(pkt) if isNotSameTarget =>
|
||||
sendResponse(pkt)
|
||||
|
||||
case AvatarResponse.EquipmentInHand(pkt) if isNotSameTarget =>
|
||||
sendResponse(pkt)
|
||||
|
||||
case AvatarResponse.PlanetsideAttribute(attributeType, attributeValue) if isNotSameTarget =>
|
||||
sendResponse(PlanetsideAttributeMessage(guid, attributeType, attributeValue))
|
||||
|
||||
case AvatarResponse.PlanetsideAttributeToAll(attributeType, attributeValue) =>
|
||||
sendResponse(PlanetsideAttributeMessage(guid, attributeType, attributeValue))
|
||||
|
||||
case AvatarResponse.PlanetsideAttributeSelf(attributeType, attributeValue) if isSameTarget =>
|
||||
sendResponse(PlanetsideAttributeMessage(guid, attributeType, attributeValue))
|
||||
|
||||
case AvatarResponse.GenericObjectAction(objectGuid, actionCode) if isNotSameTarget =>
|
||||
sendResponse(GenericObjectActionMessage(objectGuid, actionCode))
|
||||
|
||||
case AvatarResponse.HitHint(sourceGuid) if player.isAlive =>
|
||||
sendResponse(HitHint(sourceGuid, guid))
|
||||
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(ops.destroyDisplayMessage(killer, victim, method, unk))
|
||||
|
||||
case AvatarResponse.TerminalOrderResult(terminalGuid, action, result)
|
||||
if result && (action == TransactionType.Buy || action == TransactionType.Loadout) =>
|
||||
sendResponse(ItemTransactionResultMessage(terminalGuid, action, result))
|
||||
sessionLogic.terminals.lastTerminalOrderFulfillment = true
|
||||
AvatarActor.savePlayerData(player)
|
||||
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))
|
||||
sessionLogic.terminals.lastTerminalOrderFulfillment = true
|
||||
|
||||
case AvatarResponse.ChangeExosuit(
|
||||
target,
|
||||
armor,
|
||||
exosuit,
|
||||
subtype,
|
||||
_,
|
||||
maxhand,
|
||||
oldHolsters,
|
||||
holsters,
|
||||
oldInventory,
|
||||
inventory,
|
||||
drop,
|
||||
delete
|
||||
) if resolvedPlayerGuid == target =>
|
||||
sendResponse(ArmorChangedMessage(target, exosuit, subtype))
|
||||
sendResponse(PlanetsideAttributeMessage(target, attribute_type=4, armor))
|
||||
//happening to this player
|
||||
//cleanup
|
||||
sendResponse(ObjectHeldMessage(target, Player.HandsDownSlot, unk1=false))
|
||||
(oldHolsters ++ oldInventory ++ delete).foreach {
|
||||
case (_, dguid) => sendResponse(ObjectDeleteMessage(dguid, unk1=0))
|
||||
}
|
||||
//functionally delete
|
||||
delete.foreach { case (obj, _) => TaskWorkflow.execute(GUIDTask.unregisterEquipment(continent.GUID, obj)) }
|
||||
//redraw
|
||||
if (maxhand) {
|
||||
TaskWorkflow.execute(HoldNewEquipmentUp(player)(
|
||||
Tool(GlobalDefinitions.MAXArms(subtype, player.Faction)),
|
||||
0
|
||||
))
|
||||
}
|
||||
//draw free hand
|
||||
player.FreeHand.Equipment.foreach { obj =>
|
||||
val definition = obj.Definition
|
||||
sendResponse(
|
||||
ObjectCreateDetailedMessage(
|
||||
definition.ObjectId,
|
||||
obj.GUID,
|
||||
ObjectCreateMessageParent(target, Player.FreeHandSlot),
|
||||
definition.Packet.DetailedConstructorData(obj).get
|
||||
)
|
||||
)
|
||||
}
|
||||
//draw holsters and inventory
|
||||
(holsters ++ inventory).foreach {
|
||||
case InventoryItem(obj, index) =>
|
||||
val definition = obj.Definition
|
||||
sendResponse(
|
||||
ObjectCreateDetailedMessage(
|
||||
definition.ObjectId,
|
||||
obj.GUID,
|
||||
ObjectCreateMessageParent(target, index),
|
||||
definition.Packet.DetailedConstructorData(obj).get
|
||||
)
|
||||
)
|
||||
}
|
||||
DropLeftovers(player)(drop)
|
||||
|
||||
case AvatarResponse.ChangeExosuit(target, armor, exosuit, subtype, slot, _, oldHolsters, holsters, _, _, _, delete) =>
|
||||
sendResponse(ArmorChangedMessage(target, exosuit, subtype))
|
||||
sendResponse(PlanetsideAttributeMessage(target, attribute_type=4, armor))
|
||||
//happening to some other player
|
||||
sendResponse(ObjectHeldMessage(target, slot, unk1 = false))
|
||||
//cleanup
|
||||
(oldHolsters ++ delete).foreach { case (_, guid) => sendResponse(ObjectDeleteMessage(guid, unk1=0)) }
|
||||
//draw holsters
|
||||
holsters.foreach {
|
||||
case InventoryItem(obj, index) =>
|
||||
val definition = obj.Definition
|
||||
sendResponse(
|
||||
ObjectCreateMessage(
|
||||
definition.ObjectId,
|
||||
obj.GUID,
|
||||
ObjectCreateMessageParent(target, index),
|
||||
definition.Packet.ConstructorData(obj).get
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
case AvatarResponse.ChangeLoadout(
|
||||
target,
|
||||
armor,
|
||||
exosuit,
|
||||
subtype,
|
||||
_,
|
||||
maxhand,
|
||||
oldHolsters,
|
||||
holsters,
|
||||
oldInventory,
|
||||
inventory,
|
||||
drops
|
||||
) if resolvedPlayerGuid == target =>
|
||||
sendResponse(ArmorChangedMessage(target, exosuit, subtype))
|
||||
sendResponse(PlanetsideAttributeMessage(target, attribute_type = 4, armor))
|
||||
//happening to this player
|
||||
sendResponse(ObjectHeldMessage(target, Player.HandsDownSlot, unk1=true))
|
||||
//cleanup
|
||||
(oldHolsters ++ oldInventory).foreach {
|
||||
case (obj, objGuid) =>
|
||||
sendResponse(ObjectDeleteMessage(objGuid, unk1=0))
|
||||
TaskWorkflow.execute(GUIDTask.unregisterEquipment(continent.GUID, obj))
|
||||
}
|
||||
drops.foreach(item => sendResponse(ObjectDeleteMessage(item.obj.GUID, unk1=0)))
|
||||
//redraw
|
||||
if (maxhand) {
|
||||
TaskWorkflow.execute(HoldNewEquipmentUp(player)(
|
||||
Tool(GlobalDefinitions.MAXArms(subtype, player.Faction)),
|
||||
slot = 0
|
||||
))
|
||||
}
|
||||
sessionLogic.general.applyPurchaseTimersBeforePackingLoadout(player, player, holsters ++ inventory)
|
||||
DropLeftovers(player)(drops)
|
||||
|
||||
case AvatarResponse.ChangeLoadout(target, armor, exosuit, subtype, slot, _, oldHolsters, _, _, _, _) =>
|
||||
//redraw handled by callbacks
|
||||
sendResponse(ArmorChangedMessage(target, exosuit, subtype))
|
||||
sendResponse(PlanetsideAttributeMessage(target, attribute_type=4, armor))
|
||||
//happening to some other player
|
||||
sendResponse(ObjectHeldMessage(target, slot, unk1=false))
|
||||
//cleanup
|
||||
oldHolsters.foreach { case (_, guid) => sendResponse(ObjectDeleteMessage(guid, unk1=0)) }
|
||||
|
||||
case AvatarResponse.UseKit(kguid, kObjId) =>
|
||||
sendResponse(
|
||||
UseItemMessage(
|
||||
resolvedPlayerGuid,
|
||||
kguid,
|
||||
resolvedPlayerGuid,
|
||||
unk2 = 4294967295L,
|
||||
unk3 = false,
|
||||
unk4 = Vector3.Zero,
|
||||
unk5 = Vector3.Zero,
|
||||
unk6 = 126,
|
||||
unk7 = 0, //sequence time?
|
||||
unk8 = 137,
|
||||
kObjId
|
||||
)
|
||||
)
|
||||
sendResponse(ObjectDeleteMessage(kguid, unk1=0))
|
||||
|
||||
case AvatarResponse.KitNotUsed(_, "") =>
|
||||
sessionLogic.general.kitToBeUsed = None
|
||||
|
||||
case AvatarResponse.KitNotUsed(_, msg) =>
|
||||
sessionLogic.general.kitToBeUsed = None
|
||||
sendResponse(ChatMsg(ChatMessageType.UNK_225, msg))
|
||||
|
||||
case AvatarResponse.UpdateKillsDeathsAssists(_, kda) =>
|
||||
avatarActor ! AvatarActor.UpdateKillsDeathsAssists(kda)
|
||||
|
||||
case AvatarResponse.AwardBep(charId, bep, expType) =>
|
||||
//if the target player, always award (some) BEP
|
||||
if (charId == player.CharId) {
|
||||
avatarActor ! AvatarActor.AwardBep(bep, expType)
|
||||
}
|
||||
|
||||
case AvatarResponse.AwardCep(charId, cep) =>
|
||||
//if the target player, always award (some) CEP
|
||||
if (charId == player.CharId) {
|
||||
avatarActor ! AvatarActor.AwardCep(cep)
|
||||
}
|
||||
|
||||
case AvatarResponse.FacilityCaptureRewards(buildingId, zoneNumber, cep) =>
|
||||
ops.facilityCaptureRewards(buildingId, zoneNumber, cep)
|
||||
|
||||
case AvatarResponse.SendResponse(msg) =>
|
||||
sendResponse(msg)
|
||||
|
||||
case AvatarResponse.SendResponseTargeted(targetGuid, msg) if resolvedPlayerGuid == targetGuid =>
|
||||
sendResponse(msg)
|
||||
|
||||
/* common messages (maybe once every respawn) */
|
||||
case AvatarResponse.Reload(itemGuid)
|
||||
if isNotSameTarget && ops.lastSeenStreamMessage.get(guid.guid).exists { _.visible } =>
|
||||
sendResponse(ReloadMessage(itemGuid, ammo_clip=1, unk1=0))
|
||||
|
||||
case AvatarResponse.Killed(mount) =>
|
||||
//log and chat messages
|
||||
val cause = player.LastDamage.flatMap { damage =>
|
||||
val interaction = damage.interaction
|
||||
val reason = interaction.cause
|
||||
val adversarial = interaction.adversarial.map { _.attacker }
|
||||
reason match {
|
||||
case r: ExplodingEntityReason if r.entity.isInstanceOf[VehicleSpawnPad] =>
|
||||
//also, @SVCP_Killed_TooCloseToPadOnCreate^n~ or "... within n meters of pad ..."
|
||||
sendResponse(ChatMsg(ChatMessageType.UNK_227, "@SVCP_Killed_OnPadOnCreate"))
|
||||
case _ => ()
|
||||
}
|
||||
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 (sessionLogic.shooting.shotsWhileDead > 0) {
|
||||
log.warn(
|
||||
s"SHOTS_WHILE_DEAD: client of ${avatar.name} fired ${sessionLogic.shooting.shotsWhileDead} rounds while character was dead on server"
|
||||
)
|
||||
sessionLogic.shooting.shotsWhileDead = 0
|
||||
}
|
||||
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)
|
||||
}
|
||||
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 =>
|
||||
sessionLogic.vehicles.ConditionalDriverVehicleControl(obj)
|
||||
sessionLogic.general.unaccessContainer(obj)
|
||||
}
|
||||
sessionLogic.actionsToCancel()
|
||||
sessionLogic.terminals.CancelAllProximityUnits()
|
||||
AvatarActor.savePlayerLocation(player)
|
||||
sessionLogic.zoning.spawn.shiftPosition = Some(player.Position)
|
||||
|
||||
//respawn
|
||||
sessionLogic.zoning.spawn.reviveTimer.cancel()
|
||||
if (player.death_by == 0) {
|
||||
sessionLogic.zoning.spawn.randomRespawn(300.seconds)
|
||||
} else {
|
||||
sessionLogic.zoning.spawn.HandleReleaseAvatar(player, continent)
|
||||
}
|
||||
|
||||
case AvatarResponse.Release(tplayer) if isNotSameTarget =>
|
||||
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!")
|
||||
sessionLogic.zoning.spawn.reviveTimer.cancel()
|
||||
sessionLogic.zoning.spawn.deadState = DeadState.Alive
|
||||
player.Revive
|
||||
val health = player.Health
|
||||
sendResponse(PlanetsideAttributeMessage(revivalTargetGuid, attribute_type=0, health))
|
||||
sendResponse(AvatarDeadStateMessage(DeadState.Alive, timer_max=0, timer=0, player.Position, player.Faction, unk5=true))
|
||||
continent.AvatarEvents ! AvatarServiceMessage(
|
||||
continent.id,
|
||||
AvatarAction.PlanetsideAttributeToAll(revivalTargetGuid, attribute_type=0, health)
|
||||
)
|
||||
|
||||
/* uncommon messages (utility, or once in a while) */
|
||||
case AvatarResponse.ChangeAmmo(weapon_guid, weapon_slot, previous_guid, ammo_id, ammo_guid, ammo_data)
|
||||
if isNotSameTarget && ops.lastSeenStreamMessage.get(guid.guid).exists { _.visible } =>
|
||||
ops.changeAmmoProcedures(weapon_guid, previous_guid, ammo_id, ammo_guid, weapon_slot, ammo_data)
|
||||
sendResponse(ChangeAmmoMessage(weapon_guid, 1))
|
||||
|
||||
case AvatarResponse.ChangeAmmo(weapon_guid, weapon_slot, previous_guid, ammo_id, ammo_guid, ammo_data)
|
||||
if isNotSameTarget =>
|
||||
ops.changeAmmoProcedures(weapon_guid, previous_guid, ammo_id, ammo_guid, weapon_slot, ammo_data)
|
||||
|
||||
case AvatarResponse.ChangeFireMode(itemGuid, mode) if isNotSameTarget =>
|
||||
sendResponse(ChangeFireModeMessage(itemGuid, mode))
|
||||
|
||||
case AvatarResponse.ConcealPlayer() =>
|
||||
sendResponse(GenericObjectActionMessage(guid, code=9))
|
||||
|
||||
case AvatarResponse.EnvironmentalDamage(_, _, _) =>
|
||||
//TODO damage marker?
|
||||
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_dmg")
|
||||
|
||||
case AvatarResponse.DropItem(pkt) if isNotSameTarget =>
|
||||
sendResponse(pkt)
|
||||
|
||||
case AvatarResponse.ObjectDelete(itemGuid, unk) if isNotSameTarget =>
|
||||
sendResponse(ObjectDeleteMessage(itemGuid, unk))
|
||||
|
||||
/* rare messages */
|
||||
case AvatarResponse.SetEmpire(objectGuid, faction) if isNotSameTarget =>
|
||||
sendResponse(SetEmpireMessage(objectGuid, faction))
|
||||
|
||||
case AvatarResponse.DropSpecialItem() =>
|
||||
sessionLogic.general.dropSpecialSlotItem()
|
||||
|
||||
case AvatarResponse.OxygenState(player, vehicle) =>
|
||||
sendResponse(OxygenStateMessage(
|
||||
DrowningTarget(player.guid, player.progress, player.state),
|
||||
vehicle.flatMap { vinfo => Some(DrowningTarget(vinfo.guid, vinfo.progress, vinfo.state)) }
|
||||
))
|
||||
|
||||
case AvatarResponse.LoadProjectile(pkt) if isNotSameTarget =>
|
||||
sendResponse(pkt)
|
||||
|
||||
case AvatarResponse.ProjectileState(projectileGuid, shotPos, shotVel, shotOrient, seq, end, targetGuid) if isNotSameTarget =>
|
||||
sendResponse(ProjectileStateMessage(projectileGuid, shotPos, shotVel, shotOrient, seq, end, targetGuid))
|
||||
|
||||
case AvatarResponse.ProjectileExplodes(projectileGuid, projectile) =>
|
||||
sendResponse(
|
||||
ProjectileStateMessage(
|
||||
projectileGuid,
|
||||
projectile.Position,
|
||||
shot_vel = Vector3.Zero,
|
||||
projectile.Orientation,
|
||||
sequence_num=0,
|
||||
end=true,
|
||||
hit_target_guid=PlanetSideGUID(0)
|
||||
)
|
||||
)
|
||||
sendResponse(ObjectDeleteMessage(projectileGuid, unk1=2))
|
||||
|
||||
case AvatarResponse.ProjectileAutoLockAwareness(mode) =>
|
||||
sendResponse(GenericActionMessage(mode))
|
||||
|
||||
case AvatarResponse.PutDownFDU(target) if isNotSameTarget =>
|
||||
sendResponse(GenericObjectActionMessage(target, code=53))
|
||||
|
||||
case AvatarResponse.StowEquipment(target, slot, item) if isNotSameTarget =>
|
||||
val definition = item.Definition
|
||||
sendResponse(
|
||||
ObjectCreateDetailedMessage(
|
||||
definition.ObjectId,
|
||||
item.GUID,
|
||||
ObjectCreateMessageParent(target, slot),
|
||||
definition.Packet.DetailedConstructorData(item).get
|
||||
)
|
||||
)
|
||||
|
||||
case AvatarResponse.WeaponDryFire(weaponGuid)
|
||||
if isNotSameTarget && ops.lastSeenStreamMessage.get(guid.guid).exists { _.visible } =>
|
||||
continent.GUID(weaponGuid).collect {
|
||||
case tool: Tool if tool.Magazine == 0 =>
|
||||
// check that the magazine is still empty before sending WeaponDryFireMessage
|
||||
// if it has been reloaded since then, other clients will not see it firing
|
||||
sendResponse(WeaponDryFireMessage(weaponGuid))
|
||||
}
|
||||
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
// Copyright (c) 2024 PSForever
|
||||
package net.psforever.actors.session.normal
|
||||
|
||||
import akka.actor.{ActorContext, ActorRef, typed}
|
||||
import net.psforever.actors.session.AvatarActor
|
||||
import net.psforever.actors.session.support.{GalaxyHandlerFunctions, SessionGalaxyHandlers, SessionData}
|
||||
import net.psforever.packet.game.{BroadcastWarpgateUpdateMessage, FriendsResponse, HotSpotUpdateMessage, ZoneInfoMessage, ZonePopulationUpdateMessage, HotSpotInfo => PacketHotSpotInfo}
|
||||
import net.psforever.services.galaxy.{GalaxyAction, GalaxyResponse, GalaxyServiceMessage}
|
||||
import net.psforever.types.{MemberAction, PlanetSideEmpire}
|
||||
|
||||
class GalaxyHandlerLogic(val ops: SessionGalaxyHandlers) extends GalaxyHandlerFunctions {
|
||||
def sessionLogic: SessionData = ops.sessionLogic
|
||||
|
||||
implicit val context: ActorContext = ops.context
|
||||
|
||||
private val avatarActor: typed.ActorRef[AvatarActor.Command] = ops.avatarActor
|
||||
|
||||
private val galaxyService: ActorRef = ops.galaxyService
|
||||
|
||||
/* packets */
|
||||
|
||||
def handleUpdateIgnoredPlayers(pkt: FriendsResponse): Unit = {
|
||||
sendResponse(pkt)
|
||||
pkt.friends.foreach { f =>
|
||||
galaxyService ! GalaxyServiceMessage(GalaxyAction.LogStatusChange(f.name))
|
||||
}
|
||||
}
|
||||
|
||||
/* response handlers */
|
||||
|
||||
def handle(reply: GalaxyResponse.Response): Unit = {
|
||||
reply match {
|
||||
case GalaxyResponse.HotSpotUpdate(zone_index, priority, hot_spot_info) =>
|
||||
sendResponse(
|
||||
HotSpotUpdateMessage(
|
||||
zone_index,
|
||||
priority,
|
||||
hot_spot_info.map { spot => PacketHotSpotInfo(spot.DisplayLocation.x, spot.DisplayLocation.y, 40) }
|
||||
)
|
||||
)
|
||||
|
||||
case GalaxyResponse.MapUpdate(msg) =>
|
||||
sendResponse(msg)
|
||||
|
||||
case GalaxyResponse.UpdateBroadcastPrivileges(zoneId, gateMapId, fromFactions, toFactions) =>
|
||||
val faction = player.Faction
|
||||
val from = fromFactions.contains(faction)
|
||||
val to = toFactions.contains(faction)
|
||||
if (from && !to) {
|
||||
sendResponse(BroadcastWarpgateUpdateMessage(zoneId, gateMapId, PlanetSideEmpire.NEUTRAL))
|
||||
} else if (!from && to) {
|
||||
sendResponse(BroadcastWarpgateUpdateMessage(zoneId, gateMapId, faction))
|
||||
}
|
||||
|
||||
case GalaxyResponse.FlagMapUpdate(msg) =>
|
||||
sendResponse(msg)
|
||||
|
||||
case GalaxyResponse.TransferPassenger(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))
|
||||
|
||||
case GalaxyResponse.UnlockedZoneUpdate(zone) => ;
|
||||
sendResponse(ZoneInfoMessage(zone.Number, empire_status=true, lock_time=0L))
|
||||
val popBO = 0
|
||||
val popTR = zone.Players.count(_.faction == PlanetSideEmpire.TR)
|
||||
val popNC = zone.Players.count(_.faction == PlanetSideEmpire.NC)
|
||||
val popVS = zone.Players.count(_.faction == PlanetSideEmpire.VS)
|
||||
sendResponse(ZonePopulationUpdateMessage(zone.Number, 414, 138, popTR, 138, popNC, 138, popVS, 138, popBO))
|
||||
|
||||
case GalaxyResponse.LogStatusChange(name) if avatar.people.friend.exists(_.name.equals(name)) =>
|
||||
avatarActor ! AvatarActor.MemberListRequest(MemberAction.UpdateFriend, name)
|
||||
|
||||
case GalaxyResponse.SendResponse(msg) =>
|
||||
sendResponse(msg)
|
||||
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,244 @@
|
|||
// Copyright (c) 2024 PSForever
|
||||
package net.psforever.actors.session.normal
|
||||
|
||||
import akka.actor.ActorContext
|
||||
import net.psforever.actors.session.support.{LocalHandlerFunctions, SessionData, SessionLocalHandlers}
|
||||
import net.psforever.objects.ce.Deployable
|
||||
import net.psforever.objects.vehicles.MountableWeapons
|
||||
import net.psforever.objects.{BoomerDeployable, ExplosiveDeployable, TelepadDeployable, Tool, TurretDeployable}
|
||||
import net.psforever.packet.game.{ChatMsg, DeployableObjectsInfoMessage, GenericActionMessage, GenericObjectActionMessage, GenericObjectStateMsg, HackMessage, HackState, InventoryStateMessage, ObjectAttachMessage, ObjectCreateMessage, ObjectDeleteMessage, ObjectDetachMessage, OrbitalShuttleTimeMsg, PadAndShuttlePair, PlanetsideAttributeMessage, ProximityTerminalUseMessage, SetEmpireMessage, TriggerEffectMessage, TriggerSoundMessage, TriggeredSound, VehicleStateMessage}
|
||||
import net.psforever.services.Service
|
||||
import net.psforever.services.local.LocalResponse
|
||||
import net.psforever.types.{ChatMessageType, PlanetSideGUID, Vector3}
|
||||
|
||||
class LocalHandlerLogic(val ops: SessionLocalHandlers) extends LocalHandlerFunctions {
|
||||
def sessionLogic: SessionData = ops.sessionLogic
|
||||
|
||||
implicit val context: ActorContext = ops.context
|
||||
|
||||
/* response handlers */
|
||||
|
||||
/**
|
||||
* na
|
||||
* @param toChannel na
|
||||
* @param guid na
|
||||
* @param reply na
|
||||
*/
|
||||
def handle(toChannel: String, guid: PlanetSideGUID, reply: LocalResponse.Response): Unit = {
|
||||
val resolvedPlayerGuid = if (player.HasGUID) {
|
||||
player.GUID
|
||||
} else {
|
||||
Service.defaultPlayerGUID
|
||||
}
|
||||
val isNotSameTarget = resolvedPlayerGuid != guid
|
||||
reply match {
|
||||
case LocalResponse.DeployableMapIcon(behavior, deployInfo) if isNotSameTarget =>
|
||||
sendResponse(DeployableObjectsInfoMessage(behavior, deployInfo))
|
||||
|
||||
case LocalResponse.DeployableUIFor(item) =>
|
||||
sessionLogic.general.updateDeployableUIElements(avatar.deployables.UpdateUIElement(item))
|
||||
|
||||
case LocalResponse.Detonate(dguid, _: BoomerDeployable) =>
|
||||
sendResponse(TriggerEffectMessage(dguid, "detonate_boomer"))
|
||||
sendResponse(PlanetsideAttributeMessage(dguid, attribute_type=29, attribute_value=1))
|
||||
sendResponse(ObjectDeleteMessage(dguid, unk1=0))
|
||||
|
||||
case LocalResponse.Detonate(dguid, _: ExplosiveDeployable) =>
|
||||
sendResponse(GenericObjectActionMessage(dguid, code=19))
|
||||
sendResponse(PlanetsideAttributeMessage(dguid, attribute_type=29, attribute_value=1))
|
||||
sendResponse(ObjectDeleteMessage(dguid, unk1=0))
|
||||
|
||||
case LocalResponse.Detonate(_, obj) =>
|
||||
log.warn(s"LocalResponse.Detonate: ${obj.Definition.Name} not configured to explode correctly")
|
||||
|
||||
case LocalResponse.DoorOpens(doorGuid) if isNotSameTarget =>
|
||||
sendResponse(GenericObjectStateMsg(doorGuid, state=16))
|
||||
|
||||
case LocalResponse.DoorCloses(doorGuid) => //door closes for everyone
|
||||
sendResponse(GenericObjectStateMsg(doorGuid, state=17))
|
||||
|
||||
case LocalResponse.EliminateDeployable(obj: TurretDeployable, dguid, _, _) if obj.Destroyed =>
|
||||
sendResponse(ObjectDeleteMessage(dguid, unk1=0))
|
||||
|
||||
case LocalResponse.EliminateDeployable(obj: TurretDeployable, dguid, pos, _) =>
|
||||
obj.Destroyed = true
|
||||
DeconstructDeployable(
|
||||
obj,
|
||||
dguid,
|
||||
pos,
|
||||
obj.Orientation,
|
||||
deletionType= if (obj.MountPoints.isEmpty) { 2 } else { 1 }
|
||||
)
|
||||
|
||||
case LocalResponse.EliminateDeployable(obj: ExplosiveDeployable, dguid, _, _)
|
||||
if obj.Destroyed || obj.Jammed || obj.Health == 0 =>
|
||||
sendResponse(ObjectDeleteMessage(dguid, unk1=0))
|
||||
|
||||
case LocalResponse.EliminateDeployable(obj: ExplosiveDeployable, dguid, pos, effect) =>
|
||||
obj.Destroyed = true
|
||||
DeconstructDeployable(obj, dguid, pos, obj.Orientation, effect)
|
||||
|
||||
case LocalResponse.EliminateDeployable(obj: TelepadDeployable, dguid, _, _) if obj.Active && obj.Destroyed =>
|
||||
//if active, deactivate
|
||||
obj.Active = false
|
||||
sendResponse(GenericObjectActionMessage(dguid, code=29))
|
||||
sendResponse(GenericObjectActionMessage(dguid, code=30))
|
||||
//standard deployable elimination behavior
|
||||
sendResponse(ObjectDeleteMessage(dguid, unk1=0))
|
||||
|
||||
case LocalResponse.EliminateDeployable(obj: TelepadDeployable, dguid, pos, _) if obj.Active =>
|
||||
//if active, deactivate
|
||||
obj.Active = false
|
||||
sendResponse(GenericObjectActionMessage(dguid, code=29))
|
||||
sendResponse(GenericObjectActionMessage(dguid, code=30))
|
||||
//standard deployable elimination behavior
|
||||
obj.Destroyed = true
|
||||
DeconstructDeployable(obj, dguid, pos, obj.Orientation, deletionType=2)
|
||||
|
||||
case LocalResponse.EliminateDeployable(obj: TelepadDeployable, dguid, _, _) if obj.Destroyed =>
|
||||
//standard deployable elimination behavior
|
||||
sendResponse(ObjectDeleteMessage(dguid, unk1=0))
|
||||
|
||||
case LocalResponse.EliminateDeployable(obj: TelepadDeployable, dguid, pos, _) =>
|
||||
//standard deployable elimination behavior
|
||||
obj.Destroyed = true
|
||||
DeconstructDeployable(obj, dguid, pos, obj.Orientation, deletionType=2)
|
||||
|
||||
case LocalResponse.EliminateDeployable(obj, dguid, _, _) if obj.Destroyed =>
|
||||
sendResponse(ObjectDeleteMessage(dguid, unk1=0))
|
||||
|
||||
case LocalResponse.EliminateDeployable(obj, dguid, pos, effect) =>
|
||||
obj.Destroyed = true
|
||||
DeconstructDeployable(obj, dguid, pos, obj.Orientation, effect)
|
||||
|
||||
case LocalResponse.SendHackMessageHackCleared(targetGuid, unk1, unk2) =>
|
||||
sendResponse(HackMessage(unk1=0, targetGuid, guid, progress=0, unk1, HackState.HackCleared, unk2))
|
||||
|
||||
case LocalResponse.HackObject(targetGuid, unk1, unk2) =>
|
||||
sessionLogic.general.hackObject(targetGuid, unk1, unk2)
|
||||
|
||||
case LocalResponse.PlanetsideAttribute(targetGuid, attributeType, attributeValue) =>
|
||||
sessionLogic.general.sendPlanetsideAttributeMessage(targetGuid, attributeType, attributeValue)
|
||||
|
||||
case LocalResponse.GenericObjectAction(targetGuid, actionNumber) =>
|
||||
sendResponse(GenericObjectActionMessage(targetGuid, actionNumber))
|
||||
|
||||
case LocalResponse.GenericActionMessage(actionNumber) =>
|
||||
sendResponse(GenericActionMessage(actionNumber))
|
||||
|
||||
case LocalResponse.ChatMessage(msg) =>
|
||||
sendResponse(msg)
|
||||
|
||||
case LocalResponse.SendPacket(packet) =>
|
||||
sendResponse(packet)
|
||||
|
||||
case LocalResponse.LluSpawned(llu) =>
|
||||
// Create LLU on client
|
||||
sendResponse(ObjectCreateMessage(
|
||||
llu.Definition.ObjectId,
|
||||
llu.GUID,
|
||||
llu.Definition.Packet.ConstructorData(llu).get
|
||||
))
|
||||
sendResponse(TriggerSoundMessage(TriggeredSound.LLUMaterialize, llu.Position, unk=20, volume=0.8000001f))
|
||||
|
||||
case LocalResponse.LluDespawned(lluGuid, position) =>
|
||||
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
|
||||
sessionLogic.general.specialItemSlotGuid.collect { case guid if guid == lluGuid =>
|
||||
sessionLogic.general.specialItemSlotGuid = None
|
||||
player.Carrying = None
|
||||
}
|
||||
|
||||
case LocalResponse.ObjectDelete(objectGuid, unk) if isNotSameTarget =>
|
||||
sendResponse(ObjectDeleteMessage(objectGuid, unk))
|
||||
|
||||
case LocalResponse.ProximityTerminalEffect(object_guid, true) =>
|
||||
sendResponse(ProximityTerminalUseMessage(Service.defaultPlayerGUID, object_guid, unk=true))
|
||||
|
||||
case LocalResponse.ProximityTerminalEffect(objectGuid, false) =>
|
||||
sendResponse(ProximityTerminalUseMessage(Service.defaultPlayerGUID, objectGuid, unk=false))
|
||||
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) =>
|
||||
sessionLogic.general.useRouterTelepadEffect(passengerGuid, srcGuid, destGuid)
|
||||
|
||||
case LocalResponse.SendResponse(msg) =>
|
||||
sendResponse(msg)
|
||||
|
||||
case LocalResponse.SetEmpire(objectGuid, empire) =>
|
||||
sendResponse(SetEmpireMessage(objectGuid, empire))
|
||||
|
||||
case LocalResponse.ShuttleEvent(ev) =>
|
||||
val msg = OrbitalShuttleTimeMsg(
|
||||
ev.u1,
|
||||
ev.u2,
|
||||
ev.t1,
|
||||
ev.t2,
|
||||
ev.t3,
|
||||
pairs=ev.pairs.map { case ((a, b), c) => PadAndShuttlePair(a, b, c) }
|
||||
)
|
||||
sendResponse(msg)
|
||||
|
||||
case LocalResponse.ShuttleDock(pguid, sguid, slot) =>
|
||||
sendResponse(ObjectAttachMessage(pguid, sguid, slot))
|
||||
|
||||
case LocalResponse.ShuttleUndock(pguid, sguid, pos, orient) =>
|
||||
sendResponse(ObjectDetachMessage(pguid, sguid, pos, orient))
|
||||
|
||||
case LocalResponse.ShuttleState(sguid, pos, orient, state) =>
|
||||
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) =>
|
||||
sessionLogic.general.toggleTeleportSystem(router, systemPlan)
|
||||
|
||||
case LocalResponse.TriggerEffect(targetGuid, effect, effectInfo, triggerLocation) =>
|
||||
sendResponse(TriggerEffectMessage(targetGuid, effect, effectInfo, triggerLocation))
|
||||
|
||||
case LocalResponse.TriggerSound(sound, pos, unk, volume) =>
|
||||
sendResponse(TriggerSoundMessage(sound, pos, unk, volume))
|
||||
|
||||
case LocalResponse.UpdateForceDomeStatus(buildingGuid, true) =>
|
||||
sendResponse(GenericObjectActionMessage(buildingGuid, 11))
|
||||
|
||||
case LocalResponse.UpdateForceDomeStatus(buildingGuid, false) =>
|
||||
sendResponse(GenericObjectActionMessage(buildingGuid, 12))
|
||||
|
||||
case LocalResponse.RechargeVehicleWeapon(vehicleGuid, weaponGuid) if resolvedPlayerGuid == guid =>
|
||||
continent.GUID(vehicleGuid)
|
||||
.collect { case vehicle: MountableWeapons => (vehicle, vehicle.PassengerInSeat(player)) }
|
||||
.collect { case (vehicle, Some(seat_num)) => vehicle.WeaponControlledFromSeat(seat_num) }
|
||||
.getOrElse(Set.empty)
|
||||
.collect { case weapon: Tool if weapon.GUID == weaponGuid =>
|
||||
sendResponse(InventoryStateMessage(weapon.AmmoSlot.Box.GUID, weapon.GUID, weapon.Magazine))
|
||||
}
|
||||
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
|
||||
/* support functions */
|
||||
|
||||
/**
|
||||
* Common behavior for deconstructing deployables in the game environment.
|
||||
* @param obj the deployable
|
||||
* @param guid the globally unique identifier for the deployable
|
||||
* @param pos the previous position of the deployable
|
||||
* @param orient the previous orientation of the deployable
|
||||
* @param deletionType the value passed to `ObjectDeleteMessage` concerning the deconstruction animation
|
||||
*/
|
||||
def DeconstructDeployable(
|
||||
obj: Deployable,
|
||||
guid: PlanetSideGUID,
|
||||
pos: Vector3,
|
||||
orient: Vector3,
|
||||
deletionType: Int
|
||||
): Unit = {
|
||||
sendResponse(TriggerEffectMessage("spawn_object_failed_effect", pos, orient))
|
||||
sendResponse(PlanetsideAttributeMessage(guid, 29, 1)) //make deployable vanish
|
||||
sendResponse(ObjectDeleteMessage(guid, deletionType))
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,516 @@
|
|||
// Copyright (c) 2024 PSForever
|
||||
package net.psforever.actors.session.normal
|
||||
|
||||
import akka.actor.{ActorContext, typed}
|
||||
import net.psforever.actors.session.AvatarActor
|
||||
import net.psforever.actors.session.support.{MountHandlerFunctions, SessionData, SessionMountHandlers}
|
||||
import net.psforever.actors.zone.ZoneActor
|
||||
import net.psforever.objects.{GlobalDefinitions, PlanetSideGameObject, Player, Vehicle, Vehicles}
|
||||
import net.psforever.objects.definition.{BasicDefinition, ObjectDefinition}
|
||||
import net.psforever.objects.serverobject.affinity.FactionAffinity
|
||||
import net.psforever.objects.serverobject.environment.interaction.ResetAllEnvironmentInteractions
|
||||
import net.psforever.objects.serverobject.hackable.GenericHackables
|
||||
import net.psforever.objects.serverobject.mount.Mountable
|
||||
import net.psforever.objects.serverobject.terminals.implant.ImplantTerminalMech
|
||||
import net.psforever.objects.serverobject.turret.{FacilityTurret, WeaponTurret}
|
||||
import net.psforever.objects.vehicles.{AccessPermissionGroup, CargoBehavior}
|
||||
import net.psforever.objects.vital.InGameHistory
|
||||
import net.psforever.packet.game.{ChatMsg, DelayedPathMountMsg, DismountVehicleCargoMsg, DismountVehicleMsg, GenericObjectActionMessage, MountVehicleCargoMsg, MountVehicleMsg, ObjectAttachMessage, ObjectDetachMessage, PlanetsideAttributeMessage, PlayerStasisMessage, PlayerStateShiftMessage, ShiftState}
|
||||
import net.psforever.services.Service
|
||||
import net.psforever.services.local.{LocalAction, LocalServiceMessage}
|
||||
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
|
||||
import net.psforever.types.{BailType, ChatMessageType, PlanetSideGUID, Vector3}
|
||||
|
||||
import scala.concurrent.duration._
|
||||
|
||||
class MountHandlerLogic(val ops: SessionMountHandlers) extends MountHandlerFunctions {
|
||||
def sessionLogic: SessionData = ops.sessionLogic
|
||||
|
||||
implicit val context: ActorContext = ops.context
|
||||
|
||||
private val avatarActor: typed.ActorRef[AvatarActor.Command] = ops.avatarActor
|
||||
|
||||
/* packets */
|
||||
|
||||
def handleMountVehicle(pkt: MountVehicleMsg): Unit = {
|
||||
val MountVehicleMsg(_, mountable_guid, entry_point) = pkt
|
||||
sessionLogic.validObject(mountable_guid, decorator = "MountVehicle").collect {
|
||||
case obj: Mountable =>
|
||||
obj.Actor ! Mountable.TryMount(player, entry_point)
|
||||
case _ =>
|
||||
log.error(s"MountVehicleMsg: object ${mountable_guid.guid} not a mountable thing, ${player.Name}")
|
||||
}
|
||||
}
|
||||
|
||||
def handleDismountVehicle(pkt: DismountVehicleMsg): Unit = {
|
||||
val DismountVehicleMsg(player_guid, bailType, wasKickedByDriver) = pkt
|
||||
val dError: (String, Player)=> Unit = dismountError(bailType, wasKickedByDriver)
|
||||
//TODO optimize this later
|
||||
//common warning for this section
|
||||
if (player.GUID == player_guid) {
|
||||
//normally disembarking from a mount
|
||||
(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
|
||||
case _ => out //arrangement "may" be permissible
|
||||
}
|
||||
case out @ Some(_: Mountable) =>
|
||||
out
|
||||
case _ =>
|
||||
dError(s"DismountVehicleMsg: player ${player.Name} not considered seated in a mountable entity", player)
|
||||
None
|
||||
}) match {
|
||||
case Some(obj: Mountable) =>
|
||||
obj.PassengerInSeat(player) match {
|
||||
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
|
||||
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.
|
||||
//todo: kick cargo passengers out. To be added after PR #216 is merged
|
||||
obj match {
|
||||
case v: Vehicle
|
||||
if bailType == BailType.Bailed &&
|
||||
v.SeatPermissionGroup(seat_num).contains(AccessPermissionGroup.Driver) &&
|
||||
v.isFlying =>
|
||||
v.Actor ! Vehicle.Deconstruct(None) //immediate deconstruction
|
||||
case _ => ;
|
||||
}
|
||||
|
||||
case None =>
|
||||
dError(s"DismountVehicleMsg: can not find where player ${player.Name}_guid is seated in mountable ${player.VehicleSeated}", player)
|
||||
}
|
||||
case _ =>
|
||||
dError(s"DismountVehicleMsg: can not find mountable entity ${player.VehicleSeated}", player)
|
||||
}
|
||||
} else {
|
||||
//kicking someone else out of a mount; need to own that mount/mountable
|
||||
val dWarn: (String, Player)=> Unit = dismountWarning(bailType, wasKickedByDriver)
|
||||
player.avatar.vehicle match {
|
||||
case Some(obj_guid) =>
|
||||
(
|
||||
(
|
||||
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)
|
||||
case (mount @ Some(_: Mountable), tplayer) =>
|
||||
(mount, tplayer)
|
||||
case _ =>
|
||||
(None, None)
|
||||
}) match {
|
||||
case (Some(obj: Mountable), Some(tplayer: Player)) =>
|
||||
obj.PassengerInSeat(tplayer) match {
|
||||
case Some(seat_num) =>
|
||||
obj.Actor ! Mountable.TryDismount(tplayer, seat_num, bailType)
|
||||
case None =>
|
||||
dError(s"DismountVehicleMsg: can not find where other player ${tplayer.Name} is seated in mountable $obj_guid", tplayer)
|
||||
}
|
||||
case (None, _) =>
|
||||
dWarn(s"DismountVehicleMsg: ${player.Name} can not find his vehicle", player)
|
||||
case (_, None) =>
|
||||
dWarn(s"DismountVehicleMsg: player $player_guid could not be found to kick, ${player.Name}", player)
|
||||
case _ =>
|
||||
dWarn(s"DismountVehicleMsg: object is either not a Mountable or not a Player", player)
|
||||
}
|
||||
case None =>
|
||||
dWarn(s"DismountVehicleMsg: ${player.Name} does not own a vehicle", player)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def handleMountVehicleCargo(pkt: MountVehicleCargoMsg): Unit = {
|
||||
val MountVehicleCargoMsg(_, cargo_guid, carrier_guid, _) = pkt
|
||||
(continent.GUID(cargo_guid), continent.GUID(carrier_guid)) match {
|
||||
case (Some(cargo: Vehicle), Some(carrier: Vehicle)) =>
|
||||
carrier.CargoHolds.find({ case (_, hold) => !hold.isOccupied }) match {
|
||||
case Some((mountPoint, _)) =>
|
||||
cargo.Actor ! CargoBehavior.StartCargoMounting(carrier_guid, mountPoint)
|
||||
case _ =>
|
||||
log.warn(
|
||||
s"MountVehicleCargoMsg: ${player.Name} trying to load cargo into a ${carrier.Definition.Name} which oes not have a cargo hold"
|
||||
)
|
||||
}
|
||||
case (None, _) | (Some(_), None) =>
|
||||
log.warn(
|
||||
s"MountVehicleCargoMsg: ${player.Name} lost a vehicle while working with cargo - either $carrier_guid or $cargo_guid"
|
||||
)
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
|
||||
def handleDismountVehicleCargo(pkt: DismountVehicleCargoMsg): Unit = {
|
||||
val DismountVehicleCargoMsg(_, cargo_guid, bailed, _, kicked) = pkt
|
||||
continent.GUID(cargo_guid) match {
|
||||
case Some(cargo: Vehicle) =>
|
||||
cargo.Actor ! CargoBehavior.StartCargoDismounting(bailed || kicked)
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
|
||||
/* response handlers */
|
||||
|
||||
/**
|
||||
* na
|
||||
*
|
||||
* @param tplayer na
|
||||
* @param reply na
|
||||
*/
|
||||
def handle(tplayer: Player, reply: Mountable.Exchange): Unit = {
|
||||
reply match {
|
||||
case Mountable.CanMount(obj: ImplantTerminalMech, seatNumber, _) =>
|
||||
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
|
||||
log.info(s"${player.Name} mounts an implant terminal")
|
||||
sessionLogic.terminals.CancelAllProximityUnits()
|
||||
MountingAction(tplayer, obj, seatNumber)
|
||||
sessionLogic.keepAliveFunc = sessionLogic.keepAlivePersistence
|
||||
|
||||
case Mountable.CanMount(obj: Vehicle, seatNumber, _)
|
||||
if obj.Definition == GlobalDefinitions.orbital_shuttle =>
|
||||
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_mount")
|
||||
log.info(s"${player.Name} mounts the orbital shuttle")
|
||||
sessionLogic.terminals.CancelAllProximityUnits()
|
||||
MountingAction(tplayer, obj, seatNumber)
|
||||
sessionLogic.keepAliveFunc = sessionLogic.keepAlivePersistence
|
||||
|
||||
case Mountable.CanMount(obj: Vehicle, seatNumber, _)
|
||||
if obj.Definition == GlobalDefinitions.ant =>
|
||||
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
|
||||
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))
|
||||
sessionLogic.general.accessContainer(obj)
|
||||
tplayer.Actor ! ResetAllEnvironmentInteractions
|
||||
MountingAction(tplayer, obj, seatNumber)
|
||||
|
||||
case Mountable.CanMount(obj: Vehicle, seatNumber, _)
|
||||
if obj.Definition == GlobalDefinitions.quadstealth =>
|
||||
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
|
||||
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))
|
||||
sessionLogic.general.accessContainer(obj)
|
||||
tplayer.Actor ! ResetAllEnvironmentInteractions
|
||||
MountingAction(tplayer, obj, seatNumber)
|
||||
|
||||
case Mountable.CanMount(obj: Vehicle, seatNumber, _)
|
||||
if seatNumber == 0 && obj.Definition.MaxCapacitor > 0 =>
|
||||
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
|
||||
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))
|
||||
sessionLogic.general.accessContainer(obj)
|
||||
ops.updateWeaponAtSeatPosition(obj, seatNumber)
|
||||
tplayer.Actor ! ResetAllEnvironmentInteractions
|
||||
MountingAction(tplayer, obj, seatNumber)
|
||||
|
||||
case Mountable.CanMount(obj: Vehicle, seatNumber, _)
|
||||
if seatNumber == 0 =>
|
||||
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
|
||||
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))
|
||||
sessionLogic.general.accessContainer(obj)
|
||||
ops.updateWeaponAtSeatPosition(obj, seatNumber)
|
||||
tplayer.Actor ! ResetAllEnvironmentInteractions
|
||||
MountingAction(tplayer, obj, seatNumber)
|
||||
|
||||
case Mountable.CanMount(obj: Vehicle, seatNumber, _)
|
||||
if obj.Definition.MaxCapacitor > 0 =>
|
||||
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_mount")
|
||||
log.info(s"${player.Name} mounts ${
|
||||
obj.SeatPermissionGroup(seatNumber) match {
|
||||
case Some(seatType) => s"a $seatType seat (#$seatNumber)"
|
||||
case None => "a seat"
|
||||
}
|
||||
} of the ${obj.Definition.Name}")
|
||||
val obj_guid: PlanetSideGUID = obj.GUID
|
||||
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))
|
||||
sessionLogic.general.accessContainer(obj)
|
||||
ops.updateWeaponAtSeatPosition(obj, seatNumber)
|
||||
sessionLogic.keepAliveFunc = sessionLogic.keepAlivePersistence
|
||||
tplayer.Actor ! ResetAllEnvironmentInteractions
|
||||
MountingAction(tplayer, obj, seatNumber)
|
||||
|
||||
case Mountable.CanMount(obj: Vehicle, seatNumber, _) =>
|
||||
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_mount")
|
||||
log.info(s"${player.Name} mounts the ${
|
||||
obj.SeatPermissionGroup(seatNumber) match {
|
||||
case Some(seatType) => s"a $seatType seat (#$seatNumber)"
|
||||
case None => "a seat"
|
||||
}
|
||||
} of the ${obj.Definition.Name}")
|
||||
val obj_guid: PlanetSideGUID = obj.GUID
|
||||
sessionLogic.terminals.CancelAllProximityUnits()
|
||||
sendResponse(PlanetsideAttributeMessage(obj_guid, attribute_type=0, obj.Health))
|
||||
sendResponse(PlanetsideAttributeMessage(obj_guid, obj.Definition.shieldUiAttribute, obj.Shields))
|
||||
sessionLogic.general.accessContainer(obj)
|
||||
ops.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 =>
|
||||
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))
|
||||
ops.updateWeaponAtSeatPosition(obj, seatNumber)
|
||||
MountingAction(tplayer, obj, seatNumber)
|
||||
|
||||
case Mountable.CanMount(obj: FacilityTurret, seatNumber, _)
|
||||
if !obj.isUpgrading || System.currentTimeMillis() - GenericHackables.getTurretUpgradeTime >= 1500L =>
|
||||
obj.setMiddleOfUpgrade(false)
|
||||
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_mount")
|
||||
log.info(s"${player.Name} mounts the ${obj.Definition.Name}")
|
||||
sendResponse(PlanetsideAttributeMessage(obj.GUID, attribute_type=0, obj.Health))
|
||||
ops.updateWeaponAtSeatPosition(obj, seatNumber)
|
||||
MountingAction(tplayer, obj, seatNumber)
|
||||
|
||||
case Mountable.CanMount(obj: FacilityTurret, _, _) =>
|
||||
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, _) =>
|
||||
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))
|
||||
ops.updateWeaponAtSeatPosition(obj, seatNumber)
|
||||
MountingAction(tplayer, obj, seatNumber)
|
||||
|
||||
case Mountable.CanMount(obj: Mountable, _, _) =>
|
||||
log.warn(s"MountVehicleMsg: $obj is some kind of mountable object but nothing will happen for ${player.Name}")
|
||||
|
||||
case Mountable.CanDismount(obj: ImplantTerminalMech, seatNum, _) =>
|
||||
log.info(s"${tplayer.Name} dismounts the implant terminal")
|
||||
DismountAction(tplayer, obj, seatNum)
|
||||
|
||||
case Mountable.CanDismount(obj: Vehicle, _, mountPoint)
|
||||
if obj.Definition == GlobalDefinitions.orbital_shuttle && obj.MountedIn.nonEmpty =>
|
||||
//dismount to hart lobby
|
||||
val pguid = player.GUID
|
||||
log.info(s"${tplayer.Name} dismounts the orbital shuttle into the lobby")
|
||||
val sguid = obj.GUID
|
||||
val (pos, zang) = Vehicles.dismountShuttle(obj, mountPoint)
|
||||
tplayer.Position = pos
|
||||
sendResponse(DelayedPathMountMsg(pguid, sguid, u1=60, u2=true))
|
||||
continent.LocalEvents ! LocalServiceMessage(
|
||||
continent.id,
|
||||
LocalAction.SendResponse(ObjectDetachMessage(sguid, pguid, pos, roll=0, pitch=0, zang))
|
||||
)
|
||||
sessionLogic.keepAliveFunc = sessionLogic.zoning.NormalKeepAlive
|
||||
|
||||
case Mountable.CanDismount(obj: Vehicle, seatNum, _)
|
||||
if obj.Definition == GlobalDefinitions.orbital_shuttle =>
|
||||
//get ready for orbital drop
|
||||
val pguid = player.GUID
|
||||
val events = continent.VehicleEvents
|
||||
log.info(s"${player.Name} is prepped for dropping")
|
||||
DismountAction(tplayer, obj, seatNum)
|
||||
continent.actor ! ZoneActor.RemoveFromBlockMap(player) //character doesn't need it
|
||||
//DismountAction(...) uses vehicle service, so use that service to coordinate the remainder of the messages
|
||||
events ! VehicleServiceMessage(
|
||||
player.Name,
|
||||
VehicleAction.SendResponse(Service.defaultPlayerGUID, PlayerStasisMessage(pguid)) //the stasis message
|
||||
)
|
||||
//when the player dismounts, they will be positioned where the shuttle was when it disappeared in the sky
|
||||
//the player will fall to the ground and is perfectly vulnerable in this state
|
||||
//additionally, our player must exist in the current zone
|
||||
//having no in-game avatar target will throw us out of the map screen when deploying and cause softlock
|
||||
events ! VehicleServiceMessage(
|
||||
player.Name,
|
||||
VehicleAction.SendResponse(
|
||||
Service.defaultPlayerGUID,
|
||||
PlayerStateShiftMessage(ShiftState(unk=0, obj.Position, obj.Orientation.z, vel=None)) //cower in the shuttle bay
|
||||
)
|
||||
)
|
||||
events ! VehicleServiceMessage(
|
||||
continent.id,
|
||||
VehicleAction.SendResponse(pguid, GenericObjectActionMessage(pguid, code=9)) //conceal the player
|
||||
)
|
||||
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}")
|
||||
sessionLogic.general.unaccessContainer(obj)
|
||||
DismountAction(tplayer, obj, seatNum)
|
||||
obj.Actor ! Vehicle.Deconstruct()
|
||||
|
||||
case Mountable.CanDismount(obj: Vehicle, seatNum, _)
|
||||
if tplayer.GUID == player.GUID =>
|
||||
//disembarking self
|
||||
log.info(s"${player.Name} dismounts the ${obj.Definition.Name}'s ${
|
||||
obj.SeatPermissionGroup(seatNum) match {
|
||||
case Some(AccessPermissionGroup.Driver) => "driver seat"
|
||||
case Some(seatType) => s"$seatType seat (#$seatNum)"
|
||||
case None => "seat"
|
||||
}
|
||||
}")
|
||||
sessionLogic.vehicles.ConditionalDriverVehicleControl(obj)
|
||||
sessionLogic.general.unaccessContainer(obj)
|
||||
DismountVehicleAction(tplayer, obj, seatNum)
|
||||
|
||||
case Mountable.CanDismount(obj: Vehicle, seat_num, _) =>
|
||||
continent.VehicleEvents ! VehicleServiceMessage(
|
||||
continent.id,
|
||||
VehicleAction.KickPassenger(tplayer.GUID, seat_num, unk2=true, obj.GUID)
|
||||
)
|
||||
|
||||
case Mountable.CanDismount(obj: PlanetSideGameObject with PlanetSideGameObject with Mountable with FactionAffinity with InGameHistory, seatNum, _) =>
|
||||
log.info(s"${tplayer.Name} dismounts a ${obj.Definition.asInstanceOf[ObjectDefinition].Name}")
|
||||
DismountAction(tplayer, obj, seatNum)
|
||||
|
||||
case Mountable.CanDismount(obj: Mountable, _, _) =>
|
||||
log.warn(s"DismountVehicleMsg: $obj is some dismountable object but nothing will happen for ${player.Name}")
|
||||
|
||||
case Mountable.CanNotMount(obj: Vehicle, seatNumber) =>
|
||||
log.warn(s"MountVehicleMsg: ${tplayer.Name} attempted to mount $obj's seat $seatNumber, but was not allowed")
|
||||
obj.GetSeatFromMountPoint(seatNumber).collect {
|
||||
case seatNum if obj.SeatPermissionGroup(seatNum).contains(AccessPermissionGroup.Driver) =>
|
||||
sendResponse(
|
||||
ChatMsg(ChatMessageType.CMT_OPEN, wideContents=false, recipient="", "You are not the driver of this vehicle.", note=None)
|
||||
)
|
||||
}
|
||||
|
||||
case Mountable.CanNotMount(obj: Mountable, seatNumber) =>
|
||||
log.warn(s"MountVehicleMsg: ${tplayer.Name} attempted to mount $obj's seat $seatNumber, but was not allowed")
|
||||
|
||||
case Mountable.CanNotDismount(obj, seatNum) =>
|
||||
log.warn(s"DismountVehicleMsg: ${tplayer.Name} attempted to dismount $obj's mount $seatNum, but was not allowed")
|
||||
}
|
||||
}
|
||||
|
||||
/* support functions */
|
||||
|
||||
private def dismountWarning(
|
||||
bailAs: BailType.Value,
|
||||
kickedByDriver: Boolean
|
||||
)
|
||||
(
|
||||
note: String,
|
||||
player: Player
|
||||
): Unit = {
|
||||
log.warn(note)
|
||||
player.VehicleSeated = None
|
||||
sendResponse(DismountVehicleMsg(player.GUID, bailAs, kickedByDriver))
|
||||
}
|
||||
|
||||
private def dismountError(
|
||||
bailAs: BailType.Value,
|
||||
kickedByDriver: Boolean
|
||||
)
|
||||
(
|
||||
note: String,
|
||||
player: Player
|
||||
): Unit = {
|
||||
log.error(s"$note; some vehicle might not know that ${player.Name} is no longer sitting in it")
|
||||
player.VehicleSeated = None
|
||||
sendResponse(DismountVehicleMsg(player.GUID, bailAs, kickedByDriver))
|
||||
}
|
||||
|
||||
/**
|
||||
* Common activities/procedure when a player mounts a valid object.
|
||||
* @param tplayer the player
|
||||
* @param obj the mountable object
|
||||
* @param seatNum the mount into which the player is mounting
|
||||
*/
|
||||
def MountingAction(tplayer: Player, obj: PlanetSideGameObject with FactionAffinity with InGameHistory, seatNum: Int): Unit = {
|
||||
val playerGuid: PlanetSideGUID = tplayer.GUID
|
||||
val objGuid: PlanetSideGUID = obj.GUID
|
||||
sessionLogic.actionsToCancel()
|
||||
avatarActor ! AvatarActor.DeactivateActiveImplants()
|
||||
avatarActor ! AvatarActor.SuspendStaminaRegeneration(3.seconds)
|
||||
sendResponse(ObjectAttachMessage(objGuid, playerGuid, seatNum))
|
||||
continent.VehicleEvents ! VehicleServiceMessage(
|
||||
continent.id,
|
||||
VehicleAction.MountVehicle(playerGuid, objGuid, seatNum)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Common activities/procedure when a player dismounts a valid mountable object.
|
||||
* @param tplayer the player
|
||||
* @param obj the mountable object
|
||||
* @param seatNum the mount out of which which the player is disembarking
|
||||
*/
|
||||
def DismountVehicleAction(tplayer: Player, obj: PlanetSideGameObject with FactionAffinity with InGameHistory, seatNum: Int): Unit = {
|
||||
DismountAction(tplayer, obj, seatNum)
|
||||
//until vehicles maintain synchronized momentum without a driver
|
||||
obj match {
|
||||
case v: Vehicle
|
||||
if seatNum == 0 && Vector3.MagnitudeSquared(v.Velocity.getOrElse(Vector3.Zero)) > 0f =>
|
||||
sessionLogic.vehicles.serverVehicleControlVelocity.collect { _ =>
|
||||
sessionLogic.vehicles.ServerVehicleOverrideStop(v)
|
||||
}
|
||||
v.Velocity = Vector3.Zero
|
||||
continent.VehicleEvents ! VehicleServiceMessage(
|
||||
continent.id,
|
||||
VehicleAction.VehicleState(
|
||||
tplayer.GUID,
|
||||
v.GUID,
|
||||
unk1 = 0,
|
||||
v.Position,
|
||||
v.Orientation,
|
||||
vel = None,
|
||||
v.Flying,
|
||||
unk3 = 0,
|
||||
unk4 = 0,
|
||||
wheel_direction = 15,
|
||||
unk5 = false,
|
||||
unk6 = v.Cloaked
|
||||
)
|
||||
)
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Common activities/procedure when a player dismounts a valid mountable object.
|
||||
* @param tplayer the player
|
||||
* @param obj the mountable object
|
||||
* @param seatNum the mount out of which which the player is disembarking
|
||||
*/
|
||||
def DismountAction(tplayer: Player, obj: PlanetSideGameObject with FactionAffinity with InGameHistory, seatNum: Int): Unit = {
|
||||
val playerGuid: PlanetSideGUID = tplayer.GUID
|
||||
tplayer.ContributionFrom(obj)
|
||||
sessionLogic.keepAliveFunc = sessionLogic.zoning.NormalKeepAlive
|
||||
val bailType = if (tplayer.BailProtection) {
|
||||
BailType.Bailed
|
||||
} else {
|
||||
BailType.Normal
|
||||
}
|
||||
sendResponse(DismountVehicleMsg(playerGuid, bailType, wasKickedByDriver = false))
|
||||
continent.VehicleEvents ! VehicleServiceMessage(
|
||||
continent.id,
|
||||
VehicleAction.DismountVehicle(playerGuid, bailType, unk2 = false)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,11 @@
|
|||
// Copyright (c) 2024 PSForever
|
||||
package net.psforever.actors.session.support
|
||||
package net.psforever.actors.session.normal
|
||||
|
||||
import akka.actor.Actor.Receive
|
||||
import akka.actor.{ActorContext, ActorRef, typed}
|
||||
import net.psforever.actors.net.MiddlewareActor
|
||||
import akka.actor.ActorRef
|
||||
//
|
||||
import net.psforever.actors.session.{AvatarActor, SessionActor}
|
||||
import net.psforever.actors.session.support.{ModeLogic, PlayerMode, SessionData, ZoningOperations}
|
||||
import net.psforever.objects.TurretDeployable
|
||||
import net.psforever.objects.guid.{GUIDTask, TaskWorkflow}
|
||||
import net.psforever.objects.serverobject.CommonMessages
|
||||
|
|
@ -27,10 +28,18 @@ 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 {
|
||||
class NormalModeLogic(data: SessionData) extends ModeLogic {
|
||||
val avatarResponse = new AvatarHandlerLogic(data.avatarResponse)
|
||||
val galaxy = new GalaxyHandlerLogic(data.galaxyResponseHandlers)
|
||||
val general = new GeneralLogic(data.general)
|
||||
val local = new LocalHandlerLogic(data.localResponse)
|
||||
val mountResponse = new MountHandlerLogic(data.mountResponse)
|
||||
val shooting = new WeaponAndProjectileLogic(data.shooting)
|
||||
val squad = new SquadHandlerLogic(data.squad)
|
||||
val terminals = new TerminalHandlerLogic(data.terminals)
|
||||
val vehicles = new VehicleLogic(data.vehicles)
|
||||
val vehicleResponse = new VehicleHandlerLogic(data.vehicleResponseOperations)
|
||||
|
||||
def parse(sender: ActorRef): Receive = {
|
||||
/* really common messages (very frequently, every life) */
|
||||
case packet: PlanetSideGamePacket =>
|
||||
|
|
@ -40,10 +49,10 @@ class NormalUser(
|
|||
avatarResponse.handle(toChannel, guid, reply)
|
||||
|
||||
case GalaxyServiceResponse(_, reply) =>
|
||||
galaxyResponseHandlers.handle(reply)
|
||||
galaxy.handle(reply)
|
||||
|
||||
case LocalServiceResponse(toChannel, guid, reply) =>
|
||||
localResponse.handle(toChannel, guid, reply)
|
||||
local.handle(toChannel, guid, reply)
|
||||
|
||||
case Mountable.MountMessages(tplayer, reply) =>
|
||||
mountResponse.handle(tplayer, reply)
|
||||
|
|
@ -55,81 +64,81 @@ class NormalUser(
|
|||
terminals.handle(tplayer, msg, order)
|
||||
|
||||
case VehicleServiceResponse(toChannel, guid, reply) =>
|
||||
vehicleResponseOperations.handle(toChannel, guid, reply)
|
||||
vehicleResponse.handle(toChannel, guid, reply)
|
||||
|
||||
case SessionActor.PokeClient() =>
|
||||
sendResponse(KeepAliveMessage())
|
||||
data.sendResponse(KeepAliveMessage())
|
||||
|
||||
case SessionActor.SendResponse(packet) =>
|
||||
sendResponse(packet)
|
||||
data.sendResponse(packet)
|
||||
|
||||
case SessionActor.CharSaved =>
|
||||
general.renewCharSavedTimer(
|
||||
general.ops.renewCharSavedTimer(
|
||||
Config.app.game.savedMsg.interruptedByAction.fixed,
|
||||
Config.app.game.savedMsg.interruptedByAction.variable
|
||||
)
|
||||
|
||||
case SessionActor.CharSavedMsg =>
|
||||
general.displayCharSavedMsgThenRenewTimer(
|
||||
general.ops.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)
|
||||
data.zoning.handleSpawnPointResponse(response)
|
||||
|
||||
case SessionActor.NewPlayerLoaded(tplayer) =>
|
||||
zoning.spawn.handleNewPlayerLoaded(tplayer)
|
||||
data.zoning.spawn.handleNewPlayerLoaded(tplayer)
|
||||
|
||||
case SessionActor.PlayerLoaded(tplayer) =>
|
||||
zoning.spawn.handlePlayerLoaded(tplayer)
|
||||
data.zoning.spawn.handlePlayerLoaded(tplayer)
|
||||
|
||||
case Zone.Population.PlayerHasLeft(zone, None) =>
|
||||
log.debug(s"PlayerHasLeft: ${player.Name} does not have a body on ${zone.id}")
|
||||
data.log.debug(s"PlayerHasLeft: ${data.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}")
|
||||
data.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?")
|
||||
data.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?")
|
||||
data.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"
|
||||
data.log.warn(
|
||||
s"${data.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"
|
||||
data.log.warn(
|
||||
s"${data.player.Name}'s ${vehicle.Definition.Name} can not deconstruct in ${zone.id} because $reason"
|
||||
)
|
||||
|
||||
case ICS.ZoneResponse(Some(zone)) =>
|
||||
zoning.handleZoneResponse(zone)
|
||||
data.zoning.handleZoneResponse(zone)
|
||||
|
||||
/* uncommon messages (once a session) */
|
||||
case ICS.ZonesResponse(zones) =>
|
||||
zoning.handleZonesResponse(zones)
|
||||
data.zoning.handleZonesResponse(zones)
|
||||
|
||||
case SessionActor.SetAvatar(avatar) =>
|
||||
general.handleSetAvatar(avatar)
|
||||
|
||||
case PlayerToken.LoginInfo(name, Zone.Nowhere, _) =>
|
||||
zoning.spawn.handleLoginInfoNowhere(name, sender)
|
||||
data.zoning.spawn.handleLoginInfoNowhere(name, sender)
|
||||
|
||||
case PlayerToken.LoginInfo(name, inZone, optionalSavedData) =>
|
||||
zoning.spawn.handleLoginInfoSomewhere(name, inZone, optionalSavedData, sender)
|
||||
data.zoning.spawn.handleLoginInfoSomewhere(name, inZone, optionalSavedData, sender)
|
||||
|
||||
case PlayerToken.RestoreInfo(playerName, inZone, pos) =>
|
||||
zoning.spawn.handleLoginInfoRestore(playerName, inZone, pos, sender)
|
||||
data.zoning.spawn.handleLoginInfoRestore(playerName, inZone, pos, sender)
|
||||
|
||||
case PlayerToken.CanNotLogin(playerName, reason) =>
|
||||
zoning.spawn.handleLoginCanNot(playerName, reason)
|
||||
data.zoning.spawn.handleLoginCanNot(playerName, reason)
|
||||
|
||||
case ReceiveAccountData(account) =>
|
||||
general.handleReceiveAccountData(account)
|
||||
|
|
@ -138,35 +147,35 @@ class NormalUser(
|
|||
general.handleAvatarResponse(avatar)
|
||||
|
||||
case AvatarActor.AvatarLoginResponse(avatar) =>
|
||||
zoning.spawn.avatarLoginResponse(avatar)
|
||||
data.zoning.spawn.avatarLoginResponse(avatar)
|
||||
|
||||
case SessionActor.SetCurrentAvatar(tplayer, max_attempts, attempt) =>
|
||||
zoning.spawn.ReadyToSetCurrentAvatar(tplayer, max_attempts, attempt)
|
||||
data.zoning.spawn.ReadyToSetCurrentAvatar(tplayer, max_attempts, attempt)
|
||||
|
||||
case SessionActor.SetConnectionState(state) =>
|
||||
connectionState = state
|
||||
data.connectionState = state
|
||||
|
||||
case SessionActor.AvatarLoadingSync(state) =>
|
||||
zoning.spawn.handleAvatarLoadingSync(state)
|
||||
data.zoning.spawn.handleAvatarLoadingSync(state)
|
||||
|
||||
/* uncommon messages (utility, or once in a while) */
|
||||
case ZoningOperations.AvatarAwardMessageBundle(pkts, delay) =>
|
||||
zoning.spawn.performAvatarAwardMessageDelivery(pkts, delay)
|
||||
data.zoning.spawn.performAvatarAwardMessageDelivery(pkts, delay)
|
||||
|
||||
case CommonMessages.ProgressEvent(delta, finishedAction, stepAction, tick) =>
|
||||
general.handleProgressChange(delta, finishedAction, stepAction, tick)
|
||||
general.ops.handleProgressChange(delta, finishedAction, stepAction, tick)
|
||||
|
||||
case CommonMessages.Progress(rate, finishedAction, stepAction) =>
|
||||
general.setupProgressChange(rate, finishedAction, stepAction)
|
||||
general.ops.setupProgressChange(rate, finishedAction, stepAction)
|
||||
|
||||
case CavernRotationService.CavernRotationServiceKey.Listing(listings) =>
|
||||
listings.head ! SendCavernRotationUpdates(context.self)
|
||||
listings.head ! SendCavernRotationUpdates(data.context.self)
|
||||
|
||||
case LookupResult("propertyOverrideManager", endpoint) =>
|
||||
zoning.propertyOverrideManagerLoadOverrides(endpoint)
|
||||
data.zoning.propertyOverrideManagerLoadOverrides(endpoint)
|
||||
|
||||
case SessionActor.UpdateIgnoredPlayers(msg) =>
|
||||
galaxyResponseHandlers.handleUpdateIgnoredPlayers(msg)
|
||||
galaxy.handleUpdateIgnoredPlayers(msg)
|
||||
|
||||
case SessionActor.UseCooldownRenewed(definition, _) =>
|
||||
general.handleUseCooldownRenew(definition)
|
||||
|
|
@ -182,28 +191,28 @@ class NormalUser(
|
|||
|
||||
/* rare messages */
|
||||
case ProximityUnit.StopAction(term, _) =>
|
||||
terminals.LocalStopUsingProximityUnit(term)
|
||||
terminals.ops.LocalStopUsingProximityUnit(term)
|
||||
|
||||
case SessionActor.Suicide() =>
|
||||
general.suicide(player)
|
||||
general.ops.suicide(data.player)
|
||||
|
||||
case SessionActor.Recall() =>
|
||||
zoning.handleRecall()
|
||||
data.zoning.handleRecall()
|
||||
|
||||
case SessionActor.InstantAction() =>
|
||||
zoning.handleInstantAction()
|
||||
data.zoning.handleInstantAction()
|
||||
|
||||
case SessionActor.Quit() =>
|
||||
zoning.handleQuit()
|
||||
data.zoning.handleQuit()
|
||||
|
||||
case ICS.DroppodLaunchDenial(errorCode, _) =>
|
||||
zoning.handleDroppodLaunchDenial(errorCode)
|
||||
data.zoning.handleDroppodLaunchDenial(errorCode)
|
||||
|
||||
case ICS.DroppodLaunchConfirmation(zone, position) =>
|
||||
zoning.LoadZoneLaunchDroppod(zone, position)
|
||||
data.zoning.LoadZoneLaunchDroppod(zone, position)
|
||||
|
||||
case SessionActor.PlayerFailedToLoad(tplayer) =>
|
||||
failWithError(s"${tplayer.Name} failed to load anywhere")
|
||||
data.failWithError(s"${tplayer.Name} failed to load anywhere")
|
||||
|
||||
/* csr only */
|
||||
case SessionActor.SetSpeed(speed) =>
|
||||
|
|
@ -219,10 +228,10 @@ class NormalUser(
|
|||
general.handleKick(player, time)
|
||||
|
||||
case SessionActor.SetZone(zoneId, position) =>
|
||||
zoning.handleSetZone(zoneId, position)
|
||||
data.zoning.handleSetZone(zoneId, position)
|
||||
|
||||
case SessionActor.SetPosition(position) =>
|
||||
zoning.spawn.handleSetPosition(position)
|
||||
data.zoning.spawn.handleSetPosition(position)
|
||||
|
||||
case SessionActor.SetSilenced(silenced) =>
|
||||
general.handleSilenced(silenced)
|
||||
|
|
@ -235,19 +244,19 @@ class NormalUser(
|
|||
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))
|
||||
TaskWorkflow.execute(GUIDTask.unregisterDeployableTurret(data.continent.GUID, obj))
|
||||
|
||||
case Zone.Deployable.IsDismissed(obj) => //only if target deployable was never fully introduced
|
||||
TaskWorkflow.execute(GUIDTask.unregisterObject(continent.GUID, obj))
|
||||
TaskWorkflow.execute(GUIDTask.unregisterObject(data.continent.GUID, obj))
|
||||
|
||||
case msg: Containable.ItemPutInSlot =>
|
||||
log.debug(s"ItemPutInSlot: $msg")
|
||||
data.log.debug(s"ItemPutInSlot: $msg")
|
||||
|
||||
case msg: Containable.CanNotPutItemInSlot =>
|
||||
log.debug(s"CanNotPutItemInSlot: $msg")
|
||||
data.log.debug(s"CanNotPutItemInSlot: $msg")
|
||||
|
||||
case default =>
|
||||
log.warn(s"Invalid packet class received: $default from $sender")
|
||||
data.log.warn(s"Invalid packet class received: $default from $sender")
|
||||
}
|
||||
|
||||
private def handleGamePkt: PlanetSideGamePacket => Unit = {
|
||||
|
|
@ -255,10 +264,10 @@ class NormalUser(
|
|||
general.handleConnectToWorldRequest(packet)
|
||||
|
||||
case packet: MountVehicleCargoMsg =>
|
||||
vehicles.handleMountVehicleCargo(packet)
|
||||
mountResponse.handleMountVehicleCargo(packet)
|
||||
|
||||
case packet: DismountVehicleCargoMsg =>
|
||||
vehicles.handleDismountVehicleCargo(packet)
|
||||
mountResponse.handleDismountVehicleCargo(packet)
|
||||
|
||||
case packet: CharacterCreateRequestMessage =>
|
||||
general.handleCharacterCreateRequest(packet)
|
||||
|
|
@ -267,10 +276,10 @@ class NormalUser(
|
|||
general.handleCharacterRequest(packet)
|
||||
|
||||
case _: KeepAliveMessage =>
|
||||
keepAliveFunc()
|
||||
data.keepAliveFunc()
|
||||
|
||||
case packet: BeginZoningMessage =>
|
||||
zoning.handleBeginZoning(packet)
|
||||
data.zoning.handleBeginZoning(packet)
|
||||
|
||||
case packet: PlayerStateMessageUpstream =>
|
||||
general.handlePlayerStateUpstream(packet)
|
||||
|
|
@ -294,10 +303,10 @@ class NormalUser(
|
|||
shooting.handleLongRangeProjectileState(packet)
|
||||
|
||||
case packet: ReleaseAvatarRequestMessage =>
|
||||
zoning.spawn.handleReleaseAvatarRequest(packet)
|
||||
data.zoning.spawn.handleReleaseAvatarRequest(packet)
|
||||
|
||||
case packet: SpawnRequestMessage =>
|
||||
zoning.spawn.handleSpawnRequest(packet)
|
||||
data.zoning.spawn.handleSpawnRequest(packet)
|
||||
|
||||
case packet: ChatMsg =>
|
||||
general.handleChat(packet)
|
||||
|
|
@ -384,7 +393,7 @@ class NormalUser(
|
|||
terminals.handleItemTransaction(packet)
|
||||
|
||||
case packet: FavoritesRequest =>
|
||||
general.handleFavoritesRequest(packet)
|
||||
terminals.handleFavoritesRequest(packet)
|
||||
|
||||
case packet: WeaponDelayFireMessage =>
|
||||
shooting.handleWeaponDelayFire(packet)
|
||||
|
|
@ -414,13 +423,13 @@ class NormalUser(
|
|||
general.handleAvatarFirstTimeEvent(packet)
|
||||
|
||||
case packet: WarpgateRequest =>
|
||||
zoning.handleWarpgateRequest(packet)
|
||||
data.zoning.handleWarpgateRequest(packet)
|
||||
|
||||
case packet: MountVehicleMsg =>
|
||||
vehicles.handleMountVehicle(packet)
|
||||
mountResponse.handleMountVehicle(packet)
|
||||
|
||||
case packet: DismountVehicleMsg =>
|
||||
vehicles.handleDismountVehicle(packet)
|
||||
mountResponse.handleDismountVehicle(packet)
|
||||
|
||||
case packet: DeployRequestMessage =>
|
||||
vehicles.handleDeployRequest(packet)
|
||||
|
|
@ -465,7 +474,7 @@ class NormalUser(
|
|||
general.handleFriendRequest(packet)
|
||||
|
||||
case packet: DroppodLaunchRequestMessage =>
|
||||
zoning.handleDroppodLaunchRequest(packet)
|
||||
data.zoning.handleDroppodLaunchRequest(packet)
|
||||
|
||||
case packet: InvalidTerrainMessage =>
|
||||
general.handleInvalidTerrain(packet)
|
||||
|
|
@ -491,6 +500,12 @@ class NormalUser(
|
|||
case _: OutfitRequest => ()
|
||||
|
||||
case pkt =>
|
||||
log.warn(s"Unhandled GamePacket $pkt")
|
||||
data.log.warn(s"Unhandled GamePacket $pkt")
|
||||
}
|
||||
}
|
||||
|
||||
case object NormalMode extends PlayerMode {
|
||||
def setup(data: SessionData): ModeLogic = {
|
||||
new NormalModeLogic(data)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,356 @@
|
|||
// Copyright (c) 2024 PSForever
|
||||
package net.psforever.actors.session.normal
|
||||
|
||||
import akka.actor.{ActorContext, ActorRef, typed}
|
||||
import net.psforever.actors.session.support.SessionSquadHandlers.SquadUIElement
|
||||
import net.psforever.actors.session.{AvatarActor, ChatActor}
|
||||
import net.psforever.actors.session.support.{SessionData, SessionSquadHandlers, SquadHandlerFunctions}
|
||||
import net.psforever.objects.{Default, LivePlayerList}
|
||||
import net.psforever.objects.avatar.Avatar
|
||||
import net.psforever.packet.game.{CharacterKnowledgeInfo, CharacterKnowledgeMessage, ChatMsg, MemberEvent, PlanetsideAttributeMessage, ReplicationStreamMessage, SquadAction, SquadDefinitionActionMessage, SquadDetailDefinitionUpdateMessage, SquadListing, SquadMemberEvent, SquadMembershipRequest, SquadMembershipResponse, SquadState, SquadStateInfo, SquadWaypointEvent, SquadWaypointRequest, WaypointEvent, WaypointEventAction}
|
||||
import net.psforever.services.chat.ChatService
|
||||
import net.psforever.services.teamwork.{SquadResponse, SquadServiceMessage, SquadAction => SquadServiceAction}
|
||||
import net.psforever.types.{ChatMessageType, PlanetSideGUID, SquadListDecoration, SquadResponseType, WaypointSubtype}
|
||||
|
||||
class SquadHandlerLogic(val ops: SessionSquadHandlers) extends SquadHandlerFunctions {
|
||||
def sessionLogic: SessionData = ops.sessionLogic
|
||||
|
||||
implicit val context: ActorContext = ops.context
|
||||
|
||||
private val avatarActor: typed.ActorRef[AvatarActor.Command] = ops.avatarActor
|
||||
|
||||
private val chatActor: typed.ActorRef[ChatActor.Command] = ops.chatActor
|
||||
|
||||
private val squadService: ActorRef = ops.squadService
|
||||
|
||||
private var waypointCooldown: Long = 0L
|
||||
|
||||
/* packet */
|
||||
|
||||
def handleSquadDefinitionAction(pkt: SquadDefinitionActionMessage): Unit = {
|
||||
val SquadDefinitionActionMessage(u1, u2, action) = pkt
|
||||
squadService ! SquadServiceMessage(player, continent, SquadServiceAction.Definition(u1, u2, action))
|
||||
}
|
||||
|
||||
def handleSquadMemberRequest(pkt: SquadMembershipRequest): Unit = {
|
||||
val SquadMembershipRequest(request_type, char_id, unk3, player_name, unk5) = pkt
|
||||
squadService ! SquadServiceMessage(
|
||||
player,
|
||||
continent,
|
||||
SquadServiceAction.Membership(request_type, char_id, unk3, player_name, unk5)
|
||||
)
|
||||
}
|
||||
|
||||
def handleSquadWaypointRequest(pkt: SquadWaypointRequest): Unit = {
|
||||
val SquadWaypointRequest(request, _, wtype, unk, info) = pkt
|
||||
val time = System.currentTimeMillis()
|
||||
val subtype = wtype.subtype
|
||||
if(subtype == WaypointSubtype.Squad) {
|
||||
squadService ! SquadServiceMessage(player, continent, SquadServiceAction.Waypoint(request, wtype, unk, info))
|
||||
} else if (subtype == WaypointSubtype.Laze && time - waypointCooldown > 1000) {
|
||||
//guarding against duplicating laze waypoints
|
||||
waypointCooldown = time
|
||||
squadService ! SquadServiceMessage(player, continent, SquadServiceAction.Waypoint(request, wtype, unk, info))
|
||||
}
|
||||
}
|
||||
|
||||
/* response handlers */
|
||||
|
||||
def handle(response: SquadResponse.Response, excluded: Iterable[Long]): Unit = {
|
||||
if (!excluded.exists(_ == avatar.id)) {
|
||||
response match {
|
||||
case SquadResponse.ListSquadFavorite(line, task) =>
|
||||
sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), line, SquadAction.ListSquadFavorite(task)))
|
||||
|
||||
case SquadResponse.InitList(infos) =>
|
||||
sendResponse(ReplicationStreamMessage(infos))
|
||||
|
||||
case SquadResponse.UpdateList(infos) if infos.nonEmpty =>
|
||||
sendResponse(
|
||||
ReplicationStreamMessage(
|
||||
6,
|
||||
None,
|
||||
infos.map {
|
||||
case (index, squadInfo) =>
|
||||
SquadListing(index, squadInfo)
|
||||
}.toVector
|
||||
)
|
||||
)
|
||||
|
||||
case SquadResponse.RemoveFromList(infos) if infos.nonEmpty =>
|
||||
sendResponse(
|
||||
ReplicationStreamMessage(
|
||||
1,
|
||||
None,
|
||||
infos.map { index =>
|
||||
SquadListing(index, None)
|
||||
}.toVector
|
||||
)
|
||||
)
|
||||
|
||||
case SquadResponse.SquadDecoration(guid, squad) =>
|
||||
val decoration = if (
|
||||
ops.squadUI.nonEmpty ||
|
||||
squad.Size == squad.Capacity ||
|
||||
{
|
||||
val offer = avatar.certifications
|
||||
!squad.Membership.exists { _.isAvailable(offer) }
|
||||
}
|
||||
) {
|
||||
SquadListDecoration.NotAvailable
|
||||
} else {
|
||||
SquadListDecoration.Available
|
||||
}
|
||||
sendResponse(SquadDefinitionActionMessage(guid, 0, SquadAction.SquadListDecorator(decoration)))
|
||||
|
||||
case SquadResponse.Detail(guid, detail) =>
|
||||
sendResponse(SquadDetailDefinitionUpdateMessage(guid, detail))
|
||||
|
||||
case SquadResponse.IdentifyAsSquadLeader(squad_guid) =>
|
||||
sendResponse(SquadDefinitionActionMessage(squad_guid, 0, SquadAction.IdentifyAsSquadLeader()))
|
||||
|
||||
case SquadResponse.SetListSquad(squad_guid) =>
|
||||
sendResponse(SquadDefinitionActionMessage(squad_guid, 0, SquadAction.SetListSquad()))
|
||||
|
||||
case SquadResponse.Membership(request_type, unk1, unk2, charId, opt_char_id, player_name, unk5, unk6) =>
|
||||
val name = request_type match {
|
||||
case SquadResponseType.Invite if unk5 =>
|
||||
//the name of the player indicated by unk3 is needed
|
||||
LivePlayerList.WorldPopulation({ case (_, a: Avatar) => charId == a.id }).headOption match {
|
||||
case Some(player) =>
|
||||
player.name
|
||||
case None =>
|
||||
player_name
|
||||
}
|
||||
case _ =>
|
||||
player_name
|
||||
}
|
||||
sendResponse(SquadMembershipResponse(request_type, unk1, unk2, charId, opt_char_id, name, unk5, unk6))
|
||||
|
||||
case SquadResponse.WantsSquadPosition(_, name) =>
|
||||
sendResponse(
|
||||
ChatMsg(
|
||||
ChatMessageType.CMT_SQUAD,
|
||||
wideContents=true,
|
||||
name,
|
||||
s"\\#6 would like to join your squad. (respond with \\#3/accept\\#6 or \\#3/reject\\#6)",
|
||||
None
|
||||
)
|
||||
)
|
||||
|
||||
case SquadResponse.Join(squad, positionsToUpdate, _, ref) =>
|
||||
val avatarId = avatar.id
|
||||
val membershipPositions = (positionsToUpdate map squad.Membership.zipWithIndex)
|
||||
.filter { case (mem, index) =>
|
||||
mem.CharId > 0 && positionsToUpdate.contains(index)
|
||||
}
|
||||
membershipPositions.find { case (mem, _) => mem.CharId == avatarId } match {
|
||||
case Some((ourMember, ourIndex)) =>
|
||||
//we are joining the squad
|
||||
//load each member's entry (our own too)
|
||||
ops.squad_supplement_id = squad.GUID.guid + 1
|
||||
membershipPositions.foreach {
|
||||
case (member, index) =>
|
||||
sendResponse(
|
||||
SquadMemberEvent.Add(
|
||||
ops.squad_supplement_id,
|
||||
member.CharId,
|
||||
index,
|
||||
member.Name,
|
||||
member.ZoneId,
|
||||
outfit_id = 0
|
||||
)
|
||||
)
|
||||
ops.squadUI(member.CharId) =
|
||||
SquadUIElement(member.Name, outfit=0L, index, member.ZoneId, member.Health, member.Armor, member.Position)
|
||||
}
|
||||
//repeat our entry
|
||||
sendResponse(
|
||||
SquadMemberEvent.Add(
|
||||
ops.squad_supplement_id,
|
||||
ourMember.CharId,
|
||||
ourIndex,
|
||||
ourMember.Name,
|
||||
ourMember.ZoneId,
|
||||
outfit_id = 0
|
||||
)
|
||||
)
|
||||
//turn lfs off
|
||||
if (avatar.lookingForSquad) {
|
||||
avatarActor ! AvatarActor.SetLookingForSquad(false)
|
||||
}
|
||||
val playerGuid = player.GUID
|
||||
val factionChannel = s"${player.Faction}"
|
||||
//squad colors
|
||||
ops.GiveSquadColorsToMembers()
|
||||
ops.GiveSquadColorsForOthers(playerGuid, factionChannel, ops.squad_supplement_id)
|
||||
//associate with member position in squad
|
||||
sendResponse(PlanetsideAttributeMessage(playerGuid, 32, ourIndex))
|
||||
//a finalization? what does this do?
|
||||
sendResponse(SquadDefinitionActionMessage(squad.GUID, 0, SquadAction.Unknown(18)))
|
||||
squadService ! SquadServiceMessage(player, continent, SquadServiceAction.ReloadDecoration())
|
||||
ops.updateSquadRef = ref
|
||||
ops. updateSquad = ops.PeriodicUpdatesWhenEnrolledInSquad
|
||||
chatActor ! ChatActor.JoinChannel(ChatService.ChatChannel.Squad(squad.GUID))
|
||||
case _ =>
|
||||
//other player is joining our squad
|
||||
//load each member's entry
|
||||
ops.GiveSquadColorsToMembers(
|
||||
membershipPositions.map {
|
||||
case (member, index) =>
|
||||
val charId = member.CharId
|
||||
sendResponse(
|
||||
SquadMemberEvent.Add(ops.squad_supplement_id, charId, index, member.Name, member.ZoneId, outfit_id = 0)
|
||||
)
|
||||
ops.squadUI(charId) =
|
||||
SquadUIElement(member.Name, outfit=0L, index, member.ZoneId, member.Health, member.Armor, member.Position)
|
||||
charId
|
||||
}
|
||||
)
|
||||
}
|
||||
//send an initial dummy update for map icon(s)
|
||||
sendResponse(
|
||||
SquadState(
|
||||
PlanetSideGUID(ops.squad_supplement_id),
|
||||
membershipPositions.map { case (member, _) =>
|
||||
SquadStateInfo(member.CharId, member.Health, member.Armor, member.Position)
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
case SquadResponse.Leave(squad, positionsToUpdate) =>
|
||||
positionsToUpdate.find({ case (member, _) => member == avatar.id }) match {
|
||||
case Some((ourMember, ourIndex)) =>
|
||||
//we are leaving the squad
|
||||
//remove each member's entry (our own too)
|
||||
ops.updateSquadRef = Default.Actor
|
||||
positionsToUpdate.foreach {
|
||||
case (member, index) =>
|
||||
sendResponse(SquadMemberEvent.Remove(ops.squad_supplement_id, member, index))
|
||||
ops.squadUI.remove(member)
|
||||
}
|
||||
//uninitialize
|
||||
val playerGuid = player.GUID
|
||||
sendResponse(SquadMemberEvent.Remove(ops.squad_supplement_id, ourMember, ourIndex)) //repeat of our entry
|
||||
ops.GiveSquadColorsToSelf(value = 0)
|
||||
sendResponse(PlanetsideAttributeMessage(playerGuid, 32, 0)) //disassociate with member position in squad?
|
||||
sendResponse(PlanetsideAttributeMessage(playerGuid, 34, 4294967295L)) //unknown, perhaps unrelated?
|
||||
avatarActor ! AvatarActor.SetLookingForSquad(false)
|
||||
//a finalization? what does this do?
|
||||
sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SquadAction.Unknown(18)))
|
||||
ops.squad_supplement_id = 0
|
||||
ops.squadUpdateCounter = 0
|
||||
ops.updateSquad = ops.NoSquadUpdates
|
||||
chatActor ! ChatActor.LeaveChannel(ChatService.ChatChannel.Squad(squad.GUID))
|
||||
case _ =>
|
||||
//remove each member's entry
|
||||
ops.GiveSquadColorsToMembers(
|
||||
positionsToUpdate.map {
|
||||
case (member, index) =>
|
||||
sendResponse(SquadMemberEvent.Remove(ops.squad_supplement_id, member, index))
|
||||
ops.squadUI.remove(member)
|
||||
member
|
||||
},
|
||||
value = 0
|
||||
)
|
||||
}
|
||||
|
||||
case SquadResponse.AssignMember(squad, from_index, to_index) =>
|
||||
//we've already swapped position internally; now we swap the cards
|
||||
ops.SwapSquadUIElements(squad, from_index, to_index)
|
||||
|
||||
case SquadResponse.PromoteMember(squad, promotedPlayer, from_index) =>
|
||||
if (promotedPlayer != player.CharId) {
|
||||
//demoted from leader; no longer lfsm
|
||||
if (player.avatar.lookingForSquad) {
|
||||
avatarActor ! AvatarActor.SetLookingForSquad(false)
|
||||
}
|
||||
}
|
||||
sendResponse(SquadMemberEvent(MemberEvent.Promote, squad.GUID.guid, promotedPlayer, position = 0))
|
||||
//the players have already been swapped in the backend object
|
||||
ops.PromoteSquadUIElements(squad, from_index)
|
||||
|
||||
case SquadResponse.UpdateMembers(_, positions) =>
|
||||
val pairedEntries = positions.collect {
|
||||
case entry if ops.squadUI.contains(entry.char_id) =>
|
||||
(entry, ops.squadUI(entry.char_id))
|
||||
}
|
||||
//prune entries
|
||||
val updatedEntries = pairedEntries
|
||||
.collect({
|
||||
case (entry, element) if entry.zone_number != element.zone =>
|
||||
//zone gets updated for these entries
|
||||
sendResponse(
|
||||
SquadMemberEvent.UpdateZone(ops.squad_supplement_id, entry.char_id, element.index, entry.zone_number)
|
||||
)
|
||||
ops.squadUI(entry.char_id) =
|
||||
SquadUIElement(element.name, element.outfit, element.index, entry.zone_number, entry.health, entry.armor, entry.pos)
|
||||
entry
|
||||
case (entry, element)
|
||||
if entry.health != element.health || entry.armor != element.armor || entry.pos != element.position =>
|
||||
//other elements that need to be updated
|
||||
ops.squadUI(entry.char_id) =
|
||||
SquadUIElement(element.name, element.outfit, element.index, entry.zone_number, entry.health, entry.armor, entry.pos)
|
||||
entry
|
||||
})
|
||||
.filterNot(_.char_id == avatar.id) //we want to update our backend, but not our frontend
|
||||
if (updatedEntries.nonEmpty) {
|
||||
sendResponse(
|
||||
SquadState(
|
||||
PlanetSideGUID(ops.squad_supplement_id),
|
||||
updatedEntries.map { entry =>
|
||||
SquadStateInfo(entry.char_id, entry.health, entry.armor, entry.pos)
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
case SquadResponse.CharacterKnowledge(charId, name, certs, u1, u2, zone) =>
|
||||
sendResponse(CharacterKnowledgeMessage(charId, Some(CharacterKnowledgeInfo(name, certs, u1, u2, zone))))
|
||||
|
||||
case SquadResponse.SquadSearchResults(_/*results*/) =>
|
||||
//TODO positive squad search results message?
|
||||
// if(results.nonEmpty) {
|
||||
// results.foreach { guid =>
|
||||
// sendResponse(SquadDefinitionActionMessage(
|
||||
// guid,
|
||||
// 0,
|
||||
// SquadAction.SquadListDecorator(SquadListDecoration.SearchResult))
|
||||
// )
|
||||
// }
|
||||
// } else {
|
||||
// sendResponse(SquadDefinitionActionMessage(player.GUID, 0, SquadAction.NoSquadSearchResults()))
|
||||
// }
|
||||
// sendResponse(SquadDefinitionActionMessage(player.GUID, 0, SquadAction.CancelSquadSearch()))
|
||||
|
||||
case SquadResponse.InitWaypoints(char_id, waypoints) =>
|
||||
waypoints.foreach {
|
||||
case (waypoint_type, info, unk) =>
|
||||
sendResponse(
|
||||
SquadWaypointEvent.Add(
|
||||
ops.squad_supplement_id,
|
||||
char_id,
|
||||
waypoint_type,
|
||||
WaypointEvent(info.zone_number, info.pos, unk)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
case SquadResponse.WaypointEvent(WaypointEventAction.Add, char_id, waypoint_type, _, Some(info), unk) =>
|
||||
sendResponse(
|
||||
SquadWaypointEvent.Add(
|
||||
ops.squad_supplement_id,
|
||||
char_id,
|
||||
waypoint_type,
|
||||
WaypointEvent(info.zone_number, info.pos, unk)
|
||||
)
|
||||
)
|
||||
|
||||
case SquadResponse.WaypointEvent(WaypointEventAction.Remove, char_id, waypoint_type, _, _, _) =>
|
||||
sendResponse(SquadWaypointEvent.Remove(ops.squad_supplement_id, char_id, waypoint_type))
|
||||
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,180 @@
|
|||
// Copyright (c) 2024 PSForever
|
||||
package net.psforever.actors.session.normal
|
||||
|
||||
import akka.actor.{ActorContext, typed}
|
||||
import net.psforever.actors.session.AvatarActor
|
||||
import net.psforever.actors.session.support.{SessionData, SessionTerminalHandlers, TerminalHandlerFunctions}
|
||||
import net.psforever.login.WorldSession.{BuyNewEquipmentPutInInventory, SellEquipmentFromInventory}
|
||||
import net.psforever.objects.{GlobalDefinitions, Player, Vehicle}
|
||||
import net.psforever.objects.guid.TaskWorkflow
|
||||
import net.psforever.objects.serverobject.pad.VehicleSpawnPad
|
||||
import net.psforever.objects.serverobject.terminals.{ProximityUnit, Terminal}
|
||||
import net.psforever.objects.sourcing.AmenitySource
|
||||
import net.psforever.objects.vital.TerminalUsedActivity
|
||||
import net.psforever.packet.game.{FavoritesAction, FavoritesRequest, ItemTransactionMessage, ItemTransactionResultMessage, ProximityTerminalUseMessage, UnuseItemMessage}
|
||||
import net.psforever.types.{TransactionType, Vector3}
|
||||
|
||||
class TerminalHandlerLogic(val ops: SessionTerminalHandlers) extends TerminalHandlerFunctions {
|
||||
def sessionLogic: SessionData = ops.sessionLogic
|
||||
|
||||
implicit val context: ActorContext = ops.context
|
||||
|
||||
private val avatarActor: typed.ActorRef[AvatarActor.Command] = ops.avatarActor
|
||||
|
||||
def handleItemTransaction(pkt: ItemTransactionMessage): Unit = {
|
||||
val ItemTransactionMessage(terminalGuid, transactionType, _, itemName, _, _) = pkt
|
||||
continent.GUID(terminalGuid) match {
|
||||
case Some(term: Terminal) if ops.lastTerminalOrderFulfillment =>
|
||||
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")
|
||||
ops.lastTerminalOrderFulfillment = false
|
||||
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}")
|
||||
case Some(obj) =>
|
||||
log.error(s"ItemTransaction: ${obj.Definition.Name} is not a terminal, ${player.Name}")
|
||||
case _ =>
|
||||
log.error(s"ItemTransaction: entity with guid=${terminalGuid.guid} does not exist, ${player.Name}")
|
||||
}
|
||||
}
|
||||
|
||||
def handleProximityTerminalUse(pkt: ProximityTerminalUseMessage): Unit = {
|
||||
val ProximityTerminalUseMessage(_, objectGuid, _) = pkt
|
||||
continent.GUID(objectGuid) match {
|
||||
case Some(obj: Terminal with ProximityUnit) =>
|
||||
ops.HandleProximityTerminalUse(obj)
|
||||
case Some(obj) =>
|
||||
log.warn(s"ProximityTerminalUse: ${obj.Definition.Name} guid=${objectGuid.guid} is not ready to implement proximity effects")
|
||||
case None =>
|
||||
log.error(s"ProximityTerminalUse: ${player.Name} can not find an object with guid ${objectGuid.guid}")
|
||||
}
|
||||
}
|
||||
|
||||
def handleFavoritesRequest(pkt: FavoritesRequest): Unit = {
|
||||
val FavoritesRequest(_, loadoutType, action, line, label) = pkt
|
||||
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
|
||||
action match {
|
||||
case FavoritesAction.Save =>
|
||||
avatarActor ! AvatarActor.SaveLoadout(player, loadoutType, label, line)
|
||||
case FavoritesAction.Delete =>
|
||||
avatarActor ! AvatarActor.DeleteLoadout(player, loadoutType, line)
|
||||
case FavoritesAction.Unknown =>
|
||||
log.warn(s"FavoritesRequest: ${player.Name} requested an unknown favorites action")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* na
|
||||
* @param tplayer na
|
||||
* @param msg na
|
||||
* @param order na
|
||||
*/
|
||||
def handle(tplayer: Player, msg: ItemTransactionMessage, order: Terminal.Exchange): Unit = {
|
||||
order match {
|
||||
case Terminal.BuyEquipment(item)
|
||||
if tplayer.avatar.purchaseCooldown(item.Definition).nonEmpty =>
|
||||
sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, success = false))
|
||||
ops.lastTerminalOrderFulfillment = true
|
||||
|
||||
case Terminal.BuyEquipment(item) =>
|
||||
avatarActor ! AvatarActor.UpdatePurchaseTime(item.Definition)
|
||||
TaskWorkflow.execute(BuyNewEquipmentPutInInventory(
|
||||
continent.GUID(tplayer.VehicleSeated) match {
|
||||
case Some(v: Vehicle) => v
|
||||
case _ => player
|
||||
},
|
||||
tplayer,
|
||||
msg.terminal_guid
|
||||
)(item))
|
||||
|
||||
case Terminal.SellEquipment() =>
|
||||
SellEquipmentFromInventory(tplayer, tplayer, msg.terminal_guid)(Player.FreeHandSlot)
|
||||
|
||||
case Terminal.LearnCertification(cert) =>
|
||||
avatarActor ! AvatarActor.LearnCertification(msg.terminal_guid, cert)
|
||||
ops.lastTerminalOrderFulfillment = true
|
||||
|
||||
case Terminal.SellCertification(cert) =>
|
||||
avatarActor ! AvatarActor.SellCertification(msg.terminal_guid, cert)
|
||||
ops.lastTerminalOrderFulfillment = true
|
||||
|
||||
case Terminal.LearnImplant(implant) =>
|
||||
avatarActor ! AvatarActor.LearnImplant(msg.terminal_guid, implant)
|
||||
ops.lastTerminalOrderFulfillment = true
|
||||
|
||||
case Terminal.SellImplant(implant) =>
|
||||
avatarActor ! AvatarActor.SellImplant(msg.terminal_guid, implant)
|
||||
ops.lastTerminalOrderFulfillment = true
|
||||
|
||||
case Terminal.BuyVehicle(vehicle, _, _)
|
||||
if tplayer.avatar.purchaseCooldown(vehicle.Definition).nonEmpty || tplayer.spectator =>
|
||||
sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, success = false))
|
||||
ops.lastTerminalOrderFulfillment = true
|
||||
|
||||
case Terminal.BuyVehicle(vehicle, weapons, trunk) =>
|
||||
continent.map.terminalToSpawnPad
|
||||
.find { case (termid, _) => termid == msg.terminal_guid.guid }
|
||||
.map { case (a: Int, b: Int) => (continent.GUID(a), continent.GUID(b)) }
|
||||
.collect { case (Some(term: Terminal), Some(pad: VehicleSpawnPad)) =>
|
||||
avatarActor ! AvatarActor.UpdatePurchaseTime(vehicle.Definition)
|
||||
vehicle.Faction = tplayer.Faction
|
||||
vehicle.Position = pad.Position
|
||||
vehicle.Orientation = pad.Orientation + Vector3.z(pad.Definition.VehicleCreationZOrientOffset)
|
||||
//default loadout, weapons
|
||||
val vWeapons = vehicle.Weapons
|
||||
weapons.foreach { entry =>
|
||||
vWeapons.get(entry.start) match {
|
||||
case Some(slot) =>
|
||||
entry.obj.Faction = tplayer.Faction
|
||||
slot.Equipment = None
|
||||
slot.Equipment = entry.obj
|
||||
case None =>
|
||||
log.warn(
|
||||
s"BuyVehicle: ${player.Name} tries to apply default loadout to $vehicle on spawn, but can not find a mounted weapon for ${entry.start}"
|
||||
)
|
||||
}
|
||||
}
|
||||
//default loadout, trunk
|
||||
val vTrunk = vehicle.Trunk
|
||||
vTrunk.Clear()
|
||||
trunk.foreach { entry =>
|
||||
entry.obj.Faction = tplayer.Faction
|
||||
vTrunk.InsertQuickly(entry.start, entry.obj)
|
||||
}
|
||||
TaskWorkflow.execute(ops.registerVehicleFromSpawnPad(vehicle, pad, term))
|
||||
sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, success = true))
|
||||
if (GlobalDefinitions.isBattleFrameVehicle(vehicle.Definition)) {
|
||||
sendResponse(UnuseItemMessage(player.GUID, msg.terminal_guid))
|
||||
}
|
||||
player.LogActivity(TerminalUsedActivity(AmenitySource(term), msg.transaction_type))
|
||||
}
|
||||
.orElse {
|
||||
log.error(
|
||||
s"${tplayer.Name} wanted to spawn a vehicle, but there was no spawn pad associated with terminal ${msg.terminal_guid} to accept it"
|
||||
)
|
||||
sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, success = false))
|
||||
None
|
||||
}
|
||||
ops.lastTerminalOrderFulfillment = true
|
||||
|
||||
case Terminal.NoDeal() if msg != null =>
|
||||
val transaction = msg.transaction_type
|
||||
log.warn(s"NoDeal: ${tplayer.Name} made a request but the terminal rejected the ${transaction.toString} order")
|
||||
sendResponse(ItemTransactionResultMessage(msg.terminal_guid, transaction, success = false))
|
||||
ops.lastTerminalOrderFulfillment = true
|
||||
|
||||
case _ =>
|
||||
val terminal = msg.terminal_guid.guid
|
||||
continent.GUID(terminal) match {
|
||||
case Some(term: Terminal) =>
|
||||
log.warn(s"NoDeal?: ${tplayer.Name} made a request but the ${term.Definition.Name}#$terminal rejected the missing order")
|
||||
case Some(_) =>
|
||||
log.warn(s"NoDeal?: ${tplayer.Name} made a request to a non-terminal entity#$terminal")
|
||||
case None =>
|
||||
log.warn(s"NoDeal?: ${tplayer.Name} made a request to a missing entity#$terminal")
|
||||
}
|
||||
ops.lastTerminalOrderFulfillment = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,395 @@
|
|||
// Copyright (c) 2024 PSForever
|
||||
package net.psforever.actors.session.normal
|
||||
|
||||
import akka.actor.{ActorContext, ActorRef, typed}
|
||||
import net.psforever.actors.session.AvatarActor
|
||||
import net.psforever.actors.session.support.{SessionData, SessionVehicleHandlers, VehicleHandlerFunctions}
|
||||
import net.psforever.objects.{GlobalDefinitions, Player, Tool, Vehicle, Vehicles}
|
||||
import net.psforever.objects.equipment.{Equipment, JammableMountedWeapons, JammableUnit}
|
||||
import net.psforever.objects.guid.{GUIDTask, TaskWorkflow}
|
||||
import net.psforever.objects.serverobject.mount.Mountable
|
||||
import net.psforever.objects.serverobject.pad.VehicleSpawnPad
|
||||
import net.psforever.packet.game.objectcreate.ObjectCreateMessageParent
|
||||
import net.psforever.packet.game.{ChangeAmmoMessage, ChangeFireStateMessage_Start, ChangeFireStateMessage_Stop, ChatMsg, ChildObjectStateMessage, DeadState, DeployRequestMessage, DismountVehicleMsg, FrameVehicleStateMessage, GenericObjectActionMessage, HitHint, InventoryStateMessage, ObjectAttachMessage, ObjectCreateDetailedMessage, ObjectCreateMessage, ObjectDeleteMessage, ObjectDetachMessage, PlanetsideAttributeMessage, ReloadMessage, ServerVehicleOverrideMsg, VehicleStateMessage, WeaponDryFireMessage}
|
||||
import net.psforever.services.Service
|
||||
import net.psforever.services.vehicle.{VehicleResponse, VehicleServiceResponse}
|
||||
import net.psforever.types.{BailType, ChatMessageType, PlanetSideGUID, Vector3}
|
||||
|
||||
class VehicleHandlerLogic(val ops: SessionVehicleHandlers) extends VehicleHandlerFunctions {
|
||||
def sessionLogic: SessionData = ops.sessionLogic
|
||||
|
||||
implicit val context: ActorContext = ops.context
|
||||
|
||||
private val avatarActor: typed.ActorRef[AvatarActor.Command] = ops.avatarActor
|
||||
|
||||
private val galaxyService: ActorRef = ops.galaxyService
|
||||
|
||||
/**
|
||||
* na
|
||||
*
|
||||
* @param toChannel na
|
||||
* @param guid na
|
||||
* @param reply na
|
||||
*/
|
||||
def handle(toChannel: String, guid: PlanetSideGUID, reply: VehicleResponse.Response): Unit = {
|
||||
val resolvedPlayerGuid = if (player.HasGUID) {
|
||||
player.GUID
|
||||
} else {
|
||||
PlanetSideGUID(-1)
|
||||
}
|
||||
val isNotSameTarget = resolvedPlayerGuid != guid
|
||||
reply match {
|
||||
case VehicleResponse.VehicleState(
|
||||
vehicleGuid,
|
||||
unk1,
|
||||
pos,
|
||||
orient,
|
||||
vel,
|
||||
unk2,
|
||||
unk3,
|
||||
unk4,
|
||||
wheelDirection,
|
||||
unk5,
|
||||
unk6
|
||||
) if isNotSameTarget && player.VehicleSeated.contains(vehicleGuid) =>
|
||||
//player who is also in the vehicle (not driver)
|
||||
sendResponse(VehicleStateMessage(vehicleGuid, unk1, pos, orient, vel, unk2, unk3, unk4, wheelDirection, unk5, unk6))
|
||||
player.Position = pos
|
||||
player.Orientation = orient
|
||||
player.Velocity = vel
|
||||
sessionLogic.updateLocalBlockMap(pos)
|
||||
|
||||
case VehicleResponse.VehicleState(
|
||||
vehicleGuid,
|
||||
unk1,
|
||||
pos,
|
||||
ang,
|
||||
vel,
|
||||
unk2,
|
||||
unk3,
|
||||
unk4,
|
||||
wheelDirection,
|
||||
unk5,
|
||||
unk6
|
||||
) if isNotSameTarget =>
|
||||
//player who is watching the vehicle from the outside
|
||||
sendResponse(VehicleStateMessage(vehicleGuid, unk1, pos, ang, vel, unk2, unk3, unk4, wheelDirection, unk5, unk6))
|
||||
|
||||
case VehicleResponse.ChildObjectState(objectGuid, pitch, yaw) if isNotSameTarget =>
|
||||
sendResponse(ChildObjectStateMessage(objectGuid, pitch, yaw))
|
||||
|
||||
case VehicleResponse.FrameVehicleState(vguid, u1, pos, oient, vel, u2, u3, u4, is_crouched, u6, u7, u8, u9, uA)
|
||||
if isNotSameTarget =>
|
||||
sendResponse(FrameVehicleStateMessage(vguid, u1, pos, oient, vel, u2, u3, u4, is_crouched, u6, u7, u8, u9, uA))
|
||||
|
||||
case VehicleResponse.ChangeFireState_Start(weaponGuid) if isNotSameTarget =>
|
||||
sendResponse(ChangeFireStateMessage_Start(weaponGuid))
|
||||
|
||||
case VehicleResponse.ChangeFireState_Stop(weaponGuid) if isNotSameTarget =>
|
||||
sendResponse(ChangeFireStateMessage_Stop(weaponGuid))
|
||||
|
||||
case VehicleResponse.Reload(itemGuid) if isNotSameTarget =>
|
||||
sendResponse(ReloadMessage(itemGuid, ammo_clip=1, unk1=0))
|
||||
|
||||
case VehicleResponse.ChangeAmmo(weapon_guid, weapon_slot, previous_guid, ammo_id, ammo_guid, ammo_data) if isNotSameTarget =>
|
||||
sendResponse(ObjectDetachMessage(weapon_guid, previous_guid, Vector3.Zero, 0))
|
||||
//TODO? sendResponse(ObjectDeleteMessage(previousAmmoGuid, 0))
|
||||
sendResponse(
|
||||
ObjectCreateMessage(
|
||||
ammo_id,
|
||||
ammo_guid,
|
||||
ObjectCreateMessageParent(weapon_guid, weapon_slot),
|
||||
ammo_data
|
||||
)
|
||||
)
|
||||
sendResponse(ChangeAmmoMessage(weapon_guid, 1))
|
||||
|
||||
case VehicleResponse.WeaponDryFire(weaponGuid) if isNotSameTarget =>
|
||||
continent.GUID(weaponGuid).collect {
|
||||
case tool: Tool if tool.Magazine == 0 =>
|
||||
// check that the magazine is still empty before sending WeaponDryFireMessage
|
||||
// if it has been reloaded since then, other clients will not see it firing
|
||||
sendResponse(WeaponDryFireMessage(weaponGuid))
|
||||
}
|
||||
|
||||
case VehicleResponse.DismountVehicle(bailType, wasKickedByDriver) if isNotSameTarget =>
|
||||
sendResponse(DismountVehicleMsg(guid, bailType, wasKickedByDriver))
|
||||
|
||||
case VehicleResponse.MountVehicle(vehicleGuid, seat) if isNotSameTarget =>
|
||||
sendResponse(ObjectAttachMessage(vehicleGuid, guid, seat))
|
||||
|
||||
case VehicleResponse.DeployRequest(objectGuid, state, unk1, unk2, pos) if isNotSameTarget =>
|
||||
sendResponse(DeployRequestMessage(guid, objectGuid, state, unk1, unk2, pos))
|
||||
|
||||
case VehicleResponse.SendResponse(msg) =>
|
||||
sendResponse(msg)
|
||||
|
||||
case VehicleResponse.AttachToRails(vehicleGuid, padGuid) =>
|
||||
sendResponse(ObjectAttachMessage(padGuid, vehicleGuid, slot=3))
|
||||
|
||||
case VehicleResponse.ConcealPlayer(playerGuid) =>
|
||||
sendResponse(GenericObjectActionMessage(playerGuid, code=9))
|
||||
|
||||
case VehicleResponse.DetachFromRails(vehicleGuid, padGuid, padPosition, padOrientationZ) =>
|
||||
val pad = continent.GUID(padGuid).get.asInstanceOf[VehicleSpawnPad].Definition
|
||||
sendResponse(
|
||||
ObjectDetachMessage(
|
||||
padGuid,
|
||||
vehicleGuid,
|
||||
padPosition + Vector3.z(pad.VehicleCreationZOffset),
|
||||
padOrientationZ + pad.VehicleCreationZOrientOffset
|
||||
)
|
||||
)
|
||||
|
||||
case VehicleResponse.EquipmentInSlot(pkt) if isNotSameTarget =>
|
||||
sendResponse(pkt)
|
||||
|
||||
case VehicleResponse.GenericObjectAction(objectGuid, action) if isNotSameTarget =>
|
||||
sendResponse(GenericObjectActionMessage(objectGuid, action))
|
||||
|
||||
case VehicleResponse.HitHint(sourceGuid) if player.isAlive =>
|
||||
sendResponse(HitHint(sourceGuid, player.GUID))
|
||||
|
||||
case VehicleResponse.InventoryState(obj, parentGuid, start, conData) if isNotSameTarget =>
|
||||
//TODO prefer ObjectDetachMessage, but how to force ammo pools to update properly?
|
||||
val objGuid = obj.GUID
|
||||
sendResponse(ObjectDeleteMessage(objGuid, unk1=0))
|
||||
sendResponse(ObjectCreateDetailedMessage(
|
||||
obj.Definition.ObjectId,
|
||||
objGuid,
|
||||
ObjectCreateMessageParent(parentGuid, start),
|
||||
conData
|
||||
))
|
||||
|
||||
case VehicleResponse.KickPassenger(_, wasKickedByDriver, vehicleGuid) if resolvedPlayerGuid == guid =>
|
||||
//seat number (first field) seems to be correct if passenger is kicked manually by driver
|
||||
//but always seems to return 4 if user is kicked by mount permissions changing
|
||||
sendResponse(DismountVehicleMsg(guid, BailType.Kicked, wasKickedByDriver))
|
||||
val typeOfRide = continent.GUID(vehicleGuid) match {
|
||||
case Some(obj: Vehicle) =>
|
||||
sessionLogic.general.unaccessContainer(obj)
|
||||
s"the ${obj.Definition.Name}'s seat by ${obj.OwnerName.getOrElse("the pilot")}"
|
||||
case _ =>
|
||||
s"${player.Sex.possessive} ride"
|
||||
}
|
||||
log.info(s"${player.Name} has been kicked from $typeOfRide!")
|
||||
|
||||
case VehicleResponse.KickPassenger(_, wasKickedByDriver, _) =>
|
||||
//seat number (first field) seems to be correct if passenger is kicked manually by driver
|
||||
//but always seems to return 4 if user is kicked by mount permissions changing
|
||||
sendResponse(DismountVehicleMsg(guid, BailType.Kicked, wasKickedByDriver))
|
||||
|
||||
case VehicleResponse.InventoryState2(objGuid, parentGuid, value) if isNotSameTarget =>
|
||||
sendResponse(InventoryStateMessage(objGuid, unk=0, parentGuid, value))
|
||||
|
||||
case VehicleResponse.LoadVehicle(vehicle, vtype, vguid, vdata) if isNotSameTarget =>
|
||||
//this is not be suitable for vehicles with people who are seated in it before it spawns (if that is possible)
|
||||
sendResponse(ObjectCreateMessage(vtype, vguid, vdata))
|
||||
Vehicles.ReloadAccessPermissions(vehicle, player.Name)
|
||||
|
||||
case VehicleResponse.ObjectDelete(itemGuid) if isNotSameTarget =>
|
||||
sendResponse(ObjectDeleteMessage(itemGuid, unk1=0))
|
||||
|
||||
case VehicleResponse.Ownership(vehicleGuid) if resolvedPlayerGuid == guid =>
|
||||
//Only the player that owns this vehicle needs the ownership packet
|
||||
avatarActor ! AvatarActor.SetVehicle(Some(vehicleGuid))
|
||||
sendResponse(PlanetsideAttributeMessage(resolvedPlayerGuid, attribute_type=21, vehicleGuid))
|
||||
|
||||
case VehicleResponse.PlanetsideAttribute(vehicleGuid, attributeType, attributeValue) if isNotSameTarget =>
|
||||
sendResponse(PlanetsideAttributeMessage(vehicleGuid, attributeType, attributeValue))
|
||||
|
||||
case VehicleResponse.ResetSpawnPad(padGuid) =>
|
||||
sendResponse(GenericObjectActionMessage(padGuid, code=23))
|
||||
|
||||
case VehicleResponse.RevealPlayer(playerGuid) =>
|
||||
sendResponse(GenericObjectActionMessage(playerGuid, code=10))
|
||||
|
||||
case VehicleResponse.SeatPermissions(vehicleGuid, seatGroup, permission) if isNotSameTarget =>
|
||||
sendResponse(PlanetsideAttributeMessage(vehicleGuid, seatGroup, permission))
|
||||
|
||||
case VehicleResponse.StowEquipment(vehicleGuid, slot, itemType, itemGuid, itemData) if isNotSameTarget =>
|
||||
//TODO prefer ObjectAttachMessage, but how to force ammo pools to update properly?
|
||||
sendResponse(ObjectCreateDetailedMessage(itemType, itemGuid, ObjectCreateMessageParent(vehicleGuid, slot), itemData))
|
||||
|
||||
case VehicleResponse.UnloadVehicle(_, vehicleGuid) =>
|
||||
sendResponse(ObjectDeleteMessage(vehicleGuid, unk1=0))
|
||||
|
||||
case VehicleResponse.UnstowEquipment(itemGuid) if isNotSameTarget =>
|
||||
//TODO prefer ObjectDetachMessage, but how to force ammo pools to update properly?
|
||||
sendResponse(ObjectDeleteMessage(itemGuid, unk1=0))
|
||||
|
||||
case VehicleResponse.UpdateAmsSpawnPoint(list) =>
|
||||
sessionLogic.zoning.spawn.amsSpawnPoints = list.filter(tube => tube.Faction == player.Faction)
|
||||
sessionLogic.zoning.spawn.DrawCurrentAmsSpawnPoint()
|
||||
|
||||
case VehicleResponse.TransferPassengerChannel(oldChannel, tempChannel, vehicle, vehicleToDelete) if isNotSameTarget =>
|
||||
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 && 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
|
||||
sessionLogic.vehicles.ServerVehicleOverrideWithPacket(
|
||||
vehicle,
|
||||
ServerVehicleOverrideMsg(
|
||||
lock_accelerator=true,
|
||||
lock_wheel=true,
|
||||
reverse=true,
|
||||
unk4=false,
|
||||
lock_vthrust=0,
|
||||
strafe,
|
||||
reverseSpeed,
|
||||
unk8=Some(0)
|
||||
)
|
||||
)
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
import scala.concurrent.duration._
|
||||
context.system.scheduler.scheduleOnce(
|
||||
delay milliseconds,
|
||||
context.self,
|
||||
VehicleServiceResponse(toChannel, PlanetSideGUID(0), VehicleResponse.KickCargo(vehicle, speed=0, delay))
|
||||
)
|
||||
|
||||
case VehicleResponse.KickCargo(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) =>
|
||||
player.DrawnSlot = Player.HandsDownSlot
|
||||
startPlayerSeatedInVehicle(vehicle)
|
||||
|
||||
case VehicleResponse.StartPlayerSeatedInVehicle(vehicle, _) =>
|
||||
startPlayerSeatedInVehicle(vehicle)
|
||||
|
||||
case VehicleResponse.PlayerSeatedInVehicle(vehicle, _) =>
|
||||
Vehicles.ReloadAccessPermissions(vehicle, player.Name)
|
||||
sessionLogic.vehicles.ServerVehicleOverrideWithPacket(
|
||||
vehicle,
|
||||
ServerVehicleOverrideMsg(
|
||||
lock_accelerator=true,
|
||||
lock_wheel=true,
|
||||
reverse=true,
|
||||
unk4=false,
|
||||
lock_vthrust=1,
|
||||
lock_strafe=0,
|
||||
movement_speed=0,
|
||||
unk8=Some(0)
|
||||
)
|
||||
)
|
||||
sessionLogic.vehicles.serverVehicleControlVelocity = Some(0)
|
||||
|
||||
case VehicleResponse.ServerVehicleOverrideStart(vehicle, _) =>
|
||||
val vdef = vehicle.Definition
|
||||
sessionLogic.vehicles.ServerVehicleOverrideWithPacket(
|
||||
vehicle,
|
||||
ServerVehicleOverrideMsg(
|
||||
lock_accelerator=true,
|
||||
lock_wheel=true,
|
||||
reverse=false,
|
||||
unk4=false,
|
||||
lock_vthrust=if (GlobalDefinitions.isFlightVehicle(vdef)) { 1 } else { 0 },
|
||||
lock_strafe=0,
|
||||
movement_speed=vdef.AutoPilotSpeed1,
|
||||
unk8=Some(0)
|
||||
)
|
||||
)
|
||||
|
||||
case VehicleResponse.ServerVehicleOverrideEnd(vehicle, _) =>
|
||||
sessionLogic.vehicles.ServerVehicleOverrideStop(vehicle)
|
||||
|
||||
case VehicleResponse.PeriodicReminder(VehicleSpawnPad.Reminders.Blocked, data) =>
|
||||
sendResponse(ChatMsg(
|
||||
ChatMessageType.CMT_OPEN,
|
||||
wideContents=true,
|
||||
recipient="",
|
||||
s"The vehicle spawn where you placed your order is blocked. ${data.getOrElse("")}",
|
||||
note=None
|
||||
))
|
||||
|
||||
case VehicleResponse.PeriodicReminder(_, data) =>
|
||||
val (isType, flag, msg): (ChatMessageType, Boolean, String) = data match {
|
||||
case Some(msg: String) if msg.startsWith("@") => (ChatMessageType.UNK_227, false, msg)
|
||||
case Some(msg: String) => (ChatMessageType.CMT_OPEN, true, msg)
|
||||
case _ => (ChatMessageType.CMT_OPEN, true, "Your vehicle order has been cancelled.")
|
||||
}
|
||||
sendResponse(ChatMsg(isType, flag, recipient="", msg, None))
|
||||
|
||||
case VehicleResponse.ChangeLoadout(target, oldWeapons, addedWeapons, oldInventory, newInventory)
|
||||
if player.avatar.vehicle.contains(target) =>
|
||||
//TODO when vehicle weapons can be changed without visual glitches, rewrite this
|
||||
continent.GUID(target).collect { case vehicle: Vehicle =>
|
||||
import net.psforever.login.WorldSession.boolToInt
|
||||
//owner: must unregister old equipment, and register and install new equipment
|
||||
(oldWeapons ++ oldInventory).foreach {
|
||||
case (obj, eguid) =>
|
||||
sendResponse(ObjectDeleteMessage(eguid, unk1=0))
|
||||
TaskWorkflow.execute(GUIDTask.unregisterEquipment(continent.GUID, obj))
|
||||
}
|
||||
sessionLogic.general.applyPurchaseTimersBeforePackingLoadout(player, vehicle, addedWeapons ++ newInventory)
|
||||
//jammer or unjamm new weapons based on vehicle status
|
||||
val vehicleJammered = vehicle.Jammed
|
||||
addedWeapons
|
||||
.map { _.obj }
|
||||
.collect {
|
||||
case jamItem: JammableUnit if jamItem.Jammed != vehicleJammered =>
|
||||
jamItem.Jammed = vehicleJammered
|
||||
JammableMountedWeapons.JammedWeaponStatus(vehicle.Zone, jamItem, vehicleJammered)
|
||||
}
|
||||
changeLoadoutDeleteOldEquipment(vehicle, oldWeapons, oldInventory)
|
||||
}
|
||||
|
||||
case VehicleResponse.ChangeLoadout(target, oldWeapons, _, oldInventory, _)
|
||||
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
|
||||
(oldWeapons ++ oldInventory).foreach { case (_, eguid) => sendResponse(ObjectDeleteMessage(eguid, unk1=0)) }
|
||||
changeLoadoutDeleteOldEquipment(vehicle, oldWeapons, oldInventory)
|
||||
}
|
||||
|
||||
case VehicleResponse.ChangeLoadout(target, oldWeapons, _, oldInventory, _) =>
|
||||
//TODO when vehicle weapons can be changed without visual glitches, rewrite this
|
||||
continent.GUID(target).collect { case vehicle: Vehicle =>
|
||||
changeLoadoutDeleteOldEquipment(vehicle, oldWeapons, oldInventory)
|
||||
}
|
||||
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
|
||||
private def changeLoadoutDeleteOldEquipment(
|
||||
vehicle: Vehicle,
|
||||
oldWeapons: Iterable[(Equipment, PlanetSideGUID)],
|
||||
oldInventory: Iterable[(Equipment, PlanetSideGUID)]
|
||||
): Unit = {
|
||||
vehicle.PassengerInSeat(player) match {
|
||||
case Some(seatNum) =>
|
||||
//participant: observe changes to equipment
|
||||
(oldWeapons ++ oldInventory).foreach {
|
||||
case (_, eguid) => sendResponse(ObjectDeleteMessage(eguid, unk1=0))
|
||||
}
|
||||
sessionLogic.mountResponse.updateWeaponAtSeatPosition(vehicle, seatNum)
|
||||
case None =>
|
||||
//observer: observe changes to external equipment
|
||||
oldWeapons.foreach { case (_, eguid) => sendResponse(ObjectDeleteMessage(eguid, unk1=0)) }
|
||||
}
|
||||
}
|
||||
|
||||
private def startPlayerSeatedInVehicle(vehicle: Vehicle): Unit = {
|
||||
val vehicle_guid = vehicle.GUID
|
||||
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 {
|
||||
case (mountPoint, _) => vehicle.Actor ! Mountable.TryMount(player, mountPoint)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,407 @@
|
|||
// Copyright (c) 2024 PSForever
|
||||
package net.psforever.actors.session.normal
|
||||
|
||||
import akka.actor.{ActorContext, typed}
|
||||
import net.psforever.actors.session.AvatarActor
|
||||
import net.psforever.actors.session.support.{SessionData, VehicleFunctions, VehicleOperations}
|
||||
import net.psforever.objects.serverobject.PlanetSideServerObject
|
||||
import net.psforever.objects.{PlanetSideGameObject, Player, Vehicle, Vehicles}
|
||||
import net.psforever.objects.serverobject.deploy.Deployment
|
||||
import net.psforever.objects.serverobject.mount.Mountable
|
||||
import net.psforever.objects.vehicles.control.BfrFlight
|
||||
import net.psforever.objects.zones.Zone
|
||||
import net.psforever.packet.game.{ChildObjectStateMessage, DeployRequestMessage, FrameVehicleStateMessage, VehicleStateMessage, VehicleSubStateMessage}
|
||||
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
|
||||
import net.psforever.types.{DriveState, Vector3}
|
||||
|
||||
class VehicleLogic(val ops: VehicleOperations) extends VehicleFunctions {
|
||||
def sessionLogic: SessionData = ops.sessionLogic
|
||||
|
||||
implicit val context: ActorContext = ops.context
|
||||
|
||||
private val avatarActor: typed.ActorRef[AvatarActor.Command] = ops.avatarActor
|
||||
|
||||
/* packets */
|
||||
|
||||
def handleVehicleState(pkt: VehicleStateMessage): Unit = {
|
||||
val VehicleStateMessage(
|
||||
vehicle_guid,
|
||||
unk1,
|
||||
pos,
|
||||
ang,
|
||||
vel,
|
||||
is_flying,
|
||||
unk6,
|
||||
unk7,
|
||||
wheels,
|
||||
is_decelerating,
|
||||
is_cloaked
|
||||
) = pkt
|
||||
GetVehicleAndSeat() match {
|
||||
case (Some(obj), Some(0)) =>
|
||||
//we're driving the vehicle
|
||||
sessionLogic.persist()
|
||||
sessionLogic.turnCounterFunc(player.GUID)
|
||||
sessionLogic.general.fallHeightTracker(pos.z)
|
||||
if (obj.MountedIn.isEmpty) {
|
||||
sessionLogic.updateBlockMap(obj, pos)
|
||||
}
|
||||
player.Position = pos //convenient
|
||||
if (obj.WeaponControlledFromSeat(0).isEmpty) {
|
||||
player.Orientation = Vector3.z(ang.z) //convenient
|
||||
}
|
||||
obj.Position = pos
|
||||
obj.Orientation = ang
|
||||
if (obj.MountedIn.isEmpty) {
|
||||
if (obj.DeploymentState != DriveState.Deployed) {
|
||||
obj.Velocity = vel
|
||||
} else {
|
||||
obj.Velocity = Some(Vector3.Zero)
|
||||
}
|
||||
if (obj.Definition.CanFly) {
|
||||
obj.Flying = is_flying //usually Some(7)
|
||||
}
|
||||
obj.Cloaked = obj.Definition.CanCloak && is_cloaked
|
||||
} else {
|
||||
obj.Velocity = None
|
||||
obj.Flying = None
|
||||
}
|
||||
continent.VehicleEvents ! VehicleServiceMessage(
|
||||
continent.id,
|
||||
VehicleAction.VehicleState(
|
||||
player.GUID,
|
||||
vehicle_guid,
|
||||
unk1,
|
||||
obj.Position,
|
||||
ang,
|
||||
obj.Velocity,
|
||||
if (obj.isFlying) {
|
||||
is_flying
|
||||
} else {
|
||||
None
|
||||
},
|
||||
unk6,
|
||||
unk7,
|
||||
wheels,
|
||||
is_decelerating,
|
||||
obj.Cloaked
|
||||
)
|
||||
)
|
||||
sessionLogic.squad.updateSquad()
|
||||
obj.zoneInteractions()
|
||||
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
|
||||
case (_, Some(index)) =>
|
||||
log.error(
|
||||
s"VehicleState: ${player.Name} should not be dispatching this kind of packet from vehicle ${vehicle_guid.guid} when not the driver (actually, seat $index)"
|
||||
)
|
||||
case _ => ;
|
||||
}
|
||||
if (player.death_by == -1) {
|
||||
sessionLogic.kickedByAdministration()
|
||||
}
|
||||
}
|
||||
|
||||
def handleFrameVehicleState(pkt: FrameVehicleStateMessage): Unit = {
|
||||
val FrameVehicleStateMessage(
|
||||
vehicle_guid,
|
||||
unk1,
|
||||
pos,
|
||||
ang,
|
||||
vel,
|
||||
unk2,
|
||||
unk3,
|
||||
unk4,
|
||||
is_crouched,
|
||||
is_airborne,
|
||||
ascending_flight,
|
||||
flight_time,
|
||||
unk9,
|
||||
unkA
|
||||
) = pkt
|
||||
GetVehicleAndSeat() match {
|
||||
case (Some(obj), Some(0)) =>
|
||||
//we're driving the vehicle
|
||||
sessionLogic.persist()
|
||||
sessionLogic.turnCounterFunc(player.GUID)
|
||||
val (position, angle, velocity, notMountedState) = continent.GUID(obj.MountedIn) match {
|
||||
case Some(v: Vehicle) =>
|
||||
sessionLogic.updateBlockMap(obj, pos)
|
||||
(pos, v.Orientation - Vector3.z(value = 90f) * Vehicles.CargoOrientation(obj).toFloat, v.Velocity, false)
|
||||
case _ =>
|
||||
(pos, ang, vel, true)
|
||||
}
|
||||
player.Position = position //convenient
|
||||
if (obj.WeaponControlledFromSeat(seatNumber = 0).isEmpty) {
|
||||
player.Orientation = Vector3.z(ang.z) //convenient
|
||||
}
|
||||
obj.Position = position
|
||||
obj.Orientation = angle
|
||||
obj.Velocity = velocity
|
||||
// if (is_crouched && obj.DeploymentState != DriveState.Kneeling) {
|
||||
// //dev stuff goes here
|
||||
// }
|
||||
// else
|
||||
// if (!is_crouched && obj.DeploymentState == DriveState.Kneeling) {
|
||||
// //dev stuff goes here
|
||||
// }
|
||||
obj.DeploymentState = if (is_crouched || !notMountedState) DriveState.Kneeling else DriveState.Mobile
|
||||
if (notMountedState) {
|
||||
if (obj.DeploymentState != DriveState.Kneeling) {
|
||||
if (is_airborne) {
|
||||
val flight = if (ascending_flight) flight_time else -flight_time
|
||||
obj.Flying = Some(flight)
|
||||
obj.Actor ! BfrFlight.Soaring(flight)
|
||||
} else if (obj.Flying.nonEmpty) {
|
||||
obj.Flying = None
|
||||
obj.Actor ! BfrFlight.Landed
|
||||
}
|
||||
} else {
|
||||
obj.Velocity = None
|
||||
obj.Flying = None
|
||||
}
|
||||
obj.zoneInteractions()
|
||||
} else {
|
||||
obj.Velocity = None
|
||||
obj.Flying = None
|
||||
}
|
||||
continent.VehicleEvents ! VehicleServiceMessage(
|
||||
continent.id,
|
||||
VehicleAction.FrameVehicleState(
|
||||
player.GUID,
|
||||
vehicle_guid,
|
||||
unk1,
|
||||
position,
|
||||
angle,
|
||||
velocity,
|
||||
unk2,
|
||||
unk3,
|
||||
unk4,
|
||||
is_crouched,
|
||||
is_airborne,
|
||||
ascending_flight,
|
||||
flight_time,
|
||||
unk9,
|
||||
unkA
|
||||
)
|
||||
)
|
||||
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
|
||||
case (_, Some(index)) =>
|
||||
log.error(
|
||||
s"VehicleState: ${player.Name} should not be dispatching this kind of packet from vehicle ${vehicle_guid.guid} when not the driver (actually, seat $index)"
|
||||
)
|
||||
case _ => ;
|
||||
}
|
||||
if (player.death_by == -1) {
|
||||
sessionLogic.kickedByAdministration()
|
||||
}
|
||||
}
|
||||
|
||||
def handleChildObjectState(pkt: ChildObjectStateMessage): Unit = {
|
||||
val ChildObjectStateMessage(object_guid, pitch, yaw) = pkt
|
||||
val (o, tools) = sessionLogic.shooting.FindContainedWeapon
|
||||
//is COSM our primary upstream packet?
|
||||
(o match {
|
||||
case Some(mount: Mountable) => (o, mount.PassengerInSeat(player))
|
||||
case _ => (None, None)
|
||||
}) match {
|
||||
case (None, None) | (_, None) | (Some(_: Vehicle), Some(0)) => ;
|
||||
case _ =>
|
||||
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 {
|
||||
case None =>
|
||||
//todo: old warning; this state is problematic, but can trigger in otherwise valid instances
|
||||
//log.warn(
|
||||
// s"ChildObjectState: ${player.Name} is using a different controllable agent than entity ${object_guid.guid}"
|
||||
//)
|
||||
case Some(_) =>
|
||||
//TODO set tool orientation?
|
||||
player.Orientation = Vector3(0f, pitch, yaw)
|
||||
continent.VehicleEvents ! VehicleServiceMessage(
|
||||
continent.id,
|
||||
VehicleAction.ChildObjectState(player.GUID, object_guid, pitch, yaw)
|
||||
)
|
||||
}
|
||||
//TODO status condition of "playing getting out of vehicle to allow for late packets without warning
|
||||
if (player.death_by == -1) {
|
||||
sessionLogic.kickedByAdministration()
|
||||
}
|
||||
}
|
||||
|
||||
def handleVehicleSubState(pkt: VehicleSubStateMessage): Unit = {
|
||||
val VehicleSubStateMessage(vehicle_guid, _, pos, ang, vel, unk1, _) = pkt
|
||||
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
|
||||
sessionLogic.updateBlockMap(obj, pos)
|
||||
obj.zoneInteractions()
|
||||
continent.VehicleEvents ! VehicleServiceMessage(
|
||||
continent.id,
|
||||
VehicleAction.VehicleState(
|
||||
player.GUID,
|
||||
vehicle_guid,
|
||||
unk1,
|
||||
pos,
|
||||
ang,
|
||||
obj.Velocity,
|
||||
obj.Flying,
|
||||
0,
|
||||
0,
|
||||
15,
|
||||
unk5 = false,
|
||||
obj.Cloaked
|
||||
)
|
||||
)
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
|
||||
def handleDeployRequest(pkt: DeployRequestMessage): Unit = {
|
||||
val DeployRequestMessage(_, vehicle_guid, deploy_state, _, _, _) = pkt
|
||||
val vehicle = player.avatar.vehicle
|
||||
if (vehicle.contains(vehicle_guid)) {
|
||||
if (vehicle == player.VehicleSeated) {
|
||||
continent.GUID(vehicle_guid) match {
|
||||
case Some(obj: Vehicle) =>
|
||||
log.info(s"${player.Name} is requesting a deployment change for ${obj.Definition.Name} - $deploy_state")
|
||||
obj.Actor ! Deployment.TryDeploymentChange(deploy_state)
|
||||
|
||||
case _ =>
|
||||
log.error(s"DeployRequest: ${player.Name} can not find vehicle $vehicle_guid")
|
||||
avatarActor ! AvatarActor.SetVehicle(None)
|
||||
}
|
||||
} else {
|
||||
log.warn(s"${player.Name} must be mounted to request a deployment change")
|
||||
}
|
||||
} else {
|
||||
log.warn(s"DeployRequest: ${player.Name} does not own the deploying $vehicle_guid object")
|
||||
}
|
||||
}
|
||||
|
||||
/* messages */
|
||||
|
||||
def handleCanDeploy(obj: Deployment.DeploymentObject, state: DriveState.Value): Unit = {
|
||||
if (state == DriveState.Deploying) {
|
||||
log.trace(s"DeployRequest: $obj transitioning to deploy state")
|
||||
} else if (state == DriveState.Deployed) {
|
||||
log.trace(s"DeployRequest: $obj has been Deployed")
|
||||
} else {
|
||||
CanNotChangeDeployment(obj, state, "incorrect deploy state")
|
||||
}
|
||||
}
|
||||
|
||||
def handleCanUndeploy(obj: Deployment.DeploymentObject, state: DriveState.Value): Unit = {
|
||||
if (state == DriveState.Undeploying) {
|
||||
log.trace(s"DeployRequest: $obj transitioning to undeploy state")
|
||||
} else if (state == DriveState.Mobile) {
|
||||
log.trace(s"DeployRequest: $obj is Mobile")
|
||||
} else {
|
||||
CanNotChangeDeployment(obj, state, "incorrect undeploy state")
|
||||
}
|
||||
}
|
||||
|
||||
def handleCanNotChangeDeployment(obj: Deployment.DeploymentObject, state: DriveState.Value, reason: String): Unit = {
|
||||
if (Deployment.CheckForDeployState(state) && !Deployment.AngleCheck(obj)) {
|
||||
CanNotChangeDeployment(obj, state, reason = "ground too steep")
|
||||
} else {
|
||||
CanNotChangeDeployment(obj, state, reason)
|
||||
}
|
||||
}
|
||||
|
||||
/* support functions */
|
||||
|
||||
/**
|
||||
* If the player is mounted in some entity, find that entity and get the mount index number at which the player is sat.
|
||||
* The priority of object confirmation is `direct` then `occupant.VehicleSeated`.
|
||||
* Once an object is found, the remainder are ignored.
|
||||
* @param direct a game object in which the player may be sat
|
||||
* @param occupant the player who is sat and may have specified the game object in which mounted
|
||||
* @return a tuple consisting of a vehicle reference and a mount index
|
||||
* if and only if the vehicle is known to this client and the `WorldSessioNActor`-global `player` occupies it;
|
||||
* `(None, None)`, otherwise (even if the vehicle can be determined)
|
||||
*/
|
||||
def GetMountableAndSeat(
|
||||
direct: Option[PlanetSideGameObject with Mountable],
|
||||
occupant: Player,
|
||||
zone: Zone
|
||||
): (Option[PlanetSideGameObject with Mountable], Option[Int]) =
|
||||
direct.orElse(zone.GUID(occupant.VehicleSeated)) match {
|
||||
case Some(obj: PlanetSideGameObject with Mountable) =>
|
||||
obj.PassengerInSeat(occupant) match {
|
||||
case index @ Some(_) =>
|
||||
(Some(obj), index)
|
||||
case None =>
|
||||
(None, None)
|
||||
}
|
||||
case _ =>
|
||||
(None, None)
|
||||
}
|
||||
|
||||
/**
|
||||
* If the player is seated in a vehicle, find that vehicle and get the mount index number at which the player is sat.<br>
|
||||
* <br>
|
||||
* For special purposes involved in zone transfers,
|
||||
* where the vehicle may or may not exist in either of the zones (yet),
|
||||
* the value of `interstellarFerry` is also polled.
|
||||
* Making certain this field is blanked after the transfer is completed is important
|
||||
* to avoid inspecting the wrong vehicle and failing simple vehicle checks where this function may be employed.
|
||||
* @see `GetMountableAndSeat`
|
||||
* @see `interstellarFerry`
|
||||
* @return a tuple consisting of a vehicle reference and a mount index
|
||||
* if and only if the vehicle is known to this client and the `WorldSessioNActor`-global `player` occupies it;
|
||||
* `(None, None)`, otherwise (even if the vehicle can be determined)
|
||||
*/
|
||||
def GetKnownVehicleAndSeat(): (Option[Vehicle], Option[Int]) =
|
||||
GetMountableAndSeat(sessionLogic.zoning.interstellarFerry, player, continent) match {
|
||||
case (Some(v: Vehicle), Some(seat)) => (Some(v), Some(seat))
|
||||
case _ => (None, None)
|
||||
}
|
||||
|
||||
/**
|
||||
* If the player is seated in a vehicle, find that vehicle and get the mount index number at which the player is sat.
|
||||
* @see `GetMountableAndSeat`
|
||||
* @return a tuple consisting of a vehicle reference and a mount index
|
||||
* if and only if the vehicle is known to this client and the `WorldSessioNActor`-global `player` occupies it;
|
||||
* `(None, None)`, otherwise (even if the vehicle can be determined)
|
||||
*/
|
||||
def GetVehicleAndSeat(): (Option[Vehicle], Option[Int]) =
|
||||
GetMountableAndSeat(None, player, continent) match {
|
||||
case (Some(v: Vehicle), Some(seat)) => (Some(v), Some(seat))
|
||||
case _ => (None, None)
|
||||
}
|
||||
|
||||
/**
|
||||
* Common reporting behavior when a `Deployment` object fails to properly transition between states.
|
||||
* @param obj the game object that could not
|
||||
* @param state the `DriveState` that could not be promoted
|
||||
* @param reason a string explaining why the state can not or will not change
|
||||
*/
|
||||
private def CanNotChangeDeployment(
|
||||
obj: PlanetSideServerObject with Deployment,
|
||||
state: DriveState.Value,
|
||||
reason: String
|
||||
): Unit = {
|
||||
val mobileShift: String = if (obj.DeploymentState != DriveState.Mobile) {
|
||||
obj.DeploymentState = DriveState.Mobile
|
||||
sendResponse(DeployRequestMessage(player.GUID, obj.GUID, DriveState.Mobile, 0, unk3=false, Vector3.Zero))
|
||||
continent.VehicleEvents ! VehicleServiceMessage(
|
||||
continent.id,
|
||||
VehicleAction.DeployRequest(player.GUID, obj.GUID, DriveState.Mobile, 0, unk2=false, Vector3.Zero)
|
||||
)
|
||||
"; enforcing Mobile deployment state"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
log.error(s"DeployRequest: ${player.Name} can not transition $obj to $state - $reason$mobileShift")
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -23,7 +23,7 @@ trait CommonSessionInterfacingFunctionality {
|
|||
|
||||
protected def context: ActorContext
|
||||
|
||||
protected def sessionLogic: SessionLogic
|
||||
protected def sessionLogic: SessionData
|
||||
|
||||
protected def session: Session = sessionLogic.session
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,24 @@
|
|||
// Copyright (c) 2024 PSForever
|
||||
package net.psforever.actors.session.support
|
||||
|
||||
import akka.actor.Actor.Receive
|
||||
import akka.actor.ActorRef
|
||||
|
||||
trait ModeLogic {
|
||||
def avatarResponse: AvatarHandlerFunctions
|
||||
def galaxy: GalaxyHandlerFunctions
|
||||
def general: GeneralFunctions
|
||||
def local: LocalHandlerFunctions
|
||||
def mountResponse: MountHandlerFunctions
|
||||
def squad: SquadHandlerFunctions
|
||||
def shooting: WeaponAndProjectileFunctions
|
||||
def terminals: TerminalHandlerFunctions
|
||||
def vehicles: VehicleFunctions
|
||||
def vehicleResponse: VehicleHandlerFunctions
|
||||
|
||||
def parse(sender: ActorRef): Receive
|
||||
}
|
||||
|
||||
trait PlayerMode {
|
||||
def setup(data: SessionData): ModeLogic
|
||||
}
|
||||
|
|
@ -1,591 +1,38 @@
|
|||
// Copyright (c) 2023 PSForever
|
||||
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
|
||||
|
||||
import scala.collection.mutable
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
import scala.concurrent.duration._
|
||||
//
|
||||
import net.psforever.actors.session.{AvatarActor, ChatActor}
|
||||
import net.psforever.login.WorldSession.{DropEquipmentFromInventory, DropLeftovers, HoldNewEquipmentUp}
|
||||
import net.psforever.objects.guid.{GUIDTask, TaskWorkflow}
|
||||
import net.psforever.objects.inventory.InventoryItem
|
||||
import net.psforever.objects.serverobject.pad.VehicleSpawnPad
|
||||
import net.psforever.objects.serverobject.terminals.{ProximityUnit, Terminal}
|
||||
import net.psforever.objects.vital.etc.ExplodingEntityReason
|
||||
import net.psforever.objects.zones.Zoning
|
||||
import net.psforever.objects.{GlobalDefinitions, Player, Tool, Vehicle}
|
||||
import net.psforever.packet.game.objectcreate.ObjectCreateMessageParent
|
||||
import net.psforever.packet.game._
|
||||
import net.psforever.services.avatar.{AvatarAction, AvatarResponse, AvatarServiceMessage}
|
||||
import net.psforever.services.{InterstellarClusterService => ICS}
|
||||
import net.psforever.services.avatar.AvatarResponse
|
||||
import net.psforever.types._
|
||||
import net.psforever.util.Config
|
||||
import net.psforever.zones.Zones
|
||||
|
||||
trait AvatarHandlerFunctions extends CommonSessionInterfacingFunctionality {
|
||||
val ops: SessionAvatarHandlers
|
||||
|
||||
def handle(toChannel: String, guid: PlanetSideGUID, reply: AvatarResponse.Response): Unit
|
||||
}
|
||||
|
||||
class SessionAvatarHandlers(
|
||||
val sessionLogic: SessionLogic,
|
||||
avatarActor: typed.ActorRef[AvatarActor.Command],
|
||||
val sessionLogic: SessionData,
|
||||
val avatarActor: typed.ActorRef[AvatarActor.Command],
|
||||
chatActor: typed.ActorRef[ChatActor.Command],
|
||||
implicit val context: ActorContext
|
||||
) extends CommonSessionInterfacingFunctionality {
|
||||
//TODO player characters only exist within a certain range of GUIDs for a given zone; this is overkill
|
||||
private[support] var lastSeenStreamMessage: mutable.LongMap[SessionAvatarHandlers.LastUpstream] =
|
||||
private[session] var lastSeenStreamMessage: mutable.LongMap[SessionAvatarHandlers.LastUpstream] =
|
||||
mutable.LongMap[SessionAvatarHandlers.LastUpstream]()
|
||||
private[this] val hidingPlayerRandomizer = new scala.util.Random
|
||||
private[session] val hidingPlayerRandomizer = new scala.util.Random
|
||||
|
||||
/**
|
||||
* na
|
||||
* @param toChannel na
|
||||
* @param guid na
|
||||
* @param reply na
|
||||
*/
|
||||
def handle(toChannel: String, guid: PlanetSideGUID, reply: AvatarResponse.Response): Unit = {
|
||||
val resolvedPlayerGuid = if (player != null && player.HasGUID) {
|
||||
player.GUID
|
||||
} else {
|
||||
Service.defaultPlayerGUID
|
||||
}
|
||||
val isNotSameTarget = resolvedPlayerGuid != guid
|
||||
val isSameTarget = !isNotSameTarget
|
||||
reply match {
|
||||
/* special messages */
|
||||
case AvatarResponse.TeardownConnection() =>
|
||||
log.trace(s"ending ${player.Name}'s old session by event system request (relog)")
|
||||
context.stop(context.self)
|
||||
|
||||
/* really common messages (very frequently, every life) */
|
||||
case pstate @ AvatarResponse.PlayerState(
|
||||
pos,
|
||||
vel,
|
||||
yaw,
|
||||
pitch,
|
||||
yawUpper,
|
||||
_,
|
||||
isCrouching,
|
||||
isJumping,
|
||||
jumpThrust,
|
||||
isCloaking,
|
||||
isNotRendered,
|
||||
canSeeReallyFar
|
||||
) if isNotSameTarget =>
|
||||
val pstateToSave = pstate.copy(timestamp = 0)
|
||||
val (lastMsg, lastTime, lastPosition, wasVisible, wasShooting) = lastSeenStreamMessage.get(guid.guid) match {
|
||||
case Some(SessionAvatarHandlers.LastUpstream(Some(msg), visible, shooting, time)) => (Some(msg), time, msg.pos, visible, shooting)
|
||||
case _ => (None, 0L, Vector3.Zero, false, None)
|
||||
}
|
||||
val drawConfig = Config.app.game.playerDraw //m
|
||||
val maxRange = drawConfig.rangeMax * drawConfig.rangeMax //sq.m
|
||||
val ourPosition = player.Position //xyz
|
||||
val currentDistance = Vector3.DistanceSquared(ourPosition, pos) //sq.m
|
||||
val inDrawableRange = currentDistance <= maxRange
|
||||
val now = System.currentTimeMillis() //ms
|
||||
if (
|
||||
sessionLogic.zoning.zoningStatus != Zoning.Status.Deconstructing &&
|
||||
!isNotRendered && inDrawableRange
|
||||
) {
|
||||
//conditions where visibility is assured
|
||||
val durationSince = now - lastTime //ms
|
||||
lazy val previouslyInDrawableRange = Vector3.DistanceSquared(ourPosition, lastPosition) <= maxRange
|
||||
lazy val targetDelay = {
|
||||
val populationOver = math.max(
|
||||
0,
|
||||
sessionLogic.localSector.livePlayerList.size - drawConfig.populationThreshold
|
||||
)
|
||||
val distanceAdjustment = math.pow(populationOver / drawConfig.populationStep * drawConfig.rangeStep, 2) //sq.m
|
||||
val adjustedDistance = currentDistance + distanceAdjustment //sq.m
|
||||
drawConfig.ranges.lastIndexWhere { dist => adjustedDistance > dist * dist } match {
|
||||
case -1 => 1
|
||||
case index => drawConfig.delays(index)
|
||||
}
|
||||
} //ms
|
||||
if (!wasVisible ||
|
||||
!previouslyInDrawableRange ||
|
||||
durationSince > drawConfig.delayMax ||
|
||||
(!lastMsg.contains(pstateToSave) &&
|
||||
(canSeeReallyFar ||
|
||||
currentDistance < drawConfig.rangeMin * drawConfig.rangeMin ||
|
||||
sessionLogic.general.canSeeReallyFar ||
|
||||
durationSince > targetDelay
|
||||
)
|
||||
)
|
||||
) {
|
||||
//must draw
|
||||
sendResponse(
|
||||
PlayerStateMessage(
|
||||
guid,
|
||||
pos,
|
||||
vel,
|
||||
yaw,
|
||||
pitch,
|
||||
yawUpper,
|
||||
timestamp = 0, //is this okay?
|
||||
isCrouching,
|
||||
isJumping,
|
||||
jumpThrust,
|
||||
isCloaking
|
||||
)
|
||||
)
|
||||
lastSeenStreamMessage.put(guid.guid, SessionAvatarHandlers.LastUpstream(Some(pstateToSave), visible=true, wasShooting, now))
|
||||
} else {
|
||||
//is visible, but skip reinforcement
|
||||
lastSeenStreamMessage.put(guid.guid, SessionAvatarHandlers.LastUpstream(Some(pstateToSave), visible=true, wasShooting, lastTime))
|
||||
}
|
||||
} else {
|
||||
//conditions where the target is not currently visible
|
||||
if (wasVisible) {
|
||||
//the target was JUST PREVIOUSLY visible; one last draw to move target beyond a renderable distance
|
||||
val lat = (1 + hidingPlayerRandomizer.nextInt(continent.map.scale.height.toInt)).toFloat
|
||||
sendResponse(
|
||||
PlayerStateMessage(
|
||||
guid,
|
||||
Vector3(1f, lat, 1f),
|
||||
vel=None,
|
||||
facingYaw=0f,
|
||||
facingPitch=0f,
|
||||
facingYawUpper=0f,
|
||||
timestamp=0, //is this okay?
|
||||
is_cloaked = isCloaking
|
||||
)
|
||||
)
|
||||
lastSeenStreamMessage.put(guid.guid, SessionAvatarHandlers.LastUpstream(Some(pstateToSave), visible=false, wasShooting, now))
|
||||
} else {
|
||||
//skip drawing altogether
|
||||
lastSeenStreamMessage.put(guid.guid, SessionAvatarHandlers.LastUpstream(Some(pstateToSave), visible=false, wasShooting, lastTime))
|
||||
}
|
||||
}
|
||||
|
||||
case AvatarResponse.ObjectHeld(slot, _)
|
||||
if isSameTarget && player.VisibleSlots.contains(slot) =>
|
||||
sendResponse(ObjectHeldMessage(guid, slot, unk1=true))
|
||||
//Stop using proximity terminals if player unholsters a weapon
|
||||
continent.GUID(sessionLogic.terminals.usingMedicalTerminal).collect {
|
||||
case term: Terminal with ProximityUnit => sessionLogic.terminals.StopUsingProximityUnit(term)
|
||||
}
|
||||
if (sessionLogic.zoning.zoningStatus == Zoning.Status.Deconstructing) {
|
||||
sessionLogic.zoning.spawn.stopDeconstructing()
|
||||
}
|
||||
|
||||
case AvatarResponse.ObjectHeld(slot, _)
|
||||
if isSameTarget && slot > -1 =>
|
||||
sendResponse(ObjectHeldMessage(guid, slot, unk1=true))
|
||||
|
||||
case AvatarResponse.ObjectHeld(_, _)
|
||||
if isSameTarget => ()
|
||||
|
||||
case AvatarResponse.ObjectHeld(_, previousSlot) =>
|
||||
sendResponse(ObjectHeldMessage(guid, previousSlot, unk1=false))
|
||||
|
||||
case AvatarResponse.ChangeFireState_Start(weaponGuid)
|
||||
if isNotSameTarget && lastSeenStreamMessage.get(guid.guid).exists { _.visible } =>
|
||||
sendResponse(ChangeFireStateMessage_Start(weaponGuid))
|
||||
val entry = lastSeenStreamMessage(guid.guid)
|
||||
lastSeenStreamMessage.put(guid.guid, entry.copy(shooting = Some(weaponGuid)))
|
||||
|
||||
case AvatarResponse.ChangeFireState_Start(weaponGuid)
|
||||
if isNotSameTarget =>
|
||||
sendResponse(ChangeFireStateMessage_Start(weaponGuid))
|
||||
|
||||
case AvatarResponse.ChangeFireState_Stop(weaponGuid)
|
||||
if isNotSameTarget && lastSeenStreamMessage.get(guid.guid).exists { msg => msg.visible || msg.shooting.nonEmpty } =>
|
||||
sendResponse(ChangeFireStateMessage_Stop(weaponGuid))
|
||||
val entry = lastSeenStreamMessage(guid.guid)
|
||||
lastSeenStreamMessage.put(guid.guid, entry.copy(shooting = None))
|
||||
|
||||
case AvatarResponse.ChangeFireState_Stop(weaponGuid)
|
||||
if isNotSameTarget =>
|
||||
sendResponse(ChangeFireStateMessage_Stop(weaponGuid))
|
||||
|
||||
case AvatarResponse.LoadPlayer(pkt) if isNotSameTarget =>
|
||||
sendResponse(pkt)
|
||||
|
||||
case AvatarResponse.EquipmentInHand(pkt) if isNotSameTarget =>
|
||||
sendResponse(pkt)
|
||||
|
||||
case AvatarResponse.PlanetsideAttribute(attributeType, attributeValue) if isNotSameTarget =>
|
||||
sendResponse(PlanetsideAttributeMessage(guid, attributeType, attributeValue))
|
||||
|
||||
case AvatarResponse.PlanetsideAttributeToAll(attributeType, attributeValue) =>
|
||||
sendResponse(PlanetsideAttributeMessage(guid, attributeType, attributeValue))
|
||||
|
||||
case AvatarResponse.PlanetsideAttributeSelf(attributeType, attributeValue) if isSameTarget =>
|
||||
sendResponse(PlanetsideAttributeMessage(guid, attributeType, attributeValue))
|
||||
|
||||
case AvatarResponse.GenericObjectAction(objectGuid, actionCode) if isNotSameTarget =>
|
||||
sendResponse(GenericObjectActionMessage(objectGuid, actionCode))
|
||||
|
||||
case AvatarResponse.HitHint(sourceGuid) if player.isAlive =>
|
||||
sendResponse(HitHint(sourceGuid, guid))
|
||||
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(destroyDisplayMessage(killer, victim, method, unk))
|
||||
|
||||
case AvatarResponse.TerminalOrderResult(terminalGuid, action, result)
|
||||
if result && (action == TransactionType.Buy || action == TransactionType.Loadout) =>
|
||||
sendResponse(ItemTransactionResultMessage(terminalGuid, action, result))
|
||||
sessionLogic.terminals.lastTerminalOrderFulfillment = true
|
||||
AvatarActor.savePlayerData(player)
|
||||
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))
|
||||
sessionLogic.terminals.lastTerminalOrderFulfillment = true
|
||||
|
||||
case AvatarResponse.ChangeExosuit(
|
||||
target,
|
||||
armor,
|
||||
exosuit,
|
||||
subtype,
|
||||
_,
|
||||
maxhand,
|
||||
oldHolsters,
|
||||
holsters,
|
||||
oldInventory,
|
||||
inventory,
|
||||
drop,
|
||||
delete
|
||||
) if resolvedPlayerGuid == target =>
|
||||
sendResponse(ArmorChangedMessage(target, exosuit, subtype))
|
||||
sendResponse(PlanetsideAttributeMessage(target, attribute_type=4, armor))
|
||||
//happening to this player
|
||||
//cleanup
|
||||
sendResponse(ObjectHeldMessage(target, Player.HandsDownSlot, unk1=false))
|
||||
(oldHolsters ++ oldInventory ++ delete).foreach {
|
||||
case (_, dguid) => sendResponse(ObjectDeleteMessage(dguid, unk1=0))
|
||||
}
|
||||
//functionally delete
|
||||
delete.foreach { case (obj, _) => TaskWorkflow.execute(GUIDTask.unregisterEquipment(continent.GUID, obj)) }
|
||||
//redraw
|
||||
if (maxhand) {
|
||||
TaskWorkflow.execute(HoldNewEquipmentUp(player)(
|
||||
Tool(GlobalDefinitions.MAXArms(subtype, player.Faction)),
|
||||
0
|
||||
))
|
||||
}
|
||||
//draw free hand
|
||||
player.FreeHand.Equipment.foreach { obj =>
|
||||
val definition = obj.Definition
|
||||
sendResponse(
|
||||
ObjectCreateDetailedMessage(
|
||||
definition.ObjectId,
|
||||
obj.GUID,
|
||||
ObjectCreateMessageParent(target, Player.FreeHandSlot),
|
||||
definition.Packet.DetailedConstructorData(obj).get
|
||||
)
|
||||
)
|
||||
}
|
||||
//draw holsters and inventory
|
||||
(holsters ++ inventory).foreach {
|
||||
case InventoryItem(obj, index) =>
|
||||
val definition = obj.Definition
|
||||
sendResponse(
|
||||
ObjectCreateDetailedMessage(
|
||||
definition.ObjectId,
|
||||
obj.GUID,
|
||||
ObjectCreateMessageParent(target, index),
|
||||
definition.Packet.DetailedConstructorData(obj).get
|
||||
)
|
||||
)
|
||||
}
|
||||
DropLeftovers(player)(drop)
|
||||
|
||||
case AvatarResponse.ChangeExosuit(target, armor, exosuit, subtype, slot, _, oldHolsters, holsters, _, _, _, delete) =>
|
||||
sendResponse(ArmorChangedMessage(target, exosuit, subtype))
|
||||
sendResponse(PlanetsideAttributeMessage(target, attribute_type=4, armor))
|
||||
//happening to some other player
|
||||
sendResponse(ObjectHeldMessage(target, slot, unk1 = false))
|
||||
//cleanup
|
||||
(oldHolsters ++ delete).foreach { case (_, guid) => sendResponse(ObjectDeleteMessage(guid, unk1=0)) }
|
||||
//draw holsters
|
||||
holsters.foreach {
|
||||
case InventoryItem(obj, index) =>
|
||||
val definition = obj.Definition
|
||||
sendResponse(
|
||||
ObjectCreateMessage(
|
||||
definition.ObjectId,
|
||||
obj.GUID,
|
||||
ObjectCreateMessageParent(target, index),
|
||||
definition.Packet.ConstructorData(obj).get
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
case AvatarResponse.ChangeLoadout(
|
||||
target,
|
||||
armor,
|
||||
exosuit,
|
||||
subtype,
|
||||
_,
|
||||
maxhand,
|
||||
oldHolsters,
|
||||
holsters,
|
||||
oldInventory,
|
||||
inventory,
|
||||
drops
|
||||
) if resolvedPlayerGuid == target =>
|
||||
sendResponse(ArmorChangedMessage(target, exosuit, subtype))
|
||||
sendResponse(PlanetsideAttributeMessage(target, attribute_type = 4, armor))
|
||||
//happening to this player
|
||||
sendResponse(ObjectHeldMessage(target, Player.HandsDownSlot, unk1=true))
|
||||
//cleanup
|
||||
(oldHolsters ++ oldInventory).foreach {
|
||||
case (obj, objGuid) =>
|
||||
sendResponse(ObjectDeleteMessage(objGuid, unk1=0))
|
||||
TaskWorkflow.execute(GUIDTask.unregisterEquipment(continent.GUID, obj))
|
||||
}
|
||||
drops.foreach(item => sendResponse(ObjectDeleteMessage(item.obj.GUID, unk1=0)))
|
||||
//redraw
|
||||
if (maxhand) {
|
||||
TaskWorkflow.execute(HoldNewEquipmentUp(player)(
|
||||
Tool(GlobalDefinitions.MAXArms(subtype, player.Faction)),
|
||||
slot = 0
|
||||
))
|
||||
}
|
||||
sessionLogic.general.applyPurchaseTimersBeforePackingLoadout(player, player, holsters ++ inventory)
|
||||
DropLeftovers(player)(drops)
|
||||
|
||||
case AvatarResponse.ChangeLoadout(target, armor, exosuit, subtype, slot, _, oldHolsters, _, _, _, _) =>
|
||||
//redraw handled by callbacks
|
||||
sendResponse(ArmorChangedMessage(target, exosuit, subtype))
|
||||
sendResponse(PlanetsideAttributeMessage(target, attribute_type=4, armor))
|
||||
//happening to some other player
|
||||
sendResponse(ObjectHeldMessage(target, slot, unk1=false))
|
||||
//cleanup
|
||||
oldHolsters.foreach { case (_, guid) => sendResponse(ObjectDeleteMessage(guid, unk1=0)) }
|
||||
|
||||
case AvatarResponse.UseKit(kguid, kObjId) =>
|
||||
sendResponse(
|
||||
UseItemMessage(
|
||||
resolvedPlayerGuid,
|
||||
kguid,
|
||||
resolvedPlayerGuid,
|
||||
unk2 = 4294967295L,
|
||||
unk3 = false,
|
||||
unk4 = Vector3.Zero,
|
||||
unk5 = Vector3.Zero,
|
||||
unk6 = 126,
|
||||
unk7 = 0, //sequence time?
|
||||
unk8 = 137,
|
||||
kObjId
|
||||
)
|
||||
)
|
||||
sendResponse(ObjectDeleteMessage(kguid, unk1=0))
|
||||
|
||||
case AvatarResponse.KitNotUsed(_, "") =>
|
||||
sessionLogic.general.kitToBeUsed = None
|
||||
|
||||
case AvatarResponse.KitNotUsed(_, msg) =>
|
||||
sessionLogic.general.kitToBeUsed = None
|
||||
sendResponse(ChatMsg(ChatMessageType.UNK_225, msg))
|
||||
|
||||
case AvatarResponse.UpdateKillsDeathsAssists(_, kda) =>
|
||||
avatarActor ! AvatarActor.UpdateKillsDeathsAssists(kda)
|
||||
|
||||
case AvatarResponse.AwardBep(charId, bep, expType) =>
|
||||
//if the target player, always award (some) BEP
|
||||
if (charId == player.CharId) {
|
||||
avatarActor ! AvatarActor.AwardBep(bep, expType)
|
||||
}
|
||||
|
||||
case AvatarResponse.AwardCep(charId, cep) =>
|
||||
//if the target player, always award (some) CEP
|
||||
if (charId == player.CharId) {
|
||||
avatarActor ! AvatarActor.AwardCep(cep)
|
||||
}
|
||||
|
||||
case AvatarResponse.FacilityCaptureRewards(buildingId, zoneNumber, cep) =>
|
||||
facilityCaptureRewards(buildingId, zoneNumber, cep)
|
||||
|
||||
case AvatarResponse.SendResponse(msg) =>
|
||||
sendResponse(msg)
|
||||
|
||||
case AvatarResponse.SendResponseTargeted(targetGuid, msg) if resolvedPlayerGuid == targetGuid =>
|
||||
sendResponse(msg)
|
||||
|
||||
/* common messages (maybe once every respawn) */
|
||||
case AvatarResponse.Reload(itemGuid)
|
||||
if isNotSameTarget && lastSeenStreamMessage.get(guid.guid).exists { _.visible } =>
|
||||
sendResponse(ReloadMessage(itemGuid, ammo_clip=1, unk1=0))
|
||||
|
||||
case AvatarResponse.Killed(mount) =>
|
||||
//log and chat messages
|
||||
val cause = player.LastDamage.flatMap { damage =>
|
||||
val interaction = damage.interaction
|
||||
val reason = interaction.cause
|
||||
val adversarial = interaction.adversarial.map { _.attacker }
|
||||
reason match {
|
||||
case r: ExplodingEntityReason if r.entity.isInstanceOf[VehicleSpawnPad] =>
|
||||
//also, @SVCP_Killed_TooCloseToPadOnCreate^n~ or "... within n meters of pad ..."
|
||||
sendResponse(ChatMsg(ChatMessageType.UNK_227, "@SVCP_Killed_OnPadOnCreate"))
|
||||
case _ => ()
|
||||
}
|
||||
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 (sessionLogic.shooting.shotsWhileDead > 0) {
|
||||
log.warn(
|
||||
s"SHOTS_WHILE_DEAD: client of ${avatar.name} fired ${sessionLogic.shooting.shotsWhileDead} rounds while character was dead on server"
|
||||
)
|
||||
sessionLogic.shooting.shotsWhileDead = 0
|
||||
}
|
||||
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)
|
||||
}
|
||||
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 =>
|
||||
sessionLogic.vehicles.ConditionalDriverVehicleControl(obj)
|
||||
sessionLogic.general.unaccessContainer(obj)
|
||||
}
|
||||
sessionLogic.actionsToCancel()
|
||||
sessionLogic.terminals.CancelAllProximityUnits()
|
||||
AvatarActor.savePlayerLocation(player)
|
||||
sessionLogic.zoning.spawn.shiftPosition = Some(player.Position)
|
||||
|
||||
//respawn
|
||||
val respawnTimer = 300.seconds
|
||||
sessionLogic.zoning.spawn.reviveTimer.cancel()
|
||||
if (player.death_by == 0) {
|
||||
sessionLogic.zoning.spawn.reviveTimer = context.system.scheduler.scheduleOnce(respawnTimer) {
|
||||
sessionLogic.cluster ! ICS.GetRandomSpawnPoint(
|
||||
Zones.sanctuaryZoneNumber(player.Faction),
|
||||
player.Faction,
|
||||
Seq(SpawnGroup.Sanctuary),
|
||||
context.self
|
||||
)
|
||||
}
|
||||
} else {
|
||||
sessionLogic.zoning.spawn.HandleReleaseAvatar(player, continent)
|
||||
}
|
||||
|
||||
case AvatarResponse.Release(tplayer) if isNotSameTarget =>
|
||||
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!")
|
||||
sessionLogic.zoning.spawn.reviveTimer.cancel()
|
||||
sessionLogic.zoning.spawn.deadState = DeadState.Alive
|
||||
player.Revive
|
||||
val health = player.Health
|
||||
sendResponse(PlanetsideAttributeMessage(revivalTargetGuid, attribute_type=0, health))
|
||||
sendResponse(AvatarDeadStateMessage(DeadState.Alive, timer_max=0, timer=0, player.Position, player.Faction, unk5=true))
|
||||
continent.AvatarEvents ! AvatarServiceMessage(
|
||||
continent.id,
|
||||
AvatarAction.PlanetsideAttributeToAll(revivalTargetGuid, attribute_type=0, health)
|
||||
)
|
||||
|
||||
/* uncommon messages (utility, or once in a while) */
|
||||
case AvatarResponse.ChangeAmmo(weapon_guid, weapon_slot, previous_guid, ammo_id, ammo_guid, ammo_data)
|
||||
if isNotSameTarget && lastSeenStreamMessage.get(guid.guid).exists { _.visible } =>
|
||||
changeAmmoProcedures(weapon_guid, previous_guid, ammo_id, ammo_guid, weapon_slot, ammo_data)
|
||||
sendResponse(ChangeAmmoMessage(weapon_guid, 1))
|
||||
|
||||
case AvatarResponse.ChangeAmmo(weapon_guid, weapon_slot, previous_guid, ammo_id, ammo_guid, ammo_data)
|
||||
if isNotSameTarget =>
|
||||
changeAmmoProcedures(weapon_guid, previous_guid, ammo_id, ammo_guid, weapon_slot, ammo_data)
|
||||
|
||||
case AvatarResponse.ChangeFireMode(itemGuid, mode) if isNotSameTarget =>
|
||||
sendResponse(ChangeFireModeMessage(itemGuid, mode))
|
||||
|
||||
case AvatarResponse.ConcealPlayer() =>
|
||||
sendResponse(GenericObjectActionMessage(guid, code=9))
|
||||
|
||||
case AvatarResponse.EnvironmentalDamage(_, _, _) =>
|
||||
//TODO damage marker?
|
||||
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_dmg")
|
||||
|
||||
case AvatarResponse.DropItem(pkt) if isNotSameTarget =>
|
||||
sendResponse(pkt)
|
||||
|
||||
case AvatarResponse.ObjectDelete(itemGuid, unk) if isNotSameTarget =>
|
||||
sendResponse(ObjectDeleteMessage(itemGuid, unk))
|
||||
|
||||
/* rare messages */
|
||||
case AvatarResponse.SetEmpire(objectGuid, faction) if isNotSameTarget =>
|
||||
sendResponse(SetEmpireMessage(objectGuid, faction))
|
||||
|
||||
case AvatarResponse.DropSpecialItem() =>
|
||||
sessionLogic.general.dropSpecialSlotItem()
|
||||
|
||||
case AvatarResponse.OxygenState(player, vehicle) =>
|
||||
sendResponse(OxygenStateMessage(
|
||||
DrowningTarget(player.guid, player.progress, player.state),
|
||||
vehicle.flatMap { vinfo => Some(DrowningTarget(vinfo.guid, vinfo.progress, vinfo.state)) }
|
||||
))
|
||||
|
||||
case AvatarResponse.LoadProjectile(pkt) if isNotSameTarget =>
|
||||
sendResponse(pkt)
|
||||
|
||||
case AvatarResponse.ProjectileState(projectileGuid, shotPos, shotVel, shotOrient, seq, end, targetGuid) if isNotSameTarget =>
|
||||
sendResponse(ProjectileStateMessage(projectileGuid, shotPos, shotVel, shotOrient, seq, end, targetGuid))
|
||||
|
||||
case AvatarResponse.ProjectileExplodes(projectileGuid, projectile) =>
|
||||
sendResponse(
|
||||
ProjectileStateMessage(
|
||||
projectileGuid,
|
||||
projectile.Position,
|
||||
shot_vel = Vector3.Zero,
|
||||
projectile.Orientation,
|
||||
sequence_num=0,
|
||||
end=true,
|
||||
hit_target_guid=PlanetSideGUID(0)
|
||||
)
|
||||
)
|
||||
sendResponse(ObjectDeleteMessage(projectileGuid, unk1=2))
|
||||
|
||||
case AvatarResponse.ProjectileAutoLockAwareness(mode) =>
|
||||
sendResponse(GenericActionMessage(mode))
|
||||
|
||||
case AvatarResponse.PutDownFDU(target) if isNotSameTarget =>
|
||||
sendResponse(GenericObjectActionMessage(target, code=53))
|
||||
|
||||
case AvatarResponse.StowEquipment(target, slot, item) if isNotSameTarget =>
|
||||
val definition = item.Definition
|
||||
sendResponse(
|
||||
ObjectCreateDetailedMessage(
|
||||
definition.ObjectId,
|
||||
item.GUID,
|
||||
ObjectCreateMessageParent(target, slot),
|
||||
definition.Packet.DetailedConstructorData(item).get
|
||||
)
|
||||
)
|
||||
|
||||
case AvatarResponse.WeaponDryFire(weaponGuid)
|
||||
if isNotSameTarget && lastSeenStreamMessage.get(guid.guid).exists { _.visible } =>
|
||||
continent.GUID(weaponGuid).collect {
|
||||
case tool: Tool if tool.Magazine == 0 =>
|
||||
// check that the magazine is still empty before sending WeaponDryFireMessage
|
||||
// if it has been reloaded since then, other clients will not see it firing
|
||||
sendResponse(WeaponDryFireMessage(weaponGuid))
|
||||
}
|
||||
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
|
||||
private def changeAmmoProcedures(
|
||||
def changeAmmoProcedures(
|
||||
weaponGuid: PlanetSideGUID,
|
||||
previousAmmoGuid: PlanetSideGUID,
|
||||
ammoTypeId: Int,
|
||||
|
|
@ -605,7 +52,7 @@ class SessionAvatarHandlers(
|
|||
)
|
||||
}
|
||||
|
||||
private def facilityCaptureRewards(buildingId: Int, zoneNumber: Int, cep: Long): Unit = {
|
||||
def facilityCaptureRewards(buildingId: Int, zoneNumber: Int, cep: Long): Unit = {
|
||||
//TODO squad services deactivated, participation trophy rewards for now - 11-20-2023
|
||||
//must be in a squad to earn experience
|
||||
val charId = player.CharId
|
||||
|
|
@ -711,7 +158,7 @@ class SessionAvatarHandlers(
|
|||
}
|
||||
|
||||
object SessionAvatarHandlers {
|
||||
private[support] case class LastUpstream(
|
||||
private[session] case class LastUpstream(
|
||||
msg: Option[AvatarResponse.PlayerState],
|
||||
visible: Boolean,
|
||||
shooting: Option[PlanetSideGUID],
|
||||
|
|
|
|||
|
|
@ -1,14 +1,9 @@
|
|||
// 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._
|
||||
|
|
@ -27,7 +22,9 @@ 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.objects.zones.blockmap.{BlockMap, BlockMapEntity, SectorGroup, SectorPopulation}
|
||||
import net.psforever.services.ServiceManager
|
||||
import net.psforever.services.ServiceManager.Lookup
|
||||
import net.psforever.packet._
|
||||
import net.psforever.packet.game._
|
||||
import net.psforever.services.account.AccountPersistenceService
|
||||
|
|
@ -37,7 +34,7 @@ import net.psforever.services.{Service, InterstellarClusterService => ICS}
|
|||
import net.psforever.types._
|
||||
import net.psforever.util.Config
|
||||
|
||||
object SessionLogic {
|
||||
object SessionData {
|
||||
//noinspection ScalaUnusedSymbol
|
||||
private def NoTurnCounterYet(guid: PlanetSideGUID): Unit = { }
|
||||
|
||||
|
|
@ -60,11 +57,10 @@ object SessionLogic {
|
|||
}
|
||||
}
|
||||
|
||||
trait SessionLogic {
|
||||
def middlewareActor: typed.ActorRef[MiddlewareActor.Command]
|
||||
|
||||
implicit def context: ActorContext
|
||||
|
||||
class SessionData(
|
||||
val middlewareActor: typed.ActorRef[MiddlewareActor.Command],
|
||||
implicit val 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.
|
||||
|
|
@ -80,19 +76,19 @@ trait SessionLogic {
|
|||
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] val log = org.log4s.getLogger
|
||||
private[session] var theSession: Session = Session()
|
||||
private[session] var accountIntermediary: ActorRef = Default.Actor
|
||||
private[session] var accountPersistence: ActorRef = Default.Actor
|
||||
private[session] var galaxyService: ActorRef = Default.Actor
|
||||
private[session] var squadService: ActorRef = Default.Actor
|
||||
private[session] 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 persistFunc: () => Unit = noPersistence
|
||||
private[session] 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[session] var turnCounterFunc: PlanetSideGUID => Unit = SessionData.NoTurnCounterYet
|
||||
private[session] val oldRefsMap: mutable.HashMap[PlanetSideGUID, String] = new mutable.HashMap[PlanetSideGUID, String]()
|
||||
private var contextSafeEntity: PlanetSideGUID = PlanetSideGUID(0)
|
||||
|
||||
val general: GeneralOperations =
|
||||
|
|
@ -128,7 +124,7 @@ trait SessionLogic {
|
|||
* updated when an upstream packet arrives;
|
||||
* allow to be a little stale for a short while
|
||||
*/
|
||||
private[support] var localSector: SectorPopulation = SectorGroup(Nil)
|
||||
private[session] var localSector: SectorPopulation = SectorGroup(Nil)
|
||||
|
||||
def session: Session = theSession
|
||||
|
||||
|
|
@ -159,7 +155,7 @@ trait SessionLogic {
|
|||
case LookupResult("galaxy", endpoint) =>
|
||||
galaxyService = endpoint
|
||||
buildDependentOperationsForGalaxy(endpoint)
|
||||
buildDependentOperations(endpoint, cluster)
|
||||
buildDependentOperationsForZoning(endpoint, cluster)
|
||||
true
|
||||
case LookupResult("squad", endpoint) =>
|
||||
squadService = endpoint
|
||||
|
|
@ -167,7 +163,7 @@ trait SessionLogic {
|
|||
true
|
||||
case ICS.InterstellarClusterServiceKey.Listing(listings) =>
|
||||
cluster = listings.head
|
||||
buildDependentOperations(galaxyService, cluster)
|
||||
buildDependentOperationsForZoning(galaxyService, cluster)
|
||||
true
|
||||
|
||||
case _ =>
|
||||
|
|
@ -182,7 +178,7 @@ trait SessionLogic {
|
|||
}
|
||||
}
|
||||
|
||||
def buildDependentOperations(galaxyActor: ActorRef, clusterActor: typed.ActorRef[ICS.Command]): Unit = {
|
||||
def buildDependentOperationsForZoning(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))
|
||||
}
|
||||
|
|
@ -203,10 +199,6 @@ trait SessionLogic {
|
|||
zoningOpt.nonEmpty
|
||||
}
|
||||
|
||||
/* message processing */
|
||||
|
||||
def parse(sender: ActorRef): Receive
|
||||
|
||||
/* support functions */
|
||||
|
||||
def validObject(id: Int): Option[PlanetSideGameObject] = validObject(Some(PlanetSideGUID(id)), decorator = "")
|
||||
|
|
@ -487,21 +479,21 @@ trait SessionLogic {
|
|||
(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)
|
||||
case (_, slot : EquipmentSlot) if slot.Equipment.nonEmpty => SessionData.updateOldRefsMap(slot.Equipment.get)
|
||||
}.flatten ++
|
||||
SessionLogic.updateOldRefsMap(v.Inventory)
|
||||
SessionData.updateOldRefsMap(v.Inventory)
|
||||
case _ =>
|
||||
Map.empty[PlanetSideGUID, String]
|
||||
}) ++
|
||||
(general.accessedContainer match {
|
||||
case Some(cont) => SessionLogic.updateOldRefsMap(cont.Inventory)
|
||||
case Some(cont) => SessionData.updateOldRefsMap(cont.Inventory)
|
||||
case None => Map.empty[PlanetSideGUID, String]
|
||||
}) ++
|
||||
player.Holsters().toList.collect {
|
||||
case slot if slot.Equipment.nonEmpty => SessionLogic.updateOldRefsMap(slot.Equipment.get)
|
||||
case slot if slot.Equipment.nonEmpty => SessionData.updateOldRefsMap(slot.Equipment.get)
|
||||
}.flatten ++
|
||||
SessionLogic.updateOldRefsMap(player.Inventory) ++
|
||||
SessionLogic.updateOldRefsMap(player.avatar.locker.Inventory)
|
||||
SessionData.updateOldRefsMap(player.Inventory) ++
|
||||
SessionData.updateOldRefsMap(player.avatar.locker.Inventory)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -2,190 +2,22 @@
|
|||
package net.psforever.actors.session.support
|
||||
|
||||
import akka.actor.{ActorContext, ActorRef, typed}
|
||||
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.packet.game.{BroadcastWarpgateUpdateMessage, HotSpotInfo => PacketHotSpotInfo, HotSpotUpdateMessage, ZoneInfoMessage, ZonePopulationUpdateMessage}
|
||||
import net.psforever.services.galaxy.GalaxyResponse
|
||||
import net.psforever.types.{MemberAction, PlanetSideEmpire}
|
||||
|
||||
class SessionGalaxyHandlers(
|
||||
val sessionLogic: SessionLogic,
|
||||
avatarActor: typed.ActorRef[AvatarActor.Command],
|
||||
galaxyService: ActorRef,
|
||||
implicit val context: ActorContext
|
||||
) extends CommonSessionInterfacingFunctionality {
|
||||
/* packets */
|
||||
trait GalaxyHandlerFunctions extends CommonSessionInterfacingFunctionality {
|
||||
def ops: SessionGalaxyHandlers
|
||||
|
||||
def handleUpdateIgnoredPlayers: PlanetSideGamePacket => Unit = {
|
||||
case msg: FriendsResponse =>
|
||||
sendResponse(msg)
|
||||
msg.friends.foreach { f =>
|
||||
galaxyService ! GalaxyServiceMessage(GalaxyAction.LogStatusChange(f.name))
|
||||
}
|
||||
case _ => ()
|
||||
}
|
||||
def handleUpdateIgnoredPlayers(pkt: FriendsResponse): Unit
|
||||
|
||||
/* response handlers */
|
||||
|
||||
def handle(reply: GalaxyResponse.Response): Unit = {
|
||||
reply match {
|
||||
case GalaxyResponse.HotSpotUpdate(zone_index, priority, hot_spot_info) =>
|
||||
sendResponse(
|
||||
HotSpotUpdateMessage(
|
||||
zone_index,
|
||||
priority,
|
||||
hot_spot_info.map { spot => PacketHotSpotInfo(spot.DisplayLocation.x, spot.DisplayLocation.y, 40) }
|
||||
)
|
||||
)
|
||||
|
||||
case GalaxyResponse.MapUpdate(msg) =>
|
||||
sendResponse(msg)
|
||||
|
||||
case GalaxyResponse.UpdateBroadcastPrivileges(zoneId, gateMapId, fromFactions, toFactions) =>
|
||||
val faction = player.Faction
|
||||
val from = fromFactions.contains(faction)
|
||||
val to = toFactions.contains(faction)
|
||||
if (from && !to) {
|
||||
sendResponse(BroadcastWarpgateUpdateMessage(zoneId, gateMapId, PlanetSideEmpire.NEUTRAL))
|
||||
} else if (!from && to) {
|
||||
sendResponse(BroadcastWarpgateUpdateMessage(zoneId, gateMapId, faction))
|
||||
}
|
||||
|
||||
case GalaxyResponse.FlagMapUpdate(msg) =>
|
||||
sendResponse(msg)
|
||||
|
||||
case GalaxyResponse.TransferPassenger(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))
|
||||
|
||||
case GalaxyResponse.UnlockedZoneUpdate(zone) => ;
|
||||
sendResponse(ZoneInfoMessage(zone.Number, empire_status=true, lock_time=0L))
|
||||
val popBO = 0
|
||||
val popTR = zone.Players.count(_.faction == PlanetSideEmpire.TR)
|
||||
val popNC = zone.Players.count(_.faction == PlanetSideEmpire.NC)
|
||||
val popVS = zone.Players.count(_.faction == PlanetSideEmpire.VS)
|
||||
sendResponse(ZonePopulationUpdateMessage(zone.Number, 414, 138, popTR, 138, popNC, 138, popVS, 138, popBO))
|
||||
|
||||
case GalaxyResponse.LogStatusChange(name) if avatar.people.friend.exists(_.name.equals(name)) =>
|
||||
avatarActor ! AvatarActor.MemberListRequest(MemberAction.UpdateFriend, name)
|
||||
|
||||
case GalaxyResponse.SendResponse(msg) =>
|
||||
sendResponse(msg)
|
||||
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
def handle(reply: GalaxyResponse.Response): Unit
|
||||
}
|
||||
|
||||
/*package net.psforever.actors.session.support
|
||||
|
||||
import akka.actor.{ActorContext, ActorRef, typed}
|
||||
import scala.concurrent.duration._
|
||||
//
|
||||
import net.psforever.actors.session.AvatarActor
|
||||
import net.psforever.objects.Vehicle
|
||||
import net.psforever.packet.game.{AvatarDeadStateMessage, BroadcastWarpgateUpdateMessage, DeadState, HotSpotInfo => PacketHotSpotInfo, HotSpotUpdateMessage, ZoneInfoMessage, ZonePopulationUpdateMessage}
|
||||
import net.psforever.services.Service
|
||||
import net.psforever.services.galaxy.GalaxyResponse
|
||||
import net.psforever.types.{MemberAction, PlanetSideEmpire}
|
||||
|
||||
class SessionGalaxyHandlers(
|
||||
val sessionData: SessionData,
|
||||
avatarActor: typed.ActorRef[AvatarActor.Command],
|
||||
galaxyService: ActorRef,
|
||||
val sessionLogic: SessionData,
|
||||
val avatarActor: typed.ActorRef[AvatarActor.Command],
|
||||
val galaxyService: ActorRef,
|
||||
implicit val context: ActorContext
|
||||
) extends CommonSessionInterfacingFunctionality {
|
||||
def handle(reply: GalaxyResponse.Response): Unit = {
|
||||
reply match {
|
||||
case GalaxyResponse.HotSpotUpdate(zoneIndex, priority, hotSpotInfo) =>
|
||||
sendResponse(
|
||||
HotSpotUpdateMessage(
|
||||
zoneIndex,
|
||||
priority,
|
||||
hotSpotInfo.map { spot => PacketHotSpotInfo(spot.DisplayLocation.x, spot.DisplayLocation.y, 40) }
|
||||
)
|
||||
)
|
||||
|
||||
case GalaxyResponse.MapUpdate(msg) =>
|
||||
sendResponse(msg)
|
||||
|
||||
case GalaxyResponse.UpdateBroadcastPrivileges(zoneId, gateMapId, fromFactions, toFactions) =>
|
||||
val faction = player.Faction
|
||||
val from = fromFactions.contains(faction)
|
||||
val to = toFactions.contains(faction)
|
||||
if (from && !to) {
|
||||
sendResponse(BroadcastWarpgateUpdateMessage(zoneId, gateMapId, PlanetSideEmpire.NEUTRAL))
|
||||
} else if (!from && to) {
|
||||
sendResponse(BroadcastWarpgateUpdateMessage(zoneId, gateMapId, faction))
|
||||
}
|
||||
|
||||
case GalaxyResponse.FlagMapUpdate(msg) =>
|
||||
sendResponse(msg)
|
||||
|
||||
case GalaxyResponse.TransferPassenger(tempChannel, vehicle, _, manifest) =>
|
||||
val playerName = player.Name
|
||||
log.debug(s"TransferPassenger: $playerName received the summons to transfer to ${vehicle.Zone.id} ...")
|
||||
manifest.passengers
|
||||
.find { _.name.equals(playerName) }
|
||||
.collect {
|
||||
case entry if vehicle.Seats(entry.mount).occupant.isEmpty =>
|
||||
player.VehicleSeated = None
|
||||
vehicle.Seats(entry.mount).mount(player)
|
||||
player.VehicleSeated = vehicle.GUID
|
||||
Some(vehicle)
|
||||
case entry if vehicle.Seats(entry.mount).occupant.contains(player) =>
|
||||
Some(vehicle)
|
||||
case entry =>
|
||||
log.warn(
|
||||
s"TransferPassenger: $playerName tried to mount seat ${entry.mount} during summoning, but it was already occupied, and ${player.Sex.pronounSubject} was rebuked"
|
||||
)
|
||||
None
|
||||
}.orElse {
|
||||
manifest.cargo.find { _.name.equals(playerName) }.flatMap { entry =>
|
||||
vehicle.CargoHolds(entry.mount).occupant.collect {
|
||||
case cargo if cargo.Seats(0).occupants.exists(_.Name.equals(playerName)) => cargo
|
||||
}
|
||||
}
|
||||
} match {
|
||||
case Some(v: Vehicle) =>
|
||||
galaxyService ! Service.Leave(Some(tempChannel)) //temporary vehicle-specific channel (see above)
|
||||
sessionData.zoning.spawn.deadState = DeadState.Release
|
||||
sendResponse(AvatarDeadStateMessage(DeadState.Release, 0, 0, player.Position, player.Faction, unk5=true))
|
||||
sessionData.zoning.interstellarFerry = Some(v) //on the other continent and registered to that continent's GUID system
|
||||
sessionData.zoning.spawn.LoadZonePhysicalSpawnPoint(v.Continent, v.Position, v.Orientation, 1 seconds, None)
|
||||
case _ =>
|
||||
sessionData.zoning.interstellarFerry match {
|
||||
case None =>
|
||||
galaxyService ! Service.Leave(Some(tempChannel)) //no longer being transferred between zones
|
||||
sessionData.zoning.interstellarFerryTopLevelGUID = None
|
||||
case Some(_) => ;
|
||||
//wait patiently
|
||||
}
|
||||
}
|
||||
|
||||
case GalaxyResponse.LockedZoneUpdate(zone, time) =>
|
||||
sendResponse(ZoneInfoMessage(zone.Number, empire_status=false, lock_time=time))
|
||||
|
||||
case GalaxyResponse.UnlockedZoneUpdate(zone) =>
|
||||
sendResponse(ZoneInfoMessage(zone.Number, empire_status=true, lock_time=0L))
|
||||
val popBO = 0
|
||||
val popTR = zone.Players.count(_.faction == PlanetSideEmpire.TR)
|
||||
val popNC = zone.Players.count(_.faction == PlanetSideEmpire.NC)
|
||||
val popVS = zone.Players.count(_.faction == PlanetSideEmpire.VS)
|
||||
sendResponse(ZonePopulationUpdateMessage(zone.Number, 414, 138, popTR, 138, popNC, 138, popVS, 138, popBO))
|
||||
|
||||
case GalaxyResponse.LogStatusChange(name) if avatar.people.friend.exists { _.name.equals(name) } =>
|
||||
avatarActor ! AvatarActor.MemberListRequest(MemberAction.UpdateFriend, name)
|
||||
|
||||
case GalaxyResponse.SendResponse(msg) =>
|
||||
sendResponse(msg)
|
||||
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
}*/
|
||||
) extends CommonSessionInterfacingFunctionality
|
||||
|
|
|
|||
|
|
@ -2,237 +2,16 @@
|
|||
package net.psforever.actors.session.support
|
||||
|
||||
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._
|
||||
import net.psforever.services.Service
|
||||
import net.psforever.services.local.LocalResponse
|
||||
import net.psforever.types.{ChatMessageType, PlanetSideGUID, Vector3}
|
||||
import net.psforever.types.PlanetSideGUID
|
||||
|
||||
trait LocalHandlerFunctions extends CommonSessionInterfacingFunctionality {
|
||||
def ops: SessionLocalHandlers
|
||||
|
||||
def handle(toChannel: String, guid: PlanetSideGUID, reply: LocalResponse.Response): Unit
|
||||
}
|
||||
|
||||
class SessionLocalHandlers(
|
||||
val sessionLogic: SessionLogic,
|
||||
val sessionLogic: SessionData,
|
||||
implicit val context: ActorContext
|
||||
) extends CommonSessionInterfacingFunctionality {
|
||||
/**
|
||||
* na
|
||||
* @param toChannel na
|
||||
* @param guid na
|
||||
* @param reply na
|
||||
*/
|
||||
def handle(toChannel: String, guid: PlanetSideGUID, reply: LocalResponse.Response): Unit = {
|
||||
val resolvedPlayerGuid = if (player.HasGUID) {
|
||||
player.GUID
|
||||
} else {
|
||||
Service.defaultPlayerGUID
|
||||
}
|
||||
val isNotSameTarget = resolvedPlayerGuid != guid
|
||||
reply match {
|
||||
case LocalResponse.DeployableMapIcon(behavior, deployInfo) if isNotSameTarget =>
|
||||
sendResponse(DeployableObjectsInfoMessage(behavior, deployInfo))
|
||||
|
||||
case LocalResponse.DeployableUIFor(item) =>
|
||||
sessionLogic.general.updateDeployableUIElements(avatar.deployables.UpdateUIElement(item))
|
||||
|
||||
case LocalResponse.Detonate(dguid, _: BoomerDeployable) =>
|
||||
sendResponse(TriggerEffectMessage(dguid, "detonate_boomer"))
|
||||
sendResponse(PlanetsideAttributeMessage(dguid, attribute_type=29, attribute_value=1))
|
||||
sendResponse(ObjectDeleteMessage(dguid, unk1=0))
|
||||
|
||||
case LocalResponse.Detonate(dguid, _: ExplosiveDeployable) =>
|
||||
sendResponse(GenericObjectActionMessage(dguid, code=19))
|
||||
sendResponse(PlanetsideAttributeMessage(dguid, attribute_type=29, attribute_value=1))
|
||||
sendResponse(ObjectDeleteMessage(dguid, unk1=0))
|
||||
|
||||
case LocalResponse.Detonate(_, obj) =>
|
||||
log.warn(s"LocalResponse.Detonate: ${obj.Definition.Name} not configured to explode correctly")
|
||||
|
||||
case LocalResponse.DoorOpens(doorGuid) if isNotSameTarget =>
|
||||
sendResponse(GenericObjectStateMsg(doorGuid, state=16))
|
||||
|
||||
case LocalResponse.DoorCloses(doorGuid) => //door closes for everyone
|
||||
sendResponse(GenericObjectStateMsg(doorGuid, state=17))
|
||||
|
||||
case LocalResponse.EliminateDeployable(obj: TurretDeployable, dguid, _, _) if obj.Destroyed =>
|
||||
sendResponse(ObjectDeleteMessage(dguid, unk1=0))
|
||||
|
||||
case LocalResponse.EliminateDeployable(obj: TurretDeployable, dguid, pos, _) =>
|
||||
obj.Destroyed = true
|
||||
DeconstructDeployable(
|
||||
obj,
|
||||
dguid,
|
||||
pos,
|
||||
obj.Orientation,
|
||||
deletionType= if (obj.MountPoints.isEmpty) { 2 } else { 1 }
|
||||
)
|
||||
|
||||
case LocalResponse.EliminateDeployable(obj: ExplosiveDeployable, dguid, _, _)
|
||||
if obj.Destroyed || obj.Jammed || obj.Health == 0 =>
|
||||
sendResponse(ObjectDeleteMessage(dguid, unk1=0))
|
||||
|
||||
case LocalResponse.EliminateDeployable(obj: ExplosiveDeployable, dguid, pos, effect) =>
|
||||
obj.Destroyed = true
|
||||
DeconstructDeployable(obj, dguid, pos, obj.Orientation, effect)
|
||||
|
||||
case LocalResponse.EliminateDeployable(obj: TelepadDeployable, dguid, _, _) if obj.Active && obj.Destroyed =>
|
||||
//if active, deactivate
|
||||
obj.Active = false
|
||||
sendResponse(GenericObjectActionMessage(dguid, code=29))
|
||||
sendResponse(GenericObjectActionMessage(dguid, code=30))
|
||||
//standard deployable elimination behavior
|
||||
sendResponse(ObjectDeleteMessage(dguid, unk1=0))
|
||||
|
||||
case LocalResponse.EliminateDeployable(obj: TelepadDeployable, dguid, pos, _) if obj.Active =>
|
||||
//if active, deactivate
|
||||
obj.Active = false
|
||||
sendResponse(GenericObjectActionMessage(dguid, code=29))
|
||||
sendResponse(GenericObjectActionMessage(dguid, code=30))
|
||||
//standard deployable elimination behavior
|
||||
obj.Destroyed = true
|
||||
DeconstructDeployable(obj, dguid, pos, obj.Orientation, deletionType=2)
|
||||
|
||||
case LocalResponse.EliminateDeployable(obj: TelepadDeployable, dguid, _, _) if obj.Destroyed =>
|
||||
//standard deployable elimination behavior
|
||||
sendResponse(ObjectDeleteMessage(dguid, unk1=0))
|
||||
|
||||
case LocalResponse.EliminateDeployable(obj: TelepadDeployable, dguid, pos, _) =>
|
||||
//standard deployable elimination behavior
|
||||
obj.Destroyed = true
|
||||
DeconstructDeployable(obj, dguid, pos, obj.Orientation, deletionType=2)
|
||||
|
||||
case LocalResponse.EliminateDeployable(obj, dguid, _, _) if obj.Destroyed =>
|
||||
sendResponse(ObjectDeleteMessage(dguid, unk1=0))
|
||||
|
||||
case LocalResponse.EliminateDeployable(obj, dguid, pos, effect) =>
|
||||
obj.Destroyed = true
|
||||
DeconstructDeployable(obj, dguid, pos, obj.Orientation, effect)
|
||||
|
||||
case LocalResponse.SendHackMessageHackCleared(targetGuid, unk1, unk2) =>
|
||||
sendResponse(HackMessage(unk1=0, targetGuid, guid, progress=0, unk1, HackState.HackCleared, unk2))
|
||||
|
||||
case LocalResponse.HackObject(targetGuid, unk1, unk2) =>
|
||||
sessionLogic.general.hackObject(targetGuid, unk1, unk2)
|
||||
|
||||
case LocalResponse.PlanetsideAttribute(targetGuid, attributeType, attributeValue) =>
|
||||
sessionLogic.general.sendPlanetsideAttributeMessage(targetGuid, attributeType, attributeValue)
|
||||
|
||||
case LocalResponse.GenericObjectAction(targetGuid, actionNumber) =>
|
||||
sendResponse(GenericObjectActionMessage(targetGuid, actionNumber))
|
||||
|
||||
case LocalResponse.GenericActionMessage(actionNumber) =>
|
||||
sendResponse(GenericActionMessage(actionNumber))
|
||||
|
||||
case LocalResponse.ChatMessage(msg) =>
|
||||
sendResponse(msg)
|
||||
|
||||
case LocalResponse.SendPacket(packet) =>
|
||||
sendResponse(packet)
|
||||
|
||||
case LocalResponse.LluSpawned(llu) =>
|
||||
// Create LLU on client
|
||||
sendResponse(ObjectCreateMessage(
|
||||
llu.Definition.ObjectId,
|
||||
llu.GUID,
|
||||
llu.Definition.Packet.ConstructorData(llu).get
|
||||
))
|
||||
sendResponse(TriggerSoundMessage(TriggeredSound.LLUMaterialize, llu.Position, unk=20, volume=0.8000001f))
|
||||
|
||||
case LocalResponse.LluDespawned(lluGuid, position) =>
|
||||
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
|
||||
sessionLogic.general.specialItemSlotGuid.collect { case guid if guid == lluGuid =>
|
||||
sessionLogic.general.specialItemSlotGuid = None
|
||||
player.Carrying = None
|
||||
}
|
||||
|
||||
case LocalResponse.ObjectDelete(objectGuid, unk) if isNotSameTarget =>
|
||||
sendResponse(ObjectDeleteMessage(objectGuid, unk))
|
||||
|
||||
case LocalResponse.ProximityTerminalEffect(object_guid, true) =>
|
||||
sendResponse(ProximityTerminalUseMessage(Service.defaultPlayerGUID, object_guid, unk=true))
|
||||
|
||||
case LocalResponse.ProximityTerminalEffect(objectGuid, false) =>
|
||||
sendResponse(ProximityTerminalUseMessage(Service.defaultPlayerGUID, objectGuid, unk=false))
|
||||
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) =>
|
||||
sessionLogic.general.useRouterTelepadEffect(passengerGuid, srcGuid, destGuid)
|
||||
|
||||
case LocalResponse.SendResponse(msg) =>
|
||||
sendResponse(msg)
|
||||
|
||||
case LocalResponse.SetEmpire(objectGuid, empire) =>
|
||||
sendResponse(SetEmpireMessage(objectGuid, empire))
|
||||
|
||||
case LocalResponse.ShuttleEvent(ev) =>
|
||||
val msg = OrbitalShuttleTimeMsg(
|
||||
ev.u1,
|
||||
ev.u2,
|
||||
ev.t1,
|
||||
ev.t2,
|
||||
ev.t3,
|
||||
pairs=ev.pairs.map { case ((a, b), c) => PadAndShuttlePair(a, b, c) }
|
||||
)
|
||||
sendResponse(msg)
|
||||
|
||||
case LocalResponse.ShuttleDock(pguid, sguid, slot) =>
|
||||
sendResponse(ObjectAttachMessage(pguid, sguid, slot))
|
||||
|
||||
case LocalResponse.ShuttleUndock(pguid, sguid, pos, orient) =>
|
||||
sendResponse(ObjectDetachMessage(pguid, sguid, pos, orient))
|
||||
|
||||
case LocalResponse.ShuttleState(sguid, pos, orient, state) =>
|
||||
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) =>
|
||||
sessionLogic.general.toggleTeleportSystem(router, systemPlan)
|
||||
|
||||
case LocalResponse.TriggerEffect(targetGuid, effect, effectInfo, triggerLocation) =>
|
||||
sendResponse(TriggerEffectMessage(targetGuid, effect, effectInfo, triggerLocation))
|
||||
|
||||
case LocalResponse.TriggerSound(sound, pos, unk, volume) =>
|
||||
sendResponse(TriggerSoundMessage(sound, pos, unk, volume))
|
||||
|
||||
case LocalResponse.UpdateForceDomeStatus(buildingGuid, true) =>
|
||||
sendResponse(GenericObjectActionMessage(buildingGuid, 11))
|
||||
|
||||
case LocalResponse.UpdateForceDomeStatus(buildingGuid, false) =>
|
||||
sendResponse(GenericObjectActionMessage(buildingGuid, 12))
|
||||
|
||||
case LocalResponse.RechargeVehicleWeapon(vehicleGuid, weaponGuid) if resolvedPlayerGuid == guid =>
|
||||
continent.GUID(vehicleGuid)
|
||||
.collect { case vehicle: MountableWeapons => (vehicle, vehicle.PassengerInSeat(player)) }
|
||||
.collect { case (vehicle, Some(seat_num)) => vehicle.WeaponControlledFromSeat(seat_num) }
|
||||
.getOrElse(Set.empty)
|
||||
.collect { case weapon: Tool if weapon.GUID == weaponGuid =>
|
||||
sendResponse(InventoryStateMessage(weapon.AmmoSlot.Box.GUID, weapon.GUID, weapon.Magazine))
|
||||
}
|
||||
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Common behavior for deconstructing deployables in the game environment.
|
||||
* @param obj the deployable
|
||||
* @param guid the globally unique identifier for the deployable
|
||||
* @param pos the previous position of the deployable
|
||||
* @param orient the previous orientation of the deployable
|
||||
* @param deletionType the value passed to `ObjectDeleteMessage` concerning the deconstruction animation
|
||||
*/
|
||||
def DeconstructDeployable(
|
||||
obj: Deployable,
|
||||
guid: PlanetSideGUID,
|
||||
pos: Vector3,
|
||||
orient: Vector3,
|
||||
deletionType: Int
|
||||
): Unit = {
|
||||
sendResponse(TriggerEffectMessage("spawn_object_failed_effect", pos, orient))
|
||||
sendResponse(PlanetsideAttributeMessage(guid, 29, 1)) //make deployable vanish
|
||||
sendResponse(ObjectDeleteMessage(guid, deletionType))
|
||||
}
|
||||
}
|
||||
) extends CommonSessionInterfacingFunctionality
|
||||
|
|
|
|||
|
|
@ -3,366 +3,33 @@ 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._
|
||||
import net.psforever.packet.game.{DismountVehicleCargoMsg, InventoryStateMessage, MountVehicleCargoMsg, MountVehicleMsg}
|
||||
//
|
||||
import net.psforever.actors.session.AvatarActor
|
||||
import net.psforever.actors.zone.ZoneActor
|
||||
import net.psforever.objects.{GlobalDefinitions, PlanetSideGameObject, Player, Vehicle, Vehicles}
|
||||
import net.psforever.objects.definition.{BasicDefinition, ObjectDefinition}
|
||||
import net.psforever.objects.serverobject.hackable.GenericHackables.getTurretUpgradeTime
|
||||
import net.psforever.objects.Player
|
||||
import net.psforever.objects.serverobject.mount.Mountable
|
||||
import net.psforever.objects.serverobject.terminals.implant.ImplantTerminalMech
|
||||
import net.psforever.objects.serverobject.turret.{FacilityTurret, WeaponTurret}
|
||||
import net.psforever.objects.vehicles.AccessPermissionGroup
|
||||
import net.psforever.packet.game.{ChatMsg, DelayedPathMountMsg, DismountVehicleMsg, GenericObjectActionMessage, ObjectAttachMessage, ObjectDetachMessage, PlanetsideAttributeMessage, PlayerStasisMessage, PlayerStateShiftMessage, ShiftState}
|
||||
import net.psforever.services.Service
|
||||
import net.psforever.services.local.{LocalAction, LocalServiceMessage}
|
||||
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
|
||||
import net.psforever.types.{BailType, ChatMessageType, PlanetSideGUID, Vector3}
|
||||
import net.psforever.packet.game.DismountVehicleMsg
|
||||
|
||||
trait MountHandlerFunctions extends CommonSessionInterfacingFunctionality {
|
||||
val ops: SessionMountHandlers
|
||||
|
||||
def handleMountVehicle(pkt: MountVehicleMsg): Unit
|
||||
|
||||
def handleDismountVehicle(pkt: DismountVehicleMsg): Unit
|
||||
|
||||
def handleMountVehicleCargo(pkt: MountVehicleCargoMsg): Unit
|
||||
|
||||
def handleDismountVehicleCargo(pkt: DismountVehicleCargoMsg): Unit
|
||||
|
||||
def handle(tplayer: Player, reply: Mountable.Exchange): Unit
|
||||
}
|
||||
|
||||
class SessionMountHandlers(
|
||||
val sessionLogic: SessionLogic,
|
||||
avatarActor: typed.ActorRef[AvatarActor.Command],
|
||||
val sessionLogic: SessionData,
|
||||
val avatarActor: typed.ActorRef[AvatarActor.Command],
|
||||
implicit val context: ActorContext
|
||||
) extends CommonSessionInterfacingFunctionality {
|
||||
/**
|
||||
* na
|
||||
*
|
||||
* @param tplayer na
|
||||
* @param reply na
|
||||
*/
|
||||
def handle(tplayer: Player, reply: Mountable.Exchange): Unit = {
|
||||
reply match {
|
||||
case Mountable.CanMount(obj: ImplantTerminalMech, seatNumber, _) =>
|
||||
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
|
||||
log.info(s"${player.Name} mounts an implant terminal")
|
||||
sessionLogic.terminals.CancelAllProximityUnits()
|
||||
MountingAction(tplayer, obj, seatNumber)
|
||||
sessionLogic.keepAliveFunc = sessionLogic.keepAlivePersistence
|
||||
|
||||
case Mountable.CanMount(obj: Vehicle, seatNumber, _)
|
||||
if obj.Definition == GlobalDefinitions.orbital_shuttle =>
|
||||
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_mount")
|
||||
log.info(s"${player.Name} mounts the orbital shuttle")
|
||||
sessionLogic.terminals.CancelAllProximityUnits()
|
||||
MountingAction(tplayer, obj, seatNumber)
|
||||
sessionLogic.keepAliveFunc = sessionLogic.keepAlivePersistence
|
||||
|
||||
case Mountable.CanMount(obj: Vehicle, seatNumber, _)
|
||||
if obj.Definition == GlobalDefinitions.ant =>
|
||||
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
|
||||
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))
|
||||
sessionLogic.general.accessContainer(obj)
|
||||
tplayer.Actor ! ResetAllEnvironmentInteractions
|
||||
MountingAction(tplayer, obj, seatNumber)
|
||||
|
||||
case Mountable.CanMount(obj: Vehicle, seatNumber, _)
|
||||
if obj.Definition == GlobalDefinitions.quadstealth =>
|
||||
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
|
||||
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))
|
||||
sessionLogic.general.accessContainer(obj)
|
||||
tplayer.Actor ! ResetAllEnvironmentInteractions
|
||||
MountingAction(tplayer, obj, seatNumber)
|
||||
|
||||
case Mountable.CanMount(obj: Vehicle, seatNumber, _)
|
||||
if seatNumber == 0 && obj.Definition.MaxCapacitor > 0 =>
|
||||
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
|
||||
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))
|
||||
sessionLogic.general.accessContainer(obj)
|
||||
updateWeaponAtSeatPosition(obj, seatNumber)
|
||||
tplayer.Actor ! ResetAllEnvironmentInteractions
|
||||
MountingAction(tplayer, obj, seatNumber)
|
||||
|
||||
case Mountable.CanMount(obj: Vehicle, seatNumber, _)
|
||||
if seatNumber == 0 =>
|
||||
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
|
||||
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))
|
||||
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 =>
|
||||
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_mount")
|
||||
log.info(s"${player.Name} mounts ${
|
||||
obj.SeatPermissionGroup(seatNumber) match {
|
||||
case Some(seatType) => s"a $seatType seat (#$seatNumber)"
|
||||
case None => "a seat"
|
||||
}
|
||||
} of the ${obj.Definition.Name}")
|
||||
val obj_guid: PlanetSideGUID = obj.GUID
|
||||
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))
|
||||
sessionLogic.general.accessContainer(obj)
|
||||
updateWeaponAtSeatPosition(obj, seatNumber)
|
||||
sessionLogic.keepAliveFunc = sessionLogic.keepAlivePersistence
|
||||
tplayer.Actor ! ResetAllEnvironmentInteractions
|
||||
MountingAction(tplayer, obj, seatNumber)
|
||||
|
||||
case Mountable.CanMount(obj: Vehicle, seatNumber, _) =>
|
||||
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_mount")
|
||||
log.info(s"${player.Name} mounts the ${
|
||||
obj.SeatPermissionGroup(seatNumber) match {
|
||||
case Some(seatType) => s"a $seatType seat (#$seatNumber)"
|
||||
case None => "a seat"
|
||||
}
|
||||
} of the ${obj.Definition.Name}")
|
||||
val obj_guid: PlanetSideGUID = obj.GUID
|
||||
sessionLogic.terminals.CancelAllProximityUnits()
|
||||
sendResponse(PlanetsideAttributeMessage(obj_guid, attribute_type=0, obj.Health))
|
||||
sendResponse(PlanetsideAttributeMessage(obj_guid, obj.Definition.shieldUiAttribute, obj.Shields))
|
||||
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 =>
|
||||
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))
|
||||
updateWeaponAtSeatPosition(obj, seatNumber)
|
||||
MountingAction(tplayer, obj, seatNumber)
|
||||
|
||||
case Mountable.CanMount(obj: FacilityTurret, seatNumber, _)
|
||||
if !obj.isUpgrading || System.currentTimeMillis() - getTurretUpgradeTime >= 1500L =>
|
||||
obj.setMiddleOfUpgrade(false)
|
||||
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_mount")
|
||||
log.info(s"${player.Name} mounts the ${obj.Definition.Name}")
|
||||
sendResponse(PlanetsideAttributeMessage(obj.GUID, attribute_type=0, obj.Health))
|
||||
updateWeaponAtSeatPosition(obj, seatNumber)
|
||||
MountingAction(tplayer, obj, seatNumber)
|
||||
|
||||
case Mountable.CanMount(obj: FacilityTurret, _, _) =>
|
||||
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, _) =>
|
||||
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))
|
||||
updateWeaponAtSeatPosition(obj, seatNumber)
|
||||
MountingAction(tplayer, obj, seatNumber)
|
||||
|
||||
case Mountable.CanMount(obj: Mountable, _, _) =>
|
||||
log.warn(s"MountVehicleMsg: $obj is some kind of mountable object but nothing will happen for ${player.Name}")
|
||||
|
||||
case Mountable.CanDismount(obj: ImplantTerminalMech, seatNum, _) =>
|
||||
log.info(s"${tplayer.Name} dismounts the implant terminal")
|
||||
DismountAction(tplayer, obj, seatNum)
|
||||
|
||||
case Mountable.CanDismount(obj: Vehicle, _, mountPoint)
|
||||
if obj.Definition == GlobalDefinitions.orbital_shuttle && obj.MountedIn.nonEmpty =>
|
||||
//dismount to hart lobby
|
||||
val pguid = player.GUID
|
||||
log.info(s"${tplayer.Name} dismounts the orbital shuttle into the lobby")
|
||||
val sguid = obj.GUID
|
||||
val (pos, zang) = Vehicles.dismountShuttle(obj, mountPoint)
|
||||
tplayer.Position = pos
|
||||
sendResponse(DelayedPathMountMsg(pguid, sguid, u1=60, u2=true))
|
||||
continent.LocalEvents ! LocalServiceMessage(
|
||||
continent.id,
|
||||
LocalAction.SendResponse(ObjectDetachMessage(sguid, pguid, pos, roll=0, pitch=0, zang))
|
||||
)
|
||||
sessionLogic.keepAliveFunc = sessionLogic.zoning.NormalKeepAlive
|
||||
|
||||
case Mountable.CanDismount(obj: Vehicle, seatNum, _)
|
||||
if obj.Definition == GlobalDefinitions.orbital_shuttle =>
|
||||
//get ready for orbital drop
|
||||
val pguid = player.GUID
|
||||
val events = continent.VehicleEvents
|
||||
log.info(s"${player.Name} is prepped for dropping")
|
||||
DismountAction(tplayer, obj, seatNum)
|
||||
continent.actor ! ZoneActor.RemoveFromBlockMap(player) //character doesn't need it
|
||||
//DismountAction(...) uses vehicle service, so use that service to coordinate the remainder of the messages
|
||||
events ! VehicleServiceMessage(
|
||||
player.Name,
|
||||
VehicleAction.SendResponse(Service.defaultPlayerGUID, PlayerStasisMessage(pguid)) //the stasis message
|
||||
)
|
||||
//when the player dismounts, they will be positioned where the shuttle was when it disappeared in the sky
|
||||
//the player will fall to the ground and is perfectly vulnerable in this state
|
||||
//additionally, our player must exist in the current zone
|
||||
//having no in-game avatar target will throw us out of the map screen when deploying and cause softlock
|
||||
events ! VehicleServiceMessage(
|
||||
player.Name,
|
||||
VehicleAction.SendResponse(
|
||||
Service.defaultPlayerGUID,
|
||||
PlayerStateShiftMessage(ShiftState(unk=0, obj.Position, obj.Orientation.z, vel=None)) //cower in the shuttle bay
|
||||
)
|
||||
)
|
||||
events ! VehicleServiceMessage(
|
||||
continent.id,
|
||||
VehicleAction.SendResponse(pguid, GenericObjectActionMessage(pguid, code=9)) //conceal the player
|
||||
)
|
||||
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}")
|
||||
sessionLogic.general.unaccessContainer(obj)
|
||||
DismountAction(tplayer, obj, seatNum)
|
||||
obj.Actor ! Vehicle.Deconstruct()
|
||||
|
||||
case Mountable.CanDismount(obj: Vehicle, seatNum, _)
|
||||
if tplayer.GUID == player.GUID =>
|
||||
//disembarking self
|
||||
log.info(s"${player.Name} dismounts the ${obj.Definition.Name}'s ${
|
||||
obj.SeatPermissionGroup(seatNum) match {
|
||||
case Some(AccessPermissionGroup.Driver) => "driver seat"
|
||||
case Some(seatType) => s"$seatType seat (#$seatNum)"
|
||||
case None => "seat"
|
||||
}
|
||||
}")
|
||||
sessionLogic.vehicles.ConditionalDriverVehicleControl(obj)
|
||||
sessionLogic.general.unaccessContainer(obj)
|
||||
DismountVehicleAction(tplayer, obj, seatNum)
|
||||
|
||||
case Mountable.CanDismount(obj: Vehicle, seat_num, _) =>
|
||||
continent.VehicleEvents ! VehicleServiceMessage(
|
||||
continent.id,
|
||||
VehicleAction.KickPassenger(tplayer.GUID, seat_num, unk2=true, obj.GUID)
|
||||
)
|
||||
|
||||
case Mountable.CanDismount(obj: PlanetSideGameObject with PlanetSideGameObject with Mountable with FactionAffinity with InGameHistory, seatNum, _) =>
|
||||
log.info(s"${tplayer.Name} dismounts a ${obj.Definition.asInstanceOf[ObjectDefinition].Name}")
|
||||
DismountAction(tplayer, obj, seatNum)
|
||||
|
||||
case Mountable.CanDismount(obj: Mountable, _, _) =>
|
||||
log.warn(s"DismountVehicleMsg: $obj is some dismountable object but nothing will happen for ${player.Name}")
|
||||
|
||||
case Mountable.CanNotMount(obj: Vehicle, seatNumber) =>
|
||||
log.warn(s"MountVehicleMsg: ${tplayer.Name} attempted to mount $obj's seat $seatNumber, but was not allowed")
|
||||
obj.GetSeatFromMountPoint(seatNumber).collect {
|
||||
case seatNum if obj.SeatPermissionGroup(seatNum).contains(AccessPermissionGroup.Driver) =>
|
||||
sendResponse(
|
||||
ChatMsg(ChatMessageType.CMT_OPEN, wideContents=false, recipient="", "You are not the driver of this vehicle.", note=None)
|
||||
)
|
||||
}
|
||||
|
||||
case Mountable.CanNotMount(obj: Mountable, seatNumber) =>
|
||||
log.warn(s"MountVehicleMsg: ${tplayer.Name} attempted to mount $obj's seat $seatNumber, but was not allowed")
|
||||
|
||||
case Mountable.CanNotDismount(obj, seatNum) =>
|
||||
log.warn(s"DismountVehicleMsg: ${tplayer.Name} attempted to dismount $obj's mount $seatNum, but was not allowed")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Common activities/procedure when a player mounts a valid object.
|
||||
* @param tplayer the player
|
||||
* @param obj the mountable object
|
||||
* @param seatNum the mount into which the player is mounting
|
||||
*/
|
||||
def MountingAction(tplayer: Player, obj: PlanetSideGameObject with FactionAffinity with InGameHistory, seatNum: Int): Unit = {
|
||||
val playerGuid: PlanetSideGUID = tplayer.GUID
|
||||
val objGuid: PlanetSideGUID = obj.GUID
|
||||
sessionLogic.actionsToCancel()
|
||||
avatarActor ! AvatarActor.DeactivateActiveImplants()
|
||||
avatarActor ! AvatarActor.SuspendStaminaRegeneration(3.seconds)
|
||||
sendResponse(ObjectAttachMessage(objGuid, playerGuid, seatNum))
|
||||
continent.VehicleEvents ! VehicleServiceMessage(
|
||||
continent.id,
|
||||
VehicleAction.MountVehicle(playerGuid, objGuid, seatNum)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Common activities/procedure when a player dismounts a valid mountable object.
|
||||
* @param tplayer the player
|
||||
* @param obj the mountable object
|
||||
* @param seatNum the mount out of which which the player is disembarking
|
||||
*/
|
||||
def DismountVehicleAction(tplayer: Player, obj: PlanetSideGameObject with FactionAffinity with InGameHistory, seatNum: Int): Unit = {
|
||||
DismountAction(tplayer, obj, seatNum)
|
||||
//until vehicles maintain synchronized momentum without a driver
|
||||
obj match {
|
||||
case v: Vehicle
|
||||
if seatNum == 0 && Vector3.MagnitudeSquared(v.Velocity.getOrElse(Vector3.Zero)) > 0f =>
|
||||
sessionLogic.vehicles.serverVehicleControlVelocity.collect { _ =>
|
||||
sessionLogic.vehicles.ServerVehicleOverrideStop(v)
|
||||
}
|
||||
v.Velocity = Vector3.Zero
|
||||
continent.VehicleEvents ! VehicleServiceMessage(
|
||||
continent.id,
|
||||
VehicleAction.VehicleState(
|
||||
tplayer.GUID,
|
||||
v.GUID,
|
||||
unk1 = 0,
|
||||
v.Position,
|
||||
v.Orientation,
|
||||
vel = None,
|
||||
v.Flying,
|
||||
unk3 = 0,
|
||||
unk4 = 0,
|
||||
wheel_direction = 15,
|
||||
unk5 = false,
|
||||
unk6 = v.Cloaked
|
||||
)
|
||||
)
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Common activities/procedure when a player dismounts a valid mountable object.
|
||||
* @param tplayer the player
|
||||
* @param obj the mountable object
|
||||
* @param seatNum the mount out of which which the player is disembarking
|
||||
*/
|
||||
def DismountAction(tplayer: Player, obj: PlanetSideGameObject with FactionAffinity with InGameHistory, seatNum: Int): Unit = {
|
||||
val playerGuid: PlanetSideGUID = tplayer.GUID
|
||||
tplayer.ContributionFrom(obj)
|
||||
sessionLogic.keepAliveFunc = sessionLogic.zoning.NormalKeepAlive
|
||||
val bailType = if (tplayer.BailProtection) {
|
||||
BailType.Bailed
|
||||
} else {
|
||||
BailType.Normal
|
||||
}
|
||||
sendResponse(DismountVehicleMsg(playerGuid, bailType, wasKickedByDriver = false))
|
||||
continent.VehicleEvents ! VehicleServiceMessage(
|
||||
continent.id,
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -5,39 +5,48 @@ import akka.actor.{ActorContext, ActorRef, typed}
|
|||
import scala.collection.mutable
|
||||
//
|
||||
import net.psforever.actors.session.{AvatarActor, ChatActor}
|
||||
import net.psforever.objects.avatar.Avatar
|
||||
import net.psforever.objects.teamwork.Squad
|
||||
import net.psforever.objects.{Default, LivePlayerList, Player}
|
||||
import net.psforever.objects.{Default, Player}
|
||||
import net.psforever.packet.game._
|
||||
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||
import net.psforever.services.chat.ChatService
|
||||
import net.psforever.services.teamwork.{SquadResponse, SquadServiceMessage, SquadAction => SquadServiceAction}
|
||||
import net.psforever.types.{ChatMessageType, PlanetSideEmpire, PlanetSideGUID, SquadListDecoration, SquadResponseType, Vector3, WaypointSubtype}
|
||||
import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID, Vector3}
|
||||
|
||||
trait SquadHandlerFunctions extends CommonSessionInterfacingFunctionality {
|
||||
val ops: SessionSquadHandlers
|
||||
|
||||
def handleSquadDefinitionAction(pkt: SquadDefinitionActionMessage): Unit
|
||||
|
||||
def handleSquadMemberRequest(pkt: SquadMembershipRequest): Unit
|
||||
|
||||
def handleSquadWaypointRequest(pkt: SquadWaypointRequest): Unit
|
||||
|
||||
def handle(response: SquadResponse.Response, excluded: Iterable[Long]): Unit
|
||||
}
|
||||
|
||||
object SessionSquadHandlers {
|
||||
protected final case class SquadUIElement(
|
||||
name: String,
|
||||
outfit: Long,
|
||||
index: Int,
|
||||
zone: Int,
|
||||
health: Int,
|
||||
armor: Int,
|
||||
position: Vector3
|
||||
)
|
||||
final case class SquadUIElement(
|
||||
name: String,
|
||||
outfit: Long,
|
||||
index: Int,
|
||||
zone: Int,
|
||||
health: Int,
|
||||
armor: Int,
|
||||
position: Vector3
|
||||
)
|
||||
}
|
||||
|
||||
class SessionSquadHandlers(
|
||||
val sessionLogic: SessionLogic,
|
||||
avatarActor: typed.ActorRef[AvatarActor.Command],
|
||||
chatActor: typed.ActorRef[ChatActor.Command],
|
||||
squadService: ActorRef,
|
||||
val sessionLogic: SessionData,
|
||||
val avatarActor: typed.ActorRef[AvatarActor.Command],
|
||||
val chatActor: typed.ActorRef[ChatActor.Command],
|
||||
val squadService: ActorRef,
|
||||
implicit val context: ActorContext
|
||||
) extends CommonSessionInterfacingFunctionality {
|
||||
import SessionSquadHandlers._
|
||||
|
||||
private var waypointCooldown: Long = 0L
|
||||
val squadUI: mutable.LongMap[SquadUIElement] = new mutable.LongMap[SquadUIElement]()
|
||||
var squad_supplement_id: Int = 0
|
||||
private[session] val squadUI: mutable.LongMap[SquadUIElement] = new mutable.LongMap[SquadUIElement]()
|
||||
private[session] var squad_supplement_id: Int = 0
|
||||
/**
|
||||
* When joining or creating a squad, the original state of the avatar's internal LFS variable is blanked.
|
||||
* This `WorldSessionActor`-local variable is then used to indicate the ongoing state of the LFS UI component,
|
||||
|
|
@ -46,340 +55,11 @@ class SessionSquadHandlers(
|
|||
* Upon leaving or disbanding a squad, this value is made false.
|
||||
* Control switching between the `Avatar`-local and the `WorldSessionActor`-local variable is contingent on `squadUI` being populated.
|
||||
*/
|
||||
private[support] var squadSetup: () => Unit = FirstTimeSquadSetup
|
||||
private var squadUpdateCounter: Int = 0
|
||||
private[session] var squadSetup: () => Unit = FirstTimeSquadSetup
|
||||
private[session] var squadUpdateCounter: Int = 0
|
||||
private[session] var updateSquad: () => Unit = NoSquadUpdates
|
||||
private[session] var updateSquadRef: ActorRef = Default.Actor
|
||||
private val queuedSquadActions: Seq[() => Unit] = Seq(SquadUpdates, NoSquadUpdates, NoSquadUpdates, NoSquadUpdates)
|
||||
private[support] var updateSquad: () => Unit = NoSquadUpdates
|
||||
private var updateSquadRef: ActorRef = Default.Actor
|
||||
|
||||
/* packet */
|
||||
|
||||
def handleSquadDefinitionAction(pkt: SquadDefinitionActionMessage): Unit = {
|
||||
val SquadDefinitionActionMessage(u1, u2, action) = pkt
|
||||
squadService ! SquadServiceMessage(player, continent, SquadServiceAction.Definition(u1, u2, action))
|
||||
}
|
||||
|
||||
def handleSquadMemberRequest(pkt: SquadMembershipRequest): Unit = {
|
||||
val SquadMembershipRequest(request_type, char_id, unk3, player_name, unk5) = pkt
|
||||
squadService ! SquadServiceMessage(
|
||||
player,
|
||||
continent,
|
||||
SquadServiceAction.Membership(request_type, char_id, unk3, player_name, unk5)
|
||||
)
|
||||
}
|
||||
|
||||
def handleSquadWaypointRequest(pkt: SquadWaypointRequest): Unit = {
|
||||
val SquadWaypointRequest(request, _, wtype, unk, info) = pkt
|
||||
val time = System.currentTimeMillis()
|
||||
val subtype = wtype.subtype
|
||||
if(subtype == WaypointSubtype.Squad) {
|
||||
squadService ! SquadServiceMessage(player, continent, SquadServiceAction.Waypoint(request, wtype, unk, info))
|
||||
} else if (subtype == WaypointSubtype.Laze && time - waypointCooldown > 1000) {
|
||||
//guarding against duplicating laze waypoints
|
||||
waypointCooldown = time
|
||||
squadService ! SquadServiceMessage(player, continent, SquadServiceAction.Waypoint(request, wtype, unk, info))
|
||||
}
|
||||
}
|
||||
|
||||
/* response handlers */
|
||||
|
||||
def handle(response: SquadResponse.Response, excluded: Iterable[Long]): Unit = {
|
||||
if (!excluded.exists(_ == avatar.id)) {
|
||||
response match {
|
||||
case SquadResponse.ListSquadFavorite(line, task) =>
|
||||
sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), line, SquadAction.ListSquadFavorite(task)))
|
||||
|
||||
case SquadResponse.InitList(infos) =>
|
||||
sendResponse(ReplicationStreamMessage(infos))
|
||||
|
||||
case SquadResponse.UpdateList(infos) if infos.nonEmpty =>
|
||||
sendResponse(
|
||||
ReplicationStreamMessage(
|
||||
6,
|
||||
None,
|
||||
infos.map {
|
||||
case (index, squadInfo) =>
|
||||
SquadListing(index, squadInfo)
|
||||
}.toVector
|
||||
)
|
||||
)
|
||||
|
||||
case SquadResponse.RemoveFromList(infos) if infos.nonEmpty =>
|
||||
sendResponse(
|
||||
ReplicationStreamMessage(
|
||||
1,
|
||||
None,
|
||||
infos.map { index =>
|
||||
SquadListing(index, None)
|
||||
}.toVector
|
||||
)
|
||||
)
|
||||
|
||||
case SquadResponse.SquadDecoration(guid, squad) =>
|
||||
val decoration = if (
|
||||
squadUI.nonEmpty ||
|
||||
squad.Size == squad.Capacity ||
|
||||
{
|
||||
val offer = avatar.certifications
|
||||
!squad.Membership.exists { _.isAvailable(offer) }
|
||||
}
|
||||
) {
|
||||
SquadListDecoration.NotAvailable
|
||||
} else {
|
||||
SquadListDecoration.Available
|
||||
}
|
||||
sendResponse(SquadDefinitionActionMessage(guid, 0, SquadAction.SquadListDecorator(decoration)))
|
||||
|
||||
case SquadResponse.Detail(guid, detail) =>
|
||||
sendResponse(SquadDetailDefinitionUpdateMessage(guid, detail))
|
||||
|
||||
case SquadResponse.IdentifyAsSquadLeader(squad_guid) =>
|
||||
sendResponse(SquadDefinitionActionMessage(squad_guid, 0, SquadAction.IdentifyAsSquadLeader()))
|
||||
|
||||
case SquadResponse.SetListSquad(squad_guid) =>
|
||||
sendResponse(SquadDefinitionActionMessage(squad_guid, 0, SquadAction.SetListSquad()))
|
||||
|
||||
case SquadResponse.Membership(request_type, unk1, unk2, charId, opt_char_id, player_name, unk5, unk6) =>
|
||||
val name = request_type match {
|
||||
case SquadResponseType.Invite if unk5 =>
|
||||
//the name of the player indicated by unk3 is needed
|
||||
LivePlayerList.WorldPopulation({ case (_, a: Avatar) => charId == a.id }).headOption match {
|
||||
case Some(player) =>
|
||||
player.name
|
||||
case None =>
|
||||
player_name
|
||||
}
|
||||
case _ =>
|
||||
player_name
|
||||
}
|
||||
sendResponse(SquadMembershipResponse(request_type, unk1, unk2, charId, opt_char_id, name, unk5, unk6))
|
||||
|
||||
case SquadResponse.WantsSquadPosition(_, name) =>
|
||||
sendResponse(
|
||||
ChatMsg(
|
||||
ChatMessageType.CMT_SQUAD,
|
||||
wideContents=true,
|
||||
name,
|
||||
s"\\#6 would like to join your squad. (respond with \\#3/accept\\#6 or \\#3/reject\\#6)",
|
||||
None
|
||||
)
|
||||
)
|
||||
|
||||
case SquadResponse.Join(squad, positionsToUpdate, _, ref) =>
|
||||
val avatarId = avatar.id
|
||||
val membershipPositions = (positionsToUpdate map squad.Membership.zipWithIndex)
|
||||
.filter { case (mem, index) =>
|
||||
mem.CharId > 0 && positionsToUpdate.contains(index)
|
||||
}
|
||||
membershipPositions.find { case (mem, _) => mem.CharId == avatarId } match {
|
||||
case Some((ourMember, ourIndex)) =>
|
||||
//we are joining the squad
|
||||
//load each member's entry (our own too)
|
||||
squad_supplement_id = squad.GUID.guid + 1
|
||||
membershipPositions.foreach {
|
||||
case (member, index) =>
|
||||
sendResponse(
|
||||
SquadMemberEvent.Add(
|
||||
squad_supplement_id,
|
||||
member.CharId,
|
||||
index,
|
||||
member.Name,
|
||||
member.ZoneId,
|
||||
outfit_id = 0
|
||||
)
|
||||
)
|
||||
squadUI(member.CharId) =
|
||||
SquadUIElement(member.Name, outfit=0L, index, member.ZoneId, member.Health, member.Armor, member.Position)
|
||||
}
|
||||
//repeat our entry
|
||||
sendResponse(
|
||||
SquadMemberEvent.Add(
|
||||
squad_supplement_id,
|
||||
ourMember.CharId,
|
||||
ourIndex,
|
||||
ourMember.Name,
|
||||
ourMember.ZoneId,
|
||||
outfit_id = 0
|
||||
)
|
||||
)
|
||||
//turn lfs off
|
||||
if (avatar.lookingForSquad) {
|
||||
avatarActor ! AvatarActor.SetLookingForSquad(false)
|
||||
}
|
||||
val playerGuid = player.GUID
|
||||
val factionChannel = s"${player.Faction}"
|
||||
//squad colors
|
||||
GiveSquadColorsToMembers()
|
||||
GiveSquadColorsForOthers(playerGuid, factionChannel, squad_supplement_id)
|
||||
//associate with member position in squad
|
||||
sendResponse(PlanetsideAttributeMessage(playerGuid, 32, ourIndex))
|
||||
//a finalization? what does this do?
|
||||
sendResponse(SquadDefinitionActionMessage(squad.GUID, 0, SquadAction.Unknown(18)))
|
||||
squadService ! SquadServiceMessage(player, continent, SquadServiceAction.ReloadDecoration())
|
||||
updateSquadRef = ref
|
||||
updateSquad = PeriodicUpdatesWhenEnrolledInSquad
|
||||
chatActor ! ChatActor.JoinChannel(ChatService.ChatChannel.Squad(squad.GUID))
|
||||
case _ =>
|
||||
//other player is joining our squad
|
||||
//load each member's entry
|
||||
GiveSquadColorsToMembers(
|
||||
membershipPositions.map {
|
||||
case (member, index) =>
|
||||
val charId = member.CharId
|
||||
sendResponse(
|
||||
SquadMemberEvent.Add(squad_supplement_id, charId, index, member.Name, member.ZoneId, outfit_id = 0)
|
||||
)
|
||||
squadUI(charId) =
|
||||
SquadUIElement(member.Name, outfit=0L, index, member.ZoneId, member.Health, member.Armor, member.Position)
|
||||
charId
|
||||
}
|
||||
)
|
||||
}
|
||||
//send an initial dummy update for map icon(s)
|
||||
sendResponse(
|
||||
SquadState(
|
||||
PlanetSideGUID(squad_supplement_id),
|
||||
membershipPositions.map { case (member, _) =>
|
||||
SquadStateInfo(member.CharId, member.Health, member.Armor, member.Position)
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
case SquadResponse.Leave(squad, positionsToUpdate) =>
|
||||
positionsToUpdate.find({ case (member, _) => member == avatar.id }) match {
|
||||
case Some((ourMember, ourIndex)) =>
|
||||
//we are leaving the squad
|
||||
//remove each member's entry (our own too)
|
||||
updateSquadRef = Default.Actor
|
||||
positionsToUpdate.foreach {
|
||||
case (member, index) =>
|
||||
sendResponse(SquadMemberEvent.Remove(squad_supplement_id, member, index))
|
||||
squadUI.remove(member)
|
||||
}
|
||||
//uninitialize
|
||||
val playerGuid = player.GUID
|
||||
sendResponse(SquadMemberEvent.Remove(squad_supplement_id, ourMember, ourIndex)) //repeat of our entry
|
||||
GiveSquadColorsToSelf(value = 0)
|
||||
sendResponse(PlanetsideAttributeMessage(playerGuid, 32, 0)) //disassociate with member position in squad?
|
||||
sendResponse(PlanetsideAttributeMessage(playerGuid, 34, 4294967295L)) //unknown, perhaps unrelated?
|
||||
avatarActor ! AvatarActor.SetLookingForSquad(false)
|
||||
//a finalization? what does this do?
|
||||
sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SquadAction.Unknown(18)))
|
||||
squad_supplement_id = 0
|
||||
squadUpdateCounter = 0
|
||||
updateSquad = NoSquadUpdates
|
||||
chatActor ! ChatActor.LeaveChannel(ChatService.ChatChannel.Squad(squad.GUID))
|
||||
case _ =>
|
||||
//remove each member's entry
|
||||
GiveSquadColorsToMembers(
|
||||
positionsToUpdate.map {
|
||||
case (member, index) =>
|
||||
sendResponse(SquadMemberEvent.Remove(squad_supplement_id, member, index))
|
||||
squadUI.remove(member)
|
||||
member
|
||||
},
|
||||
value = 0
|
||||
)
|
||||
}
|
||||
|
||||
case SquadResponse.AssignMember(squad, from_index, to_index) =>
|
||||
//we've already swapped position internally; now we swap the cards
|
||||
SwapSquadUIElements(squad, from_index, to_index)
|
||||
|
||||
case SquadResponse.PromoteMember(squad, promotedPlayer, from_index) =>
|
||||
if (promotedPlayer != player.CharId) {
|
||||
//demoted from leader; no longer lfsm
|
||||
if (player.avatar.lookingForSquad) {
|
||||
avatarActor ! AvatarActor.SetLookingForSquad(false)
|
||||
}
|
||||
}
|
||||
sendResponse(SquadMemberEvent(MemberEvent.Promote, squad.GUID.guid, promotedPlayer, position = 0))
|
||||
//the players have already been swapped in the backend object
|
||||
PromoteSquadUIElements(squad, from_index)
|
||||
|
||||
case SquadResponse.UpdateMembers(_, positions) =>
|
||||
val pairedEntries = positions.collect {
|
||||
case entry if squadUI.contains(entry.char_id) =>
|
||||
(entry, squadUI(entry.char_id))
|
||||
}
|
||||
//prune entries
|
||||
val updatedEntries = pairedEntries
|
||||
.collect({
|
||||
case (entry, element) if entry.zone_number != element.zone =>
|
||||
//zone gets updated for these entries
|
||||
sendResponse(
|
||||
SquadMemberEvent.UpdateZone(squad_supplement_id, entry.char_id, element.index, entry.zone_number)
|
||||
)
|
||||
squadUI(entry.char_id) =
|
||||
SquadUIElement(element.name, element.outfit, element.index, entry.zone_number, entry.health, entry.armor, entry.pos)
|
||||
entry
|
||||
case (entry, element)
|
||||
if entry.health != element.health || entry.armor != element.armor || entry.pos != element.position =>
|
||||
//other elements that need to be updated
|
||||
squadUI(entry.char_id) =
|
||||
SquadUIElement(element.name, element.outfit, element.index, entry.zone_number, entry.health, entry.armor, entry.pos)
|
||||
entry
|
||||
})
|
||||
.filterNot(_.char_id == avatar.id) //we want to update our backend, but not our frontend
|
||||
if (updatedEntries.nonEmpty) {
|
||||
sendResponse(
|
||||
SquadState(
|
||||
PlanetSideGUID(squad_supplement_id),
|
||||
updatedEntries.map { entry =>
|
||||
SquadStateInfo(entry.char_id, entry.health, entry.armor, entry.pos)
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
case SquadResponse.CharacterKnowledge(charId, name, certs, u1, u2, zone) =>
|
||||
sendResponse(CharacterKnowledgeMessage(charId, Some(CharacterKnowledgeInfo(name, certs, u1, u2, zone))))
|
||||
|
||||
case SquadResponse.SquadSearchResults(results) =>
|
||||
//TODO positive squad search results message?
|
||||
// if(results.nonEmpty) {
|
||||
// results.foreach { guid =>
|
||||
// sendResponse(SquadDefinitionActionMessage(
|
||||
// guid,
|
||||
// 0,
|
||||
// SquadAction.SquadListDecorator(SquadListDecoration.SearchResult))
|
||||
// )
|
||||
// }
|
||||
// } else {
|
||||
// sendResponse(SquadDefinitionActionMessage(player.GUID, 0, SquadAction.NoSquadSearchResults()))
|
||||
// }
|
||||
// sendResponse(SquadDefinitionActionMessage(player.GUID, 0, SquadAction.CancelSquadSearch()))
|
||||
|
||||
case SquadResponse.InitWaypoints(char_id, waypoints) =>
|
||||
waypoints.foreach {
|
||||
case (waypoint_type, info, unk) =>
|
||||
sendResponse(
|
||||
SquadWaypointEvent.Add(
|
||||
squad_supplement_id,
|
||||
char_id,
|
||||
waypoint_type,
|
||||
WaypointEvent(info.zone_number, info.pos, unk)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
case SquadResponse.WaypointEvent(WaypointEventAction.Add, char_id, waypoint_type, _, Some(info), unk) =>
|
||||
sendResponse(
|
||||
SquadWaypointEvent.Add(
|
||||
squad_supplement_id,
|
||||
char_id,
|
||||
waypoint_type,
|
||||
WaypointEvent(info.zone_number, info.pos, unk)
|
||||
)
|
||||
)
|
||||
|
||||
case SquadResponse.WaypointEvent(WaypointEventAction.Remove, char_id, waypoint_type, _, _, _) =>
|
||||
sendResponse(SquadWaypointEvent.Remove(squad_supplement_id, char_id, waypoint_type))
|
||||
|
||||
case _ => ;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* These messages are dispatched when first starting up the client and connecting to the server for the first time.
|
||||
|
|
|
|||
|
|
@ -3,181 +3,41 @@ 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
|
||||
import net.psforever.packet.game.FavoritesRequest
|
||||
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
import scala.concurrent.Future
|
||||
//
|
||||
import net.psforever.actors.session.AvatarActor
|
||||
import net.psforever.login.WorldSession.{BuyNewEquipmentPutInInventory, SellEquipmentFromInventory}
|
||||
import net.psforever.objects.{GlobalDefinitions, Player, Vehicle}
|
||||
import net.psforever.objects.guid.{StraightforwardTask, TaskBundle, TaskWorkflow}
|
||||
import net.psforever.objects.guid.{StraightforwardTask, TaskBundle}
|
||||
import net.psforever.objects.PlanetSideGameObject
|
||||
import net.psforever.objects.equipment.EffectTarget
|
||||
import net.psforever.objects.serverobject.CommonMessages
|
||||
import net.psforever.objects.serverobject.pad.VehicleSpawnPad
|
||||
import net.psforever.objects.serverobject.terminals.{ProximityDefinition, ProximityUnit, Terminal}
|
||||
import net.psforever.packet.game.{ItemTransactionMessage, ItemTransactionResultMessage,ProximityTerminalUseMessage, UnuseItemMessage}
|
||||
import net.psforever.types.{PlanetSideGUID, TransactionType, Vector3}
|
||||
import net.psforever.packet.game.{ItemTransactionMessage,ProximityTerminalUseMessage}
|
||||
import net.psforever.types.PlanetSideGUID
|
||||
|
||||
trait TerminalHandlerFunctions extends CommonSessionInterfacingFunctionality {
|
||||
def ops: SessionTerminalHandlers
|
||||
|
||||
def handleItemTransaction(pkt: ItemTransactionMessage): Unit
|
||||
|
||||
def handleProximityTerminalUse(pkt: ProximityTerminalUseMessage): Unit
|
||||
|
||||
def handleFavoritesRequest(pkt: FavoritesRequest): Unit
|
||||
|
||||
def handle(tplayer: Player, msg: ItemTransactionMessage, order: Terminal.Exchange): Unit
|
||||
}
|
||||
|
||||
class SessionTerminalHandlers(
|
||||
val sessionLogic: SessionLogic,
|
||||
avatarActor: typed.ActorRef[AvatarActor.Command],
|
||||
val sessionLogic: SessionData,
|
||||
val avatarActor: typed.ActorRef[AvatarActor.Command],
|
||||
implicit val context: ActorContext
|
||||
) extends CommonSessionInterfacingFunctionality {
|
||||
private[support] var lastTerminalOrderFulfillment: Boolean = true
|
||||
private[support] var usingMedicalTerminal: Option[PlanetSideGUID] = None
|
||||
|
||||
/* packets */
|
||||
|
||||
def handleItemTransaction(pkt: ItemTransactionMessage): Unit = {
|
||||
val ItemTransactionMessage(terminalGuid, transactionType, _, itemName, _, _) = pkt
|
||||
continent.GUID(terminalGuid) match {
|
||||
case Some(term: Terminal) if lastTerminalOrderFulfillment =>
|
||||
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
|
||||
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}")
|
||||
case Some(obj) =>
|
||||
log.error(s"ItemTransaction: ${obj.Definition.Name} is not a terminal, ${player.Name}")
|
||||
case _ =>
|
||||
log.error(s"ItemTransaction: entity with guid=${terminalGuid.guid} does not exist, ${player.Name}")
|
||||
}
|
||||
}
|
||||
|
||||
def handleProximityTerminalUse(pkt: ProximityTerminalUseMessage): Unit = {
|
||||
val ProximityTerminalUseMessage(_, objectGuid, _) = pkt
|
||||
continent.GUID(objectGuid) match {
|
||||
case Some(obj: Terminal with ProximityUnit) =>
|
||||
HandleProximityTerminalUse(obj)
|
||||
case Some(obj) =>
|
||||
log.warn(s"ProximityTerminalUse: ${obj.Definition.Name} guid=${objectGuid.guid} is not ready to implement proximity effects")
|
||||
case None =>
|
||||
log.error(s"ProximityTerminalUse: ${player.Name} can not find an object with guid ${objectGuid.guid}")
|
||||
}
|
||||
}
|
||||
|
||||
/* response handler */
|
||||
|
||||
/**
|
||||
* na
|
||||
* @param tplayer na
|
||||
* @param msg na
|
||||
* @param order na
|
||||
*/
|
||||
def handle(tplayer: Player, msg: ItemTransactionMessage, order: Terminal.Exchange): Unit = {
|
||||
order match {
|
||||
case Terminal.BuyEquipment(item)
|
||||
if tplayer.avatar.purchaseCooldown(item.Definition).nonEmpty =>
|
||||
sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, success = false))
|
||||
lastTerminalOrderFulfillment = true
|
||||
|
||||
case Terminal.BuyEquipment(item) =>
|
||||
avatarActor ! AvatarActor.UpdatePurchaseTime(item.Definition)
|
||||
TaskWorkflow.execute(BuyNewEquipmentPutInInventory(
|
||||
continent.GUID(tplayer.VehicleSeated) match {
|
||||
case Some(v: Vehicle) => v
|
||||
case _ => player
|
||||
},
|
||||
tplayer,
|
||||
msg.terminal_guid
|
||||
)(item))
|
||||
|
||||
case Terminal.SellEquipment() =>
|
||||
SellEquipmentFromInventory(tplayer, tplayer, msg.terminal_guid)(Player.FreeHandSlot)
|
||||
|
||||
case Terminal.LearnCertification(cert) =>
|
||||
avatarActor ! AvatarActor.LearnCertification(msg.terminal_guid, cert)
|
||||
lastTerminalOrderFulfillment = true
|
||||
|
||||
case Terminal.SellCertification(cert) =>
|
||||
avatarActor ! AvatarActor.SellCertification(msg.terminal_guid, cert)
|
||||
lastTerminalOrderFulfillment = true
|
||||
|
||||
case Terminal.LearnImplant(implant) =>
|
||||
avatarActor ! AvatarActor.LearnImplant(msg.terminal_guid, implant)
|
||||
lastTerminalOrderFulfillment = true
|
||||
|
||||
case Terminal.SellImplant(implant) =>
|
||||
avatarActor ! AvatarActor.SellImplant(msg.terminal_guid, implant)
|
||||
lastTerminalOrderFulfillment = true
|
||||
|
||||
case Terminal.BuyVehicle(vehicle, _, _)
|
||||
if tplayer.avatar.purchaseCooldown(vehicle.Definition).nonEmpty || tplayer.spectator =>
|
||||
sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, success = false))
|
||||
lastTerminalOrderFulfillment = true
|
||||
|
||||
case Terminal.BuyVehicle(vehicle, weapons, trunk) =>
|
||||
continent.map.terminalToSpawnPad
|
||||
.find { case (termid, _) => termid == msg.terminal_guid.guid }
|
||||
.map { case (a: Int, b: Int) => (continent.GUID(a), continent.GUID(b)) }
|
||||
.collect { case (Some(term: Terminal), Some(pad: VehicleSpawnPad)) =>
|
||||
avatarActor ! AvatarActor.UpdatePurchaseTime(vehicle.Definition)
|
||||
vehicle.Faction = tplayer.Faction
|
||||
vehicle.Position = pad.Position
|
||||
vehicle.Orientation = pad.Orientation + Vector3.z(pad.Definition.VehicleCreationZOrientOffset)
|
||||
//default loadout, weapons
|
||||
val vWeapons = vehicle.Weapons
|
||||
weapons.foreach { entry =>
|
||||
vWeapons.get(entry.start) match {
|
||||
case Some(slot) =>
|
||||
entry.obj.Faction = tplayer.Faction
|
||||
slot.Equipment = None
|
||||
slot.Equipment = entry.obj
|
||||
case None =>
|
||||
log.warn(
|
||||
s"BuyVehicle: ${player.Name} tries to apply default loadout to $vehicle on spawn, but can not find a mounted weapon for ${entry.start}"
|
||||
)
|
||||
}
|
||||
}
|
||||
//default loadout, trunk
|
||||
val vTrunk = vehicle.Trunk
|
||||
vTrunk.Clear()
|
||||
trunk.foreach { entry =>
|
||||
entry.obj.Faction = tplayer.Faction
|
||||
vTrunk.InsertQuickly(entry.start, entry.obj)
|
||||
}
|
||||
TaskWorkflow.execute(registerVehicleFromSpawnPad(vehicle, pad, term))
|
||||
sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, success = true))
|
||||
if (GlobalDefinitions.isBattleFrameVehicle(vehicle.Definition)) {
|
||||
sendResponse(UnuseItemMessage(player.GUID, msg.terminal_guid))
|
||||
}
|
||||
player.LogActivity(TerminalUsedActivity(AmenitySource(term), msg.transaction_type))
|
||||
}
|
||||
.orElse {
|
||||
log.error(
|
||||
s"${tplayer.Name} wanted to spawn a vehicle, but there was no spawn pad associated with terminal ${msg.terminal_guid} to accept it"
|
||||
)
|
||||
sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, success = false))
|
||||
None
|
||||
}
|
||||
lastTerminalOrderFulfillment = true
|
||||
|
||||
case Terminal.NoDeal() if msg != null =>
|
||||
val transaction = msg.transaction_type
|
||||
log.warn(s"NoDeal: ${tplayer.Name} made a request but the terminal rejected the ${transaction.toString} order")
|
||||
sendResponse(ItemTransactionResultMessage(msg.terminal_guid, transaction, success = false))
|
||||
lastTerminalOrderFulfillment = true
|
||||
|
||||
case _ =>
|
||||
val terminal = msg.terminal_guid.guid
|
||||
continent.GUID(terminal) match {
|
||||
case Some(term: Terminal) =>
|
||||
log.warn(s"NoDeal?: ${tplayer.Name} made a request but the ${term.Definition.Name}#$terminal rejected the missing order")
|
||||
case Some(_) =>
|
||||
log.warn(s"NoDeal?: ${tplayer.Name} made a request to a non-terminal entity#$terminal")
|
||||
case None =>
|
||||
log.warn(s"NoDeal?: ${tplayer.Name} made a request to a missing entity#$terminal")
|
||||
}
|
||||
lastTerminalOrderFulfillment = true
|
||||
}
|
||||
}
|
||||
|
||||
/* support */
|
||||
private[session] var lastTerminalOrderFulfillment: Boolean = true
|
||||
private[session] var usingMedicalTerminal: Option[PlanetSideGUID] = None
|
||||
|
||||
/**
|
||||
* Construct tasking that adds a completed and registered vehicle into the scene.
|
||||
|
|
@ -189,7 +49,7 @@ class SessionTerminalHandlers(
|
|||
* @see `RegisterVehicle`
|
||||
* @return a `TaskBundle` message
|
||||
*/
|
||||
private[session] def registerVehicleFromSpawnPad(vehicle: Vehicle, pad: VehicleSpawnPad, terminal: Terminal): TaskBundle = {
|
||||
def registerVehicleFromSpawnPad(vehicle: Vehicle, pad: VehicleSpawnPad, terminal: Terminal): TaskBundle = {
|
||||
TaskBundle(
|
||||
new StraightforwardTask() {
|
||||
private val localVehicle = vehicle
|
||||
|
|
@ -335,7 +195,7 @@ class SessionTerminalHandlers(
|
|||
* @see `RegisterVehicleFromSpawnPad`
|
||||
* @return a `TaskBundle` message
|
||||
*/
|
||||
private[session] def registerVehicle(vehicle: Vehicle): TaskBundle = {
|
||||
def registerVehicle(vehicle: Vehicle): TaskBundle = {
|
||||
TaskBundle(
|
||||
new StraightforwardTask() {
|
||||
private val localVehicle = vehicle
|
||||
|
|
@ -350,5 +210,6 @@ class SessionTerminalHandlers(
|
|||
|
||||
override protected[support] def actionsToCancel(): Unit = {
|
||||
lastTerminalOrderFulfillment = true
|
||||
usingMedicalTerminal = None
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,390 +3,18 @@ package net.psforever.actors.session.support
|
|||
|
||||
import akka.actor.{ActorContext, ActorRef, typed}
|
||||
import net.psforever.actors.session.AvatarActor
|
||||
import net.psforever.objects.equipment.{Equipment, JammableMountedWeapons, JammableUnit}
|
||||
import net.psforever.objects.guid.{GUIDTask, TaskWorkflow}
|
||||
import net.psforever.objects.serverobject.mount.Mountable
|
||||
import net.psforever.objects.serverobject.pad.VehicleSpawnPad
|
||||
import net.psforever.objects.{GlobalDefinitions, Player, Tool, Vehicle, Vehicles}
|
||||
import net.psforever.packet.game.objectcreate.ObjectCreateMessageParent
|
||||
import net.psforever.packet.game._
|
||||
import net.psforever.services.Service
|
||||
import net.psforever.services.vehicle.{VehicleResponse, VehicleServiceResponse}
|
||||
import net.psforever.types.{BailType, ChatMessageType, PlanetSideGUID, Vector3}
|
||||
import net.psforever.services.vehicle.VehicleResponse
|
||||
import net.psforever.types.PlanetSideGUID
|
||||
|
||||
import scala.concurrent.duration._
|
||||
trait VehicleHandlerFunctions extends CommonSessionInterfacingFunctionality {
|
||||
def ops: SessionVehicleHandlers
|
||||
|
||||
def handle(toChannel: String, guid: PlanetSideGUID, reply: VehicleResponse.Response): Unit
|
||||
}
|
||||
|
||||
class SessionVehicleHandlers(
|
||||
val sessionLogic: SessionLogic,
|
||||
avatarActor: typed.ActorRef[AvatarActor.Command],
|
||||
galaxyService: ActorRef,
|
||||
val sessionLogic: SessionData,
|
||||
val avatarActor: typed.ActorRef[AvatarActor.Command],
|
||||
val galaxyService: ActorRef,
|
||||
implicit val context: ActorContext
|
||||
) extends CommonSessionInterfacingFunctionality {
|
||||
/**
|
||||
* na
|
||||
*
|
||||
* @param toChannel na
|
||||
* @param guid na
|
||||
* @param reply na
|
||||
*/
|
||||
def handle(toChannel: String, guid: PlanetSideGUID, reply: VehicleResponse.Response): Unit = {
|
||||
val resolvedPlayerGuid = if (player.HasGUID) {
|
||||
player.GUID
|
||||
} else {
|
||||
PlanetSideGUID(-1)
|
||||
}
|
||||
val isNotSameTarget = resolvedPlayerGuid != guid
|
||||
reply match {
|
||||
case VehicleResponse.VehicleState(
|
||||
vehicleGuid,
|
||||
unk1,
|
||||
pos,
|
||||
orient,
|
||||
vel,
|
||||
unk2,
|
||||
unk3,
|
||||
unk4,
|
||||
wheelDirection,
|
||||
unk5,
|
||||
unk6
|
||||
) if isNotSameTarget && player.VehicleSeated.contains(vehicleGuid) =>
|
||||
//player who is also in the vehicle (not driver)
|
||||
sendResponse(VehicleStateMessage(vehicleGuid, unk1, pos, orient, vel, unk2, unk3, unk4, wheelDirection, unk5, unk6))
|
||||
player.Position = pos
|
||||
player.Orientation = orient
|
||||
player.Velocity = vel
|
||||
sessionLogic.updateLocalBlockMap(pos)
|
||||
|
||||
case VehicleResponse.VehicleState(
|
||||
vehicleGuid,
|
||||
unk1,
|
||||
pos,
|
||||
ang,
|
||||
vel,
|
||||
unk2,
|
||||
unk3,
|
||||
unk4,
|
||||
wheelDirection,
|
||||
unk5,
|
||||
unk6
|
||||
) if isNotSameTarget =>
|
||||
//player who is watching the vehicle from the outside
|
||||
sendResponse(VehicleStateMessage(vehicleGuid, unk1, pos, ang, vel, unk2, unk3, unk4, wheelDirection, unk5, unk6))
|
||||
|
||||
case VehicleResponse.ChildObjectState(objectGuid, pitch, yaw) if isNotSameTarget =>
|
||||
sendResponse(ChildObjectStateMessage(objectGuid, pitch, yaw))
|
||||
|
||||
case VehicleResponse.FrameVehicleState(vguid, u1, pos, oient, vel, u2, u3, u4, is_crouched, u6, u7, u8, u9, uA)
|
||||
if isNotSameTarget =>
|
||||
sendResponse(FrameVehicleStateMessage(vguid, u1, pos, oient, vel, u2, u3, u4, is_crouched, u6, u7, u8, u9, uA))
|
||||
|
||||
case VehicleResponse.ChangeFireState_Start(weaponGuid) if isNotSameTarget =>
|
||||
sendResponse(ChangeFireStateMessage_Start(weaponGuid))
|
||||
|
||||
case VehicleResponse.ChangeFireState_Stop(weaponGuid) if isNotSameTarget =>
|
||||
sendResponse(ChangeFireStateMessage_Stop(weaponGuid))
|
||||
|
||||
case VehicleResponse.Reload(itemGuid) if isNotSameTarget =>
|
||||
sendResponse(ReloadMessage(itemGuid, ammo_clip=1, unk1=0))
|
||||
|
||||
case VehicleResponse.ChangeAmmo(weapon_guid, weapon_slot, previous_guid, ammo_id, ammo_guid, ammo_data) if isNotSameTarget =>
|
||||
sendResponse(ObjectDetachMessage(weapon_guid, previous_guid, Vector3.Zero, 0))
|
||||
//TODO? sendResponse(ObjectDeleteMessage(previousAmmoGuid, 0))
|
||||
sendResponse(
|
||||
ObjectCreateMessage(
|
||||
ammo_id,
|
||||
ammo_guid,
|
||||
ObjectCreateMessageParent(weapon_guid, weapon_slot),
|
||||
ammo_data
|
||||
)
|
||||
)
|
||||
sendResponse(ChangeAmmoMessage(weapon_guid, 1))
|
||||
|
||||
case VehicleResponse.WeaponDryFire(weaponGuid) if isNotSameTarget =>
|
||||
continent.GUID(weaponGuid).collect {
|
||||
case tool: Tool if tool.Magazine == 0 =>
|
||||
// check that the magazine is still empty before sending WeaponDryFireMessage
|
||||
// if it has been reloaded since then, other clients will not see it firing
|
||||
sendResponse(WeaponDryFireMessage(weaponGuid))
|
||||
}
|
||||
|
||||
case VehicleResponse.DismountVehicle(bailType, wasKickedByDriver) if isNotSameTarget =>
|
||||
sendResponse(DismountVehicleMsg(guid, bailType, wasKickedByDriver))
|
||||
|
||||
case VehicleResponse.MountVehicle(vehicleGuid, seat) if isNotSameTarget =>
|
||||
sendResponse(ObjectAttachMessage(vehicleGuid, guid, seat))
|
||||
|
||||
case VehicleResponse.DeployRequest(objectGuid, state, unk1, unk2, pos) if isNotSameTarget =>
|
||||
sendResponse(DeployRequestMessage(guid, objectGuid, state, unk1, unk2, pos))
|
||||
|
||||
case VehicleResponse.SendResponse(msg) =>
|
||||
sendResponse(msg)
|
||||
|
||||
case VehicleResponse.AttachToRails(vehicleGuid, padGuid) =>
|
||||
sendResponse(ObjectAttachMessage(padGuid, vehicleGuid, slot=3))
|
||||
|
||||
case VehicleResponse.ConcealPlayer(playerGuid) =>
|
||||
sendResponse(GenericObjectActionMessage(playerGuid, code=9))
|
||||
|
||||
case VehicleResponse.DetachFromRails(vehicleGuid, padGuid, padPosition, padOrientationZ) =>
|
||||
val pad = continent.GUID(padGuid).get.asInstanceOf[VehicleSpawnPad].Definition
|
||||
sendResponse(
|
||||
ObjectDetachMessage(
|
||||
padGuid,
|
||||
vehicleGuid,
|
||||
padPosition + Vector3.z(pad.VehicleCreationZOffset),
|
||||
padOrientationZ + pad.VehicleCreationZOrientOffset
|
||||
)
|
||||
)
|
||||
|
||||
case VehicleResponse.EquipmentInSlot(pkt) if isNotSameTarget =>
|
||||
sendResponse(pkt)
|
||||
|
||||
case VehicleResponse.GenericObjectAction(objectGuid, action) if isNotSameTarget =>
|
||||
sendResponse(GenericObjectActionMessage(objectGuid, action))
|
||||
|
||||
case VehicleResponse.HitHint(sourceGuid) if player.isAlive =>
|
||||
sendResponse(HitHint(sourceGuid, player.GUID))
|
||||
|
||||
case VehicleResponse.InventoryState(obj, parentGuid, start, conData) if isNotSameTarget =>
|
||||
//TODO prefer ObjectDetachMessage, but how to force ammo pools to update properly?
|
||||
val objGuid = obj.GUID
|
||||
sendResponse(ObjectDeleteMessage(objGuid, unk1=0))
|
||||
sendResponse(ObjectCreateDetailedMessage(
|
||||
obj.Definition.ObjectId,
|
||||
objGuid,
|
||||
ObjectCreateMessageParent(parentGuid, start),
|
||||
conData
|
||||
))
|
||||
|
||||
case VehicleResponse.KickPassenger(_, wasKickedByDriver, vehicleGuid) if resolvedPlayerGuid == guid =>
|
||||
//seat number (first field) seems to be correct if passenger is kicked manually by driver
|
||||
//but always seems to return 4 if user is kicked by mount permissions changing
|
||||
sendResponse(DismountVehicleMsg(guid, BailType.Kicked, wasKickedByDriver))
|
||||
val typeOfRide = continent.GUID(vehicleGuid) match {
|
||||
case Some(obj: Vehicle) =>
|
||||
sessionLogic.general.unaccessContainer(obj)
|
||||
s"the ${obj.Definition.Name}'s seat by ${obj.OwnerName.getOrElse("the pilot")}"
|
||||
case _ =>
|
||||
s"${player.Sex.possessive} ride"
|
||||
}
|
||||
log.info(s"${player.Name} has been kicked from $typeOfRide!")
|
||||
|
||||
case VehicleResponse.KickPassenger(_, wasKickedByDriver, _) =>
|
||||
//seat number (first field) seems to be correct if passenger is kicked manually by driver
|
||||
//but always seems to return 4 if user is kicked by mount permissions changing
|
||||
sendResponse(DismountVehicleMsg(guid, BailType.Kicked, wasKickedByDriver))
|
||||
|
||||
case VehicleResponse.InventoryState2(objGuid, parentGuid, value) if isNotSameTarget =>
|
||||
sendResponse(InventoryStateMessage(objGuid, unk=0, parentGuid, value))
|
||||
|
||||
case VehicleResponse.LoadVehicle(vehicle, vtype, vguid, vdata) if isNotSameTarget =>
|
||||
//this is not be suitable for vehicles with people who are seated in it before it spawns (if that is possible)
|
||||
sendResponse(ObjectCreateMessage(vtype, vguid, vdata))
|
||||
Vehicles.ReloadAccessPermissions(vehicle, player.Name)
|
||||
|
||||
case VehicleResponse.ObjectDelete(itemGuid) if isNotSameTarget =>
|
||||
sendResponse(ObjectDeleteMessage(itemGuid, unk1=0))
|
||||
|
||||
case VehicleResponse.Ownership(vehicleGuid) if resolvedPlayerGuid == guid =>
|
||||
//Only the player that owns this vehicle needs the ownership packet
|
||||
avatarActor ! AvatarActor.SetVehicle(Some(vehicleGuid))
|
||||
sendResponse(PlanetsideAttributeMessage(resolvedPlayerGuid, attribute_type=21, vehicleGuid))
|
||||
|
||||
case VehicleResponse.PlanetsideAttribute(vehicleGuid, attributeType, attributeValue) if isNotSameTarget =>
|
||||
sendResponse(PlanetsideAttributeMessage(vehicleGuid, attributeType, attributeValue))
|
||||
|
||||
case VehicleResponse.ResetSpawnPad(padGuid) =>
|
||||
sendResponse(GenericObjectActionMessage(padGuid, code=23))
|
||||
|
||||
case VehicleResponse.RevealPlayer(playerGuid) =>
|
||||
sendResponse(GenericObjectActionMessage(playerGuid, code=10))
|
||||
|
||||
case VehicleResponse.SeatPermissions(vehicleGuid, seatGroup, permission) if isNotSameTarget =>
|
||||
sendResponse(PlanetsideAttributeMessage(vehicleGuid, seatGroup, permission))
|
||||
|
||||
case VehicleResponse.StowEquipment(vehicleGuid, slot, itemType, itemGuid, itemData) if isNotSameTarget =>
|
||||
//TODO prefer ObjectAttachMessage, but how to force ammo pools to update properly?
|
||||
sendResponse(ObjectCreateDetailedMessage(itemType, itemGuid, ObjectCreateMessageParent(vehicleGuid, slot), itemData))
|
||||
|
||||
case VehicleResponse.UnloadVehicle(_, vehicleGuid) =>
|
||||
sendResponse(ObjectDeleteMessage(vehicleGuid, unk1=0))
|
||||
|
||||
case VehicleResponse.UnstowEquipment(itemGuid) if isNotSameTarget =>
|
||||
//TODO prefer ObjectDetachMessage, but how to force ammo pools to update properly?
|
||||
sendResponse(ObjectDeleteMessage(itemGuid, unk1=0))
|
||||
|
||||
case VehicleResponse.UpdateAmsSpawnPoint(list) =>
|
||||
sessionLogic.zoning.spawn.amsSpawnPoints = list.filter(tube => tube.Faction == player.Faction)
|
||||
sessionLogic.zoning.spawn.DrawCurrentAmsSpawnPoint()
|
||||
|
||||
case VehicleResponse.TransferPassengerChannel(oldChannel, tempChannel, vehicle, vehicleToDelete) if isNotSameTarget =>
|
||||
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 && 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
|
||||
sessionLogic.vehicles.ServerVehicleOverrideWithPacket(
|
||||
vehicle,
|
||||
ServerVehicleOverrideMsg(
|
||||
lock_accelerator=true,
|
||||
lock_wheel=true,
|
||||
reverse=true,
|
||||
unk4=false,
|
||||
lock_vthrust=0,
|
||||
strafe,
|
||||
reverseSpeed,
|
||||
unk8=Some(0)
|
||||
)
|
||||
)
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
context.system.scheduler.scheduleOnce(
|
||||
delay milliseconds,
|
||||
context.self,
|
||||
VehicleServiceResponse(toChannel, PlanetSideGUID(0), VehicleResponse.KickCargo(vehicle, speed=0, delay))
|
||||
)
|
||||
|
||||
case VehicleResponse.KickCargo(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) =>
|
||||
player.DrawnSlot = Player.HandsDownSlot
|
||||
startPlayerSeatedInVehicle(vehicle)
|
||||
|
||||
case VehicleResponse.StartPlayerSeatedInVehicle(vehicle, _) =>
|
||||
startPlayerSeatedInVehicle(vehicle)
|
||||
|
||||
case VehicleResponse.PlayerSeatedInVehicle(vehicle, _) =>
|
||||
Vehicles.ReloadAccessPermissions(vehicle, player.Name)
|
||||
sessionLogic.vehicles.ServerVehicleOverrideWithPacket(
|
||||
vehicle,
|
||||
ServerVehicleOverrideMsg(
|
||||
lock_accelerator=true,
|
||||
lock_wheel=true,
|
||||
reverse=true,
|
||||
unk4=false,
|
||||
lock_vthrust=1,
|
||||
lock_strafe=0,
|
||||
movement_speed=0,
|
||||
unk8=Some(0)
|
||||
)
|
||||
)
|
||||
sessionLogic.vehicles.serverVehicleControlVelocity = Some(0)
|
||||
|
||||
case VehicleResponse.ServerVehicleOverrideStart(vehicle, _) =>
|
||||
val vdef = vehicle.Definition
|
||||
sessionLogic.vehicles.ServerVehicleOverrideWithPacket(
|
||||
vehicle,
|
||||
ServerVehicleOverrideMsg(
|
||||
lock_accelerator=true,
|
||||
lock_wheel=true,
|
||||
reverse=false,
|
||||
unk4=false,
|
||||
lock_vthrust=if (GlobalDefinitions.isFlightVehicle(vdef)) { 1 } else { 0 },
|
||||
lock_strafe=0,
|
||||
movement_speed=vdef.AutoPilotSpeed1,
|
||||
unk8=Some(0)
|
||||
)
|
||||
)
|
||||
|
||||
case VehicleResponse.ServerVehicleOverrideEnd(vehicle, _) =>
|
||||
sessionLogic.vehicles.ServerVehicleOverrideStop(vehicle)
|
||||
|
||||
case VehicleResponse.PeriodicReminder(VehicleSpawnPad.Reminders.Blocked, data) =>
|
||||
sendResponse(ChatMsg(
|
||||
ChatMessageType.CMT_OPEN,
|
||||
wideContents=true,
|
||||
recipient="",
|
||||
s"The vehicle spawn where you placed your order is blocked. ${data.getOrElse("")}",
|
||||
note=None
|
||||
))
|
||||
|
||||
case VehicleResponse.PeriodicReminder(_, data) =>
|
||||
val (isType, flag, msg): (ChatMessageType, Boolean, String) = data match {
|
||||
case Some(msg: String) if msg.startsWith("@") => (ChatMessageType.UNK_227, false, msg)
|
||||
case Some(msg: String) => (ChatMessageType.CMT_OPEN, true, msg)
|
||||
case _ => (ChatMessageType.CMT_OPEN, true, "Your vehicle order has been cancelled.")
|
||||
}
|
||||
sendResponse(ChatMsg(isType, flag, recipient="", msg, None))
|
||||
|
||||
case VehicleResponse.ChangeLoadout(target, oldWeapons, addedWeapons, oldInventory, newInventory)
|
||||
if player.avatar.vehicle.contains(target) =>
|
||||
//TODO when vehicle weapons can be changed without visual glitches, rewrite this
|
||||
continent.GUID(target).collect { case vehicle: Vehicle =>
|
||||
import net.psforever.login.WorldSession.boolToInt
|
||||
//owner: must unregister old equipment, and register and install new equipment
|
||||
(oldWeapons ++ oldInventory).foreach {
|
||||
case (obj, eguid) =>
|
||||
sendResponse(ObjectDeleteMessage(eguid, unk1=0))
|
||||
TaskWorkflow.execute(GUIDTask.unregisterEquipment(continent.GUID, obj))
|
||||
}
|
||||
sessionLogic.general.applyPurchaseTimersBeforePackingLoadout(player, vehicle, addedWeapons ++ newInventory)
|
||||
//jammer or unjamm new weapons based on vehicle status
|
||||
val vehicleJammered = vehicle.Jammed
|
||||
addedWeapons
|
||||
.map { _.obj }
|
||||
.collect {
|
||||
case jamItem: JammableUnit if jamItem.Jammed != vehicleJammered =>
|
||||
jamItem.Jammed = vehicleJammered
|
||||
JammableMountedWeapons.JammedWeaponStatus(vehicle.Zone, jamItem, vehicleJammered)
|
||||
}
|
||||
changeLoadoutDeleteOldEquipment(vehicle, oldWeapons, oldInventory)
|
||||
}
|
||||
|
||||
case VehicleResponse.ChangeLoadout(target, oldWeapons, _, oldInventory, _)
|
||||
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
|
||||
(oldWeapons ++ oldInventory).foreach { case (_, eguid) => sendResponse(ObjectDeleteMessage(eguid, unk1=0)) }
|
||||
changeLoadoutDeleteOldEquipment(vehicle, oldWeapons, oldInventory)
|
||||
}
|
||||
|
||||
case VehicleResponse.ChangeLoadout(target, oldWeapons, _, oldInventory, _) =>
|
||||
//TODO when vehicle weapons can be changed without visual glitches, rewrite this
|
||||
continent.GUID(target).collect { case vehicle: Vehicle =>
|
||||
changeLoadoutDeleteOldEquipment(vehicle, oldWeapons, oldInventory)
|
||||
}
|
||||
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
|
||||
private def changeLoadoutDeleteOldEquipment(
|
||||
vehicle: Vehicle,
|
||||
oldWeapons: Iterable[(Equipment, PlanetSideGUID)],
|
||||
oldInventory: Iterable[(Equipment, PlanetSideGUID)]
|
||||
): Unit = {
|
||||
vehicle.PassengerInSeat(player) match {
|
||||
case Some(seatNum) =>
|
||||
//participant: observe changes to equipment
|
||||
(oldWeapons ++ oldInventory).foreach {
|
||||
case (_, eguid) => sendResponse(ObjectDeleteMessage(eguid, unk1=0))
|
||||
}
|
||||
sessionLogic.mountResponse.updateWeaponAtSeatPosition(vehicle, seatNum)
|
||||
case None =>
|
||||
//observer: observe changes to external equipment
|
||||
oldWeapons.foreach { case (_, eguid) => sendResponse(ObjectDeleteMessage(eguid, unk1=0)) }
|
||||
}
|
||||
}
|
||||
|
||||
private def startPlayerSeatedInVehicle(vehicle: Vehicle): Unit = {
|
||||
val vehicle_guid = vehicle.GUID
|
||||
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 {
|
||||
case (mountPoint, _) => vehicle.Actor ! Mountable.TryMount(player, mountPoint)
|
||||
}
|
||||
}
|
||||
}
|
||||
) extends CommonSessionInterfacingFunctionality
|
||||
|
|
|
|||
|
|
@ -5,466 +5,39 @@ import akka.actor.{ActorContext, typed}
|
|||
import net.psforever.actors.session.AvatarActor
|
||||
import net.psforever.objects.serverobject.deploy.Deployment
|
||||
import net.psforever.objects.serverobject.mount.Mountable
|
||||
import net.psforever.objects.vehicles.control.BfrFlight
|
||||
import net.psforever.objects.vehicles.{AccessPermissionGroup, CargoBehavior}
|
||||
import net.psforever.objects.zones.Zone
|
||||
import net.psforever.objects._
|
||||
import net.psforever.objects.serverobject.PlanetSideServerObject
|
||||
import net.psforever.packet.game.{ChildObjectStateMessage, DeployRequestMessage, DismountVehicleCargoMsg, DismountVehicleMsg, MountVehicleCargoMsg, MountVehicleMsg, VehicleSubStateMessage, _}
|
||||
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
|
||||
import net.psforever.types.{BailType, DriveState, Vector3}
|
||||
import net.psforever.packet.game.{ChildObjectStateMessage, DeployRequestMessage, VehicleSubStateMessage, _}
|
||||
import net.psforever.types.DriveState
|
||||
|
||||
class VehicleOperations(
|
||||
val sessionLogic: SessionLogic,
|
||||
avatarActor: typed.ActorRef[AvatarActor.Command],
|
||||
implicit val context: ActorContext
|
||||
) extends CommonSessionInterfacingFunctionality {
|
||||
private[support] var serverVehicleControlVelocity: Option[Int] = None
|
||||
trait VehicleFunctions extends CommonSessionInterfacingFunctionality {
|
||||
def ops: VehicleOperations
|
||||
|
||||
/* packets */
|
||||
def handleVehicleState(pkt: VehicleStateMessage): Unit
|
||||
|
||||
def handleVehicleState(pkt: VehicleStateMessage): Unit = {
|
||||
val VehicleStateMessage(
|
||||
vehicle_guid,
|
||||
unk1,
|
||||
pos,
|
||||
ang,
|
||||
vel,
|
||||
is_flying,
|
||||
unk6,
|
||||
unk7,
|
||||
wheels,
|
||||
is_decelerating,
|
||||
is_cloaked
|
||||
) = pkt
|
||||
GetVehicleAndSeat() match {
|
||||
case (Some(obj), Some(0)) =>
|
||||
//we're driving the vehicle
|
||||
sessionLogic.persist()
|
||||
sessionLogic.turnCounterFunc(player.GUID)
|
||||
sessionLogic.general.fallHeightTracker(pos.z)
|
||||
if (obj.MountedIn.isEmpty) {
|
||||
sessionLogic.updateBlockMap(obj, pos)
|
||||
}
|
||||
player.Position = pos //convenient
|
||||
if (obj.WeaponControlledFromSeat(0).isEmpty) {
|
||||
player.Orientation = Vector3.z(ang.z) //convenient
|
||||
}
|
||||
obj.Position = pos
|
||||
obj.Orientation = ang
|
||||
if (obj.MountedIn.isEmpty) {
|
||||
if (obj.DeploymentState != DriveState.Deployed) {
|
||||
obj.Velocity = vel
|
||||
} else {
|
||||
obj.Velocity = Some(Vector3.Zero)
|
||||
}
|
||||
if (obj.Definition.CanFly) {
|
||||
obj.Flying = is_flying //usually Some(7)
|
||||
}
|
||||
obj.Cloaked = obj.Definition.CanCloak && is_cloaked
|
||||
} else {
|
||||
obj.Velocity = None
|
||||
obj.Flying = None
|
||||
}
|
||||
continent.VehicleEvents ! VehicleServiceMessage(
|
||||
continent.id,
|
||||
VehicleAction.VehicleState(
|
||||
player.GUID,
|
||||
vehicle_guid,
|
||||
unk1,
|
||||
obj.Position,
|
||||
ang,
|
||||
obj.Velocity,
|
||||
if (obj.isFlying) {
|
||||
is_flying
|
||||
} else {
|
||||
None
|
||||
},
|
||||
unk6,
|
||||
unk7,
|
||||
wheels,
|
||||
is_decelerating,
|
||||
obj.Cloaked
|
||||
)
|
||||
)
|
||||
sessionLogic.squad.updateSquad()
|
||||
obj.zoneInteractions()
|
||||
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
|
||||
case (_, Some(index)) =>
|
||||
log.error(
|
||||
s"VehicleState: ${player.Name} should not be dispatching this kind of packet from vehicle ${vehicle_guid.guid} when not the driver (actually, seat $index)"
|
||||
)
|
||||
case _ => ;
|
||||
}
|
||||
if (player.death_by == -1) {
|
||||
sessionLogic.kickedByAdministration()
|
||||
}
|
||||
}
|
||||
def handleFrameVehicleState(pkt: FrameVehicleStateMessage): Unit
|
||||
|
||||
def handleFrameVehicleState(pkt: FrameVehicleStateMessage): Unit = {
|
||||
val FrameVehicleStateMessage(
|
||||
vehicle_guid,
|
||||
unk1,
|
||||
pos,
|
||||
ang,
|
||||
vel,
|
||||
unk2,
|
||||
unk3,
|
||||
unk4,
|
||||
is_crouched,
|
||||
is_airborne,
|
||||
ascending_flight,
|
||||
flight_time,
|
||||
unk9,
|
||||
unkA
|
||||
) = pkt
|
||||
GetVehicleAndSeat() match {
|
||||
case (Some(obj), Some(0)) =>
|
||||
//we're driving the vehicle
|
||||
sessionLogic.persist()
|
||||
sessionLogic.turnCounterFunc(player.GUID)
|
||||
val (position, angle, velocity, notMountedState) = continent.GUID(obj.MountedIn) match {
|
||||
case Some(v: Vehicle) =>
|
||||
sessionLogic.updateBlockMap(obj, pos)
|
||||
(pos, v.Orientation - Vector3.z(value = 90f) * Vehicles.CargoOrientation(obj).toFloat, v.Velocity, false)
|
||||
case _ =>
|
||||
(pos, ang, vel, true)
|
||||
}
|
||||
player.Position = position //convenient
|
||||
if (obj.WeaponControlledFromSeat(seatNumber = 0).isEmpty) {
|
||||
player.Orientation = Vector3.z(ang.z) //convenient
|
||||
}
|
||||
obj.Position = position
|
||||
obj.Orientation = angle
|
||||
obj.Velocity = velocity
|
||||
// if (is_crouched && obj.DeploymentState != DriveState.Kneeling) {
|
||||
// //dev stuff goes here
|
||||
// }
|
||||
// else
|
||||
// if (!is_crouched && obj.DeploymentState == DriveState.Kneeling) {
|
||||
// //dev stuff goes here
|
||||
// }
|
||||
obj.DeploymentState = if (is_crouched || !notMountedState) DriveState.Kneeling else DriveState.Mobile
|
||||
if (notMountedState) {
|
||||
if (obj.DeploymentState != DriveState.Kneeling) {
|
||||
if (is_airborne) {
|
||||
val flight = if (ascending_flight) flight_time else -flight_time
|
||||
obj.Flying = Some(flight)
|
||||
obj.Actor ! BfrFlight.Soaring(flight)
|
||||
} else if (obj.Flying.nonEmpty) {
|
||||
obj.Flying = None
|
||||
obj.Actor ! BfrFlight.Landed
|
||||
}
|
||||
} else {
|
||||
obj.Velocity = None
|
||||
obj.Flying = None
|
||||
}
|
||||
obj.zoneInteractions()
|
||||
} else {
|
||||
obj.Velocity = None
|
||||
obj.Flying = None
|
||||
}
|
||||
continent.VehicleEvents ! VehicleServiceMessage(
|
||||
continent.id,
|
||||
VehicleAction.FrameVehicleState(
|
||||
player.GUID,
|
||||
vehicle_guid,
|
||||
unk1,
|
||||
position,
|
||||
angle,
|
||||
velocity,
|
||||
unk2,
|
||||
unk3,
|
||||
unk4,
|
||||
is_crouched,
|
||||
is_airborne,
|
||||
ascending_flight,
|
||||
flight_time,
|
||||
unk9,
|
||||
unkA
|
||||
)
|
||||
)
|
||||
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
|
||||
case (_, Some(index)) =>
|
||||
log.error(
|
||||
s"VehicleState: ${player.Name} should not be dispatching this kind of packet from vehicle ${vehicle_guid.guid} when not the driver (actually, seat $index)"
|
||||
)
|
||||
case _ => ;
|
||||
}
|
||||
if (player.death_by == -1) {
|
||||
sessionLogic.kickedByAdministration()
|
||||
}
|
||||
}
|
||||
def handleChildObjectState(pkt: ChildObjectStateMessage): Unit
|
||||
|
||||
def handleChildObjectState(pkt: ChildObjectStateMessage): Unit = {
|
||||
val ChildObjectStateMessage(object_guid, pitch, yaw) = pkt
|
||||
val (o, tools) = sessionLogic.shooting.FindContainedWeapon
|
||||
//is COSM our primary upstream packet?
|
||||
(o match {
|
||||
case Some(mount: Mountable) => (o, mount.PassengerInSeat(player))
|
||||
case _ => (None, None)
|
||||
}) match {
|
||||
case (None, None) | (_, None) | (Some(_: Vehicle), Some(0)) => ;
|
||||
case _ =>
|
||||
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 {
|
||||
case None =>
|
||||
//todo: old warning; this state is problematic, but can trigger in otherwise valid instances
|
||||
//log.warn(
|
||||
// s"ChildObjectState: ${player.Name} is using a different controllable agent than entity ${object_guid.guid}"
|
||||
//)
|
||||
case Some(_) =>
|
||||
//TODO set tool orientation?
|
||||
player.Orientation = Vector3(0f, pitch, yaw)
|
||||
continent.VehicleEvents ! VehicleServiceMessage(
|
||||
continent.id,
|
||||
VehicleAction.ChildObjectState(player.GUID, object_guid, pitch, yaw)
|
||||
)
|
||||
}
|
||||
//TODO status condition of "playing getting out of vehicle to allow for late packets without warning
|
||||
if (player.death_by == -1) {
|
||||
sessionLogic.kickedByAdministration()
|
||||
}
|
||||
}
|
||||
def handleVehicleSubState(pkt: VehicleSubStateMessage): Unit
|
||||
|
||||
def handleVehicleSubState(pkt: VehicleSubStateMessage): Unit = {
|
||||
val VehicleSubStateMessage(vehicle_guid, _, pos, ang, vel, unk1, _) = pkt
|
||||
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
|
||||
sessionLogic.updateBlockMap(obj, pos)
|
||||
obj.zoneInteractions()
|
||||
continent.VehicleEvents ! VehicleServiceMessage(
|
||||
continent.id,
|
||||
VehicleAction.VehicleState(
|
||||
player.GUID,
|
||||
vehicle_guid,
|
||||
unk1,
|
||||
pos,
|
||||
ang,
|
||||
obj.Velocity,
|
||||
obj.Flying,
|
||||
0,
|
||||
0,
|
||||
15,
|
||||
unk5 = false,
|
||||
obj.Cloaked
|
||||
)
|
||||
)
|
||||
case _ => ;
|
||||
}
|
||||
}
|
||||
|
||||
def handleMountVehicle(pkt: MountVehicleMsg): Unit = {
|
||||
val MountVehicleMsg(_, mountable_guid, entry_point) = pkt
|
||||
sessionLogic.validObject(mountable_guid, decorator = "MountVehicle").collect {
|
||||
case obj: Mountable =>
|
||||
obj.Actor ! Mountable.TryMount(player, entry_point)
|
||||
case _ =>
|
||||
log.error(s"MountVehicleMsg: object ${mountable_guid.guid} not a mountable thing, ${player.Name}")
|
||||
}
|
||||
}
|
||||
|
||||
def handleDismountVehicle(pkt: DismountVehicleMsg): Unit = {
|
||||
val DismountVehicleMsg(player_guid, bailType, wasKickedByDriver) = pkt
|
||||
val dError: (String, Player)=> Unit = dismountError(bailType, wasKickedByDriver)
|
||||
//TODO optimize this later
|
||||
//common warning for this section
|
||||
if (player.GUID == player_guid) {
|
||||
//normally disembarking from a mount
|
||||
(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
|
||||
case _ => out //arrangement "may" be permissible
|
||||
}
|
||||
case out @ Some(_: Mountable) =>
|
||||
out
|
||||
case _ =>
|
||||
dError(s"DismountVehicleMsg: player ${player.Name} not considered seated in a mountable entity", player)
|
||||
None
|
||||
}) match {
|
||||
case Some(obj: Mountable) =>
|
||||
obj.PassengerInSeat(player) match {
|
||||
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
|
||||
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.
|
||||
//todo: kick cargo passengers out. To be added after PR #216 is merged
|
||||
obj match {
|
||||
case v: Vehicle
|
||||
if bailType == BailType.Bailed &&
|
||||
v.SeatPermissionGroup(seat_num).contains(AccessPermissionGroup.Driver) &&
|
||||
v.isFlying =>
|
||||
v.Actor ! Vehicle.Deconstruct(None) //immediate deconstruction
|
||||
case _ => ;
|
||||
}
|
||||
|
||||
case None =>
|
||||
dError(s"DismountVehicleMsg: can not find where player ${player.Name}_guid is seated in mountable ${player.VehicleSeated}", player)
|
||||
}
|
||||
case _ =>
|
||||
dError(s"DismountVehicleMsg: can not find mountable entity ${player.VehicleSeated}", player)
|
||||
}
|
||||
} else {
|
||||
//kicking someone else out of a mount; need to own that mount/mountable
|
||||
val dWarn: (String, Player)=> Unit = dismountWarning(bailType, wasKickedByDriver)
|
||||
player.avatar.vehicle match {
|
||||
case Some(obj_guid) =>
|
||||
(
|
||||
(
|
||||
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)
|
||||
case (mount @ Some(_: Mountable), tplayer) =>
|
||||
(mount, tplayer)
|
||||
case _ =>
|
||||
(None, None)
|
||||
}) match {
|
||||
case (Some(obj: Mountable), Some(tplayer: Player)) =>
|
||||
obj.PassengerInSeat(tplayer) match {
|
||||
case Some(seat_num) =>
|
||||
obj.Actor ! Mountable.TryDismount(tplayer, seat_num, bailType)
|
||||
case None =>
|
||||
dError(s"DismountVehicleMsg: can not find where other player ${tplayer.Name} is seated in mountable $obj_guid", tplayer)
|
||||
}
|
||||
case (None, _) =>
|
||||
dWarn(s"DismountVehicleMsg: ${player.Name} can not find his vehicle", player)
|
||||
case (_, None) =>
|
||||
dWarn(s"DismountVehicleMsg: player $player_guid could not be found to kick, ${player.Name}", player)
|
||||
case _ =>
|
||||
dWarn(s"DismountVehicleMsg: object is either not a Mountable or not a Player", player)
|
||||
}
|
||||
case None =>
|
||||
dWarn(s"DismountVehicleMsg: ${player.Name} does not own a vehicle", player)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def dismountWarning(
|
||||
bailAs: BailType.Value,
|
||||
kickedByDriver: Boolean
|
||||
)
|
||||
(
|
||||
note: String,
|
||||
player: Player
|
||||
): Unit = {
|
||||
log.warn(note)
|
||||
player.VehicleSeated = None
|
||||
sendResponse(DismountVehicleMsg(player.GUID, bailAs, kickedByDriver))
|
||||
}
|
||||
|
||||
private def dismountError(
|
||||
bailAs: BailType.Value,
|
||||
kickedByDriver: Boolean
|
||||
)
|
||||
(
|
||||
note: String,
|
||||
player: Player
|
||||
): Unit = {
|
||||
log.error(s"$note; some vehicle might not know that ${player.Name} is no longer sitting in it")
|
||||
player.VehicleSeated = None
|
||||
sendResponse(DismountVehicleMsg(player.GUID, bailAs, kickedByDriver))
|
||||
}
|
||||
|
||||
def handleMountVehicleCargo(pkt: MountVehicleCargoMsg): Unit = {
|
||||
val MountVehicleCargoMsg(_, cargo_guid, carrier_guid, _) = pkt
|
||||
(continent.GUID(cargo_guid), continent.GUID(carrier_guid)) match {
|
||||
case (Some(cargo: Vehicle), Some(carrier: Vehicle)) =>
|
||||
carrier.CargoHolds.find({ case (_, hold) => !hold.isOccupied }) match {
|
||||
case Some((mountPoint, _)) =>
|
||||
cargo.Actor ! CargoBehavior.StartCargoMounting(carrier_guid, mountPoint)
|
||||
case _ =>
|
||||
log.warn(
|
||||
s"MountVehicleCargoMsg: ${player.Name} trying to load cargo into a ${carrier.Definition.Name} which oes not have a cargo hold"
|
||||
)
|
||||
}
|
||||
case (None, _) | (Some(_), None) =>
|
||||
log.warn(
|
||||
s"MountVehicleCargoMsg: ${player.Name} lost a vehicle while working with cargo - either $carrier_guid or $cargo_guid"
|
||||
)
|
||||
case _ => ;
|
||||
}
|
||||
}
|
||||
|
||||
def handleDismountVehicleCargo(pkt: DismountVehicleCargoMsg): Unit = {
|
||||
val DismountVehicleCargoMsg(_, cargo_guid, bailed, _, kicked) = pkt
|
||||
continent.GUID(cargo_guid) match {
|
||||
case Some(cargo: Vehicle) =>
|
||||
cargo.Actor ! CargoBehavior.StartCargoDismounting(bailed || kicked)
|
||||
case _ => ;
|
||||
}
|
||||
}
|
||||
|
||||
def handleDeployRequest(pkt: DeployRequestMessage): Unit = {
|
||||
val DeployRequestMessage(_, vehicle_guid, deploy_state, _, _, _) = pkt
|
||||
val vehicle = player.avatar.vehicle
|
||||
if (vehicle.contains(vehicle_guid)) {
|
||||
if (vehicle == player.VehicleSeated) {
|
||||
continent.GUID(vehicle_guid) match {
|
||||
case Some(obj: Vehicle) =>
|
||||
log.info(s"${player.Name} is requesting a deployment change for ${obj.Definition.Name} - $deploy_state")
|
||||
obj.Actor ! Deployment.TryDeploymentChange(deploy_state)
|
||||
|
||||
case _ =>
|
||||
log.error(s"DeployRequest: ${player.Name} can not find vehicle $vehicle_guid")
|
||||
avatarActor ! AvatarActor.SetVehicle(None)
|
||||
}
|
||||
} else {
|
||||
log.warn(s"${player.Name} must be mounted to request a deployment change")
|
||||
}
|
||||
} else {
|
||||
log.warn(s"DeployRequest: ${player.Name} does not own the deploying $vehicle_guid object")
|
||||
}
|
||||
}
|
||||
def handleDeployRequest(pkt: DeployRequestMessage): Unit
|
||||
|
||||
/* messages */
|
||||
|
||||
def handleCanDeploy(obj: Deployment.DeploymentObject, state: DriveState.Value): Unit = {
|
||||
if (state == DriveState.Deploying) {
|
||||
log.trace(s"DeployRequest: $obj transitioning to deploy state")
|
||||
} else if (state == DriveState.Deployed) {
|
||||
log.trace(s"DeployRequest: $obj has been Deployed")
|
||||
} else {
|
||||
CanNotChangeDeployment(obj, state, "incorrect deploy state")
|
||||
}
|
||||
}
|
||||
def handleCanDeploy(obj: Deployment.DeploymentObject, state: DriveState.Value): Unit
|
||||
|
||||
def handleCanUndeploy(obj: Deployment.DeploymentObject, state: DriveState.Value): Unit = {
|
||||
if (state == DriveState.Undeploying) {
|
||||
log.trace(s"DeployRequest: $obj transitioning to undeploy state")
|
||||
} else if (state == DriveState.Mobile) {
|
||||
log.trace(s"DeployRequest: $obj is Mobile")
|
||||
} else {
|
||||
CanNotChangeDeployment(obj, state, "incorrect undeploy state")
|
||||
}
|
||||
}
|
||||
def handleCanUndeploy(obj: Deployment.DeploymentObject, state: DriveState.Value): Unit
|
||||
|
||||
def handleCanNotChangeDeployment(obj: Deployment.DeploymentObject, state: DriveState.Value, reason: String): Unit = {
|
||||
if (Deployment.CheckForDeployState(state) && !Deployment.AngleCheck(obj)) {
|
||||
CanNotChangeDeployment(obj, state, reason = "ground too steep")
|
||||
} else {
|
||||
CanNotChangeDeployment(obj, state, reason)
|
||||
}
|
||||
}
|
||||
def handleCanNotChangeDeployment(obj: Deployment.DeploymentObject, state: DriveState.Value, reason: String): Unit
|
||||
}
|
||||
|
||||
/* support functions */
|
||||
class VehicleOperations(
|
||||
val sessionLogic: SessionData,
|
||||
val avatarActor: typed.ActorRef[AvatarActor.Command],
|
||||
implicit val context: ActorContext
|
||||
) extends CommonSessionInterfacingFunctionality {
|
||||
private[session] var serverVehicleControlVelocity: Option[Int] = None
|
||||
|
||||
/**
|
||||
* If the player is mounted in some entity, find that entity and get the mount index number at which the player is sat.
|
||||
|
|
@ -604,34 +177,9 @@ class VehicleOperations(
|
|||
* the client's player who is receiving this packet should be mounted as its driver, but this is not explicitly tested
|
||||
* @param pkt packet to instigate cancellable control
|
||||
*/
|
||||
def TotalDriverVehicleControlWithPacket(vehicle: Vehicle, pkt: ServerVehicleOverrideMsg): Unit = {
|
||||
private def TotalDriverVehicleControlWithPacket(vehicle: Vehicle, pkt: ServerVehicleOverrideMsg): Unit = {
|
||||
serverVehicleControlVelocity = None
|
||||
vehicle.DeploymentState = DriveState.Mobile
|
||||
sendResponse(pkt)
|
||||
}
|
||||
|
||||
/**
|
||||
* Common reporting behavior when a `Deployment` object fails to properly transition between states.
|
||||
* @param obj the game object that could not
|
||||
* @param state the `DriveState` that could not be promoted
|
||||
* @param reason a string explaining why the state can not or will not change
|
||||
*/
|
||||
def CanNotChangeDeployment(
|
||||
obj: PlanetSideServerObject with Deployment,
|
||||
state: DriveState.Value,
|
||||
reason: String
|
||||
): Unit = {
|
||||
val mobileShift: String = if (obj.DeploymentState != DriveState.Mobile) {
|
||||
obj.DeploymentState = DriveState.Mobile
|
||||
sendResponse(DeployRequestMessage(player.GUID, obj.GUID, DriveState.Mobile, 0, unk3=false, Vector3.Zero))
|
||||
continent.VehicleEvents ! VehicleServiceMessage(
|
||||
continent.id,
|
||||
VehicleAction.DeployRequest(player.GUID, obj.GUID, DriveState.Mobile, 0, unk2=false, Vector3.Zero)
|
||||
)
|
||||
"; enforcing Mobile deployment state"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
log.error(s"DeployRequest: ${player.Name} can not transition $obj to $state - $reason$mobileShift")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -73,7 +73,7 @@ import net.psforever.util.{Config, DefinitionUtil}
|
|||
import net.psforever.zones.Zones
|
||||
|
||||
object ZoningOperations {
|
||||
private[support] final case class AvatarAwardMessageBundle(
|
||||
private[session] final case class AvatarAwardMessageBundle(
|
||||
bundle: Iterable[Iterable[PlanetSideGamePacket]],
|
||||
delay: Long
|
||||
)
|
||||
|
|
@ -106,24 +106,26 @@ object ZoningOperations {
|
|||
}
|
||||
|
||||
class ZoningOperations(
|
||||
val sessionLogic: SessionLogic,
|
||||
val sessionLogic: SessionData,
|
||||
avatarActor: typed.ActorRef[AvatarActor.Command],
|
||||
galaxyService: ActorRef,
|
||||
cluster: typed.ActorRef[ICS.Command],
|
||||
implicit val context: ActorContext
|
||||
) extends CommonSessionInterfacingFunctionality {
|
||||
private var zoningType: Zoning.Method = Zoning.Method.None
|
||||
private var zoningChatMessageType: ChatMessageType = ChatMessageType.CMT_QUIT
|
||||
private[support] var zoningStatus: Zoning.Status = Zoning.Status.None
|
||||
private var zoningCounter: Int = 0
|
||||
private var instantActionFallbackDestination: Option[Zoning.InstantAction.Located] = None
|
||||
private[session] var zoningStatus: Zoning.Status = Zoning.Status.None
|
||||
/** a flag for the zone having finished loading during zoning
|
||||
* `None` when no zone is loaded
|
||||
* `Some(true)` when a zone has successfully loaded
|
||||
* `Some(false)` when the loading process has failed or was executed but did not complete for some reason
|
||||
*/
|
||||
private[session] var zoneLoaded: Option[Boolean] = None
|
||||
/**
|
||||
* used during zone transfers to maintain reference to seated vehicle (which does not yet exist in the new zone)
|
||||
* used during intrazone gate transfers, but not in a way distinct from prior zone transfer procedures
|
||||
* should only be set during the transient period when moving between one spawn point and the next
|
||||
* leaving set prior to a subsequent transfers may cause unstable vehicle associations, with memory leak potential
|
||||
*/
|
||||
private[support] var interstellarFerry: Option[Vehicle] = None
|
||||
private[session] var interstellarFerry: Option[Vehicle] = None
|
||||
/**
|
||||
* used during zone transfers for cleanup to refer to the vehicle that instigated a transfer
|
||||
* "top level" is the carrier in a carrier/ferried association or a projected carrier/(ferried carrier)/ferried association
|
||||
|
|
@ -131,20 +133,18 @@ class ZoningOperations(
|
|||
* the old-zone unique identifier for the carrier
|
||||
* no harm should come from leaving the field set to an old unique identifier value after the transfer period
|
||||
*/
|
||||
private[support] var interstellarFerryTopLevelGUID: Option[PlanetSideGUID] = None
|
||||
private var loadConfZone: Boolean = false
|
||||
/** a flag for the zone having finished loading during zoning
|
||||
* `None` when no zone is loaded
|
||||
* `Some(true)` when a zone has successfully loaded
|
||||
* `Some(false)` when the loading process has failed or was executed but did not complete for some reason
|
||||
*/
|
||||
private[support] var zoneLoaded: Option[Boolean] = None
|
||||
private[session] var interstellarFerryTopLevelGUID: Option[PlanetSideGUID] = None
|
||||
/** a flag that forces the current zone to reload itself during a zoning operation */
|
||||
private[support] var zoneReload: Boolean = false
|
||||
private var zoningTimer: Cancellable = Default.Cancellable
|
||||
|
||||
private[session] var zoneReload: Boolean = false
|
||||
private[session] val spawn: SpawnOperations = new SpawnOperations()
|
||||
|
||||
private var loadConfZone: Boolean = false
|
||||
private var instantActionFallbackDestination: Option[Zoning.InstantAction.Located] = None
|
||||
private var zoningType: Zoning.Method = Zoning.Method.None
|
||||
private var zoningChatMessageType: ChatMessageType = ChatMessageType.CMT_QUIT
|
||||
private var zoningCounter: Int = 0
|
||||
private var zoningTimer: Cancellable = Default.Cancellable
|
||||
|
||||
/* packets */
|
||||
|
||||
def handleWarpgateRequest(pkt: WarpgateRequest): Unit = {
|
||||
|
|
@ -181,7 +181,7 @@ class ZoningOperations(
|
|||
}
|
||||
}
|
||||
|
||||
def handleDroppodLaunchRequest(pkt: DroppodLaunchRequestMessage)(implicit context: ActorContext): Unit = {
|
||||
def handleDroppodLaunchRequest(pkt: DroppodLaunchRequestMessage): Unit = {
|
||||
val DroppodLaunchRequestMessage(info, _) = pkt
|
||||
cluster ! ICS.DroppodLaunchRequest(
|
||||
info.zone_number,
|
||||
|
|
@ -1752,15 +1752,15 @@ class ZoningOperations(
|
|||
/* nested class - spawn operations */
|
||||
|
||||
class SpawnOperations() {
|
||||
private[support] var deadState: DeadState.Value = DeadState.Dead
|
||||
private[support] var loginChatMessage: mutable.ListBuffer[String] = new mutable.ListBuffer[String]()
|
||||
private[support] var amsSpawnPoints: List[SpawnPoint] = Nil
|
||||
private[support] var noSpawnPointHere: Boolean = false
|
||||
private[support] var setupAvatarFunc: () => Unit = AvatarCreate
|
||||
private[support] var setCurrentAvatarFunc: Player => Unit = SetCurrentAvatarNormally
|
||||
private[support] var nextSpawnPoint: Option[SpawnPoint] = None
|
||||
private[support] var interimUngunnedVehicle: Option[PlanetSideGUID] = None
|
||||
private[support] var interimUngunnedVehicleSeat: Option[Int] = None
|
||||
private[session] var deadState: DeadState.Value = DeadState.Dead
|
||||
private[session] var loginChatMessage: mutable.ListBuffer[String] = new mutable.ListBuffer[String]()
|
||||
private[session] var amsSpawnPoints: List[SpawnPoint] = Nil
|
||||
private[session] var noSpawnPointHere: Boolean = false
|
||||
private[session] var setupAvatarFunc: () => Unit = AvatarCreate
|
||||
private[session] var setCurrentAvatarFunc: Player => Unit = SetCurrentAvatarNormally
|
||||
private[session] var nextSpawnPoint: Option[SpawnPoint] = None
|
||||
private[session] var interimUngunnedVehicle: Option[PlanetSideGUID] = None
|
||||
private[session] var interimUngunnedVehicleSeat: Option[Int] = None
|
||||
/** Upstream message counter<br>
|
||||
* Checks for server acknowledgement of the following messages in the following conditions:<br>
|
||||
* `PlayerStateMessageUpstream` (infantry)<br>
|
||||
|
|
@ -1769,14 +1769,14 @@ class ZoningOperations(
|
|||
* `KeepAliveMessage` (any passenger mount that is not the driver)<br>
|
||||
* As they should arrive roughly every 250 milliseconds this allows for a very crude method of scheduling tasks up to four times per second
|
||||
*/
|
||||
private[support] var upstreamMessageCount: Int = 0
|
||||
private[support] var shiftPosition: Option[Vector3] = None
|
||||
private[support] var shiftOrientation: Option[Vector3] = None
|
||||
private[support] var drawDeloyableIcon: PlanetSideGameObject with Deployable => Unit = RedrawDeployableIcons
|
||||
private[support] var populateAvatarAwardRibbonsFunc: (Int, Long) => Unit = setupAvatarAwardMessageDelivery
|
||||
private[support] var setAvatar: Boolean = false
|
||||
private[support] var reviveTimer: Cancellable = Default.Cancellable
|
||||
private[support] var respawnTimer: Cancellable = Default.Cancellable
|
||||
private[session] var upstreamMessageCount: Int = 0
|
||||
private[session] var shiftPosition: Option[Vector3] = None
|
||||
private[session] var shiftOrientation: Option[Vector3] = None
|
||||
private[session] var drawDeloyableIcon: PlanetSideGameObject with Deployable => Unit = RedrawDeployableIcons
|
||||
private[session] var populateAvatarAwardRibbonsFunc: (Int, Long) => Unit = setupAvatarAwardMessageDelivery
|
||||
private[session] var setAvatar: Boolean = false
|
||||
private[session] var reviveTimer: Cancellable = Default.Cancellable
|
||||
private[session] var respawnTimer: Cancellable = Default.Cancellable
|
||||
|
||||
private var statisticsPacketFunc: () => Unit = loginAvatarStatisticsFields
|
||||
|
||||
|
|
@ -1790,7 +1790,7 @@ class ZoningOperations(
|
|||
HandleReleaseAvatar(player, continent)
|
||||
}
|
||||
|
||||
def handleSpawnRequest(pkt: SpawnRequestMessage)(implicit context: ActorContext): Unit = {
|
||||
def handleSpawnRequest(pkt: SpawnRequestMessage): Unit = {
|
||||
val SpawnRequestMessage(_, spawnGroup, _, _, zoneNumber) = pkt
|
||||
log.info(s"${player.Name} on ${continent.id} wants to respawn in zone #$zoneNumber")
|
||||
if (deadState != DeadState.RespawnTime) {
|
||||
|
|
@ -3567,6 +3567,17 @@ class ZoningOperations(
|
|||
nextSpawnPoint = None
|
||||
}
|
||||
}
|
||||
|
||||
def randomRespawn(time: FiniteDuration = 300.seconds): Unit = {
|
||||
reviveTimer = context.system.scheduler.scheduleOnce(time) {
|
||||
cluster ! ICS.GetRandomSpawnPoint(
|
||||
Zones.sanctuaryZoneNumber(player.Faction),
|
||||
player.Faction,
|
||||
Seq(SpawnGroup.Sanctuary),
|
||||
context.self
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override protected[session] def stop(): Unit = {
|
||||
|
|
|
|||
Loading…
Reference in a new issue