too many changes and refactors, but the code compiles properly; need to push these to the repo and sort out issues with it all later; hopefully, nothing has changed except for the csr stuff

This commit is contained in:
Fate-JH 2024-10-11 01:48:24 -04:00
parent cce9506e7f
commit b701fe824b
44 changed files with 2601 additions and 5941 deletions

View file

@ -261,10 +261,6 @@ class AvatarHandlerLogic(val ops: SessionAvatarHandlers, implicit val context: A
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))

View file

@ -2,8 +2,11 @@
package net.psforever.actors.session.csr
import akka.actor.ActorContext
import net.psforever.actors.session.SessionActor
import net.psforever.actors.session.normal.NormalMode
import net.psforever.actors.session.support.{ChatFunctions, ChatOperations, SessionData}
import net.psforever.objects.Session
import net.psforever.objects.avatar.ModePermissions
import net.psforever.packet.game.{ChatMsg, SetChatFilterMessage}
import net.psforever.services.chat.DefaultChannel
import net.psforever.types.ChatMessageType
@ -30,10 +33,9 @@ class ChatLogic(val ops: ChatOperations, implicit val context: ActorContext) ext
case (CMT_FLY, recipient, contents) =>
ops.commandFly(contents, recipient)
case (CMT_ANONYMOUS, _, _) =>
// ?
case (CMT_ANONYMOUS, _, _) => ()
case (CMT_TOGGLE_GM, _, contents)=>
case (CMT_TOGGLE_GM, _, contents) =>
customCommandModerator(contents)
case (CMT_CULLWATERMARK, _, contents) =>
@ -196,7 +198,6 @@ class ChatLogic(val ops: ChatOperations, implicit val context: ActorContext) ext
case "ntu" => ops.customCommandNtu(session, params)
case "zonerotate" => ops.customCommandZonerotate(params)
case "nearby" => ops.customCommandNearby(session)
case "csr" | "gm" | "op" => customCommandModerator(params.headOption.getOrElse(""))
case _ =>
// command was not handled
sendResponse(
@ -216,22 +217,28 @@ class ChatLogic(val ops: ChatOperations, implicit val context: ActorContext) ext
}
def commandToggleSpectatorMode(contents: String): Unit = {
// val currentSpectatorActivation = (if (avatar != null) avatar.permissions else ModePermissions()).canSpectate
// contents.toLowerCase() match {
// case "on" | "o" | "" if !currentSpectatorActivation =>
// context.self ! SessionActor.SetMode(SessionSpectatorMode)
// case "off" | "of" if currentSpectatorActivation =>
// context.self ! SessionActor.SetMode(SessionCustomerServiceRepresentativeMode)
// case _ => ()
// }
val currentSpectatorActivation = (if (avatar != null) avatar.permissions else ModePermissions()).canSpectate
contents.toLowerCase() match {
case "on" | "o" | "" if currentSpectatorActivation && player.spectator =>
context.self ! SessionActor.SetMode(SpectateAsCustomerServiceRepresentativeMode)
case "off" | "of" if currentSpectatorActivation && !player.spectator =>
context.self ! SessionActor.SetMode(CustomerServiceRepresentativeMode)
case _ => ()
}
}
def customCommandModerator(contents : String): Boolean = {
if (player.spectator) {
sendResponse(ChatMsg(ChatMessageType.UNK_225, "CSR SPECTATOR MODE"))
sendResponse(ChatMsg(ChatMessageType.UNK_227, "Disable spectator mode first."))
if (sessionLogic.zoning.maintainInitialGmState) {
sessionLogic.zoning.maintainInitialGmState = false
} else {
ops.customCommandModerator(contents)
contents.toLowerCase() match {
case "off" | "of" if player.spectator =>
context.self ! SessionActor.SetMode(CustomerServiceRepresentativeMode)
context.self ! SessionActor.SetMode(NormalMode)
case "off" | "of" =>
context.self ! SessionActor.SetMode(NormalMode)
case _ => ()
}
}
true
}

View file

@ -1,7 +1,7 @@
// Copyright (c) 2024 PSForever
package net.psforever.actors.session.csr
import net.psforever.actors.session.support.{ChatFunctions, GeneralFunctions, LocalHandlerFunctions, ModeLogic, MountHandlerFunctions, PlayerMode, SessionData, SquadHandlerFunctions, TerminalHandlerFunctions, VehicleFunctions, VehicleHandlerFunctions, WeaponAndProjectileFunctions}
import net.psforever.actors.session.support.{ChatFunctions, GalaxyHandlerFunctions, GeneralFunctions, LocalHandlerFunctions, ModeLogic, MountHandlerFunctions, PlayerMode, SessionData, SquadHandlerFunctions, TerminalHandlerFunctions, VehicleFunctions, VehicleHandlerFunctions, WeaponAndProjectileFunctions}
import net.psforever.actors.zone.ZoneActor
import net.psforever.objects.Session
import net.psforever.packet.PlanetSidePacket
@ -11,15 +11,15 @@ import net.psforever.types.ChatMessageType
class CustomerServiceRepresentativeMode(data: SessionData) extends ModeLogic {
val avatarResponse: AvatarHandlerLogic = AvatarHandlerLogic(data.avatarResponse)
val chat: ChatFunctions = ChatLogic(data.chat)
val galaxy: GalaxyHandlerLogic = GalaxyHandlerLogic(data.galaxyResponseHandlers)
val galaxy: GalaxyHandlerFunctions = net.psforever.actors.session.normal.GalaxyHandlerLogic(data.galaxyResponseHandlers)
val general: GeneralFunctions = GeneralLogic(data.general)
val local: LocalHandlerFunctions = LocalHandlerLogic(data.localResponse)
val local: LocalHandlerFunctions = net.psforever.actors.session.normal.LocalHandlerLogic(data.localResponse)
val mountResponse: MountHandlerFunctions = MountHandlerLogic(data.mountResponse)
val shooting: WeaponAndProjectileFunctions = WeaponAndProjectileLogic(data.shooting)
val squad: SquadHandlerFunctions = SquadHandlerLogic(data.squad)
val terminals: TerminalHandlerFunctions = TerminalHandlerLogic(data.terminals)
val vehicles: VehicleFunctions = VehicleLogic(data.vehicles)
val vehicleResponse: VehicleHandlerFunctions = VehicleHandlerLogic(data.vehicleResponseOperations)
val vehicleResponse: VehicleHandlerFunctions = net.psforever.actors.session.normal.VehicleHandlerLogic(data.vehicleResponseOperations)
override def switchTo(session: Session): Unit = {
val player = session.player

View file

@ -1,86 +0,0 @@
// Copyright (c) 2024 PSForever
package net.psforever.actors.session.csr
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}
object GalaxyHandlerLogic {
def apply(ops: SessionGalaxyHandlers): GalaxyHandlerLogic = {
new GalaxyHandlerLogic(ops, ops.context)
}
}
class GalaxyHandlerLogic(val ops: SessionGalaxyHandlers, implicit val context: ActorContext) extends GalaxyHandlerFunctions {
def sessionLogic: SessionData = ops.sessionLogic
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 pop = zone.LivePlayers.distinctBy(_.CharId)
val popTR = pop.count(_.Faction == PlanetSideEmpire.TR)
val popNC = pop.count(_.Faction == PlanetSideEmpire.NC)
val popVS = pop.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 _ => ()
}
}
}

View file

@ -1,268 +0,0 @@
// Copyright (c) 2024 PSForever
package net.psforever.actors.session.csr
import akka.actor.ActorContext
import net.psforever.actors.session.support.{LocalHandlerFunctions, SessionData, SessionLocalHandlers}
import net.psforever.objects.ce.Deployable
import net.psforever.objects.serverobject.doors.Door
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, HackState1, 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}
object LocalHandlerLogic {
def apply(ops: SessionLocalHandlers): LocalHandlerLogic = {
new LocalHandlerLogic(ops, ops.context)
}
}
class LocalHandlerLogic(val ops: SessionLocalHandlers, implicit val context: ActorContext) extends LocalHandlerFunctions {
def sessionLogic: SessionData = ops.sessionLogic
/* messages */
def handleTurretDeployableIsDismissed(obj: TurretDeployable): Unit = {
ops.handleTurretDeployableIsDismissed(obj)
}
def handleDeployableIsDismissed(obj: Deployable): Unit = {
ops.handleDeployableIsDismissed(obj)
}
/* 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 =>
val pos = player.Position.xy
val range = ops.doorLoadRange()
val foundDoor = continent
.blockMap
.sector(pos, range)
.amenityList
.collect { case door: Door => door }
.find(_.GUID == doorGuid)
val doorExistsInRange: Boolean = foundDoor.nonEmpty
if (doorExistsInRange) {
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
ops.deactivateTelpadDeployableMessages(dguid)
//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
ops.deactivateTelpadDeployableMessages(dguid)
//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(HackState1.Unk0, targetGuid, guid, progress=0, unk1.toFloat, 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))
}
}

View file

@ -1,27 +1,24 @@
// Copyright (c) 2024 PSForever
package net.psforever.actors.session.csr
import akka.actor.{ActorContext, typed}
import net.psforever.actors.session.AvatarActor
import akka.actor.ActorContext
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.structures.WarpGate
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.vehicles.AccessPermissionGroup
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.packet.game.{ChatMsg, DelayedPathMountMsg, DismountVehicleCargoMsg, DismountVehicleMsg, GenericObjectActionMessage, MountVehicleCargoMsg, MountVehicleMsg, 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._
import net.psforever.types.{BailType, ChatMessageType, DriveState, PlanetSideGUID, Vector3}
object MountHandlerLogic {
def apply(ops: SessionMountHandlers): MountHandlerLogic = {
@ -32,128 +29,28 @@ object MountHandlerLogic {
class MountHandlerLogic(val ops: SessionMountHandlers, implicit val context: ActorContext) extends MountHandlerFunctions {
def sessionLogic: SessionData = ops.sessionLogic
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}")
//can only mount vehicle when not in csr spectator mode
if (!player.spectator) {
ops.handleMountVehicle(pkt)
}
}
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)
}
}
//can't do this if we're not in vehicle, so also not csr spectator
ops.handleDismountVehicle(pkt.copy(bailType = BailType.Bailed))
}
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 _ => ()
}
//can't do this if we're not in vehicle, so also not csr spectator
ops.handleMountVehicleCargo(pkt)
}
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 _ => ()
}
//can't do this if we're not in vehicle, so also not csr spectator
ops.handleDismountVehicleCargo(pkt.copy(bailed = true))
}
/* response handlers */
@ -167,24 +64,21 @@ class MountHandlerLogic(val ops: SessionMountHandlers, implicit val context: Act
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.zoning.CancelZoningProcess()
sessionLogic.terminals.CancelAllProximityUnits()
MountingAction(tplayer, obj, seatNumber)
ops.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.zoning.CancelZoningProcess()
sessionLogic.terminals.CancelAllProximityUnits()
MountingAction(tplayer, obj, seatNumber)
ops.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}")
sessionLogic.zoning.CancelZoningProcess()
val obj_guid: PlanetSideGUID = obj.GUID
sessionLogic.terminals.CancelAllProximityUnits()
sendResponse(PlanetsideAttributeMessage(obj_guid, attribute_type=0, obj.Health))
@ -193,12 +87,11 @@ class MountHandlerLogic(val ops: SessionMountHandlers, implicit val context: Act
sendResponse(GenericObjectActionMessage(obj_guid, code=11))
sessionLogic.general.accessContainer(obj)
tplayer.Actor ! ResetAllEnvironmentInteractions
MountingAction(tplayer, obj, seatNumber)
ops.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}")
sessionLogic.zoning.CancelZoningProcess()
val obj_guid: PlanetSideGUID = obj.GUID
sessionLogic.terminals.CancelAllProximityUnits()
sendResponse(PlanetsideAttributeMessage(obj_guid, attribute_type=0, obj.Health))
@ -209,12 +102,11 @@ class MountHandlerLogic(val ops: SessionMountHandlers, implicit val context: Act
sendResponse(GenericObjectActionMessage(obj_guid, code=11))
sessionLogic.general.accessContainer(obj)
tplayer.Actor ! ResetAllEnvironmentInteractions
MountingAction(tplayer, obj, seatNumber)
ops.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}")
sessionLogic.zoning.CancelZoningProcess()
val obj_guid: PlanetSideGUID = obj.GUID
sessionLogic.terminals.CancelAllProximityUnits()
sendResponse(PlanetsideAttributeMessage(obj_guid, attribute_type=0, obj.Health))
@ -224,12 +116,11 @@ class MountHandlerLogic(val ops: SessionMountHandlers, implicit val context: Act
sessionLogic.general.accessContainer(obj)
ops.updateWeaponAtSeatPosition(obj, seatNumber)
tplayer.Actor ! ResetAllEnvironmentInteractions
MountingAction(tplayer, obj, seatNumber)
ops.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}")
sessionLogic.zoning.CancelZoningProcess()
val obj_guid: PlanetSideGUID = obj.GUID
sessionLogic.terminals.CancelAllProximityUnits()
sendResponse(PlanetsideAttributeMessage(obj_guid, attribute_type=0, obj.Health))
@ -238,17 +129,11 @@ class MountHandlerLogic(val ops: SessionMountHandlers, implicit val context: Act
sessionLogic.general.accessContainer(obj)
ops.updateWeaponAtSeatPosition(obj, seatNumber)
tplayer.Actor ! ResetAllEnvironmentInteractions
MountingAction(tplayer, obj, seatNumber)
ops.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}")
sessionLogic.zoning.CancelZoningProcess()
val obj_guid: PlanetSideGUID = obj.GUID
sessionLogic.terminals.CancelAllProximityUnits()
sendResponse(PlanetsideAttributeMessage(obj_guid, attribute_type=0, obj.Health))
@ -258,16 +143,10 @@ class MountHandlerLogic(val ops: SessionMountHandlers, implicit val context: Act
ops.updateWeaponAtSeatPosition(obj, seatNumber)
sessionLogic.keepAliveFunc = sessionLogic.keepAlivePersistence
tplayer.Actor ! ResetAllEnvironmentInteractions
MountingAction(tplayer, obj, seatNumber)
ops.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}")
sessionLogic.zoning.CancelZoningProcess()
val obj_guid: PlanetSideGUID = obj.GUID
sessionLogic.terminals.CancelAllProximityUnits()
sendResponse(PlanetsideAttributeMessage(obj_guid, attribute_type=0, obj.Health))
@ -276,51 +155,46 @@ class MountHandlerLogic(val ops: SessionMountHandlers, implicit val context: Act
ops.updateWeaponAtSeatPosition(obj, seatNumber)
sessionLogic.keepAliveFunc = sessionLogic.keepAlivePersistence
tplayer.Actor ! ResetAllEnvironmentInteractions
MountingAction(tplayer, obj, seatNumber)
ops.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}")
sessionLogic.zoning.CancelZoningProcess()
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)
ops.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}")
sessionLogic.zoning.CancelZoningProcess()
sendResponse(PlanetsideAttributeMessage(obj.GUID, attribute_type=0, obj.Health))
ops.updateWeaponAtSeatPosition(obj, seatNumber)
MountingAction(tplayer, obj, seatNumber)
ops.MountingAction(tplayer, obj, seatNumber)
case Mountable.CanMount(obj: FacilityTurret, _, _) =>
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_mount")
sessionLogic.zoning.CancelZoningProcess()
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}")
sessionLogic.zoning.CancelZoningProcess()
sendResponse(PlanetsideAttributeMessage(obj.GUID, attribute_type=0, obj.Health))
ops.updateWeaponAtSeatPosition(obj, seatNumber)
MountingAction(tplayer, obj, seatNumber)
ops.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)
ops.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
@ -336,8 +210,7 @@ class MountHandlerLogic(val ops: SessionMountHandlers, implicit val context: Act
//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)
ops.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(
@ -363,24 +236,24 @@ class MountHandlerLogic(val ops: SessionMountHandlers, implicit val context: Act
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)
ops.DismountAction(tplayer, obj, seatNum)
obj.Actor ! Vehicle.Deconstruct()
case Mountable.CanDismount(obj: Vehicle, seatNum, _)
if tplayer.GUID == player.GUID &&
obj.isFlying &&
obj.SeatPermissionGroup(seatNum).contains(AccessPermissionGroup.Driver) =>
// 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
ops.DismountVehicleAction(tplayer, obj, seatNum)
obj.Actor ! Vehicle.Deconstruct(None) //immediate deconstruction
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)
ops.DismountVehicleAction(tplayer, obj, seatNum)
case Mountable.CanDismount(obj: Vehicle, seat_num, _) =>
continent.VehicleEvents ! VehicleServiceMessage(
@ -389,8 +262,7 @@ class MountHandlerLogic(val ops: SessionMountHandlers, implicit val context: Act
)
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)
ops.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}")
@ -407,114 +279,52 @@ class MountHandlerLogic(val ops: SessionMountHandlers, implicit val context: Act
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) =>
case Mountable.CanNotDismount(obj: Vehicle, _, BailType.Normal)
if obj.DeploymentState == DriveState.AutoPilot =>
if (!player.spectator) {
sendResponse(ChatMsg(ChatMessageType.UNK_224, "@SA_CannotDismountAtThisTime"))
}
case Mountable.CanNotDismount(obj: Vehicle, _, BailType.Bailed)
if obj.Definition == GlobalDefinitions.droppod =>
if (!player.spectator) {
sendResponse(ChatMsg(ChatMessageType.UNK_224, "@CannotBailFromDroppod"))
}
case Mountable.CanNotDismount(obj: Vehicle, _, BailType.Bailed)
if obj.DeploymentState == DriveState.AutoPilot =>
if (!player.spectator) {
sendResponse(ChatMsg(ChatMessageType.UNK_224, "@SA_CannotBailAtThisTime"))
}
case Mountable.CanNotDismount(obj: Vehicle, _, BailType.Bailed)
if {
continent
.blockMap
.sector(obj)
.buildingList
.exists {
case wg: WarpGate =>
Vector3.DistanceSquared(obj.Position, wg.Position) < math.pow(wg.Definition.SOIRadius, 2)
case _ =>
false
}
} =>
if (!player.spectator) {
sendResponse(ChatMsg(ChatMessageType.UNK_227, "@Vehicle_CannotBailInWarpgateEnvelope"))
}
case Mountable.CanNotDismount(obj: Vehicle, _, _)
if obj.isMoving(test = 1f) =>
ops.handleDismountVehicle(DismountVehicleMsg(player.GUID, BailType.Bailed, wasKickedByDriver=true))
if (!player.spectator) {
sendResponse(ChatMsg(ChatMessageType.UNK_224, "@TooFastToDismount"))
}
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
*/
private 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
*/
private 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
*/
private 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)
)
}
}

View file

@ -6,29 +6,26 @@ import net.psforever.actors.zone.ZoneActor
import net.psforever.objects.serverobject.ServerObject
import net.psforever.objects.{Session, Vehicle}
import net.psforever.packet.PlanetSidePacket
import net.psforever.packet.game.ObjectDeleteMessage
import net.psforever.packet.game.objectcreate.ObjectCreateMessageParent
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
import net.psforever.services.chat.SpectatorChannel
import net.psforever.services.teamwork.{SquadAction, SquadServiceMessage}
import net.psforever.types.{CapacitorStateType, ChatMessageType, SquadRequestType}
import net.psforever.types.{ChatMessageType, SquadRequestType}
//
import net.psforever.actors.session.support.{ModeLogic, PlayerMode, SessionData}
import net.psforever.objects.serverobject.terminals.{ProximityUnit, Terminal}
import net.psforever.packet.game.{ChatMsg, UnuseItemMessage}
import net.psforever.packet.game.ChatMsg
class SpectatorCSRModeLogic(data: SessionData) extends ModeLogic {
val avatarResponse: AvatarHandlerFunctions = AvatarHandlerLogic(data.avatarResponse)
val chat: ChatFunctions = ChatLogic(data.chat)
val galaxy: GalaxyHandlerFunctions = GalaxyHandlerLogic(data.galaxyResponseHandlers)
val galaxy: GalaxyHandlerFunctions = net.psforever.actors.session.normal.GalaxyHandlerLogic(data.galaxyResponseHandlers)
val general: GeneralFunctions = GeneralLogic(data.general)
val local: LocalHandlerFunctions = LocalHandlerLogic(data.localResponse)
val local: LocalHandlerFunctions = net.psforever.actors.session.normal.LocalHandlerLogic(data.localResponse)
val mountResponse: MountHandlerFunctions = MountHandlerLogic(data.mountResponse)
val shooting: WeaponAndProjectileFunctions = WeaponAndProjectileLogic(data.shooting)
val squad: SquadHandlerFunctions = SquadHandlerLogic(data.squad)
val terminals: TerminalHandlerFunctions = TerminalHandlerLogic(data.terminals)
val vehicles: VehicleFunctions = VehicleLogic(data.vehicles)
val vehicleResponse: VehicleHandlerFunctions = VehicleHandlerLogic(data.vehicleResponseOperations)
val vehicleResponse: VehicleHandlerFunctions = net.psforever.actors.session.normal.VehicleHandlerLogic(data.vehicleResponseOperations)
override def switchTo(session: Session): Unit = {
val player = session.player
@ -37,53 +34,17 @@ class SpectatorCSRModeLogic(data: SessionData) extends ModeLogic {
val sendResponse: PlanetSidePacket=>Unit = data.sendResponse
//
continent.actor ! ZoneActor.RemoveFromBlockMap(player)
continent
.GUID(data.terminals.usingMedicalTerminal)
.foreach { case term: Terminal with ProximityUnit =>
data.terminals.StopUsingProximityUnit(term)
}
data.general.accessedContainer
.collect {
case veh: Vehicle if player.VehicleSeated.isEmpty || player.VehicleSeated.get != veh.GUID =>
sendResponse(UnuseItemMessage(pguid, veh.GUID))
sendResponse(UnuseItemMessage(pguid, pguid))
data.general.unaccessContainer(veh)
case container => //just in case
if (player.VehicleSeated.isEmpty || player.VehicleSeated.get != container.GUID) {
// Ensure we don't close the container if the player is seated in it
// If the container is a corpse and gets removed just as this runs it can cause a client disconnect, so we'll check the container has a GUID first.
if (container.HasGUID) {
sendResponse(UnuseItemMessage(pguid, container.GUID))
}
sendResponse(UnuseItemMessage(pguid, pguid))
data.general.unaccessContainer(container)
}
}
player.CapacitorState = CapacitorStateType.Idle
player.Capacitor = 0f
player.Inventory.Items
.foreach { entry => sendResponse(ObjectDeleteMessage(entry.GUID, 0)) }
sendResponse(ObjectDeleteMessage(player.avatar.locker.GUID, 0))
continent.AvatarEvents ! AvatarServiceMessage(continent.id, AvatarAction.ObjectDelete(pguid, pguid))
player.Holsters()
.collect { case slot if slot.Equipment.nonEmpty => sendResponse(ObjectDeleteMessage(slot.Equipment.get.GUID, 0)) }
data.vehicles.GetMountableAndSeat(None, player, continent) match {
case (Some(obj: Vehicle), Some(seatNum)) if seatNum == 0 =>
data.vehicles.ServerVehicleOverrideStop(obj)
obj.Actor ! ServerObject.AttributeMsg(10, 3) //faction-accessible driver seat
obj.Seat(seatNum).foreach(_.unmount(player))
player.VehicleSeated = None
Some(ObjectCreateMessageParent(obj.GUID, seatNum))
case (Some(obj), Some(seatNum)) =>
obj.Seat(seatNum).foreach(_.unmount(player))
player.VehicleSeated = None
Some(ObjectCreateMessageParent(obj.GUID, seatNum))
case _ => ()
}
data.general.dropSpecialSlotItem()
data.general.toggleMaxSpecialState(enable = false)
data.terminals.CancelAllProximityUnits()
data.terminals.lastTerminalOrderFulfillment = true
data.squadService ! SquadServiceMessage(
player,
continent,
@ -94,22 +55,30 @@ class SpectatorCSRModeLogic(data: SessionData) extends ModeLogic {
}
//
player.spectator = true
player.bops = true
continent.AvatarEvents ! AvatarServiceMessage(continent.id, AvatarAction.ObjectDelete(pguid, pguid))
data.chat.JoinChannel(SpectatorChannel)
sendResponse(ChatMsg(ChatMessageType.CMT_TOGGLESPECTATORMODE, "on"))
sendResponse(ChatMsg(ChatMessageType.UNK_225, "CSR SPECTATOR MODE"))
data.session = session.copy(player = player)
sendResponse(ChatMsg(ChatMessageType.UNK_225, "CSR SPECTATOR MODE ON"))
}
override def switchFrom(session: Session): Unit = {
val player = data.player
val pguid = player.GUID
val continent = data.continent
val avatarId = player.Definition.ObjectId
val sendResponse: PlanetSidePacket => Unit = data.sendResponse
//
data.continent.actor ! ZoneActor.AddToBlockMap(player, player.Position)
data.general.stop()
data.chat.LeaveChannel(SpectatorChannel)
player.spectator = false
player.bops = false
continent.AvatarEvents ! AvatarServiceMessage(
continent.id,
AvatarAction.LoadPlayer(pguid, avatarId, pguid, player.Definition.Packet.ConstructorData(player).get, None)
)
data.continent.actor ! ZoneActor.AddToBlockMap(player, player.Position)
sendResponse(ChatMsg(ChatMessageType.CMT_TOGGLESPECTATORMODE, "off"))
sendResponse(ChatMsg(ChatMessageType.UNK_227, "@SpectatorDisabled"))
sendResponse(ChatMsg(ChatMessageType.UNK_225, "CSR SPECTATOR MODE OFF"))
}
}

View file

@ -25,34 +25,38 @@ class SquadHandlerLogic(val ops: SessionSquadHandlers, implicit val context: Act
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))
if (!player.spectator) {
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)
)
if (!player.spectator) {
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))
if (!player.spectator) {
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))
}
}
}

View file

@ -5,14 +5,16 @@ 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.definition.VehicleDefinition
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.serverobject.terminals.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.packet.game.{FavoritesRequest, ItemTransactionMessage, ItemTransactionResultMessage, ProximityTerminalUseMessage, UnuseItemMessage}
import net.psforever.types.{TransactionType, Vector3}
import net.psforever.util.DefinitionUtil
object TerminalHandlerLogic {
def apply(ops: SessionTerminalHandlers): TerminalHandlerLogic = {
@ -26,46 +28,23 @@ class TerminalHandlerLogic(val ops: SessionTerminalHandlers, implicit val contex
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}")
val ItemTransactionMessage(_, transactionType, _, itemName, _, _) = pkt
DefinitionUtil.fromString(itemName) match {
case _: VehicleDefinition if transactionType == TransactionType.Buy && player.spectator =>
() //can not buy vehicle as csr spectator
case _ =>
log.error(s"ItemTransaction: entity with guid=${terminalGuid.guid} does not exist, ${player.Name}")
sessionLogic.zoning.CancelZoningProcess()
ops.handleItemTransaction(pkt)
}
}
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}")
}
ops.handleProximityTerminalUse(pkt)
}
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")
}
sessionLogic.zoning.CancelZoningProcess()
ops.handleFavoritesRequest(pkt)
}
/**
@ -112,54 +91,12 @@ class TerminalHandlerLogic(val ops: SessionTerminalHandlers, implicit val contex
ops.lastTerminalOrderFulfillment = true
case Terminal.BuyVehicle(vehicle, _, _)
if tplayer.avatar.purchaseCooldown(vehicle.Definition).nonEmpty =>
if tplayer.avatar.purchaseCooldown(vehicle.Definition).nonEmpty || player.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.buyVehicle(msg.terminal_guid, msg.transaction_type, vehicle, weapons, trunk)
ops.lastTerminalOrderFulfillment = true
case Terminal.NoDeal() if msg != null =>

View file

@ -1,399 +0,0 @@
// Copyright (c) 2024 PSForever
package net.psforever.actors.session.csr
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}
object VehicleHandlerLogic {
def apply(ops: SessionVehicleHandlers): VehicleHandlerLogic = {
new VehicleHandlerLogic(ops, ops.context)
}
}
class VehicleHandlerLogic(val ops: SessionVehicleHandlers, implicit val context: ActorContext) extends VehicleHandlerFunctions {
def sessionLogic: SessionData = ops.sessionLogic
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)
}
}
}

View file

@ -10,7 +10,8 @@ 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.packet.game.{ChildObjectStateMessage, DeployRequestMessage, FrameVehicleStateMessage, PlanetsideAttributeMessage, VehicleStateMessage, VehicleSubStateMessage}
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
import net.psforever.types.{DriveState, Vector3}
@ -46,6 +47,7 @@ class VehicleLogic(val ops: VehicleOperations, implicit val context: ActorContex
//we're driving the vehicle
sessionLogic.persist()
sessionLogic.turnCounterFunc(player.GUID)
topOffHealth(obj)
sessionLogic.general.fallHeightTracker(pos.z)
if (obj.MountedIn.isEmpty) {
sessionLogic.updateBlockMap(obj, pos)
@ -129,6 +131,7 @@ class VehicleLogic(val ops: VehicleOperations, implicit val context: ActorContex
//we're driving the vehicle
sessionLogic.persist()
sessionLogic.turnCounterFunc(player.GUID)
topOffHealth(obj)
val (position, angle, velocity, notMountedState) = continent.GUID(obj.MountedIn) match {
case Some(v: Vehicle) =>
sessionLogic.updateBlockMap(obj, pos)
@ -217,6 +220,7 @@ class VehicleLogic(val ops: VehicleOperations, implicit val context: ActorContex
case _ =>
sessionLogic.persist()
sessionLogic.turnCounterFunc(player.GUID)
topOffHealthOfPlayer()
}
//the majority of the following check retrieves information to determine if we are in control of the child
tools.find { _.GUID == object_guid } match {
@ -275,15 +279,7 @@ class VehicleLogic(val ops: VehicleOperations, implicit val context: ActorContex
continent.GUID(vehicle_guid)
.collect {
case obj: Vehicle =>
val vehicle = player.avatar.vehicle
if (!vehicle.contains(vehicle_guid)) {
log.warn(s"DeployRequest: ${player.Name} does not own the would-be-deploying ${obj.Definition.Name}")
} else if (vehicle != player.VehicleSeated) {
log.warn(s"${player.Name} must be mounted as the driver to request a deployment change")
} else {
log.info(s"${player.Name} is requesting a deployment change for ${obj.Definition.Name} - $deploy_state")
continent.Transport ! Zone.Vehicle.TryDeploymentChange(obj, deploy_state)
}
continent.Transport ! Zone.Vehicle.TryDeploymentChange(obj, deploy_state)
obj
case obj =>
log.error(s"DeployRequest: ${player.Name} expected a vehicle, but found a ${obj.Definition.Name} instead")
@ -299,31 +295,19 @@ class VehicleLogic(val ops: VehicleOperations, implicit val context: ActorContex
/* 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 {
if (!Deployment.CheckForDeployState(state)) {
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 {
if (!Deployment.CheckForUndeployState(state)) {
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)
}
CanNotChangeDeployment(obj, state, reason)
}
/* support functions */
@ -339,17 +323,35 @@ class VehicleLogic(val ops: VehicleOperations, implicit val context: ActorContex
state: DriveState.Value,
reason: String
): Unit = {
val mobileShift: String = if (obj.DeploymentState != DriveState.Mobile) {
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")
}
private def topOffHealthOfPlayer(): Unit = {
//driver below half health, full heal
val maxHealthOfPlayer = player.MaxHealth.toLong
if (player.Health < maxHealthOfPlayer * 0.5f) {
player.Health = maxHealthOfPlayer.toInt
player.LogActivity(player.ClearHistory().head)
sendResponse(PlanetsideAttributeMessage(player.GUID, 0, maxHealthOfPlayer))
continent.AvatarEvents ! AvatarServiceMessage(continent.id, AvatarAction.PlanetsideAttribute(player.GUID, 0, maxHealthOfPlayer))
}
}
private def topOffHealth(vehicle: Vehicle): Unit = {
topOffHealthOfPlayer()
//vehicle below half health, full heal
val maxHealthOfVehicle = vehicle.MaxHealth.toLong
if (vehicle.Health < maxHealthOfVehicle * 0.5f) {
vehicle.Health = maxHealthOfVehicle.toInt
sendResponse(PlanetsideAttributeMessage(vehicle.GUID, 0, maxHealthOfVehicle))
continent.AvatarEvents ! AvatarServiceMessage(continent.id, AvatarAction.PlanetsideAttribute(vehicle.GUID, 0, maxHealthOfVehicle))
}
}
}

View file

@ -2,9 +2,11 @@
package net.psforever.actors.session.normal
import akka.actor.ActorContext
import net.psforever.actors.session.SessionActor
import net.psforever.actors.session.spectator.SpectatorMode
import net.psforever.actors.session.support.{ChatFunctions, ChatOperations, SessionData}
import net.psforever.objects.Session
import net.psforever.objects.avatar.ModePermissions
import net.psforever.packet.game.{ChatMsg, SetChatFilterMessage}
import net.psforever.services.chat.DefaultChannel
import net.psforever.types.ChatMessageType
@ -28,17 +30,16 @@ class ChatLogic(val ops: ChatOperations, implicit val context: ActorContext) ext
case (_, _, contents) if contents.startsWith("!") &&
customCommandMessages(message, session) => ()
case (CMT_ANONYMOUS, _, _) =>
// ?
case (CMT_ANONYMOUS, _, _) => ()
case (CMT_TOGGLE_GM, _, contents) =>
ops.customCommandModerator(contents)
customCommandModerator(contents)
case (CMT_CULLWATERMARK, _, contents) =>
ops.commandWatermark(contents)
case (CMT_TOGGLESPECTATORMODE, _, contents) if isAlive =>
ops.commandToggleSpectatorMode(contents)
commandToggleSpectatorMode(contents)
case (CMT_RECALL, _, _) =>
ops.commandRecall(session)
@ -135,7 +136,6 @@ class ChatLogic(val ops: ChatOperations, implicit val context: ActorContext) ext
case "grenade" => ops.customCommandGrenade(session, log)
case "macro" => ops.customCommandMacro(session, params)
case "progress" => ops.customCommandProgress(session, params)
case "csr" | "gm" | "op" => ops.customCommandModerator(params.headOption.getOrElse(""))
case _ =>
// command was not handled
sendResponse(
@ -147,10 +147,30 @@ class ChatLogic(val ops: ChatOperations, implicit val context: ActorContext) ext
message.note
)
)
false
true
}
} else {
false
}
}
def commandToggleSpectatorMode(contents: String): Unit = {
val currentSpectatorActivation = (if (avatar != null) avatar.permissions else ModePermissions()).canSpectate
contents.toLowerCase() match {
case "on" | "o" | "" if currentSpectatorActivation && !player.spectator =>
context.self ! SessionActor.SetMode(ops.SpectatorMode)
case _ => ()
}
}
def customCommandModerator(contents: String): Boolean = {
val currentCsrActivation = (if (avatar != null) avatar.permissions else ModePermissions()).canGM
contents.toLowerCase() match {
case "on" | "o" | "" if currentCsrActivation =>
import net.psforever.actors.session.csr.CustomerServiceRepresentativeMode
context.self ! SessionActor.SetMode(CustomerServiceRepresentativeMode)
case _ => ()
}
true
}
}

View file

@ -5,17 +5,16 @@ import akka.actor.typed.scaladsl.adapter._
import akka.actor.{ActorContext, ActorRef, typed}
import net.psforever.actors.session.{AvatarActor, SessionActor}
import net.psforever.actors.session.support.{GeneralFunctions, GeneralOperations, SessionData}
import net.psforever.login.WorldSession.{CallBackForTask, ContainableMoveItem, DropEquipmentFromInventory, PickUpEquipmentFromGround, RemoveOldEquipmentFromInventory}
import net.psforever.objects.{Account, BoomerDeployable, BoomerTrigger, ConstructionItem, Deployables, GlobalDefinitions, Kit, LivePlayerList, PlanetSideGameObject, Player, SensorDeployable, ShieldGeneratorDeployable, SpecialEmp, TelepadDeployable, Tool, TrapDeployable, TurretDeployable, Vehicle}
import net.psforever.login.WorldSession.{ContainableMoveItem, DropEquipmentFromInventory, PickUpEquipmentFromGround, RemoveOldEquipmentFromInventory}
import net.psforever.objects.{Account, BoomerDeployable, BoomerTrigger, ConstructionItem, GlobalDefinitions, LivePlayerList, Player, SensorDeployable, ShieldGeneratorDeployable, SpecialEmp, TelepadDeployable, Tool, TrapDeployable, TurretDeployable, Vehicle}
import net.psforever.objects.avatar.{Avatar, PlayerControl, SpecialCarry}
import net.psforever.objects.ballistics.Projectile
import net.psforever.objects.ce.{Deployable, DeployedItem, TelepadLike}
import net.psforever.objects.ce.{Deployable, DeployedItem}
import net.psforever.objects.definition.{BasicDefinition, KitDefinition, SpecialExoSuitDefinition}
import net.psforever.objects.entity.WorldEntity
import net.psforever.objects.equipment.Equipment
import net.psforever.objects.guid.{GUIDTask, TaskBundle, TaskWorkflow}
import net.psforever.objects.inventory.Container
import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject, ServerObject}
import net.psforever.objects.serverobject.{PlanetSideServerObject, ServerObject}
import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.serverobject.containable.Containable
import net.psforever.objects.serverobject.doors.Door
@ -24,30 +23,25 @@ import net.psforever.objects.serverobject.llu.CaptureFlag
import net.psforever.objects.serverobject.locks.IFFLock
import net.psforever.objects.serverobject.mblocker.Locker
import net.psforever.objects.serverobject.resourcesilo.ResourceSilo
import net.psforever.objects.serverobject.structures.{Building, WarpGate}
import net.psforever.objects.serverobject.structures.WarpGate
import net.psforever.objects.serverobject.terminals.capture.CaptureTerminal
import net.psforever.objects.serverobject.terminals.{MatrixTerminalDefinition, ProximityUnit, Terminal}
import net.psforever.objects.serverobject.terminals.{ProximityUnit, Terminal}
import net.psforever.objects.serverobject.terminals.implant.ImplantTerminalMech
import net.psforever.objects.serverobject.tube.SpawnTube
import net.psforever.objects.serverobject.turret.FacilityTurret
import net.psforever.objects.sourcing.{PlayerSource, SourceEntry, VehicleSource}
import net.psforever.objects.vehicles.{AccessPermissionGroup, Utility, UtilityType, VehicleLockState}
import net.psforever.objects.vehicles.Utility.InternalTelepad
import net.psforever.objects.vital.{VehicleDismountActivity, VehicleMountActivity, Vitality}
import net.psforever.objects.sourcing.SourceEntry
import net.psforever.objects.vehicles.Utility
import net.psforever.objects.vital.Vitality
import net.psforever.objects.vital.collision.{CollisionReason, CollisionWithReason}
import net.psforever.objects.vital.etc.SuicideReason
import net.psforever.objects.vital.interaction.DamageInteraction
import net.psforever.objects.zones.blockmap.BlockMapEntity
import net.psforever.objects.zones.{Zone, ZoneProjectile, Zoning}
import net.psforever.objects.zones.{ZoneProjectile, Zoning}
import net.psforever.packet.PlanetSideGamePacket
import net.psforever.packet.game.objectcreate.ObjectClass
import net.psforever.packet.game.{ActionCancelMessage, ActionResultMessage, AvatarFirstTimeEventMessage, AvatarImplantMessage, AvatarJumpMessage, BattleplanMessage, BindPlayerMessage, BindStatus, BugReportMessage, ChangeFireModeMessage, ChangeShortcutBankMessage, CharacterCreateRequestMessage, CharacterRequestAction, CharacterRequestMessage, ChatMsg, CollisionIs, ConnectToWorldRequestMessage, CreateShortcutMessage, DeadState, DeployObjectMessage, DisplayedAwardMessage, DropItemMessage, EmoteMsg, FacilityBenefitShieldChargeRequestMessage, FriendsRequest, GenericAction, GenericActionMessage, GenericCollisionMsg, GenericObjectActionAtPositionMessage, GenericObjectActionMessage, GenericObjectStateMsg, HitHint, ImplantAction, InvalidTerrainMessage, ItemTransactionMessage, LootItemMessage, MoveItemMessage, ObjectDeleteMessage, ObjectDetectedMessage, ObjectHeldMessage, PickupItemMessage, PlanetsideAttributeMessage, PlayerStateMessageUpstream, PlayerStateShiftMessage, RequestDestroyMessage, ShiftState, TargetInfo, TargetingImplantRequest, TargetingInfoMessage, TerrainCondition, TradeMessage, UnuseItemMessage, UseItemMessage, VoiceHostInfo, VoiceHostRequest, ZipLineMessage}
import net.psforever.services.RemoverActor
import net.psforever.packet.game.{ActionCancelMessage, ActionResultMessage, AvatarFirstTimeEventMessage, AvatarImplantMessage, AvatarJumpMessage, BattleplanMessage, BindPlayerMessage, BugReportMessage, ChangeFireModeMessage, ChangeShortcutBankMessage, CharacterCreateRequestMessage, CharacterRequestAction, CharacterRequestMessage, ChatMsg, CollisionIs, ConnectToWorldRequestMessage, CreateShortcutMessage, DeadState, DeployObjectMessage, DisplayedAwardMessage, DropItemMessage, EmoteMsg, FacilityBenefitShieldChargeRequestMessage, FriendsRequest, GenericAction, GenericActionMessage, GenericCollisionMsg, GenericObjectActionAtPositionMessage, GenericObjectActionMessage, GenericObjectStateMsg, HitHint, ImplantAction, InvalidTerrainMessage, LootItemMessage, MoveItemMessage, ObjectDetectedMessage, ObjectHeldMessage, PickupItemMessage, PlanetsideAttributeMessage, PlayerStateMessageUpstream, PlayerStateShiftMessage, RequestDestroyMessage, ShiftState, TargetInfo, TargetingImplantRequest, TargetingInfoMessage, TerrainCondition, TradeMessage, UnuseItemMessage, UseItemMessage, VoiceHostInfo, VoiceHostRequest, ZipLineMessage}
import net.psforever.services.account.{AccountPersistenceService, RetrieveAccountData}
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
import net.psforever.services.local.{LocalAction, LocalServiceMessage}
import net.psforever.services.local.support.CaptureFlagManager
import net.psforever.types.{CapacitorStateType, ChatMessageType, Cosmetic, DriveState, ExoSuitType, ImplantType, PlanetSideEmpire, PlanetSideGUID, SpawnGroup, TransactionType, Vector3}
import net.psforever.types.{CapacitorStateType, ChatMessageType, Cosmetic, ExoSuitType, ImplantType, PlanetSideEmpire, PlanetSideGUID, Vector3}
import net.psforever.util.Config
import scala.concurrent.duration._
@ -311,7 +305,7 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
continent.Projectile ! ZoneProjectile.Remove(objectGuid)
case Some(obj: BoomerTrigger) =>
if (findEquipmentToDelete(objectGuid, obj)) {
if (ops.findEquipmentToDelete(objectGuid, obj)) {
continent.GUID(obj.Companion) match {
case Some(boomer: BoomerDeployable) =>
boomer.Trigger = None
@ -330,7 +324,7 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
}
case Some(obj: Equipment) =>
findEquipmentToDelete(objectGuid, obj)
ops.findEquipmentToDelete(objectGuid, obj)
case Some(thing) =>
log.warn(s"RequestDestroy: not allowed to delete this ${thing.Definition.Name}")
@ -447,47 +441,47 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
}
sessionLogic.validObject(pkt.object_guid, decorator = "UseItem") match {
case Some(door: Door) =>
handleUseDoor(door, equipment)
ops.handleUseDoor(door, equipment)
case Some(resourceSilo: ResourceSilo) =>
handleUseResourceSilo(resourceSilo, equipment)
ops.handleUseResourceSilo(resourceSilo, equipment)
case Some(panel: IFFLock) =>
handleUseGeneralEntity(panel, equipment)
ops.handleUseGeneralEntity(panel, equipment)
case Some(obj: Player) =>
handleUsePlayer(obj, equipment, pkt)
ops.handleUsePlayer(obj, equipment, pkt)
case Some(locker: Locker) =>
handleUseLocker(locker, equipment, pkt)
ops.handleUseLocker(locker, equipment, pkt)
case Some(gen: Generator) =>
handleUseGeneralEntity(gen, equipment)
ops.handleUseGeneralEntity(gen, equipment)
case Some(mech: ImplantTerminalMech) =>
handleUseGeneralEntity(mech, equipment)
ops.handleUseGeneralEntity(mech, equipment)
case Some(captureTerminal: CaptureTerminal) =>
handleUseCaptureTerminal(captureTerminal, equipment)
ops.handleUseCaptureTerminal(captureTerminal, equipment)
case Some(obj: FacilityTurret) =>
handleUseFacilityTurret(obj, equipment, pkt)
ops.handleUseFacilityTurret(obj, equipment, pkt)
case Some(obj: Vehicle) =>
handleUseVehicle(obj, equipment, pkt)
ops.handleUseVehicle(obj, equipment, pkt)
case Some(terminal: Terminal) =>
handleUseTerminal(terminal, equipment, pkt)
ops.handleUseTerminal(terminal, equipment, pkt)
case Some(obj: SpawnTube) =>
handleUseSpawnTube(obj, equipment)
ops.handleUseSpawnTube(obj, equipment)
case Some(obj: SensorDeployable) =>
handleUseGeneralEntity(obj, equipment)
ops.handleUseGeneralEntity(obj, equipment)
case Some(obj: TurretDeployable) =>
handleUseGeneralEntity(obj, equipment)
ops.handleUseGeneralEntity(obj, equipment)
case Some(obj: TrapDeployable) =>
handleUseGeneralEntity(obj, equipment)
ops.handleUseGeneralEntity(obj, equipment)
case Some(obj: ShieldGeneratorDeployable) =>
handleUseGeneralEntity(obj, equipment)
ops.handleUseGeneralEntity(obj, equipment)
case Some(obj: TelepadDeployable) =>
handleUseTelepadDeployable(obj, equipment, pkt)
ops.handleUseTelepadDeployable(obj, equipment, pkt, ops.useRouterTelepadSystem)
case Some(obj: Utility.InternalTelepad) =>
handleUseInternalTelepad(obj, pkt)
ops.handleUseInternalTelepad(obj, pkt, ops.useRouterTelepadSystem)
case Some(obj: CaptureFlag) =>
handleUseCaptureFlag(obj)
ops.handleUseCaptureFlag(obj)
case Some(_: WarpGate) =>
handleUseWarpGate(equipment)
ops.handleUseWarpGate(equipment)
case Some(obj) =>
handleUseDefaultEntity(obj, equipment)
ops.handleUseDefaultEntity(obj, equipment)
case None => ()
}
}
@ -518,19 +512,7 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
}
log.info(s"${player.Name} is constructing a $ammoType deployable")
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
val dObj: Deployable = Deployables.Make(ammoType)()
dObj.Position = pos
dObj.Orientation = orient
dObj.WhichSide = player.WhichSide
dObj.Faction = player.Faction
dObj.AssignOwnership(player)
val tasking: TaskBundle = dObj match {
case turret: TurretDeployable =>
GUIDTask.registerDeployableTurret(continent.GUID, turret)
case _ =>
GUIDTask.registerObject(continent.GUID, dObj)
}
TaskWorkflow.execute(CallBackForTask(tasking, continent.Deployables, Zone.Deployable.BuildByOwner(dObj, player, obj), context.self))
ops.handleDeployObject(continent, ammoType, pos, orient, player.WhichSide, player.Faction, Some((player, obj)))
case Some(obj) =>
log.warn(s"DeployObject: what is $obj, ${player.Name}? It's not a construction tool!")
case None =>
@ -567,7 +549,7 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
) {
//maelstrom primary fire mode discharge (no target)
//aphelion_laser discharge (no target)
sessionLogic.shooting.HandleWeaponFireAccountability(objectGuid, PlanetSideGUID(Projectile.baseUID))
sessionLogic.shooting.handleWeaponFireAccountability(objectGuid, PlanetSideGUID(Projectile.baseUID))
} else {
sessionLogic.validObject(player.VehicleSeated, decorator = "GenericObjectAction/Vehicle") collect {
case vehicle: Vehicle
@ -819,10 +801,8 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
.foreach {
case obj: Vitality if obj.Destroyed => () //some entities will try to charge even if destroyed
case obj: Vehicle if obj.MountedIn.nonEmpty => () //cargo vehicles need to be excluded
case obj: Vehicle =>
commonFacilityShieldCharging(obj)
case obj: TurretDeployable =>
commonFacilityShieldCharging(obj)
case obj: Vehicle => ops.commonFacilityShieldCharging(obj)
case obj: TurretDeployable => ops.commonFacilityShieldCharging(obj)
case _ if vehicleGuid.nonEmpty =>
log.warn(
s"FacilityBenefitShieldChargeRequest: ${player.Name} can not find chargeable entity ${vehicleGuid.get.guid} in ${continent.id}"
@ -1014,413 +994,6 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
/* supporting functions */
private def handleUseDoor(door: Door, equipment: Option[Equipment]): Unit = {
equipment match {
case Some(tool: Tool) if tool.Definition == GlobalDefinitions.medicalapplicator =>
val distance: Float = math.max(
Config.app.game.doorsCanBeOpenedByMedAppFromThisDistance,
door.Definition.initialOpeningDistance
)
door.Actor ! CommonMessages.Use(player, Some(distance))
case _ =>
door.Actor ! CommonMessages.Use(player)
}
}
private def handleUseResourceSilo(resourceSilo: ResourceSilo, equipment: Option[Equipment]): Unit = {
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
val vehicleOpt = continent.GUID(player.avatar.vehicle)
(vehicleOpt, equipment) match {
case (Some(vehicle: Vehicle), Some(item))
if GlobalDefinitions.isBattleFrameVehicle(vehicle.Definition) &&
GlobalDefinitions.isBattleFrameNTUSiphon(item.Definition) =>
resourceSilo.Actor ! CommonMessages.Use(player, Some(vehicle))
case (Some(vehicle: Vehicle), _)
if vehicle.Definition == GlobalDefinitions.ant &&
vehicle.DeploymentState == DriveState.Deployed &&
Vector3.DistanceSquared(resourceSilo.Position.xy, vehicle.Position.xy) < math.pow(resourceSilo.Definition.UseRadius, 2) =>
resourceSilo.Actor ! CommonMessages.Use(player, Some(vehicle))
case _ => ()
}
}
private def handleUsePlayer(obj: Player, equipment: Option[Equipment], msg: UseItemMessage): Unit = {
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
if (obj.isBackpack) {
if (equipment.isEmpty) {
log.info(s"${player.Name} is looting the corpse of ${obj.Name}")
sendResponse(msg)
ops.accessContainer(obj)
}
} else if (!msg.unk3 && player.isAlive) { //potential kit use
(continent.GUID(msg.item_used_guid), ops.kitToBeUsed) match {
case (Some(kit: Kit), None) =>
ops.kitToBeUsed = Some(msg.item_used_guid)
player.Actor ! CommonMessages.Use(player, Some(kit))
case (Some(_: Kit), Some(_)) | (None, Some(_)) =>
//a kit is already queued to be used; ignore this request
sendResponse(ChatMsg(ChatMessageType.UNK_225, wideContents=false, "", "Please wait ...", None))
case (Some(item), _) =>
log.error(s"UseItem: ${player.Name} looking for Kit to use, but found $item instead")
case (None, None) =>
log.warn(s"UseItem: anticipated a Kit ${msg.item_used_guid} for ${player.Name}, but can't find it") }
} else if (msg.object_id == ObjectClass.avatar && msg.unk3) {
equipment match {
case Some(tool: Tool) if tool.Definition == GlobalDefinitions.bank =>
obj.Actor ! CommonMessages.Use(player, equipment)
case Some(tool: Tool) if tool.Definition == GlobalDefinitions.medicalapplicator =>
obj.Actor ! CommonMessages.Use(player, equipment)
case _ => ()
}
}
}
private def handleUseLocker(locker: Locker, equipment: Option[Equipment], msg: UseItemMessage): Unit = {
equipment match {
case Some(item) =>
sendUseGeneralEntityMessage(locker, item)
case None if locker.Faction == player.Faction || locker.HackedBy.nonEmpty =>
log.info(s"${player.Name} is accessing a locker")
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
val playerLocker = player.avatar.locker
sendResponse(msg.copy(object_guid = playerLocker.GUID, object_id = 456))
ops.accessContainer(playerLocker)
case _ => ()
}
}
private def handleUseCaptureTerminal(captureTerminal: CaptureTerminal, equipment: Option[Equipment]): Unit = {
equipment match {
case Some(item) =>
sendUseGeneralEntityMessage(captureTerminal, item)
case _ if ops.specialItemSlotGuid.nonEmpty =>
continent.GUID(ops.specialItemSlotGuid) match {
case Some(llu: CaptureFlag) =>
if (llu.Target.GUID == captureTerminal.Owner.GUID) {
continent.LocalEvents ! LocalServiceMessage(continent.id, LocalAction.LluCaptured(llu))
} else {
log.info(
s"LLU target is not this base. Target GUID: ${llu.Target.GUID} This base: ${captureTerminal.Owner.GUID}"
)
}
case _ => log.warn("Item in specialItemSlotGuid is not registered with continent or is not a LLU")
}
case _ => ()
}
}
private def handleUseFacilityTurret(obj: FacilityTurret, equipment: Option[Equipment], msg: UseItemMessage): Unit = {
equipment.foreach { item =>
sendUseGeneralEntityMessage(obj, item)
obj.Actor ! CommonMessages.Use(player, Some((item, msg.unk2.toInt))) //try upgrade path
}
}
private def handleUseVehicle(obj: Vehicle, equipment: Option[Equipment], msg: UseItemMessage): Unit = {
equipment match {
case Some(item) =>
sendUseGeneralEntityMessage(obj, item)
case None if player.Faction == obj.Faction =>
//access to trunk
if (
obj.AccessingTrunk.isEmpty &&
(!obj.PermissionGroup(AccessPermissionGroup.Trunk.id).contains(VehicleLockState.Locked) || obj.OwnerGuid
.contains(player.GUID))
) {
log.info(s"${player.Name} is looking in the ${obj.Definition.Name}'s trunk")
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
obj.AccessingTrunk = player.GUID
ops.accessContainer(obj)
sendResponse(msg)
}
case _ => ()
}
}
private def handleUseTerminal(terminal: Terminal, equipment: Option[Equipment], msg: UseItemMessage): Unit = {
equipment match {
case Some(item) =>
sendUseGeneralEntityMessage(terminal, item)
case None
if terminal.Owner == Building.NoBuilding || terminal.Faction == player.Faction ||
terminal.HackedBy.nonEmpty || terminal.Faction == PlanetSideEmpire.NEUTRAL =>
val tdef = terminal.Definition
if (tdef.isInstanceOf[MatrixTerminalDefinition]) {
//TODO matrix spawn point; for now, just blindly bind to show work (and hope nothing breaks)
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
sendResponse(
BindPlayerMessage(BindStatus.Bind, "", display_icon=true, logging=true, SpawnGroup.Sanctuary, 0, 0, terminal.Position)
)
} else if (
tdef == GlobalDefinitions.multivehicle_rearm_terminal || tdef == GlobalDefinitions.bfr_rearm_terminal ||
tdef == GlobalDefinitions.air_rearm_terminal || tdef == GlobalDefinitions.ground_rearm_terminal
) {
findLocalVehicle match {
case Some(vehicle) =>
log.info(
s"${player.Name} is accessing a ${terminal.Definition.Name} for ${player.Sex.possessive} ${vehicle.Definition.Name}"
)
sendResponse(msg)
sendResponse(msg.copy(object_guid = vehicle.GUID, object_id = vehicle.Definition.ObjectId))
case None =>
log.error(s"UseItem: Expecting a seated vehicle, ${player.Name} found none")
}
} else if (tdef == GlobalDefinitions.teleportpad_terminal) {
//explicit request
log.info(s"${player.Name} is purchasing a router telepad")
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
terminal.Actor ! Terminal.Request(
player,
ItemTransactionMessage(msg.object_guid, TransactionType.Buy, 0, "router_telepad", 0, PlanetSideGUID(0))
)
} else if (tdef == GlobalDefinitions.targeting_laser_dispenser) {
//explicit request
log.info(s"${player.Name} is purchasing a targeting laser")
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
terminal.Actor ! Terminal.Request(
player,
ItemTransactionMessage(msg.object_guid, TransactionType.Buy, 0, "flail_targeting_laser", 0, PlanetSideGUID(0))
)
} else {
log.info(s"${player.Name} is accessing a ${terminal.Definition.Name}")
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
sendResponse(msg)
}
case _ => ()
}
}
private def handleUseSpawnTube(obj: SpawnTube, equipment: Option[Equipment]): Unit = {
equipment match {
case Some(item) =>
sendUseGeneralEntityMessage(obj, item)
case None if player.Faction == obj.Faction =>
//deconstruction
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
sessionLogic.actionsToCancel()
sessionLogic.terminals.CancelAllProximityUnits()
sessionLogic.zoning.spawn.startDeconstructing(obj)
case _ => ()
}
}
private def handleUseTelepadDeployable(obj: TelepadDeployable, equipment: Option[Equipment], msg: UseItemMessage): Unit = {
if (equipment.isEmpty) {
(continent.GUID(obj.Router) match {
case Some(vehicle: Vehicle) => Some((vehicle, vehicle.Utility(UtilityType.internal_router_telepad_deployable)))
case Some(vehicle) => Some(vehicle, None)
case None => None
}) match {
case Some((vehicle: Vehicle, Some(util: Utility.InternalTelepad))) =>
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel")
player.WhichSide = vehicle.WhichSide
useRouterTelepadSystem(
router = vehicle,
internalTelepad = util,
remoteTelepad = obj,
src = obj,
dest = util
)
case Some((vehicle: Vehicle, None)) =>
log.error(
s"telepad@${msg.object_guid.guid} is not linked to a router - ${vehicle.Definition.Name}"
)
case Some((o, _)) =>
log.error(
s"telepad@${msg.object_guid.guid} is linked to wrong kind of object - ${o.Definition.Name}, ${obj.Router}"
)
obj.Actor ! Deployable.Deconstruct()
case _ => ()
}
}
}
private def handleUseInternalTelepad(obj: InternalTelepad, msg: UseItemMessage): Unit = {
continent.GUID(obj.Telepad) match {
case Some(pad: TelepadDeployable) =>
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel")
player.WhichSide = pad.WhichSide
useRouterTelepadSystem(
router = obj.Owner.asInstanceOf[Vehicle],
internalTelepad = obj,
remoteTelepad = pad,
src = obj,
dest = pad
)
case Some(o) =>
log.error(
s"internal telepad@${msg.object_guid.guid} is not linked to a remote telepad - ${o.Definition.Name}@${o.GUID.guid}"
)
case None => ()
}
}
private def handleUseCaptureFlag(obj: CaptureFlag): Unit = {
// LLU can normally only be picked up the faction that owns it
ops.specialItemSlotGuid match {
case None if obj.Faction == player.Faction =>
ops.specialItemSlotGuid = Some(obj.GUID)
player.Carrying = SpecialCarry.CaptureFlag
continent.LocalEvents ! CaptureFlagManager.PickupFlag(obj, player)
case None =>
log.warn(s"${player.Faction} player ${player.toString} tried to pick up a ${obj.Faction} LLU - ${obj.GUID}")
case Some(guid) if guid != obj.GUID =>
// Ignore duplicate pickup requests
log.warn(
s"${player.Faction} player ${player.toString} tried to pick up a ${obj.Faction} LLU, but their special slot already contains $guid"
)
case _ => ()
}
}
private def handleUseWarpGate(equipment: Option[Equipment]): Unit = {
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
(continent.GUID(player.VehicleSeated), equipment) match {
case (Some(vehicle: Vehicle), Some(item))
if GlobalDefinitions.isBattleFrameVehicle(vehicle.Definition) &&
GlobalDefinitions.isBattleFrameNTUSiphon(item.Definition) =>
vehicle.Actor ! CommonMessages.Use(player, equipment)
case _ => ()
}
}
private def handleUseGeneralEntity(obj: PlanetSideServerObject, equipment: Option[Equipment]): Unit = {
equipment.foreach { item =>
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
obj.Actor ! CommonMessages.Use(player, Some(item))
}
}
private def sendUseGeneralEntityMessage(obj: PlanetSideServerObject, equipment: Equipment): Unit = {
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
obj.Actor ! CommonMessages.Use(player, Some(equipment))
}
private def handleUseDefaultEntity(obj: PlanetSideGameObject, equipment: Option[Equipment]): Unit = {
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
equipment match {
case Some(item)
if GlobalDefinitions.isBattleFrameArmorSiphon(item.Definition) ||
GlobalDefinitions.isBattleFrameNTUSiphon(item.Definition) => ()
case _ =>
log.warn(s"UseItem: ${player.Name} does not know how to handle $obj")
}
}
/**
* Get the current `Vehicle` object that the player is riding/driving.
* The vehicle must be found solely through use of `player.VehicleSeated`.
* @return the vehicle
*/
private def findLocalVehicle: Option[Vehicle] = {
continent.GUID(player.VehicleSeated) match {
case Some(obj: Vehicle) => Some(obj)
case _ => None
}
}
/**
* A simple object searching algorithm that is limited to containers currently known and accessible by the player.
* If all relatively local containers are checked and the object is not found,
* the player's locker inventory will be checked, and then
* the game environment (items on the ground) will be checked too.
* If the target object is discovered, it is removed from its current location and is completely destroyed.
* @see `RequestDestroyMessage`
* @see `Zone.ItemIs.Where`
* @param objectGuid the target object's globally unique identifier;
* it is not expected that the object will be unregistered, but it is also not gauranteed
* @param obj the target object
* @return `true`, if the target object was discovered and removed;
* `false`, otherwise
*/
private def findEquipmentToDelete(objectGuid: PlanetSideGUID, obj: Equipment): Boolean = {
val findFunc
: PlanetSideServerObject with Container => Option[(PlanetSideServerObject with Container, Option[Int])] =
ops.findInLocalContainer(objectGuid)
findFunc(player)
.orElse(ops.accessedContainer match {
case Some(parent: PlanetSideServerObject) =>
findFunc(parent)
case _ =>
None
})
.orElse(findLocalVehicle match {
case Some(parent: PlanetSideServerObject) =>
findFunc(parent)
case _ =>
None
}) match {
case Some((parent, Some(_))) =>
obj.Position = Vector3.Zero
RemoveOldEquipmentFromInventory(parent)(obj)
true
case _ if player.avatar.locker.Inventory.Remove(objectGuid) =>
sendResponse(ObjectDeleteMessage(objectGuid, 0))
true
case _ if continent.EquipmentOnGround.contains(obj) =>
obj.Position = Vector3.Zero
continent.Ground ! Zone.Ground.RemoveItem(objectGuid)
continent.AvatarEvents ! AvatarServiceMessage.Ground(RemoverActor.ClearSpecific(List(obj), continent))
true
case _ =>
Zone.EquipmentIs.Where(obj, objectGuid, continent) match {
case None =>
true
case Some(Zone.EquipmentIs.Orphaned()) if obj.HasGUID =>
TaskWorkflow.execute(GUIDTask.unregisterEquipment(continent.GUID, obj))
true
case Some(Zone.EquipmentIs.Orphaned()) =>
true
case _ =>
log.warn(s"RequestDestroy: equipment $obj exists, but ${player.Name} can not reach it to dispose of it")
false
}
}
}
/**
* A player uses a fully-linked Router teleportation system.
* @param router the Router vehicle
* @param internalTelepad the internal telepad within the Router vehicle
* @param remoteTelepad the remote telepad that is currently associated with this Router
* @param src the origin of the teleportation (where the player starts)
* @param dest the destination of the teleportation (where the player is going)
*/
private def useRouterTelepadSystem(
router: Vehicle,
internalTelepad: InternalTelepad,
remoteTelepad: TelepadDeployable,
src: PlanetSideGameObject with TelepadLike,
dest: PlanetSideGameObject with TelepadLike
): Unit = {
val time = System.currentTimeMillis()
if (
time - ops.recentTeleportAttempt > 2000L && router.DeploymentState == DriveState.Deployed &&
internalTelepad.Active &&
remoteTelepad.Active
) {
val pguid = player.GUID
val sguid = src.GUID
val dguid = dest.GUID
sendResponse(PlayerStateShiftMessage(ShiftState(0, dest.Position, player.Orientation.z)))
ops.useRouterTelepadEffect(pguid, sguid, dguid)
continent.LocalEvents ! LocalServiceMessage(
continent.id,
LocalAction.RouterTelepadTransport(pguid, pguid, sguid, dguid)
)
val vSource = VehicleSource(router)
val zoneNumber = continent.Number
player.LogActivity(VehicleMountActivity(vSource, PlayerSource(player), zoneNumber))
player.Position = dest.Position
player.LogActivity(VehicleDismountActivity(vSource, PlayerSource(player), zoneNumber))
} else {
log.warn(s"UseRouterTelepadSystem: ${player.Name} can not teleport")
}
ops.recentTeleportAttempt = time
}
private def maxCapacitorTick(jumpThrust: Boolean): Unit = {
if (player.ExoSuit == ExoSuitType.MAX) {
val activate = (jumpThrust || player.isOverdrived || player.isShielded) && player.Capacitor > 0
@ -1540,11 +1113,4 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
)
)
}
private def commonFacilityShieldCharging(obj: PlanetSideServerObject with BlockMapEntity): Unit = {
obj.Actor ! CommonMessages.ChargeShields(
15,
Some(continent.blockMap.sector(obj).buildingList.maxBy(_.Definition.SOIRadius))
)
}
}

View file

@ -10,7 +10,7 @@ import net.psforever.objects.{BoomerDeployable, ExplosiveDeployable, TelepadDepl
import net.psforever.packet.game.{ChatMsg, DeployableObjectsInfoMessage, GenericActionMessage, GenericObjectActionMessage, GenericObjectStateMsg, HackMessage, HackState, HackState1, 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}
import net.psforever.types.{ChatMessageType, PlanetSideGUID}
object LocalHandlerLogic {
def apply(ops: SessionLocalHandlers): LocalHandlerLogic = {
@ -88,7 +88,7 @@ class LocalHandlerLogic(val ops: SessionLocalHandlers, implicit val context: Act
case LocalResponse.EliminateDeployable(obj: TurretDeployable, dguid, pos, _) =>
obj.Destroyed = true
DeconstructDeployable(
ops.DeconstructDeployable(
obj,
dguid,
pos,
@ -102,7 +102,7 @@ class LocalHandlerLogic(val ops: SessionLocalHandlers, implicit val context: Act
case LocalResponse.EliminateDeployable(obj: ExplosiveDeployable, dguid, pos, effect) =>
obj.Destroyed = true
DeconstructDeployable(obj, dguid, pos, obj.Orientation, effect)
ops.DeconstructDeployable(obj, dguid, pos, obj.Orientation, effect)
case LocalResponse.EliminateDeployable(obj: TelepadDeployable, dguid, _, _) if obj.Active && obj.Destroyed =>
//if active, deactivate
@ -117,7 +117,7 @@ class LocalHandlerLogic(val ops: SessionLocalHandlers, implicit val context: Act
ops.deactivateTelpadDeployableMessages(dguid)
//standard deployable elimination behavior
obj.Destroyed = true
DeconstructDeployable(obj, dguid, pos, obj.Orientation, deletionType=2)
ops.DeconstructDeployable(obj, dguid, pos, obj.Orientation, deletionType=2)
case LocalResponse.EliminateDeployable(obj: TelepadDeployable, dguid, _, _) if obj.Destroyed =>
//standard deployable elimination behavior
@ -126,14 +126,14 @@ class LocalHandlerLogic(val ops: SessionLocalHandlers, implicit val context: Act
case LocalResponse.EliminateDeployable(obj: TelepadDeployable, dguid, pos, _) =>
//standard deployable elimination behavior
obj.Destroyed = true
DeconstructDeployable(obj, dguid, pos, obj.Orientation, deletionType=2)
ops.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)
ops.DeconstructDeployable(obj, dguid, pos, obj.Orientation, effect)
case LocalResponse.SendHackMessageHackCleared(targetGuid, unk1, unk2) =>
sendResponse(HackMessage(HackState1.Unk0, targetGuid, guid, progress=0, unk1.toFloat, HackState.HackCleared, unk2))
@ -245,24 +245,4 @@ class LocalHandlerLogic(val ops: SessionLocalHandlers, implicit val context: Act
}
/* 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))
}
}

View file

@ -1,8 +1,7 @@
// Copyright (c) 2024 PSForever
package net.psforever.actors.session.normal
import akka.actor.{ActorContext, typed}
import net.psforever.actors.session.AvatarActor
import akka.actor.ActorContext
import net.psforever.actors.session.support.{MountHandlerFunctions, SessionData, SessionMountHandlers}
import net.psforever.actors.zone.ZoneActor
import net.psforever.objects.{GlobalDefinitions, PlanetSideGameObject, Player, Vehicle, Vehicles}
@ -14,16 +13,14 @@ import net.psforever.objects.serverobject.mount.Mountable
import net.psforever.objects.serverobject.structures.WarpGate
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.vehicles.AccessPermissionGroup
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.packet.game.{ChatMsg, DelayedPathMountMsg, DismountVehicleCargoMsg, DismountVehicleMsg, GenericObjectActionMessage, MountVehicleCargoMsg, MountVehicleMsg, 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, DriveState, PlanetSideGUID, Vector3}
import scala.concurrent.duration._
object MountHandlerLogic {
def apply(ops: SessionMountHandlers): MountHandlerLogic = {
new MountHandlerLogic(ops, ops.context)
@ -33,116 +30,22 @@ object MountHandlerLogic {
class MountHandlerLogic(val ops: SessionMountHandlers, implicit val context: ActorContext) extends MountHandlerFunctions {
def sessionLogic: SessionData = ops.sessionLogic
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}")
}
ops.handleMountVehicle(pkt)
}
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
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)
}
}
ops.handleDismountVehicle(pkt)
}
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 _ => ()
}
ops.handleMountVehicleCargo(pkt)
}
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 _ => ()
}
ops.handleDismountVehicleCargo(pkt)
}
/* response handlers */
@ -156,24 +59,24 @@ class MountHandlerLogic(val ops: SessionMountHandlers, implicit val context: Act
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.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
sessionLogic.terminals.CancelAllProximityUnits()
MountingAction(tplayer, obj, seatNumber)
ops.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.zoning.CancelZoningProcessWithDescriptiveReason("cancel_mount")
sessionLogic.terminals.CancelAllProximityUnits()
MountingAction(tplayer, obj, seatNumber)
ops.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}")
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_mount")
val obj_guid: PlanetSideGUID = obj.GUID
sessionLogic.terminals.CancelAllProximityUnits()
sendResponse(PlanetsideAttributeMessage(obj_guid, attribute_type=0, obj.Health))
@ -182,12 +85,12 @@ class MountHandlerLogic(val ops: SessionMountHandlers, implicit val context: Act
sendResponse(GenericObjectActionMessage(obj_guid, code=11))
sessionLogic.general.accessContainer(obj)
tplayer.Actor ! ResetAllEnvironmentInteractions
MountingAction(tplayer, obj, seatNumber)
ops.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}")
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_mount")
val obj_guid: PlanetSideGUID = obj.GUID
sessionLogic.terminals.CancelAllProximityUnits()
sendResponse(PlanetsideAttributeMessage(obj_guid, attribute_type=0, obj.Health))
@ -198,12 +101,12 @@ class MountHandlerLogic(val ops: SessionMountHandlers, implicit val context: Act
sendResponse(GenericObjectActionMessage(obj_guid, code=11))
sessionLogic.general.accessContainer(obj)
tplayer.Actor ! ResetAllEnvironmentInteractions
MountingAction(tplayer, obj, seatNumber)
ops.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}")
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_mount")
val obj_guid: PlanetSideGUID = obj.GUID
sessionLogic.terminals.CancelAllProximityUnits()
sendResponse(PlanetsideAttributeMessage(obj_guid, attribute_type=0, obj.Health))
@ -213,12 +116,12 @@ class MountHandlerLogic(val ops: SessionMountHandlers, implicit val context: Act
sessionLogic.general.accessContainer(obj)
ops.updateWeaponAtSeatPosition(obj, seatNumber)
tplayer.Actor ! ResetAllEnvironmentInteractions
MountingAction(tplayer, obj, seatNumber)
ops.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}")
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_mount")
val obj_guid: PlanetSideGUID = obj.GUID
sessionLogic.terminals.CancelAllProximityUnits()
sendResponse(PlanetsideAttributeMessage(obj_guid, attribute_type=0, obj.Health))
@ -227,17 +130,17 @@ class MountHandlerLogic(val ops: SessionMountHandlers, implicit val context: Act
sessionLogic.general.accessContainer(obj)
ops.updateWeaponAtSeatPosition(obj, seatNumber)
tplayer.Actor ! ResetAllEnvironmentInteractions
MountingAction(tplayer, obj, seatNumber)
ops.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}")
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_mount")
val obj_guid: PlanetSideGUID = obj.GUID
sessionLogic.terminals.CancelAllProximityUnits()
sendResponse(PlanetsideAttributeMessage(obj_guid, attribute_type=0, obj.Health))
@ -247,16 +150,16 @@ class MountHandlerLogic(val ops: SessionMountHandlers, implicit val context: Act
ops.updateWeaponAtSeatPosition(obj, seatNumber)
sessionLogic.keepAliveFunc = sessionLogic.keepAlivePersistence
tplayer.Actor ! ResetAllEnvironmentInteractions
MountingAction(tplayer, obj, seatNumber)
ops.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}")
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_mount")
val obj_guid: PlanetSideGUID = obj.GUID
sessionLogic.terminals.CancelAllProximityUnits()
sendResponse(PlanetsideAttributeMessage(obj_guid, attribute_type=0, obj.Health))
@ -265,51 +168,51 @@ class MountHandlerLogic(val ops: SessionMountHandlers, implicit val context: Act
ops.updateWeaponAtSeatPosition(obj, seatNumber)
sessionLogic.keepAliveFunc = sessionLogic.keepAlivePersistence
tplayer.Actor ! ResetAllEnvironmentInteractions
MountingAction(tplayer, obj, seatNumber)
ops.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}")
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_mount")
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)
ops.MountingAction(tplayer, obj, seatNumber)
case Mountable.CanMount(obj: FacilityTurret, seatNumber, _)
if !obj.isUpgrading || System.currentTimeMillis() - GenericHackables.getTurretUpgradeTime >= 1500L =>
log.info(s"${player.Name} mounts the ${obj.Definition.Name}")
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)
ops.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"
)
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_mount")
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}")
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_mount")
sendResponse(PlanetsideAttributeMessage(obj.GUID, attribute_type=0, obj.Health))
ops.updateWeaponAtSeatPosition(obj, seatNumber)
MountingAction(tplayer, obj, seatNumber)
ops.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)
ops.DismountAction(tplayer, obj, seatNum)
case Mountable.CanDismount(obj: Vehicle, _, mountPoint)
if obj.Definition == GlobalDefinitions.orbital_shuttle && obj.MountedIn.nonEmpty =>
log.info(s"${tplayer.Name} dismounts the orbital shuttle into the lobby")
//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
@ -322,11 +225,11 @@ class MountHandlerLogic(val ops: SessionMountHandlers, implicit val context: Act
case Mountable.CanDismount(obj: Vehicle, seatNum, _)
if obj.Definition == GlobalDefinitions.orbital_shuttle =>
log.info(s"${player.Name} is prepped for dropping")
//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)
ops.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(
@ -354,7 +257,7 @@ class MountHandlerLogic(val ops: SessionMountHandlers, implicit val context: Act
if obj.Definition == GlobalDefinitions.droppod =>
log.info(s"${tplayer.Name} has landed on ${continent.id}")
sessionLogic.general.unaccessContainer(obj)
DismountAction(tplayer, obj, seatNum)
ops.DismountAction(tplayer, obj, seatNum)
obj.Actor ! Vehicle.Deconstruct()
case Mountable.CanDismount(obj: Vehicle, seatNum, _)
@ -365,12 +268,12 @@ class MountHandlerLogic(val ops: SessionMountHandlers, implicit val context: Act
//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
DismountVehicleAction(tplayer, obj, seatNum)
ops.DismountVehicleAction(tplayer, obj, seatNum)
obj.Actor ! Vehicle.Deconstruct(None) //immediate deconstruction
case Mountable.CanDismount(obj: Vehicle, seatNum, _)
if tplayer.GUID == player.GUID =>
DismountVehicleAction(tplayer, obj, seatNum)
ops.DismountVehicleAction(tplayer, obj, seatNum)
case Mountable.CanDismount(obj: Vehicle, seat_num, _) =>
continent.VehicleEvents ! VehicleServiceMessage(
@ -380,7 +283,7 @@ class MountHandlerLogic(val ops: SessionMountHandlers, implicit val context: Act
case Mountable.CanDismount(obj: PlanetSideGameObject with Mountable with FactionAffinity with InGameHistory, seatNum, _) =>
log.info(s"${tplayer.Name} dismounts a ${obj.Definition.asInstanceOf[ObjectDefinition].Name}")
DismountAction(tplayer, obj, seatNum)
ops.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}")
@ -434,118 +337,4 @@ class MountHandlerLogic(val ops: SessionMountHandlers, implicit val context: Act
}
/* 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
*/
private 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
*/
private def DismountVehicleAction(tplayer: Player, obj: Vehicle, seatNum: Int): Unit = {
//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)
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
*/
private 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)
)
}
}

View file

@ -1,13 +1,12 @@
// Copyright (c) 2024 PSForever
package net.psforever.actors.session.normal
import net.psforever.actors.session.support.{ChatFunctions, GeneralFunctions, LocalHandlerFunctions, MountHandlerFunctions, SquadHandlerFunctions, TerminalHandlerFunctions, VehicleFunctions, VehicleHandlerFunctions, WeaponAndProjectileFunctions}
import net.psforever.actors.session.support.{ModeLogic, PlayerMode, SessionData}
import net.psforever.actors.session.support.{ChatFunctions, GalaxyHandlerFunctions, GeneralFunctions, LocalHandlerFunctions, ModeLogic, MountHandlerFunctions, PlayerMode, SessionData, SquadHandlerFunctions, TerminalHandlerFunctions, VehicleFunctions, VehicleHandlerFunctions, WeaponAndProjectileFunctions}
class NormalModeLogic(data: SessionData) extends ModeLogic {
val avatarResponse: AvatarHandlerLogic = AvatarHandlerLogic(data.avatarResponse)
val chat: ChatFunctions = ChatLogic(data.chat)
val galaxy: GalaxyHandlerLogic = GalaxyHandlerLogic(data.galaxyResponseHandlers)
val galaxy: GalaxyHandlerFunctions = GalaxyHandlerLogic(data.galaxyResponseHandlers)
val general: GeneralFunctions = GeneralLogic(data.general)
val local: LocalHandlerFunctions = LocalHandlerLogic(data.localResponse)
val mountResponse: MountHandlerFunctions = MountHandlerLogic(data.mountResponse)

View file

@ -25,8 +25,6 @@ class SquadHandlerLogic(val ops: SessionSquadHandlers, implicit val context: Act
private val squadService: ActorRef = ops.squadService
private var waypointCooldown: Long = 0L
/* packet */
def handleSquadDefinitionAction(pkt: SquadDefinitionActionMessage): Unit = {

View file

@ -8,10 +8,10 @@ import net.psforever.login.WorldSession.{BuyNewEquipmentPutInInventory, SellEqui
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.serverobject.terminals.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.packet.game.{FavoritesRequest, ItemTransactionMessage, ItemTransactionResultMessage, ProximityTerminalUseMessage, UnuseItemMessage}
import net.psforever.types.{TransactionType, Vector3}
object TerminalHandlerLogic {
@ -26,46 +26,17 @@ class TerminalHandlerLogic(val ops: SessionTerminalHandlers, implicit val contex
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}")
}
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
ops.handleItemTransaction(pkt)
}
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}")
}
ops.handleProximityTerminalUse(pkt)
}
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")
}
ops.handleFavoritesRequest(pkt)
}
/**
@ -117,49 +88,7 @@ class TerminalHandlerLogic(val ops: SessionTerminalHandlers, implicit val contex
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.buyVehicle(msg.terminal_guid, msg.transaction_type, vehicle, weapons, trunk)
ops.lastTerminalOrderFulfillment = true
case Terminal.NoDeal() if msg != null =>

View file

@ -235,10 +235,6 @@ class AvatarHandlerLogic(val ops: SessionAvatarHandlers, implicit val context: A
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))
@ -452,7 +448,6 @@ class AvatarHandlerLogic(val ops: SessionAvatarHandlers, implicit val context: A
sessionLogic.shooting.shotsWhileDead = 0
}
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason(msg = "cancel")
sessionLogic.general.renewCharSavedTimer(fixedLen = 1800L, varLen = 0L)
//player state changes
AvatarActor.updateToolDischargeFor(avatar)

View file

@ -31,11 +31,9 @@ class ChatLogic(val ops: ChatOperations, implicit val context: ActorContext) ext
case (CMT_FLY, recipient, contents) =>
ops.commandFly(contents, recipient)
case (CMT_ANONYMOUS, _, _) =>
// ?
case (CMT_ANONYMOUS, _, _) => ()
case (CMT_TOGGLE_GM, _, _) =>
// ?
case (CMT_TOGGLE_GM, _, _) => ()
case (CMT_CULLWATERMARK, _, contents) =>
ops.commandWatermark(contents)

View file

@ -1,85 +0,0 @@
// Copyright (c) 2024 PSForever
package net.psforever.actors.session.spectator
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}
object GalaxyHandlerLogic {
def apply(ops: SessionGalaxyHandlers): GalaxyHandlerLogic = {
new GalaxyHandlerLogic(ops, ops.context)
}
}
class GalaxyHandlerLogic(val ops: SessionGalaxyHandlers, implicit val context: ActorContext) extends GalaxyHandlerFunctions {
def sessionLogic: SessionData = ops.sessionLogic
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 _ => ()
}
}
}

View file

@ -4,24 +4,19 @@ package net.psforever.actors.session.spectator
import akka.actor.{ActorContext, ActorRef, typed}
import net.psforever.actors.session.AvatarActor
import net.psforever.actors.session.support.{GeneralFunctions, GeneralOperations, SessionData}
import net.psforever.objects.{Account, GlobalDefinitions, LivePlayerList, PlanetSideGameObject, Player, TelepadDeployable, Tool, Vehicle}
import net.psforever.objects.{Account, GlobalDefinitions, LivePlayerList, Player, TelepadDeployable, Tool, Vehicle}
import net.psforever.objects.avatar.{Avatar, Implant}
import net.psforever.objects.ballistics.Projectile
import net.psforever.objects.ce.{Deployable, TelepadLike}
import net.psforever.objects.definition.{BasicDefinition, KitDefinition, SpecialExoSuitDefinition}
import net.psforever.objects.equipment.Equipment
import net.psforever.objects.serverobject.CommonMessages
import net.psforever.objects.serverobject.containable.Containable
import net.psforever.objects.serverobject.doors.Door
import net.psforever.objects.vehicles.{Utility, UtilityType}
import net.psforever.objects.vehicles.Utility.InternalTelepad
import net.psforever.objects.vehicles.Utility
import net.psforever.objects.zones.ZoneProjectile
import net.psforever.packet.PlanetSideGamePacket
import net.psforever.packet.game.{ActionCancelMessage, AvatarFirstTimeEventMessage, AvatarImplantMessage, AvatarJumpMessage, BattleplanMessage, BindPlayerMessage, BugReportMessage, ChangeFireModeMessage, ChangeShortcutBankMessage, CharacterCreateRequestMessage, CharacterRequestMessage, ConnectToWorldRequestMessage, CreateShortcutMessage, DeployObjectMessage, DisplayedAwardMessage, DropItemMessage, EmoteMsg, FacilityBenefitShieldChargeRequestMessage, FriendsRequest, GenericAction, GenericActionMessage, GenericCollisionMsg, GenericObjectActionAtPositionMessage, GenericObjectActionMessage, GenericObjectStateMsg, HitHint, ImplantAction, InvalidTerrainMessage, LootItemMessage, MoveItemMessage, ObjectDetectedMessage, ObjectHeldMessage, PickupItemMessage, PlanetsideAttributeMessage, PlayerStateMessageUpstream, PlayerStateShiftMessage, RequestDestroyMessage, ShiftState, TargetInfo, TargetingImplantRequest, TargetingInfoMessage, TradeMessage, UnuseItemMessage, UseItemMessage, VoiceHostInfo, VoiceHostRequest, ZipLineMessage}
import net.psforever.services.account.AccountPersistenceService
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
import net.psforever.types.{DriveState, ExoSuitType, PlanetSideGUID, Vector3}
import net.psforever.util.Config
import net.psforever.types.{ExoSuitType, PlanetSideGUID, Vector3}
object GeneralLogic {
def apply(ops: GeneralOperations): GeneralLogic = {
@ -168,11 +163,11 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
def handleUseItem(pkt: UseItemMessage): Unit = {
sessionLogic.validObject(pkt.object_guid, decorator = "UseItem") match {
case Some(door: Door) =>
handleUseDoor(door, None)
ops.handleUseDoor(door, None)
case Some(obj: TelepadDeployable) =>
handleUseTelepadDeployable(obj, None, pkt)
ops.handleUseTelepadDeployable(obj, None, pkt, ops.useRouterTelepadSystemSecretly)
case Some(obj: Utility.InternalTelepad) =>
handleUseInternalTelepad(obj, pkt)
ops.handleUseInternalTelepad(obj, pkt, ops.useRouterTelepadSystemSecretly)
case _ => ()
}
}
@ -267,15 +262,10 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
case GenericAction.AwayFromKeyboard_RCV =>
log.info(s"${player.Name} is AFK")
AvatarActor.savePlayerLocation(player)
ops.displayCharSavedMsgThenRenewTimer(fixedLen=1800L, varLen=0L) //~30min
player.AwayFromKeyboard = true
case GenericAction.BackInGame_RCV =>
log.info(s"${player.Name} is back")
player.AwayFromKeyboard = false
ops.renewCharSavedTimer(
Config.app.game.savedMsg.renewal.fixed,
Config.app.game.savedMsg.renewal.variable
)
case GenericAction.LookingForSquad_RCV => //Looking For Squad ON
if (!avatar.lookingForSquad && (sessionLogic.squad.squadUI.isEmpty || sessionLogic.squad.squadUI(player.CharId).index == 0)) {
avatarActor ! AvatarActor.SetLookingForSquad(true)
@ -460,101 +450,6 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
/* supporting functions */
private def handleUseDoor(door: Door, equipment: Option[Equipment]): Unit = {
equipment match {
case Some(tool: Tool) if tool.Definition == GlobalDefinitions.medicalapplicator =>
val distance: Float = math.max(
Config.app.game.doorsCanBeOpenedByMedAppFromThisDistance,
door.Definition.initialOpeningDistance
)
door.Actor ! CommonMessages.Use(player, Some(distance))
case _ =>
door.Actor ! CommonMessages.Use(player)
}
}
private def handleUseTelepadDeployable(obj: TelepadDeployable, equipment: Option[Equipment], msg: UseItemMessage): Unit = {
if (equipment.isEmpty) {
(continent.GUID(obj.Router) match {
case Some(vehicle: Vehicle) => Some((vehicle, vehicle.Utility(UtilityType.internal_router_telepad_deployable)))
case Some(vehicle) => Some(vehicle, None)
case None => None
}) match {
case Some((vehicle: Vehicle, Some(util: Utility.InternalTelepad))) =>
player.WhichSide = vehicle.WhichSide
useRouterTelepadSystem(
router = vehicle,
internalTelepad = util,
remoteTelepad = obj,
src = obj,
dest = util
)
case Some((vehicle: Vehicle, None)) =>
log.error(
s"telepad@${msg.object_guid.guid} is not linked to a router - ${vehicle.Definition.Name}"
)
case Some((o, _)) =>
log.error(
s"telepad@${msg.object_guid.guid} is linked to wrong kind of object - ${o.Definition.Name}, ${obj.Router}"
)
obj.Actor ! Deployable.Deconstruct()
case _ => ()
}
}
}
private def handleUseInternalTelepad(obj: InternalTelepad, msg: UseItemMessage): Unit = {
continent.GUID(obj.Telepad) match {
case Some(pad: TelepadDeployable) =>
player.WhichSide = pad.WhichSide
useRouterTelepadSystem(
router = obj.Owner.asInstanceOf[Vehicle],
internalTelepad = obj,
remoteTelepad = pad,
src = obj,
dest = pad
)
case Some(o) =>
log.error(
s"internal telepad@${msg.object_guid.guid} is not linked to a remote telepad - ${o.Definition.Name}@${o.GUID.guid}"
)
case None => ()
}
}
/**
* A player uses a fully-linked Router teleportation system.
* @param router the Router vehicle
* @param internalTelepad the internal telepad within the Router vehicle
* @param remoteTelepad the remote telepad that is currently associated with this Router
* @param src the origin of the teleportation (where the player starts)
* @param dest the destination of the teleportation (where the player is going)
*/
private def useRouterTelepadSystem(
router: Vehicle,
internalTelepad: InternalTelepad,
remoteTelepad: TelepadDeployable,
src: PlanetSideGameObject with TelepadLike,
dest: PlanetSideGameObject with TelepadLike
): Unit = {
val time = System.currentTimeMillis()
if (
time - ops.recentTeleportAttempt > 2000L && router.DeploymentState == DriveState.Deployed &&
internalTelepad.Active &&
remoteTelepad.Active
) {
val pguid = player.GUID
val sguid = src.GUID
val dguid = dest.GUID
sendResponse(PlayerStateShiftMessage(ShiftState(0, dest.Position, player.Orientation.z)))
ops.useRouterTelepadEffect(pguid, sguid, dguid)
player.Position = dest.Position
} else {
log.warn(s"UseRouterTelepadSystem: ${player.Name} can not teleport")
}
ops.recentTeleportAttempt = time
}
private def administrativeKick(tplayer: Player): Unit = {
log.warn(s"${tplayer.Name} has been kicked by ${player.Name}")
tplayer.death_by = -1

View file

@ -1,257 +0,0 @@
// Copyright (c) 2024 PSForever
package net.psforever.actors.session.spectator
import akka.actor.ActorContext
import net.psforever.actors.session.support.{LocalHandlerFunctions, SessionData, SessionLocalHandlers}
import net.psforever.objects.ce.Deployable
import net.psforever.objects.guid.{GUIDTask, TaskWorkflow}
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, HackState1, 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}
object LocalHandlerLogic {
def apply(ops: SessionLocalHandlers): LocalHandlerLogic = {
new LocalHandlerLogic(ops, ops.context)
}
}
class LocalHandlerLogic(val ops: SessionLocalHandlers, implicit val context: ActorContext) extends LocalHandlerFunctions {
def sessionLogic: SessionData = ops.sessionLogic
/* messages */
def handleTurretDeployableIsDismissed(obj: TurretDeployable): Unit = {
TaskWorkflow.execute(GUIDTask.unregisterDeployableTurret(continent.GUID, obj))
}
def handleDeployableIsDismissed(obj: Deployable): Unit = {
TaskWorkflow.execute(GUIDTask.unregisterObject(continent.GUID, obj))
}
/* 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
ops.deactivateTelpadDeployableMessages(dguid)
//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
ops.deactivateTelpadDeployableMessages(dguid)
//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(HackState1.Unk0, targetGuid, guid, progress=0, unk1.toFloat, 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))
}
}

View file

@ -10,13 +10,11 @@ import net.psforever.objects.{GlobalDefinitions, PlanetSideGameObject, Player, V
import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.serverobject.mount.Mountable
import net.psforever.objects.serverobject.terminals.implant.ImplantTerminalMech
import net.psforever.objects.vehicles.{AccessPermissionGroup, CargoBehavior}
import net.psforever.objects.vital.InGameHistory
import net.psforever.packet.game.{DelayedPathMountMsg, DismountVehicleCargoMsg, DismountVehicleMsg, GenericObjectActionMessage, MountVehicleCargoMsg, MountVehicleMsg, ObjectDetachMessage, 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, PlanetSideGUID, Vector3}
object MountHandlerLogic {
def apply(ops: SessionMountHandlers): MountHandlerLogic = {
@ -29,98 +27,16 @@ class MountHandlerLogic(val ops: SessionMountHandlers, implicit val context: Act
/* packets */
def handleMountVehicle(pkt: MountVehicleMsg): Unit = { /* intentionally blank */ }
def handleMountVehicle(pkt: MountVehicleMsg): Unit = { /* can not mount as spectator */ }
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)
}
}
ops.handleDismountVehicle(pkt)
}
def handleMountVehicleCargo(pkt: MountVehicleCargoMsg): Unit = { /* intentionally blank */ }
def handleMountVehicleCargo(pkt: MountVehicleCargoMsg): Unit = { /* can not mount as spectator */ }
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 _ => ()
}
ops.handleDismountVehicleCargo(pkt)
}
/* response handlers */
@ -134,7 +50,7 @@ class MountHandlerLogic(val ops: SessionMountHandlers, implicit val context: Act
def handle(tplayer: Player, reply: Mountable.Exchange): Unit = {
reply match {
case Mountable.CanDismount(obj: ImplantTerminalMech, seatNum, _) =>
DismountAction(tplayer, obj, seatNum)
ops.DismountAction(tplayer, obj, seatNum)
obj.Zone.actor ! ZoneActor.RemoveFromBlockMap(player)
case Mountable.CanDismount(obj: Vehicle, _, mountPoint)
@ -157,7 +73,7 @@ class MountHandlerLogic(val ops: SessionMountHandlers, implicit val context: Act
//get ready for orbital drop
val pguid = player.GUID
val events = continent.VehicleEvents
DismountAction(tplayer, obj, seatNum)
ops.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(
@ -185,14 +101,14 @@ class MountHandlerLogic(val ops: SessionMountHandlers, implicit val context: Act
case Mountable.CanDismount(obj: Vehicle, seatNum, _)
if obj.Definition == GlobalDefinitions.droppod =>
sessionLogic.general.unaccessContainer(obj)
DismountAction(tplayer, obj, seatNum)
ops.DismountAction(tplayer, obj, seatNum)
obj.Actor ! Vehicle.Deconstruct()
case Mountable.CanDismount(obj: Vehicle, seatNum, _)
if tplayer.GUID == player.GUID =>
sessionLogic.vehicles.ConditionalDriverVehicleControl(obj)
sessionLogic.general.unaccessContainer(obj)
DismountVehicleAction(tplayer, obj, seatNum)
ops.DismountVehicleAction(tplayer, obj, seatNum)
case Mountable.CanDismount(obj: Vehicle, seat_num, _) =>
continent.VehicleEvents ! VehicleServiceMessage(
@ -201,7 +117,7 @@ class MountHandlerLogic(val ops: SessionMountHandlers, implicit val context: Act
)
case Mountable.CanDismount(obj: PlanetSideGameObject with PlanetSideGameObject with Mountable with FactionAffinity with InGameHistory, seatNum, _) =>
DismountAction(tplayer, obj, seatNum)
ops.DismountAction(tplayer, obj, seatNum)
case Mountable.CanDismount(_: Mountable, _, _) => ()
@ -213,90 +129,4 @@ class MountHandlerLogic(val ops: SessionMountHandlers, implicit val context: Act
}
/* 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 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
*/
private 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
)
)
v.Zone.actor ! ZoneActor.RemoveFromBlockMap(player)
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
*/
private 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)
)
}
}

View file

@ -24,9 +24,9 @@ import net.psforever.packet.game.{ChatMsg, CreateShortcutMessage, UnuseItemMessa
class SpectatorModeLogic(data: SessionData) extends ModeLogic {
val avatarResponse: AvatarHandlerFunctions = AvatarHandlerLogic(data.avatarResponse)
val chat: ChatFunctions = ChatLogic(data.chat)
val galaxy: GalaxyHandlerFunctions = GalaxyHandlerLogic(data.galaxyResponseHandlers)
val galaxy: GalaxyHandlerFunctions = net.psforever.actors.session.normal.GalaxyHandlerLogic(data.galaxyResponseHandlers)
val general: GeneralFunctions = GeneralLogic(data.general)
val local: LocalHandlerFunctions = LocalHandlerLogic(data.localResponse)
val local: LocalHandlerFunctions = net.psforever.actors.session.normal.LocalHandlerLogic(data.localResponse)
val mountResponse: MountHandlerFunctions = MountHandlerLogic(data.mountResponse)
val shooting: WeaponAndProjectileFunctions = WeaponAndProjectileLogic(data.shooting)
val squad: SquadHandlerFunctions = SquadHandlerLogic(data.squad)
@ -118,7 +118,6 @@ class SpectatorModeLogic(data: SessionData) extends ModeLogic {
data.chat.commandIncomingSilence(session, ChatMsg(ChatMessageType.CMT_SILENCE, "player 0"))
}
//
player.spectator = true
data.chat.JoinChannel(SpectatorChannel)
val newPlayer = SpectatorModeLogic.spectatorCharacter(player)
newPlayer.LogActivity(player.History.headOption)
@ -159,7 +158,6 @@ class SpectatorModeLogic(data: SessionData) extends ModeLogic {
.map(CreateShortcutMessage(pguid, _, None))
.foreach(sendResponse)
data.chat.LeaveChannel(SpectatorChannel)
player.spectator = false
sendResponse(ObjectDeleteMessage(player.avatar.locker.GUID, 0)) //free up the slot
sendResponse(ChatMsg(ChatMessageType.CMT_TOGGLESPECTATORMODE, "off"))
sendResponse(ChatMsg(ChatMessageType.UNK_227, "@SpectatorDisabled"))
@ -212,6 +210,7 @@ object SpectatorModeLogic {
newPlayer.Position = player.Position
newPlayer.Orientation = player.Orientation
newPlayer.spectator = true
newPlayer.bops = true
newPlayer.Spawn()
newPlayer
}

View file

@ -308,13 +308,13 @@ class VehicleHandlerLogic(val ops: SessionVehicleHandlers, implicit val context:
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
))
val str = s"${data.getOrElse("The vehicle spawn pad where you placed your order is blocked.")}"
val msg = if (str.contains("@")) {
ChatMsg(ChatMessageType.UNK_229, str)
} else {
ChatMsg(ChatMessageType.CMT_OPEN, wideContents = true, recipient = "", str, note = None)
}
sendResponse(msg)
case VehicleResponse.PeriodicReminder(_, data) =>
val (isType, flag, msg): (ChatMessageType, Boolean, String) = data match {

View file

@ -1,14 +1,11 @@
// Copyright (c) 2024 PSForever
package net.psforever.actors.session.spectator
import akka.actor.{ActorContext, typed}
import net.psforever.actors.session.AvatarActor
import akka.actor.ActorContext
import net.psforever.actors.session.support.{SessionData, VehicleFunctions, VehicleOperations}
import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.{Vehicle, Vehicles}
import net.psforever.objects.Vehicle
import net.psforever.objects.serverobject.deploy.Deployment
import net.psforever.objects.serverobject.mount.Mountable
import net.psforever.objects.vehicles.control.BfrFlight
import net.psforever.packet.game.{ChildObjectStateMessage, DeployRequestMessage, FrameVehicleStateMessage, VehicleStateMessage, VehicleSubStateMessage}
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
import net.psforever.types.{DriveState, Vector3}
@ -22,210 +19,15 @@ object VehicleLogic {
class VehicleLogic(val ops: VehicleOperations, implicit val context: ActorContext) extends VehicleFunctions {
def sessionLogic: SessionData = ops.sessionLogic
private val avatarActor: typed.ActorRef[AvatarActor.Command] = ops.avatarActor
//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
ops.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 handleVehicleState(pkt: VehicleStateMessage): Unit = { /* can not drive vehicle as spectator */ }
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
ops.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 handleFrameVehicleState(pkt: FrameVehicleStateMessage): Unit = { /* can not drive vehicle as spectator */ }
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 => ()
case Some(_) => player.Orientation = Vector3(0f, pitch, yaw)
}
if (player.death_by == -1) {
sessionLogic.kickedByAdministration()
}
}
def handleChildObjectState(pkt: ChildObjectStateMessage): Unit = { /* can not drive vehicle as spectator */ }
def handleVehicleSubState(pkt: VehicleSubStateMessage): Unit = {
val VehicleSubStateMessage(vehicle_guid, _, pos, ang, vel, unk1, _) = pkt
@ -258,22 +60,7 @@ class VehicleLogic(val ops: VehicleOperations, implicit val context: ActorContex
}
}
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) =>
if (obj.DeploymentState == DriveState.Deployed) {
obj.Actor ! Deployment.TryDeploymentChange(deploy_state)
}
case _ => ()
avatarActor ! AvatarActor.SetVehicle(None)
}
}
}
}
def handleDeployRequest(pkt: DeployRequestMessage): Unit = { /* can not drive vehicle as spectator */ }
/* messages */

View file

@ -4,12 +4,10 @@ package net.psforever.actors.session.spectator
import akka.actor.ActorContext
import net.psforever.actors.session.support.{SessionData, WeaponAndProjectileFunctions, WeaponAndProjectileOperations}
import net.psforever.login.WorldSession.{CountGrenades, FindEquipmentStock, FindToolThatUses, RemoveOldEquipmentFromInventory}
import net.psforever.objects.ballistics.Projectile
import net.psforever.objects.equipment.ChargeFireModeDefinition
import net.psforever.objects.inventory.Container
import net.psforever.objects.serverobject.CommonMessages
import net.psforever.objects.{AmmoBox, BoomerDeployable, BoomerTrigger, GlobalDefinitions, PlanetSideGameObject, Tool}
import net.psforever.packet.game.{AIDamage, AvatarGrenadeStateMessage, ChangeAmmoMessage, ChangeFireModeMessage, ChangeFireStateMessage_Start, ChangeFireStateMessage_Stop, HitMessage, InventoryStateMessage, LashMessage, LongRangeProjectileInfoMessage, ProjectileStateMessage, QuantityUpdateMessage, ReloadMessage, SplashHitMessage, UplinkRequest, UplinkRequestType, UplinkResponse, WeaponDelayFireMessage, WeaponDryFireMessage, WeaponFireMessage, WeaponLazeTargetPositionMessage}
import net.psforever.objects.{BoomerDeployable, BoomerTrigger, GlobalDefinitions, Tool}
import net.psforever.packet.game.{AIDamage, AvatarGrenadeStateMessage, ChangeAmmoMessage, ChangeFireModeMessage, ChangeFireStateMessage_Start, ChangeFireStateMessage_Stop, HitMessage, LashMessage, LongRangeProjectileInfoMessage, ProjectileStateMessage, QuantityUpdateMessage, ReloadMessage, SplashHitMessage, UplinkRequest, UplinkRequestType, UplinkResponse, WeaponDelayFireMessage, WeaponDryFireMessage, WeaponFireMessage, WeaponLazeTargetPositionMessage}
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
import net.psforever.types.PlanetSideGUID
@ -85,32 +83,7 @@ class WeaponAndProjectileLogic(val ops: WeaponAndProjectileOperations, implicit
def handleChangeFireMode(pkt: ChangeFireModeMessage): Unit = { /* intentionally blank */ }
def handleProjectileState(pkt: ProjectileStateMessage): Unit = {
val ProjectileStateMessage(projectile_guid, shot_pos, shot_vel, shot_orient, seq, end, target_guid) = pkt
val index = projectile_guid.guid - Projectile.baseUID
ops.projectiles(index) match {
case Some(projectile) if projectile.HasGUID =>
val projectileGlobalUID = projectile.GUID
projectile.Position = shot_pos
projectile.Orientation = shot_orient
projectile.Velocity = shot_vel
continent.AvatarEvents ! AvatarServiceMessage(
continent.id,
AvatarAction.ProjectileState(
player.GUID,
projectileGlobalUID,
shot_pos,
shot_vel,
shot_orient,
seq,
end,
target_guid
)
)
case _ if seq == 0 =>
/* missing the first packet in the sequence is permissible */
case _ =>
log.warn(s"ProjectileState: constructed projectile ${projectile_guid.guid} can not be found")
}
ops.handleProjectileState(pkt)
}
def handleLongRangeProjectileState(pkt: LongRangeProjectileInfoMessage): Unit = { /* intentionally blank */ }
@ -148,11 +121,11 @@ class WeaponAndProjectileLogic(val ops: WeaponAndProjectileOperations, implicit
RemoveOldEquipmentFromInventory(player)(x.obj)
sumReloadValue
} else {
ModifyAmmunition(player)(box.AmmoSlot.Box, 3 - tailReloadValue)
ops.modifyAmmunition(player)(box.AmmoSlot.Box, 3 - tailReloadValue)
3
}
log.info(s"${player.Name} found $actualReloadValue more $ammoType grenades to throw")
ModifyAmmunition(player)(
ops.modifyAmmunition(player)(
tool.AmmoSlot.Box,
-actualReloadValue
) //grenade item already in holster (negative because empty)
@ -163,20 +136,6 @@ class WeaponAndProjectileLogic(val ops: WeaponAndProjectileOperations, implicit
}
}
/**
* Given an object that contains a box of amunition in its `Inventory` at a certain location,
* change the amount of ammunition within that box.
* @param obj the `Container`
* @param box an `AmmoBox` to modify
* @param reloadValue the value to modify the `AmmoBox`;
* subtracted from the current `Capacity` of `Box`
*/
private def ModifyAmmunition(obj: PlanetSideGameObject with Container)(box: AmmoBox, reloadValue: Int): Unit = {
val capacity = box.Capacity - reloadValue
box.Capacity = capacity
sendResponse(InventoryStateMessage(box.GUID, obj.GUID, capacity))
}
private def fireStateStartPlayerMessages(itemGuid: PlanetSideGUID): Unit = {
continent.AvatarEvents ! AvatarServiceMessage(
continent.id,

View file

@ -5,11 +5,7 @@ import akka.actor.Cancellable
import akka.actor.typed.ActorRef
import akka.actor.{ActorContext, typed}
import net.psforever.actors.session.{AvatarActor, SessionActor}
import net.psforever.actors.session.normal.{NormalMode => SessionNormalMode}
import net.psforever.actors.session.spectator.{SpectatorMode => SessionSpectatorMode}
import net.psforever.actors.session.csr.{CustomerServiceRepresentativeMode => SessionCustomerServiceRepresentativeMode}
import net.psforever.actors.zone.ZoneActor
import net.psforever.objects.avatar.ModePermissions
import net.psforever.objects.sourcing.PlayerSource
import net.psforever.objects.zones.ZoneInfo
import net.psforever.packet.game.SetChatFilterMessage
@ -69,7 +65,7 @@ class ChatOperations(
*/
private val ignoredEmoteCooldown: mutable.LongMap[Long] = mutable.LongMap[Long]()
private[session] var SpectatorMode: PlayerMode = SessionSpectatorMode
private[session] var SpectatorMode: PlayerMode = SpectatorMode
import akka.actor.typed.scaladsl.adapter._
private val chatServiceAdapter: ActorRef[ChatService.MessageResponse] = context.self.toTyped[ChatService.MessageResponse]
@ -116,17 +112,6 @@ class ChatOperations(
sendResponse(message.copy(contents = f"$speed%.3f"))
}
def commandToggleSpectatorMode(contents: String): Unit = {
val currentSpectatorActivation = (if (avatar != null) avatar.permissions else ModePermissions()).canSpectate
contents.toLowerCase() match {
case "on" | "o" | "" if !currentSpectatorActivation =>
context.self ! SessionActor.SetMode(SessionSpectatorMode)
case "off" | "of" if currentSpectatorActivation =>
context.self ! SessionActor.SetMode(SessionNormalMode)
case _ => ()
}
}
def commandRecall(session: Session): Unit = {
val player = session.player
val errorMessage = session.zoningType match {
@ -1257,18 +1242,6 @@ class ChatOperations(
true
}
def customCommandModerator(contents: String): Boolean = {
val currentCsrActivation = (if (avatar != null) avatar.permissions else ModePermissions()).canGM
contents.toLowerCase() match {
case "on" | "o" | "" if currentCsrActivation =>
context.self ! SessionActor.SetMode(SessionCustomerServiceRepresentativeMode)
case "off" | "of" if currentCsrActivation =>
context.self ! SessionActor.SetMode(SessionNormalMode)
case _ => ()
}
true
}
def firstParam[T](
session: Session,
buffer: Iterable[String],

View file

@ -3,7 +3,20 @@ package net.psforever.actors.session.support
import akka.actor.{ActorContext, ActorRef, Cancellable, typed}
import net.psforever.objects.serverobject.containable.Containable
import net.psforever.objects.sourcing.PlayerSource
import net.psforever.objects.serverobject.doors.Door
import net.psforever.objects.serverobject.interior.Sidedness
import net.psforever.objects.serverobject.mblocker.Locker
import net.psforever.objects.serverobject.resourcesilo.ResourceSilo
import net.psforever.objects.serverobject.structures.Building
import net.psforever.objects.serverobject.terminals.capture.CaptureTerminal
import net.psforever.objects.serverobject.terminals.{MatrixTerminalDefinition, Terminal}
import net.psforever.objects.serverobject.tube.SpawnTube
import net.psforever.objects.serverobject.turret.FacilityTurret
import net.psforever.objects.sourcing.{PlayerSource, VehicleSource}
import net.psforever.objects.vehicles.Utility.InternalTelepad
import net.psforever.objects.zones.blockmap.BlockMapEntity
import net.psforever.services.RemoverActor
import net.psforever.services.local.{LocalAction, LocalServiceMessage}
import scala.collection.mutable
import scala.concurrent.ExecutionContext.Implicits.global
@ -560,6 +573,66 @@ class GeneralOperations(
parent.Find(objectGuid).flatMap { slot => Some((parent, Some(slot))) }
}
/**
* A simple object searching algorithm that is limited to containers currently known and accessible by the player.
* If all relatively local containers are checked and the object is not found,
* the player's locker inventory will be checked, and then
* the game environment (items on the ground) will be checked too.
* If the target object is discovered, it is removed from its current location and is completely destroyed.
* @see `RequestDestroyMessage`
* @see `Zone.ItemIs.Where`
* @param objectGuid the target object's globally unique identifier;
* it is not expected that the object will be unregistered, but it is also not gauranteed
* @param obj the target object
* @return `true`, if the target object was discovered and removed;
* `false`, otherwise
*/
def findEquipmentToDelete(objectGuid: PlanetSideGUID, obj: Equipment): Boolean = {
val findFunc
: PlanetSideServerObject with Container => Option[(PlanetSideServerObject with Container, Option[Int])] =
findInLocalContainer(objectGuid)
findFunc(player)
.orElse(accessedContainer match {
case Some(parent: PlanetSideServerObject) =>
findFunc(parent)
case _ =>
None
})
.orElse(sessionLogic.vehicles.findLocalVehicle match {
case Some(parent: PlanetSideServerObject) =>
findFunc(parent)
case _ =>
None
}) match {
case Some((parent, Some(_))) =>
obj.Position = Vector3.Zero
RemoveOldEquipmentFromInventory(parent)(obj)
true
case _ if player.avatar.locker.Inventory.Remove(objectGuid) =>
sendResponse(ObjectDeleteMessage(objectGuid, 0))
true
case _ if continent.EquipmentOnGround.contains(obj) =>
obj.Position = Vector3.Zero
continent.Ground ! Zone.Ground.RemoveItem(objectGuid)
continent.AvatarEvents ! AvatarServiceMessage.Ground(RemoverActor.ClearSpecific(List(obj), continent))
true
case _ =>
Zone.EquipmentIs.Where(obj, objectGuid, continent) match {
case None =>
true
case Some(Zone.EquipmentIs.Orphaned()) if obj.HasGUID =>
TaskWorkflow.execute(GUIDTask.unregisterEquipment(continent.GUID, obj))
true
case Some(Zone.EquipmentIs.Orphaned()) =>
true
case _ =>
log.warn(s"RequestDestroy: equipment $obj exists, but ${player.Name} can not reach it to dispose of it")
false
}
}
}
/**
* na
* @param targetGuid na
@ -767,6 +840,407 @@ class GeneralOperations(
)
}
def handleDeployObject(
zone: Zone,
deployableType: DeployedItem.Value,
position: Vector3,
orientation: Vector3,
side: Sidedness,
faction: PlanetSideEmpire.Value,
optionalOwnerBuiltWith: Option[(Player, ConstructionItem)]
): Unit = {
val deployableEntity: Deployable = Deployables.Make(deployableType)()
deployableEntity.Position = position
deployableEntity.Orientation = orientation
deployableEntity.WhichSide = side
deployableEntity.Faction = faction
val tasking: TaskBundle = deployableEntity match {
case turret: TurretDeployable =>
GUIDTask.registerDeployableTurret(zone.GUID, turret)
case _ =>
GUIDTask.registerObject(zone.GUID, deployableEntity)
}
val zoneBuildCommand = optionalOwnerBuiltWith
.collect { case (owner, tool) =>
deployableEntity.AssignOwnership(owner)
Zone.Deployable.BuildByOwner(deployableEntity, owner, tool)
}
.getOrElse(Zone.Deployable.Build(deployableEntity))
//execute
TaskWorkflow.execute(CallBackForTask(tasking, zone.Deployables, zoneBuildCommand, context.self))
}
def handleUseDoor(door: Door, equipment: Option[Equipment]): Unit = {
equipment match {
case Some(tool: Tool) if tool.Definition == GlobalDefinitions.medicalapplicator =>
val distance: Float = math.max(
Config.app.game.doorsCanBeOpenedByMedAppFromThisDistance,
door.Definition.initialOpeningDistance
)
door.Actor ! CommonMessages.Use(player, Some(distance))
case _ =>
door.Actor ! CommonMessages.Use(player)
}
}
def handleUseResourceSilo(resourceSilo: ResourceSilo, equipment: Option[Equipment]): Unit = {
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
val vehicleOpt = continent.GUID(player.avatar.vehicle)
(vehicleOpt, equipment) match {
case (Some(vehicle: Vehicle), Some(item))
if GlobalDefinitions.isBattleFrameVehicle(vehicle.Definition) &&
GlobalDefinitions.isBattleFrameNTUSiphon(item.Definition) =>
resourceSilo.Actor ! CommonMessages.Use(player, Some(vehicle))
case (Some(vehicle: Vehicle), _)
if vehicle.Definition == GlobalDefinitions.ant &&
vehicle.DeploymentState == DriveState.Deployed &&
Vector3.DistanceSquared(resourceSilo.Position.xy, vehicle.Position.xy) < math.pow(resourceSilo.Definition.UseRadius, 2) =>
resourceSilo.Actor ! CommonMessages.Use(player, Some(vehicle))
case _ => ()
}
}
def handleUsePlayer(obj: Player, equipment: Option[Equipment], msg: UseItemMessage): Unit = {
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
if (obj.isBackpack) {
if (equipment.isEmpty) {
log.info(s"${player.Name} is looting the corpse of ${obj.Name}")
sendResponse(msg)
accessContainer(obj)
}
} else if (!msg.unk3 && player.isAlive) { //potential kit use
(continent.GUID(msg.item_used_guid), kitToBeUsed) match {
case (Some(kit: Kit), None) =>
kitToBeUsed = Some(msg.item_used_guid)
player.Actor ! CommonMessages.Use(player, Some(kit))
case (Some(_: Kit), Some(_)) | (None, Some(_)) =>
//a kit is already queued to be used; ignore this request
sendResponse(ChatMsg(ChatMessageType.UNK_225, wideContents=false, "", "Please wait ...", None))
case (Some(item), _) =>
log.error(s"UseItem: ${player.Name} looking for Kit to use, but found $item instead")
case (None, None) =>
log.warn(s"UseItem: anticipated a Kit ${msg.item_used_guid} for ${player.Name}, but can't find it") }
} else if (msg.object_id == ObjectClass.avatar && msg.unk3) {
equipment match {
case Some(tool: Tool) if tool.Definition == GlobalDefinitions.bank =>
obj.Actor ! CommonMessages.Use(player, equipment)
case Some(tool: Tool) if tool.Definition == GlobalDefinitions.medicalapplicator =>
obj.Actor ! CommonMessages.Use(player, equipment)
case _ => ()
}
}
}
def handleUseLocker(locker: Locker, equipment: Option[Equipment], msg: UseItemMessage): Unit = {
equipment match {
case Some(item) =>
sendUseGeneralEntityMessage(locker, item)
case None if locker.Faction == player.Faction || locker.HackedBy.nonEmpty =>
log.info(s"${player.Name} is accessing a locker")
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
val playerLocker = player.avatar.locker
sendResponse(msg.copy(object_guid = playerLocker.GUID, object_id = 456))
accessContainer(playerLocker)
case _ => ()
}
}
def handleUseCaptureTerminal(captureTerminal: CaptureTerminal, equipment: Option[Equipment]): Unit = {
equipment match {
case Some(item) =>
sendUseGeneralEntityMessage(captureTerminal, item)
case _ if specialItemSlotGuid.nonEmpty =>
continent.GUID(specialItemSlotGuid) match {
case Some(llu: CaptureFlag) =>
if (llu.Target.GUID == captureTerminal.Owner.GUID) {
continent.LocalEvents ! LocalServiceMessage(continent.id, LocalAction.LluCaptured(llu))
} else {
log.info(
s"LLU target is not this base. Target GUID: ${llu.Target.GUID} This base: ${captureTerminal.Owner.GUID}"
)
}
case _ => log.warn("Item in specialItemSlotGuid is not registered with continent or is not a LLU")
}
case _ => ()
}
}
def handleUseFacilityTurret(obj: FacilityTurret, equipment: Option[Equipment], msg: UseItemMessage): Unit = {
equipment.foreach { item =>
sendUseGeneralEntityMessage(obj, item)
obj.Actor ! CommonMessages.Use(player, Some((item, msg.unk2.toInt))) //try upgrade path
}
}
def handleUseVehicle(obj: Vehicle, equipment: Option[Equipment], msg: UseItemMessage): Unit = {
equipment match {
case Some(item) =>
sendUseGeneralEntityMessage(obj, item)
case None if player.Faction == obj.Faction =>
//access to trunk
if (
obj.AccessingTrunk.isEmpty &&
(!obj.PermissionGroup(AccessPermissionGroup.Trunk.id).contains(VehicleLockState.Locked) || obj.OwnerGuid
.contains(player.GUID))
) {
log.info(s"${player.Name} is looking in the ${obj.Definition.Name}'s trunk")
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
obj.AccessingTrunk = player.GUID
accessContainer(obj)
sendResponse(msg)
}
case _ => ()
}
}
def handleUseTerminal(terminal: Terminal, equipment: Option[Equipment], msg: UseItemMessage): Unit = {
equipment match {
case Some(item) =>
sendUseGeneralEntityMessage(terminal, item)
case None
if terminal.Owner == Building.NoBuilding || terminal.Faction == player.Faction ||
terminal.HackedBy.nonEmpty || terminal.Faction == PlanetSideEmpire.NEUTRAL =>
val tdef = terminal.Definition
if (tdef.isInstanceOf[MatrixTerminalDefinition]) {
//TODO matrix spawn point; for now, just blindly bind to show work (and hope nothing breaks)
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
sendResponse(
BindPlayerMessage(BindStatus.Bind, "", display_icon=true, logging=true, SpawnGroup.Sanctuary, 0, 0, terminal.Position)
)
} else if (
tdef == GlobalDefinitions.multivehicle_rearm_terminal || tdef == GlobalDefinitions.bfr_rearm_terminal ||
tdef == GlobalDefinitions.air_rearm_terminal || tdef == GlobalDefinitions.ground_rearm_terminal
) {
sessionLogic.vehicles.findLocalVehicle match {
case Some(vehicle) =>
log.info(
s"${player.Name} is accessing a ${terminal.Definition.Name} for ${player.Sex.possessive} ${vehicle.Definition.Name}"
)
sendResponse(msg)
sendResponse(msg.copy(object_guid = vehicle.GUID, object_id = vehicle.Definition.ObjectId))
case None =>
log.error(s"UseItem: Expecting a seated vehicle, ${player.Name} found none")
}
} else if (tdef == GlobalDefinitions.teleportpad_terminal) {
//explicit request
log.info(s"${player.Name} is purchasing a router telepad")
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
terminal.Actor ! Terminal.Request(
player,
ItemTransactionMessage(msg.object_guid, TransactionType.Buy, 0, "router_telepad", 0, PlanetSideGUID(0))
)
} else if (tdef == GlobalDefinitions.targeting_laser_dispenser) {
//explicit request
log.info(s"${player.Name} is purchasing a targeting laser")
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
terminal.Actor ! Terminal.Request(
player,
ItemTransactionMessage(msg.object_guid, TransactionType.Buy, 0, "flail_targeting_laser", 0, PlanetSideGUID(0))
)
} else {
log.info(s"${player.Name} is accessing a ${terminal.Definition.Name}")
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
sendResponse(msg)
}
case _ => ()
}
}
def handleUseSpawnTube(obj: SpawnTube, equipment: Option[Equipment]): Unit = {
equipment match {
case Some(item) =>
sendUseGeneralEntityMessage(obj, item)
case None if player.Faction == obj.Faction =>
//deconstruction
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
sessionLogic.actionsToCancel()
sessionLogic.terminals.CancelAllProximityUnits()
sessionLogic.zoning.spawn.startDeconstructing(obj)
case _ => ()
}
}
def handleUseTelepadDeployable(
obj: TelepadDeployable,
equipment: Option[Equipment],
msg: UseItemMessage,
useTelepadFunc: (Vehicle, InternalTelepad, TelepadDeployable, PlanetSideGameObject with TelepadLike, PlanetSideGameObject with TelepadLike) => Unit
): Unit = {
if (equipment.isEmpty) {
(continent.GUID(obj.Router) match {
case Some(vehicle: Vehicle) => Some((vehicle, vehicle.Utility(UtilityType.internal_router_telepad_deployable)))
case Some(vehicle) => Some(vehicle, None)
case None => None
}) match {
case Some((vehicle: Vehicle, Some(util: Utility.InternalTelepad))) =>
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel")
player.WhichSide = vehicle.WhichSide
useTelepadFunc(vehicle, util, obj, obj, util)
case Some((vehicle: Vehicle, None)) =>
log.error(
s"telepad@${msg.object_guid.guid} is not linked to a router - ${vehicle.Definition.Name}"
)
case Some((o, _)) =>
log.error(
s"telepad@${msg.object_guid.guid} is linked to wrong kind of object - ${o.Definition.Name}, ${obj.Router}"
)
obj.Actor ! Deployable.Deconstruct()
case _ => ()
}
}
}
def handleUseInternalTelepad(
obj: InternalTelepad,
msg: UseItemMessage,
useTelepadFunc: (Vehicle, InternalTelepad, TelepadDeployable, PlanetSideGameObject with TelepadLike, PlanetSideGameObject with TelepadLike) => Unit
): Unit = {
continent.GUID(obj.Telepad) match {
case Some(pad: TelepadDeployable) =>
player.WhichSide = pad.WhichSide
useTelepadFunc(obj.Owner.asInstanceOf[Vehicle], obj, pad, obj, pad)
case Some(o) =>
log.error(
s"internal telepad@${msg.object_guid.guid} is not linked to a remote telepad - ${o.Definition.Name}@${o.GUID.guid}"
)
case None => ()
}
}
/**
* A player uses a fully-linked Router teleportation system.
* @param router the Router vehicle
* @param internalTelepad the internal telepad within the Router vehicle
* @param remoteTelepad the remote telepad that is currently associated with this Router
* @param src the origin of the teleportation (where the player starts)
* @param dest the destination of the teleportation (where the player is going)
*/
def useRouterTelepadSystem(
router: Vehicle,
internalTelepad: InternalTelepad,
remoteTelepad: TelepadDeployable,
src: PlanetSideGameObject with TelepadLike,
dest: PlanetSideGameObject with TelepadLike
): Unit = {
val time = System.currentTimeMillis()
if (
time - recentTeleportAttempt > 2000L && router.DeploymentState == DriveState.Deployed &&
internalTelepad.Active &&
remoteTelepad.Active
) {
val pguid = player.GUID
val sguid = src.GUID
val dguid = dest.GUID
sendResponse(PlayerStateShiftMessage(ShiftState(0, dest.Position, player.Orientation.z)))
useRouterTelepadEffect(pguid, sguid, dguid)
continent.LocalEvents ! LocalServiceMessage(
continent.id,
LocalAction.RouterTelepadTransport(pguid, pguid, sguid, dguid)
)
val vSource = VehicleSource(router)
val zoneNumber = continent.Number
player.LogActivity(VehicleMountActivity(vSource, PlayerSource(player), zoneNumber))
player.Position = dest.Position
player.LogActivity(VehicleDismountActivity(vSource, PlayerSource(player), zoneNumber))
} else {
log.warn(s"UseRouterTelepadSystem: ${player.Name} can not teleport")
}
recentTeleportAttempt = time
}
/**
* A player uses a fully-linked Router teleportation system.
* @param router the Router vehicle
* @param internalTelepad the internal telepad within the Router vehicle
* @param remoteTelepad the remote telepad that is currently associated with this Router
* @param src the origin of the teleportation (where the player starts)
* @param dest the destination of the teleportation (where the player is going)
*/
def useRouterTelepadSystemSecretly(
router: Vehicle,
internalTelepad: InternalTelepad,
remoteTelepad: TelepadDeployable,
src: PlanetSideGameObject with TelepadLike,
dest: PlanetSideGameObject with TelepadLike
): Unit = {
val time = System.currentTimeMillis()
if (
time - recentTeleportAttempt > 2000L && router.DeploymentState == DriveState.Deployed &&
internalTelepad.Active &&
remoteTelepad.Active
) {
val pguid = player.GUID
val sguid = src.GUID
val dguid = dest.GUID
sendResponse(PlayerStateShiftMessage(ShiftState(0, dest.Position, player.Orientation.z)))
useRouterTelepadEffect(pguid, sguid, dguid)
player.Position = dest.Position
} else {
log.warn(s"UseRouterTelepadSystem: ${player.Name} can not teleport")
}
recentTeleportAttempt = time
}
def handleUseCaptureFlag(obj: CaptureFlag): Unit = {
// LLU can normally only be picked up the faction that owns it
specialItemSlotGuid match {
case None if obj.Faction == player.Faction =>
specialItemSlotGuid = Some(obj.GUID)
player.Carrying = SpecialCarry.CaptureFlag
continent.LocalEvents ! CaptureFlagManager.PickupFlag(obj, player)
case None =>
log.warn(s"${player.Faction} player ${player.toString} tried to pick up a ${obj.Faction} LLU - ${obj.GUID}")
case Some(guid) if guid != obj.GUID =>
// Ignore duplicate pickup requests
log.warn(
s"${player.Faction} player ${player.toString} tried to pick up a ${obj.Faction} LLU, but their special slot already contains $guid"
)
case _ => ()
}
}
def handleUseWarpGate(equipment: Option[Equipment]): Unit = {
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
(continent.GUID(player.VehicleSeated), equipment) match {
case (Some(vehicle: Vehicle), Some(item))
if GlobalDefinitions.isBattleFrameVehicle(vehicle.Definition) &&
GlobalDefinitions.isBattleFrameNTUSiphon(item.Definition) =>
vehicle.Actor ! CommonMessages.Use(player, equipment)
case _ => ()
}
}
def handleUseGeneralEntity(obj: PlanetSideServerObject, equipment: Option[Equipment]): Unit = {
equipment.foreach { item =>
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
obj.Actor ! CommonMessages.Use(player, Some(item))
}
}
def sendUseGeneralEntityMessage(obj: PlanetSideServerObject, equipment: Equipment): Unit = {
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
obj.Actor ! CommonMessages.Use(player, Some(equipment))
}
def handleUseDefaultEntity(obj: PlanetSideGameObject, equipment: Option[Equipment]): Unit = {
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
equipment match {
case Some(item)
if GlobalDefinitions.isBattleFrameArmorSiphon(item.Definition) ||
GlobalDefinitions.isBattleFrameNTUSiphon(item.Definition) => ()
case _ =>
log.warn(s"UseItem: ${player.Name} does not know how to handle $obj")
}
}
def commonFacilityShieldCharging(obj: PlanetSideServerObject with BlockMapEntity): Unit = {
obj.Actor ! CommonMessages.ChargeShields(
15,
Some(continent.blockMap.sector(obj).buildingList.maxBy(_.Definition.SOIRadius))
)
}
override protected[session] def actionsToCancel(): Unit = {
progressBarValue = None
kitToBeUsed = None

View file

@ -6,9 +6,9 @@ import net.psforever.objects.{Players, TurretDeployable}
import net.psforever.objects.ce.Deployable
import net.psforever.objects.guid.{GUIDTask, TaskWorkflow}
import net.psforever.objects.serverobject.interior.Sidedness
import net.psforever.packet.game.GenericObjectActionMessage
import net.psforever.packet.game.{GenericObjectActionMessage, ObjectDeleteMessage, PlanetsideAttributeMessage, TriggerEffectMessage}
import net.psforever.services.local.LocalResponse
import net.psforever.types.PlanetSideGUID
import net.psforever.types.{PlanetSideGUID, Vector3}
trait LocalHandlerFunctions extends CommonSessionInterfacingFunctionality {
def ops: SessionLocalHandlers
@ -47,4 +47,26 @@ class SessionLocalHandlers(
else
400f
}
/**
* 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))
}
}

View file

@ -2,15 +2,21 @@
package net.psforever.actors.session.support
import akka.actor.{ActorContext, typed}
import net.psforever.objects.Tool
import net.psforever.objects.vehicles.MountableWeapons
import net.psforever.packet.game.{DismountVehicleCargoMsg, InventoryStateMessage, MountVehicleCargoMsg, MountVehicleMsg}
import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.{PlanetSideGameObject, Tool, Vehicle}
import net.psforever.objects.vehicles.{CargoBehavior, MountableWeapons}
import net.psforever.objects.vital.InGameHistory
import net.psforever.packet.game.{DismountVehicleCargoMsg, InventoryStateMessage, MountVehicleCargoMsg, MountVehicleMsg, ObjectAttachMessage}
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
import net.psforever.types.{BailType, PlanetSideGUID, Vector3}
//
import net.psforever.actors.session.AvatarActor
import net.psforever.objects.Player
import net.psforever.objects.serverobject.mount.Mountable
import net.psforever.packet.game.DismountVehicleMsg
import scala.concurrent.duration._
trait MountHandlerFunctions extends CommonSessionInterfacingFunctionality {
val ops: SessionMountHandlers
@ -30,6 +36,218 @@ class SessionMountHandlers(
val avatarActor: typed.ActorRef[AvatarActor.Command],
implicit val context: ActorContext
) extends CommonSessionInterfacingFunctionality {
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
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 _ => ()
}
}
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)
)
}
/**
* 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)

View file

@ -15,6 +15,8 @@ import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID, Vector3}
trait SquadHandlerFunctions extends CommonSessionInterfacingFunctionality {
val ops: SessionSquadHandlers
protected var waypointCooldown: Long = 0L
def handleSquadDefinitionAction(pkt: SquadDefinitionActionMessage): Unit
def handleSquadMemberRequest(pkt: SquadMembershipRequest): Unit

View file

@ -2,8 +2,12 @@
package net.psforever.actors.session.support
import akka.actor.{ActorContext, typed}
import net.psforever.objects.guid.GUIDTask
import net.psforever.packet.game.FavoritesRequest
import net.psforever.objects.guid.{GUIDTask, TaskWorkflow}
import net.psforever.objects.inventory.InventoryItem
import net.psforever.objects.sourcing.AmenitySource
import net.psforever.objects.vital.TerminalUsedActivity
import net.psforever.packet.game.{FavoritesAction, FavoritesRequest, ItemTransactionResultMessage, UnuseItemMessage}
import net.psforever.types.{TransactionType, Vector3}
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
@ -39,6 +43,99 @@ class SessionTerminalHandlers(
private[session] var lastTerminalOrderFulfillment: Boolean = true
private[session] var usingMedicalTerminal: Option[PlanetSideGUID] = None
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
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) =>
performProximityTerminalUse(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
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")
}
}
def buyVehicle(
terminalGuid: PlanetSideGUID,
transactionType: TransactionType.Value,
vehicle: Vehicle,
weapons: List[InventoryItem],
trunk: List[InventoryItem]
): Unit = {
continent.map.terminalToSpawnPad
.find { case (termid, _) => termid == terminalGuid.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 = player.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 = player.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 = player.Faction
vTrunk.InsertQuickly(entry.start, entry.obj)
}
TaskWorkflow.execute(registerVehicleFromSpawnPad(vehicle, pad, term))
sendResponse(ItemTransactionResultMessage(terminalGuid, TransactionType.Buy, success = true))
if (GlobalDefinitions.isBattleFrameVehicle(vehicle.Definition)) {
sendResponse(UnuseItemMessage(player.GUID, terminalGuid))
}
player.LogActivity(TerminalUsedActivity(AmenitySource(term), transactionType))
}
.orElse {
log.error(
s"${player.Name} wanted to spawn a vehicle, but there was no spawn pad associated with terminal ${terminalGuid.guid} to accept it"
)
sendResponse(ItemTransactionResultMessage(terminalGuid, TransactionType.Buy, success = false))
None
}
}
/**
* Construct tasking that adds a completed and registered vehicle into the scene.
* The major difference between `RegisterVehicle` and `RegisterVehicleFromSpawnPad` is the assumption that this vehicle lacks an internal `Actor`.
@ -92,7 +189,7 @@ class SessionTerminalHandlers(
* na
* @param terminal na
*/
def HandleProximityTerminalUse(terminal: Terminal with ProximityUnit): Unit = {
def performProximityTerminalUse(terminal: Terminal with ProximityUnit): Unit = {
val term_guid = terminal.GUID
val targets = FindProximityUnitTargetsInScope(terminal)
val currentTargets = terminal.Targets

View file

@ -39,6 +39,18 @@ class VehicleOperations(
) extends CommonSessionInterfacingFunctionality {
private[session] var serverVehicleControlVelocity: Option[Int] = None
/**
* Get the current `Vehicle` object that the player is riding/driving.
* The vehicle must be found solely through use of `player.VehicleSeated`.
* @return the vehicle
*/
def findLocalVehicle: Option[Vehicle] = {
continent.GUID(player.VehicleSeated) match {
case Some(obj: Vehicle) => Some(obj)
case _ => None
}
}
/**
* 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`.

View file

@ -6,7 +6,6 @@ import akka.actor.typed.scaladsl.adapter._
import akka.actor.{ActorContext, ActorRef, Cancellable, typed}
import akka.pattern.ask
import akka.util.Timeout
import net.psforever.actors.session.spectator.SpectatorMode
import net.psforever.login.WorldSession
import net.psforever.objects.avatar.{BattleRank, DeployableToolbox}
import net.psforever.objects.avatar.scoring.{CampaignStatistics, ScoreCard, SessionStatistics}
@ -193,6 +192,7 @@ class ZoningOperations(
/** a flag that forces the current zone to reload itself during a zoning operation */
private[session] var zoneReload: Boolean = false
private[session] val spawn: SpawnOperations = new SpawnOperations()
private[session] var maintainInitialGmState: Boolean = false
private var loadConfZone: Boolean = false
private var instantActionFallbackDestination: Option[Zoning.InstantAction.Located] = None
@ -609,6 +609,7 @@ class ZoningOperations(
def handleZoneResponse(foundZone: Zone): Unit = {
log.trace(s"ZoneResponse: zone ${foundZone.id} will now load for ${player.Name}")
loadConfZone = true
maintainInitialGmState = true
val oldZone = session.zone
session = session.copy(zone = foundZone)
sessionLogic.persist()

View file

@ -81,6 +81,7 @@ class Player(var avatar: Avatar)
Continent = "home2" //the zone id
var spectator: Boolean = false
var bops: Boolean = false
var silenced: Boolean = false
var death_by: Int = 0
var lastShotSeq_time: Int = -1

View file

@ -69,7 +69,7 @@ object AvatarConverter {
obj.avatar.basic,
CommonFieldData(
obj.Faction,
bops = obj.spectator,
bops = obj.bops,
alt_model_flag,
v1 = false,
None,

View file

@ -185,6 +185,10 @@ class Sector(val longitude: Int, val latitude: Int, val span: Int)
*/
def addTo(o: BlockMapEntity): Boolean = {
o match {
case p: Player if p.spectator =>
livePlayers.removeFrom(p)
corpses.removeFrom(p)
false
case p: Player if p.isBackpack =>
//when adding to the "corpse" list, first attempt to remove from the "player" list
livePlayers.removeFrom(p)

View file

@ -204,6 +204,7 @@ final case class DetailedCharacterB(
*/
final case class DetailedCharacterData(a: DetailedCharacterA, b: DetailedCharacterB)(pad_length: Option[Int])
extends ConstructorData {
val padLength: Option[Int] = pad_length
override def bitsize: Long = a.bitsize + b.bitsize
}