From 720bc9783854e7ebf5400279e4e90af0d3463c7f Mon Sep 17 00:00:00 2001 From: Fate-JH Date: Tue, 29 Oct 2024 12:06:36 -0400 Subject: [PATCH] corrected various issues identified in the comments of PR#1247 --- .../actors/session/AvatarActor.scala | 12 +- .../session/csr/AvatarHandlerLogic.scala | 25 +- .../CustomerServiceRepresentativeMode.scala | 178 ++++++++----- .../actors/session/csr/GeneralLogic.scala | 167 ++++++------ .../session/csr/MountHandlerLogic.scala | 8 +- ...eAsCustomerServiceRepresentativeMode.scala | 42 ++- .../session/csr/TerminalHandlerLogic.scala | 12 - .../actors/session/csr/VehicleLogic.scala | 57 +++-- .../session/normal/AvatarHandlerLogic.scala | 27 +- .../actors/session/normal/GeneralLogic.scala | 107 ++++---- .../session/normal/MountHandlerLogic.scala | 8 +- .../session/normal/TerminalHandlerLogic.scala | 13 +- .../actors/session/normal/VehicleLogic.scala | 6 +- .../session/support/GeneralOperations.scala | 240 +++++++++--------- .../actors/session/support/SessionData.scala | 3 +- .../support/SessionLocalHandlers.scala | 4 +- .../support/SessionTerminalHandlers.scala | 4 +- .../session/support/ZoningOperations.scala | 9 +- .../net/psforever/login/WorldSession.scala | 15 ++ .../scala/net/psforever/objects/Players.scala | 10 +- .../objects/avatar/PlayerControl.scala | 29 +-- 21 files changed, 541 insertions(+), 435 deletions(-) diff --git a/src/main/scala/net/psforever/actors/session/AvatarActor.scala b/src/main/scala/net/psforever/actors/session/AvatarActor.scala index 2852315d6..1dd805e3e 100644 --- a/src/main/scala/net/psforever/actors/session/AvatarActor.scala +++ b/src/main/scala/net/psforever/actors/session/AvatarActor.scala @@ -1131,6 +1131,11 @@ class AvatarActor( var supportExperienceTimer: Cancellable = Default.Cancellable var experienceDebt: Long = 0L + private def setSession(newSession: Session): Unit = { + session = Some(newSession) + _avatar = Option(newSession.avatar) + } + def avatar: Avatar = _avatar.get def avatar_=(avatar: Avatar): Unit = { @@ -1156,8 +1161,7 @@ class AvatarActor( postLoginBehaviour() case SetSession(newSession) => - session = Some(newSession) - _avatar = Option(newSession.avatar) + setSession(newSession) postLoginBehaviour() case other => @@ -1177,7 +1181,7 @@ class AvatarActor( Behaviors .receiveMessage[Command] { case SetSession(newSession) => - session = Some(newSession) + setSession(newSession) Behaviors.same case SetLookingForSquad(lfs) => @@ -1330,7 +1334,7 @@ class AvatarActor( Behaviors .receiveMessagePartial[Command] { case SetSession(newSession) => - session = Some(newSession) + setSession(newSession) Behaviors.same case ReplaceAvatar(newAvatar) => diff --git a/src/main/scala/net/psforever/actors/session/csr/AvatarHandlerLogic.scala b/src/main/scala/net/psforever/actors/session/csr/AvatarHandlerLogic.scala index aaac406c8..f67919e26 100644 --- a/src/main/scala/net/psforever/actors/session/csr/AvatarHandlerLogic.scala +++ b/src/main/scala/net/psforever/actors/session/csr/AvatarHandlerLogic.scala @@ -3,9 +3,14 @@ package net.psforever.actors.session.csr import akka.actor.{ActorContext, typed} import net.psforever.actors.session.support.AvatarHandlerFunctions +import net.psforever.login.WorldSession.PutLoadoutEquipmentInInventory +import net.psforever.objects.PlanetSideGameObject import net.psforever.objects.definition.converter.OCM +import net.psforever.objects.inventory.Container import net.psforever.objects.serverobject.containable.ContainableBehavior +import net.psforever.objects.serverobject.mount.Mountable import net.psforever.packet.game.{AvatarImplantMessage, CreateShortcutMessage, ImplantAction} +import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage} import net.psforever.types.ImplantType // @@ -382,7 +387,9 @@ class AvatarHandlerLogic(val ops: SessionAvatarHandlers, implicit val context: A slot = 0 )) } - sessionLogic.general.applyPurchaseTimersBeforePackingLoadout(player, player, holsters ++ inventory) + (holsters ++ inventory).foreach { case InventoryItem(item, slot) => + TaskWorkflow.execute(PutLoadoutEquipmentInInventory(player)(item, slot)) + } DropLeftovers(player)(drops) case AvatarResponse.ChangeLoadout(target, armor, exosuit, subtype, slot, _, oldHolsters, _, _, _, _) => @@ -431,6 +438,8 @@ class AvatarHandlerLogic(val ops: SessionAvatarHandlers, implicit val context: A sendResponse(ReloadMessage(itemGuid, ammo_clip=1, unk1=0)) case AvatarResponse.Killed(mount) => + val pguid = player.GUID + val avatarId = player.Definition.ObjectId //pure logic sessionLogic.shooting.shotsWhileDead = 0 sessionLogic.zoning.CancelZoningProcess() @@ -456,12 +465,16 @@ class AvatarHandlerLogic(val ops: SessionAvatarHandlers, implicit val context: A case obj: Vehicle if obj.Destroyed => sessionLogic.vehicles.ConditionalDriverVehicleControl(obj) sessionLogic.general.unaccessContainer(obj) - player.VehicleSeated = None - sendResponse(OCM.detailed(player)) - case _: Vehicle => - player.VehicleSeated = None - sendResponse(OCM.detailed(player)) + case obj: PlanetSideGameObject with Mountable with Container if obj.Destroyed => + sessionLogic.general.unaccessContainer(obj) + case _ => () } + player.VehicleSeated = None + sendResponse(OCM.detailed(player)) + continent.AvatarEvents ! AvatarServiceMessage( + continent.id, + AvatarAction.LoadPlayer(pguid, avatarId, pguid, player.Definition.Packet.ConstructorData(player).get, None) + ) sendResponse(ChatMsg(ChatMessageType.UNK_225, "CSR MODE")) case AvatarResponse.Release(tplayer) if isNotSameTarget => diff --git a/src/main/scala/net/psforever/actors/session/csr/CustomerServiceRepresentativeMode.scala b/src/main/scala/net/psforever/actors/session/csr/CustomerServiceRepresentativeMode.scala index 67dd120d7..ba3b85987 100644 --- a/src/main/scala/net/psforever/actors/session/csr/CustomerServiceRepresentativeMode.scala +++ b/src/main/scala/net/psforever/actors/session/csr/CustomerServiceRepresentativeMode.scala @@ -1,16 +1,19 @@ // Copyright (c) 2024 PSForever package net.psforever.actors.session.csr -import net.psforever.actors.session.SessionActor import net.psforever.actors.session.support.{ChatFunctions, GalaxyHandlerFunctions, GeneralFunctions, LocalHandlerFunctions, ModeLogic, MountHandlerFunctions, PlayerMode, SessionData, SquadHandlerFunctions, TerminalHandlerFunctions, VehicleFunctions, VehicleHandlerFunctions, WeaponAndProjectileFunctions} -import net.psforever.objects.{Deployables, Session, Vehicle} +import net.psforever.objects.{Deployables, PlanetSideGameObject, Player, Session, Vehicle} import net.psforever.objects.avatar.Certification -import net.psforever.packet.PlanetSidePacket -import net.psforever.packet.game.{ChatMsg, ObjectCreateDetailedMessage} -import net.psforever.packet.game.objectcreate.{ObjectCreateMessageParent, RibbonBars} +import net.psforever.objects.serverobject.ServerObject +import net.psforever.objects.serverobject.mount.Mountable +import net.psforever.objects.vital.Vitality +import net.psforever.objects.zones.Zone +import net.psforever.packet.game.{ChatMsg, ObjectCreateDetailedMessage, PlanetsideAttributeMessage} +import net.psforever.packet.game.objectcreate.RibbonBars import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage} import net.psforever.services.chat.{CustomerServiceChannel, SpectatorChannel} -import net.psforever.types.{ChatMessageType, MeritCommendation} +import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage} +import net.psforever.types.{ChatMessageType, MeritCommendation, PlanetSideGUID} class CustomerServiceRepresentativeMode(data: SessionData) extends ModeLogic { val avatarResponse: AvatarHandlerLogic = AvatarHandlerLogic(data.avatarResponse) @@ -32,8 +35,8 @@ class CustomerServiceRepresentativeMode(data: SessionData) extends ModeLogic { val player = session.player val avatar = session.avatar val continent = session.zone - val sendResponse: PlanetSidePacket=>Unit = data.sendResponse // + data.zoning.displayZoningMessageWhenCancelled = false if (oldCertifications.isEmpty) { oldCertifications = avatar.certifications oldRibbons = avatar.decoration.ribbonBars @@ -47,49 +50,27 @@ class CustomerServiceRepresentativeMode(data: SessionData) extends ModeLogic { )) ) player.avatar = newAvatar - data.context.self ! SessionActor.SetAvatar(newAvatar) + data.session = session.copy(avatar = newAvatar, player = player) Deployables.InitializeDeployableQuantities(newAvatar) } - val vehicleAndSeat = data.vehicles.GetMountableAndSeat(None, player, continent) match { - case (Some(obj: Vehicle), Some(seatNum)) => - Some(ObjectCreateMessageParent(obj.GUID, seatNum)) - case _ => - None - } + requireDismount(continent, player) + data.keepAlivePersistenceFunc = keepAlivePersistanceCSR // - val pguid = player.GUID - val definition = player.Definition - val objectClass = definition.ObjectId - val packet = definition.Packet - sendResponse(ObjectCreateDetailedMessage( - 0L, - objectClass, - pguid, - vehicleAndSeat, - packet.DetailedConstructorData(player).get - )) - data.zoning.spawn.HandleSetCurrentAvatar(player) - continent.AvatarEvents ! AvatarServiceMessage(continent.id, AvatarAction.LoadPlayer( - pguid, - objectClass, - pguid, - packet.ConstructorData(player).get, - vehicleAndSeat - )) + CustomerServiceRepresentativeMode.renderPlayer(data, continent, player) if (player.silenced) { data.chat.commandIncomingSilence(session, ChatMsg(ChatMessageType.CMT_SILENCE, "player 0")) } data.chat.JoinChannel(SpectatorChannel) data.chat.JoinChannel(CustomerServiceChannel) - sendResponse(ChatMsg(ChatMessageType.UNK_225, "CSR MODE ON")) + data.sendResponse(ChatMsg(ChatMessageType.UNK_225, "CSR MODE ON")) } override def switchFrom(session: Session): Unit = { val player = session.player val avatar = session.avatar val continent = session.zone - val sendResponse: PlanetSidePacket => Unit = data.sendResponse // + data.zoning.displayZoningMessageWhenCancelled = true val newAvatar = avatar.copy( certifications = oldCertifications, decoration = avatar.decoration.copy(ribbonBars = oldRibbons) @@ -97,37 +78,90 @@ class CustomerServiceRepresentativeMode(data: SessionData) extends ModeLogic { oldCertifications = Set() oldRibbons = RibbonBars() player.avatar = newAvatar - data.context.self ! SessionActor.SetAvatar(newAvatar) - val vehicleAndSeat = data.vehicles.GetMountableAndSeat(None, player, continent) match { - case (Some(obj: Vehicle), Some(seatNum)) => - Some(ObjectCreateMessageParent(obj.GUID, seatNum)) - case _ => - None - } + data.session = session.copy(avatar = newAvatar, player = player) Deployables.InitializeDeployableQuantities(newAvatar) // - val pguid = player.GUID - val definition = player.Definition - val objectClass = definition.ObjectId - val packet = definition.Packet - sendResponse(ObjectCreateDetailedMessage( - 0L, - objectClass, - pguid, - vehicleAndSeat, - packet.DetailedConstructorData(player).get - )) - data.zoning.spawn.HandleSetCurrentAvatar(player) - continent.AvatarEvents ! AvatarServiceMessage(continent.id, AvatarAction.LoadPlayer( - pguid, - objectClass, - pguid, - packet.ConstructorData(player).get, - vehicleAndSeat - )) + requireDismount(continent, player) + data.keepAlivePersistenceFunc = data.keepAlivePersistence + // + CustomerServiceRepresentativeMode.renderPlayer(data, continent, player) data.chat.LeaveChannel(SpectatorChannel) data.chat.LeaveChannel(CustomerServiceChannel) - sendResponse(ChatMsg(ChatMessageType.UNK_225, "CSR MODE OFF")) + data.sendResponse(ChatMsg(ChatMessageType.UNK_225, "CSR MODE OFF")) + } + + private def requireDismount(zone: Zone, player: Player): Unit = { + data.vehicles.GetMountableAndSeat(None, player, zone) 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.Actor ! Mountable.TryDismount(player, seatNum) + player.VehicleSeated = None + case (Some(obj), Some(seatNum)) => + obj.Actor ! Mountable.TryDismount(player, seatNum) + player.VehicleSeated = None + case _ => + player.VehicleSeated = None + } + } + + private def keepAlivePersistanceCSR(): Unit = { + data.keepAlivePersistence() + topOffHealthOfPlayer(data.player) + data.continent.GUID(data.player.VehicleSeated) + .collect { + case obj: PlanetSideGameObject with Vitality => topOffHealth(obj) + } + } + + private def topOffHealth(obj: PlanetSideGameObject with Vitality): Unit = { + obj match { + case p: Player => topOffHealthOfPlayer(p) + case v: Vehicle => topOffHealthOfVehicle(v) + case o: PlanetSideGameObject with Vitality => topOffHealthOfGeneric(o) + case _ => () + } + } + + private def topOffHealthOfPlayer(player: Player): 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) + data.sendResponse(PlanetsideAttributeMessage(player.GUID, 0, maxHealthOfPlayer)) + data.continent.AvatarEvents ! AvatarServiceMessage(data.zoning.zoneChannel, AvatarAction.PlanetsideAttribute(player.GUID, 0, maxHealthOfPlayer)) + } + } + + private def topOffHealthOfVehicle(vehicle: Vehicle): Unit = { + topOffHealthOfGeneric(vehicle) + //vehicle shields below half, full shields + val maxShieldsOfVehicle = vehicle.MaxShields.toLong + val shieldsUi = vehicle.Definition.shieldUiAttribute + if (vehicle.Shields < maxShieldsOfVehicle) { + val guid = vehicle.GUID + vehicle.Shields = maxShieldsOfVehicle.toInt + data.sendResponse(PlanetsideAttributeMessage(guid, shieldsUi, maxShieldsOfVehicle)) + data.continent.VehicleEvents ! VehicleServiceMessage( + data.continent.id, + VehicleAction.PlanetsideAttribute(PlanetSideGUID(0), guid, shieldsUi, maxShieldsOfVehicle) + ) + } + } + + private def topOffHealthOfGeneric(obj: PlanetSideGameObject with Vitality): Unit = { + //below half health, full heal + val guid = obj.GUID + val maxHealthOf = obj.MaxHealth.toLong + if (obj.Health < maxHealthOf) { + obj.Health = maxHealthOf.toInt + data.sendResponse(PlanetsideAttributeMessage(guid, 0, maxHealthOf)) + data.continent.VehicleEvents ! VehicleServiceMessage( + data.continent.id, + VehicleAction.PlanetsideAttribute(PlanetSideGUID(0), guid, 0, maxHealthOf) + ) + } } } @@ -135,4 +169,24 @@ case object CustomerServiceRepresentativeMode extends PlayerMode { def setup(data: SessionData): ModeLogic = { new CustomerServiceRepresentativeMode(data) } + + private[csr] def renderPlayer(data: SessionData, zone: Zone, player: Player): Unit = { + val pguid = player.GUID + val definition = player.Definition + val objectClass = definition.ObjectId + val packet = definition.Packet + data.sendResponse(ObjectCreateDetailedMessage( + objectClass, + pguid, + packet.DetailedConstructorData(player).get + )) + data.zoning.spawn.HandleSetCurrentAvatar(player) + zone.AvatarEvents ! AvatarServiceMessage(zone.id, AvatarAction.LoadPlayer( + pguid, + objectClass, + pguid, + packet.ConstructorData(player).get, + None + )) + } } diff --git a/src/main/scala/net/psforever/actors/session/csr/GeneralLogic.scala b/src/main/scala/net/psforever/actors/session/csr/GeneralLogic.scala index 0640ceb4a..099f543cd 100644 --- a/src/main/scala/net/psforever/actors/session/csr/GeneralLogic.scala +++ b/src/main/scala/net/psforever/actors/session/csr/GeneralLogic.scala @@ -131,7 +131,7 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex sessionLogic.zoning.CancelZoningProcess() } player.Cloaked = player.ExoSuit == ExoSuitType.Infiltration && isCloaking - maxCapacitorTick() + maxCapacitorTick(jumpThrust) if (isMovingPlus && sessionLogic.terminals.usingMedicalTerminal.isDefined) { continent.GUID(sessionLogic.terminals.usingMedicalTerminal) match { case Some(term: Terminal with ProximityUnit) => @@ -297,60 +297,55 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex case _ => None } - cancelZoningWhenGeneralHandled( - sessionLogic.validObject(pkt.object_guid, decorator = "UseItem") match { - case Some(door: Door) => - handleUseDoor(door, equipment) - GeneralOperations.UseItem.Unhandled - case Some(resourceSilo: ResourceSilo) => - ops.handleUseResourceSilo(resourceSilo, equipment) - case Some(panel: IFFLock) => - ops.handleUseGeneralEntity(panel, equipment) - case Some(obj: Player) => - ops.handleUsePlayer(obj, equipment, pkt) - GeneralOperations.UseItem.Unhandled - case Some(locker: Locker) => - ops.handleUseLocker(locker, equipment, pkt) - case Some(gen: Generator) => - ops.handleUseGeneralEntity(gen, equipment) - case Some(mech: ImplantTerminalMech) => - ops.handleUseGeneralEntity(mech, equipment) - case Some(captureTerminal: CaptureTerminal) => - ops.handleUseCaptureTerminal(captureTerminal, equipment) - case Some(obj: FacilityTurret) => - ops.handleUseFacilityTurret(obj, equipment, pkt) - case Some(obj: Vehicle) => - ops.handleUseVehicle(obj, equipment, pkt) - case Some(terminal: Terminal) => - ops.handleUseTerminal(terminal, equipment, pkt) - case Some(obj: SpawnTube) => - ops.handleUseSpawnTube(obj, equipment) - case Some(obj: SensorDeployable) => - ops.handleUseGeneralEntity(obj, equipment) - case Some(obj: TurretDeployable) => - ops.handleUseGeneralEntity(obj, equipment) - case Some(obj: TrapDeployable) => - ops.handleUseGeneralEntity(obj, equipment) - case Some(obj: ShieldGeneratorDeployable) => - ops.handleUseGeneralEntity(obj, equipment) - case Some(obj: TelepadDeployable) if player.spectator => - ops.handleUseTelepadDeployable(obj, equipment, pkt, ops.useRouterTelepadSystemSecretly) - case Some(obj: Utility.InternalTelepad) if player.spectator => - ops.handleUseInternalTelepad(obj, pkt, ops.useRouterTelepadSystemSecretly) - case Some(obj: TelepadDeployable) => - ops.handleUseTelepadDeployable(obj, equipment, pkt, ops.useRouterTelepadSystem) - case Some(obj: Utility.InternalTelepad) => - ops.handleUseInternalTelepad(obj, pkt, ops.useRouterTelepadSystem) - case Some(obj: CaptureFlag) => - ops.handleUseCaptureFlag(obj) - case Some(_: WarpGate) => - ops.handleUseWarpGate(equipment) - case Some(obj) => - ops.handleUseDefaultEntity(obj, equipment) - case None => - GeneralOperations.UseItem.Unhandled - } - ) + sessionLogic.validObject(pkt.object_guid, decorator = "UseItem") match { + case Some(door: Door) => + handleUseDoor(door, equipment) + case Some(resourceSilo: ResourceSilo) => + ops.handleUseResourceSilo(resourceSilo, equipment) + case Some(panel: IFFLock) => + ops.handleUseGeneralEntity(panel, equipment) + case Some(obj: Player) => + ops.handleUsePlayer(obj, equipment, pkt) + case Some(locker: Locker) => + ops.handleUseLocker(locker, equipment, pkt) + case Some(gen: Generator) => + ops.handleUseGeneralEntity(gen, equipment) + case Some(mech: ImplantTerminalMech) => + ops.handleUseGeneralEntity(mech, equipment) + case Some(captureTerminal: CaptureTerminal) => + ops.handleUseCaptureTerminal(captureTerminal, equipment) + case Some(obj: FacilityTurret) => + ops.handleUseFacilityTurret(obj, equipment, pkt) + case Some(obj: Vehicle) => + ops.handleUseVehicle(obj, equipment, pkt) + case Some(terminal: Terminal) => + ops.handleUseTerminal(terminal, equipment, pkt) + case Some(obj: SpawnTube) => + ops.handleUseSpawnTube(obj, equipment) + case Some(obj: SensorDeployable) => + ops.handleUseGeneralEntity(obj, equipment) + case Some(obj: TurretDeployable) => + ops.handleUseGeneralEntity(obj, equipment) + case Some(obj: TrapDeployable) => + ops.handleUseGeneralEntity(obj, equipment) + case Some(obj: ShieldGeneratorDeployable) => + ops.handleUseGeneralEntity(obj, equipment) + case Some(obj: TelepadDeployable) if player.spectator => + ops.handleUseTelepadDeployable(obj, equipment, pkt, ops.useRouterTelepadSystemSecretly) + case Some(obj: Utility.InternalTelepad) if player.spectator => + ops.handleUseInternalTelepad(obj, pkt, ops.useRouterTelepadSystemSecretly) + case Some(obj: TelepadDeployable) => + ops.handleUseTelepadDeployable(obj, equipment, pkt, ops.useRouterTelepadSystem) + case Some(obj: Utility.InternalTelepad) => + ops.handleUseInternalTelepad(obj, pkt, ops.useRouterTelepadSystem) + case Some(obj: CaptureFlag) => + ops.handleUseCaptureFlag(obj) + case Some(_: WarpGate) => + ops.handleUseWarpGate(equipment) + case Some(obj) => + ops.handleUseDefaultEntity(obj, equipment) + case None => () + } } def handleUnuseItem(pkt: UnuseItemMessage): Unit = { @@ -379,7 +374,7 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex case dtype => dtype } sessionLogic.zoning.CancelZoningProcess() - ops.handleDeployObject(continent, ammoType, pos, orient, player.WhichSide, PlanetSideEmpire.NEUTRAL, None) + ops.handleDeployObject(continent, ammoType, pos, orient, player.WhichSide, PlanetSideEmpire.NEUTRAL) case Some(obj) => log.warn(s"DeployObject: what is $obj, ${player.Name}? It's not a construction tool!") case None => @@ -727,16 +722,7 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex /* supporting functions */ def handleUseDoor(door: Door, equipment: Option[Equipment]): Unit = { - if (player.spectator) { - //opens just for us - val guid = door.GUID - openDoor.collect { - case oldDoor if guid != oldDoor.GUID => - sendResponse(GenericObjectStateMsg(oldDoor.GUID, state=17)) - } - sendResponse(GenericObjectStateMsg(guid, state=16)) - openDoor = Some(door) - } else { + if (!player.spectator) { //opens for everyone equipment match { case Some(tool: Tool) if tool.Definition == GlobalDefinitions.medicalapplicator => @@ -747,39 +733,58 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex } } - private def maxCapacitorTick(): Unit = { + private def maxCapacitorTick(jumpThrust: Boolean): Unit = { if (player.ExoSuit == ExoSuitType.MAX) { + val activate = (jumpThrust || player.isOverdrived || player.isShielded) && player.Capacitor > 0 player.CapacitorState match { - case CapacitorStateType.Idle => maxCapacitorTickIdle() - case _ => maxCapacitorTickCharging() + case CapacitorStateType.Discharging => maxCapacitorTickDischarging(activate) + case CapacitorStateType.Charging => maxCapacitorTickCharging(activate) + case _ => maxCapacitorTickIdle(activate) } } else if (player.CapacitorState != CapacitorStateType.Idle) { player.CapacitorState = CapacitorStateType.Idle } } - private def maxCapacitorTickIdle(): Unit = { - if (player.Capacitor < player.ExoSuitDef.MaxCapacitor) { + private def maxCapacitorTickIdle(activate: Boolean): Unit = { + if (activate) { + player.CapacitorState = CapacitorStateType.Discharging + //maxCapacitorTickDischarging(activate) + } else if (player.Capacitor < player.ExoSuitDef.MaxCapacitor) { player.CapacitorState = CapacitorStateType.Charging - maxCapacitorTickCharging() } } - private def maxCapacitorTickCharging(): Unit = { - if (player.Capacitor < player.ExoSuitDef.MaxCapacitor) { - val timeDiff = (System.currentTimeMillis() - player.CapacitorLastChargedMillis).toFloat / 1000 - val chargeAmount = player.ExoSuitDef.CapacitorRechargePerSecond * timeDiff - player.Capacitor += chargeAmount + private def maxCapacitorTickDischarging(activate: Boolean): Unit = { + if (activate) { + val timeDiff = (System.currentTimeMillis() - player.CapacitorLastUsedMillis).toFloat / 1000 + val drainAmount = player.ExoSuitDef.CapacitorDrainPerSecond.toFloat * timeDiff + player.Capacitor -= drainAmount sendResponse(PlanetsideAttributeMessage(player.GUID, 7, player.Capacitor.toInt)) + } else if (player.Capacitor == 0) { + if (player.Faction == PlanetSideEmpire.TR) { + ops.toggleMaxSpecialState(enable = false) + } + player.Capacitor = player.ExoSuitDef.MaxCapacitor.toFloat + sendResponse(PlanetsideAttributeMessage(player.GUID, 7, player.Capacitor.toInt)) + player.CapacitorState = CapacitorStateType.Idle + } else if (player.Capacitor < player.ExoSuitDef.MaxCapacitor) { + player.CapacitorState = CapacitorStateType.Charging } else { player.CapacitorState = CapacitorStateType.Idle } } - private def cancelZoningWhenGeneralHandled(results: GeneralOperations.UseItem.Behavior): Unit = { - results match { - case GeneralOperations.UseItem.Unhandled => () - case _ => sessionLogic.zoning.CancelZoningProcess() + private def maxCapacitorTickCharging(activate: Boolean): Unit = { + val maxCapacitor = player.ExoSuitDef.MaxCapacitor + if (activate) { + player.CapacitorState = CapacitorStateType.Discharging + //maxCapacitorTickDischarging(activate) + } else if (player.Capacitor < player.ExoSuitDef.MaxCapacitor) { + player.Capacitor = maxCapacitor.toFloat + sendResponse(PlanetsideAttributeMessage(player.GUID, 7, maxCapacitor)) + } else { + player.CapacitorState = CapacitorStateType.Idle } } } diff --git a/src/main/scala/net/psforever/actors/session/csr/MountHandlerLogic.scala b/src/main/scala/net/psforever/actors/session/csr/MountHandlerLogic.scala index 94ba103a7..5ca48d683 100644 --- a/src/main/scala/net/psforever/actors/session/csr/MountHandlerLogic.scala +++ b/src/main/scala/net/psforever/actors/session/csr/MountHandlerLogic.scala @@ -67,14 +67,14 @@ class MountHandlerLogic(val ops: SessionMountHandlers, implicit val context: Act sessionLogic.zoning.CancelZoningProcess() sessionLogic.terminals.CancelAllProximityUnits() ops.MountingAction(tplayer, obj, seatNumber) - sessionLogic.keepAliveFunc = sessionLogic.keepAlivePersistence + sessionLogic.keepAliveFunc = sessionLogic.keepAlivePersistenceFunc case Mountable.CanMount(obj: Vehicle, seatNumber, _) if obj.Definition == GlobalDefinitions.orbital_shuttle => sessionLogic.zoning.CancelZoningProcess() sessionLogic.terminals.CancelAllProximityUnits() ops.MountingAction(tplayer, obj, seatNumber) - sessionLogic.keepAliveFunc = sessionLogic.keepAlivePersistence + sessionLogic.keepAliveFunc = sessionLogic.keepAlivePersistenceFunc case Mountable.CanMount(obj: Vehicle, seatNumber, _) if obj.Definition == GlobalDefinitions.ant => @@ -141,7 +141,7 @@ class MountHandlerLogic(val ops: SessionMountHandlers, implicit val context: Act sendResponse(PlanetsideAttributeMessage(obj_guid, attribute_type=113, obj.Capacitor)) sessionLogic.general.accessContainer(obj) ops.updateWeaponAtSeatPosition(obj, seatNumber) - sessionLogic.keepAliveFunc = sessionLogic.keepAlivePersistence + sessionLogic.keepAliveFunc = sessionLogic.keepAlivePersistenceFunc tplayer.Actor ! ResetAllEnvironmentInteractions ops.MountingAction(tplayer, obj, seatNumber) @@ -153,7 +153,7 @@ class MountHandlerLogic(val ops: SessionMountHandlers, implicit val context: Act sendResponse(PlanetsideAttributeMessage(obj_guid, obj.Definition.shieldUiAttribute, obj.Shields)) sessionLogic.general.accessContainer(obj) ops.updateWeaponAtSeatPosition(obj, seatNumber) - sessionLogic.keepAliveFunc = sessionLogic.keepAlivePersistence + sessionLogic.keepAliveFunc = sessionLogic.keepAlivePersistenceFunc tplayer.Actor ! ResetAllEnvironmentInteractions ops.MountingAction(tplayer, obj, seatNumber) diff --git a/src/main/scala/net/psforever/actors/session/csr/SpectateAsCustomerServiceRepresentativeMode.scala b/src/main/scala/net/psforever/actors/session/csr/SpectateAsCustomerServiceRepresentativeMode.scala index 2903a75e3..d1fd6e257 100644 --- a/src/main/scala/net/psforever/actors/session/csr/SpectateAsCustomerServiceRepresentativeMode.scala +++ b/src/main/scala/net/psforever/actors/session/csr/SpectateAsCustomerServiceRepresentativeMode.scala @@ -4,7 +4,9 @@ package net.psforever.actors.session.csr import net.psforever.actors.session.support.{AvatarHandlerFunctions, ChatFunctions, GalaxyHandlerFunctions, GeneralFunctions, LocalHandlerFunctions, MountHandlerFunctions, SquadHandlerFunctions, TerminalHandlerFunctions, VehicleFunctions, VehicleHandlerFunctions, WeaponAndProjectileFunctions} import net.psforever.actors.zone.ZoneActor import net.psforever.objects.serverobject.ServerObject -import net.psforever.objects.{Session, Vehicle} +import net.psforever.objects.serverobject.mount.Mountable +import net.psforever.objects.{Player, Session, Vehicle} +import net.psforever.objects.zones.Zone import net.psforever.packet.PlanetSidePacket import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage} import net.psforever.services.chat.SpectatorChannel @@ -31,24 +33,16 @@ class SpectatorCSRModeLogic(data: SessionData) extends ModeLogic { val player = session.player val continent = session.zone val pguid = player.GUID - val sendResponse: PlanetSidePacket=>Unit = data.sendResponse + val sendResponse: PlanetSidePacket => Unit = data.sendResponse // - 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 - case (Some(obj), Some(seatNum)) => - obj.Seat(seatNum).foreach(_.unmount(player)) - player.VehicleSeated = None - case _ => () - } data.squadService ! SquadServiceMessage( player, continent, SquadAction.Membership(SquadRequestType.Leave, player.CharId, Some(player.CharId), player.Name, None) ) + if (requireDismount(data, continent, player)) { + CustomerServiceRepresentativeMode.renderPlayer(data, continent, player) + } // player.spectator = true //player.bops = true @@ -56,6 +50,7 @@ class SpectatorCSRModeLogic(data: SessionData) extends ModeLogic { data.chat.JoinChannel(SpectatorChannel) continent.actor ! ZoneActor.RemoveFromBlockMap(player) continent.AvatarEvents ! AvatarServiceMessage(continent.id, AvatarAction.ObjectDelete(pguid, pguid)) + sendResponse(ChatMsg(ChatMessageType.CMT_TOGGLESPECTATORMODE, "on")) sendResponse(ChatMsg(ChatMessageType.UNK_225, "CSR SPECTATOR MODE ON")) } @@ -64,7 +59,7 @@ class SpectatorCSRModeLogic(data: SessionData) extends ModeLogic { val pguid = player.GUID val continent = data.continent val avatarId = player.Definition.ObjectId - //val sendResponse: PlanetSidePacket => Unit = data.sendResponse + val sendResponse: PlanetSidePacket => Unit = data.sendResponse // player.spectator = false player.bops = false @@ -75,6 +70,25 @@ class SpectatorCSRModeLogic(data: SessionData) extends ModeLogic { continent.id, AvatarAction.LoadPlayer(pguid, avatarId, pguid, player.Definition.Packet.ConstructorData(player).get, None) ) + sendResponse(ChatMsg(ChatMessageType.CMT_TOGGLESPECTATORMODE, "off")) + } + + private def requireDismount(data: SessionData, zone: Zone, player: Player): Boolean = { + data.vehicles.GetMountableAndSeat(None, player, zone) 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.Actor ! Mountable.TryDismount(player, seatNum) + player.VehicleSeated = None + true + case (Some(obj), Some(seatNum)) => + obj.Actor ! Mountable.TryDismount(player, seatNum) + player.VehicleSeated = None + true + case _ => + player.VehicleSeated = None + false + } } } diff --git a/src/main/scala/net/psforever/actors/session/csr/TerminalHandlerLogic.scala b/src/main/scala/net/psforever/actors/session/csr/TerminalHandlerLogic.scala index 36ee884ab..1527398d2 100644 --- a/src/main/scala/net/psforever/actors/session/csr/TerminalHandlerLogic.scala +++ b/src/main/scala/net/psforever/actors/session/csr/TerminalHandlerLogic.scala @@ -9,7 +9,6 @@ import net.psforever.objects.{Player, Vehicle} import net.psforever.objects.guid.TaskWorkflow import net.psforever.objects.serverobject.terminals.{OrderTerminalDefinition, Terminal} import net.psforever.packet.game.{FavoritesRequest, ItemTransactionMessage, ItemTransactionResultMessage, ProximityTerminalUseMessage} -import net.psforever.types.TransactionType object TerminalHandlerLogic { def apply(ops: SessionTerminalHandlers): TerminalHandlerLogic = { @@ -62,13 +61,7 @@ class TerminalHandlerLogic(val ops: SessionTerminalHandlers, implicit val contex */ def handle(tplayer: Player, msg: ItemTransactionMessage, order: Terminal.Exchange): Unit = { order match { - case Terminal.BuyEquipment(item) - if tplayer.avatar.purchaseCooldown(item.Definition).nonEmpty => - sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, success = false)) - ops.lastTerminalOrderFulfillment = true - case Terminal.BuyEquipment(item) => - avatarActor ! AvatarActor.UpdatePurchaseTime(item.Definition) TaskWorkflow.execute(BuyNewEquipmentPutInInventory( continent.GUID(tplayer.VehicleSeated) match { case Some(v: Vehicle) => v @@ -97,11 +90,6 @@ class TerminalHandlerLogic(val ops: SessionTerminalHandlers, implicit val contex avatarActor ! AvatarActor.SellImplant(msg.terminal_guid, implant) ops.lastTerminalOrderFulfillment = true - case Terminal.BuyVehicle(vehicle, _, _) - 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) => ops.buyVehicle(msg.terminal_guid, msg.transaction_type, vehicle, weapons, trunk) ops.lastTerminalOrderFulfillment = true diff --git a/src/main/scala/net/psforever/actors/session/csr/VehicleLogic.scala b/src/main/scala/net/psforever/actors/session/csr/VehicleLogic.scala index 27c0baeb2..bea267d6a 100644 --- a/src/main/scala/net/psforever/actors/session/csr/VehicleLogic.scala +++ b/src/main/scala/net/psforever/actors/session/csr/VehicleLogic.scala @@ -5,10 +5,11 @@ import akka.actor.{ActorContext, typed} import net.psforever.actors.session.AvatarActor import net.psforever.actors.session.support.{SessionData, VehicleFunctions, VehicleOperations} import net.psforever.objects.serverobject.PlanetSideServerObject -import net.psforever.objects.{Vehicle, Vehicles} +import net.psforever.objects.{PlanetSideGameObject, Player, Vehicle, Vehicles} import net.psforever.objects.serverobject.deploy.Deployment import net.psforever.objects.serverobject.mount.Mountable import net.psforever.objects.vehicles.control.BfrFlight +import net.psforever.objects.vital.Vitality import net.psforever.objects.zones.Zone import net.psforever.packet.game.{ChildObjectStateMessage, DeployRequestMessage, FrameVehicleStateMessage, PlanetsideAttributeMessage, VehicleStateMessage, VehicleSubStateMessage} import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage} @@ -47,6 +48,7 @@ class VehicleLogic(val ops: VehicleOperations, implicit val context: ActorContex //we're driving the vehicle sessionLogic.persist() sessionLogic.turnCounterFunc(player.GUID) + topOffHealthOfPlayer() topOffHealth(obj) sessionLogic.general.fallHeightTracker(pos.z) if (obj.MountedIn.isEmpty) { @@ -131,6 +133,7 @@ class VehicleLogic(val ops: VehicleOperations, implicit val context: ActorContex //we're driving the vehicle sessionLogic.persist() sessionLogic.turnCounterFunc(player.GUID) + topOffHealthOfPlayer() topOffHealth(obj) val (position, angle, velocity, notMountedState) = continent.GUID(obj.MountedIn) match { case Some(v: Vehicle) => @@ -216,11 +219,16 @@ class VehicleLogic(val ops: VehicleOperations, implicit val context: ActorContex case Some(mount: Mountable) => (o, mount.PassengerInSeat(player)) case _ => (None, None) }) match { - case (None, None) | (_, None) | (Some(_: Vehicle), Some(0)) => () - case _ => + case (None, None) | (_, None) | (Some(_: Vehicle), Some(0)) => + () + case (Some(obj: PlanetSideGameObject with Vitality), _) => sessionLogic.persist() sessionLogic.turnCounterFunc(player.GUID) topOffHealthOfPlayer() + topOffHealth(obj) + 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 { @@ -333,6 +341,15 @@ class VehicleLogic(val ops: VehicleOperations, implicit val context: ActorContex } } + private def topOffHealth(obj: PlanetSideGameObject with Vitality): Unit = { + obj match { + case _: Player => topOffHealthOfPlayer() + case v: Vehicle => topOffHealthOfVehicle(v) + case o: PlanetSideGameObject with Vitality => topOffHealthOfGeneric(o) + case _ => () + } + } + private def topOffHealthOfPlayer(): Unit = { //driver below half health, full heal val maxHealthOfPlayer = player.MaxHealth.toLong @@ -340,32 +357,38 @@ class VehicleLogic(val ops: VehicleOperations, implicit val context: ActorContex 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)) + continent.AvatarEvents ! AvatarServiceMessage(sessionLogic.zoning.zoneChannel, AvatarAction.PlanetsideAttribute(player.GUID, 0, maxHealthOfPlayer)) } } - private def topOffHealth(vehicle: Vehicle): Unit = { + private def topOffHealthOfVehicle(vehicle: Vehicle): Unit = { topOffHealthOfPlayer() - //vehicle below half health, full heal - val guid = vehicle.GUID - val maxHealthOfVehicle = vehicle.MaxHealth.toLong - if (vehicle.Health < maxHealthOfVehicle) { - vehicle.Health = maxHealthOfVehicle.toInt - sendResponse(PlanetsideAttributeMessage(guid, 0, maxHealthOfVehicle)) - continent.VehicleEvents ! VehicleServiceMessage( - continent.id, - VehicleAction.PlanetsideAttribute(PlanetSideGUID(0), guid, 0, maxHealthOfVehicle) - ) - } + topOffHealthOfGeneric(vehicle) //vehicle shields below half, full shields val maxShieldsOfVehicle = vehicle.MaxShields.toLong val shieldsUi = vehicle.Definition.shieldUiAttribute if (vehicle.Shields < maxShieldsOfVehicle) { + val guid = vehicle.GUID vehicle.Shields = maxShieldsOfVehicle.toInt sendResponse(PlanetsideAttributeMessage(guid, shieldsUi, maxShieldsOfVehicle)) continent.VehicleEvents ! VehicleServiceMessage( continent.id, - VehicleAction.PlanetsideAttribute(PlanetSideGUID(0), guid, shieldsUi, maxHealthOfVehicle) + VehicleAction.PlanetsideAttribute(PlanetSideGUID(0), guid, shieldsUi, maxShieldsOfVehicle) + ) + } + } + + private def topOffHealthOfGeneric(obj: PlanetSideGameObject with Vitality): Unit = { + topOffHealthOfPlayer() + //vehicle below half health, full heal + val guid = obj.GUID + val maxHealthOf = obj.MaxHealth.toLong + if (obj.Health < maxHealthOf) { + obj.Health = maxHealthOf.toInt + sendResponse(PlanetsideAttributeMessage(guid, 0, maxHealthOf)) + continent.VehicleEvents ! VehicleServiceMessage( + continent.id, + VehicleAction.PlanetsideAttribute(PlanetSideGUID(0), guid, 0, maxHealthOf) ) } } diff --git a/src/main/scala/net/psforever/actors/session/normal/AvatarHandlerLogic.scala b/src/main/scala/net/psforever/actors/session/normal/AvatarHandlerLogic.scala index 0a1c863f8..f770de36c 100644 --- a/src/main/scala/net/psforever/actors/session/normal/AvatarHandlerLogic.scala +++ b/src/main/scala/net/psforever/actors/session/normal/AvatarHandlerLogic.scala @@ -312,10 +312,14 @@ class AvatarHandlerLogic(val ops: SessionAvatarHandlers, implicit val context: A //redraw if (maxhand) { sendResponse(PlanetsideAttributeMessage(target, attribute_type=7, player.Capacitor.toLong)) - TaskWorkflow.execute(HoldNewEquipmentUp(player)( - Tool(GlobalDefinitions.MAXArms(subtype, player.Faction)), - 0 - )) + val maxArmDefinition = GlobalDefinitions.MAXArms(subtype, player.Faction) + TaskWorkflow.execute(HoldNewEquipmentUp(player)(Tool(maxArmDefinition), slot = 0)) + player.avatar.purchaseCooldown(maxArmDefinition) + .collect(a => a) + .getOrElse { + avatarActor ! AvatarActor.UpdatePurchaseTime(maxArmDefinition) + None + } } //draw free hand player.FreeHand.Equipment.foreach { obj => @@ -343,6 +347,8 @@ class AvatarHandlerLogic(val ops: SessionAvatarHandlers, implicit val context: A ) } DropLeftovers(player)(drop) + //deactivate non-passive implants + avatarActor ! AvatarActor.DeactivateActiveImplants case AvatarResponse.ChangeExosuit(target, armor, exosuit, subtype, slot, _, oldHolsters, holsters, _, _, drop, delete) => sendResponse(ArmorChangedMessage(target, exosuit, subtype)) @@ -395,14 +401,19 @@ class AvatarHandlerLogic(val ops: SessionAvatarHandlers, implicit val context: A drops.foreach(item => sendResponse(ObjectDeleteMessage(item.obj.GUID, unk1=0))) //redraw if (maxhand) { + val maxArmWeapon = GlobalDefinitions.MAXArms(subtype, player.Faction) sendResponse(PlanetsideAttributeMessage(target, attribute_type=7, player.Capacitor.toLong)) - TaskWorkflow.execute(HoldNewEquipmentUp(player)( - Tool(GlobalDefinitions.MAXArms(subtype, player.Faction)), - slot = 0 - )) + TaskWorkflow.execute(HoldNewEquipmentUp(player)(Tool(maxArmWeapon), slot = 0)) + val cooldown = player.avatar.purchaseCooldown(maxArmWeapon) + if (!oldHolsters.exists { case (e, _) => e.Definition == maxArmWeapon } && + player.avatar.purchaseCooldown(maxArmWeapon).isEmpty) { + avatarActor ! AvatarActor.UpdatePurchaseTime(maxArmWeapon) //switching for first time causes cooldown + } } sessionLogic.general.applyPurchaseTimersBeforePackingLoadout(player, player, holsters ++ inventory) DropLeftovers(player)(drops) + //deactivate non-passive implants + avatarActor ! AvatarActor.DeactivateActiveImplants case AvatarResponse.ChangeLoadout(target, armor, exosuit, subtype, slot, _, oldHolsters, _, _, _, _) => //redraw handled by callbacks diff --git a/src/main/scala/net/psforever/actors/session/normal/GeneralLogic.scala b/src/main/scala/net/psforever/actors/session/normal/GeneralLogic.scala index 0a4993876..3a84a4324 100644 --- a/src/main/scala/net/psforever/actors/session/normal/GeneralLogic.scala +++ b/src/main/scala/net/psforever/actors/session/normal/GeneralLogic.scala @@ -333,56 +333,51 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex case _ => None } - cancelZoningWhenGeneralHandled( - sessionLogic.validObject(pkt.object_guid, decorator = "UseItem") match { - case Some(door: Door) => - ops.handleUseDoor(door, equipment) - GeneralOperations.UseItem.Unhandled - case Some(resourceSilo: ResourceSilo) => - ops.handleUseResourceSilo(resourceSilo, equipment) - case Some(panel: IFFLock) => - ops.handleUseGeneralEntity(panel, equipment) - case Some(obj: Player) => - ops.handleUsePlayer(obj, equipment, pkt) - GeneralOperations.UseItem.Unhandled - case Some(locker: Locker) => - ops.handleUseLocker(locker, equipment, pkt) - case Some(gen: Generator) => - ops.handleUseGeneralEntity(gen, equipment) - case Some(mech: ImplantTerminalMech) => - ops.handleUseGeneralEntity(mech, equipment) - case Some(captureTerminal: CaptureTerminal) => - ops.handleUseCaptureTerminal(captureTerminal, equipment) - case Some(obj: FacilityTurret) => - ops.handleUseFacilityTurret(obj, equipment, pkt) - case Some(obj: Vehicle) => - ops.handleUseVehicle(obj, equipment, pkt) - case Some(terminal: Terminal) => - ops.handleUseTerminal(terminal, equipment, pkt) - case Some(obj: SpawnTube) => - ops.handleUseSpawnTube(obj, equipment) - case Some(obj: SensorDeployable) => - ops.handleUseGeneralEntity(obj, equipment) - case Some(obj: TurretDeployable) => - ops.handleUseGeneralEntity(obj, equipment) - case Some(obj: TrapDeployable) => - ops.handleUseGeneralEntity(obj, equipment) - case Some(obj: ShieldGeneratorDeployable) => - ops.handleUseGeneralEntity(obj, equipment) - case Some(obj: TelepadDeployable) => - ops.handleUseTelepadDeployable(obj, equipment, pkt, ops.useRouterTelepadSystem) - case Some(obj: Utility.InternalTelepad) => - ops.handleUseInternalTelepad(obj, pkt, ops.useRouterTelepadSystem) - case Some(obj: CaptureFlag) => - ops.handleUseCaptureFlag(obj) - case Some(_: WarpGate) => - ops.handleUseWarpGate(equipment) - case Some(obj) => - ops.handleUseDefaultEntity(obj, equipment) - case None => - GeneralOperations.UseItem.Unhandled - } - ) + sessionLogic.validObject(pkt.object_guid, decorator = "UseItem") match { + case Some(door: Door) => + ops.handleUseDoor(door, equipment) + case Some(resourceSilo: ResourceSilo) => + ops.handleUseResourceSilo(resourceSilo, equipment) + case Some(panel: IFFLock) => + ops.handleUseGeneralEntity(panel, equipment) + case Some(obj: Player) => + ops.handleUsePlayer(obj, equipment, pkt) + case Some(locker: Locker) => + ops.handleUseLocker(locker, equipment, pkt) + case Some(gen: Generator) => + ops.handleUseGeneralEntity(gen, equipment) + case Some(mech: ImplantTerminalMech) => + ops.handleUseGeneralEntity(mech, equipment) + case Some(captureTerminal: CaptureTerminal) => + ops.handleUseCaptureTerminal(captureTerminal, equipment) + case Some(obj: FacilityTurret) => + ops.handleUseFacilityTurret(obj, equipment, pkt) + case Some(obj: Vehicle) => + ops.handleUseVehicle(obj, equipment, pkt) + case Some(terminal: Terminal) => + ops.handleUseTerminal(terminal, equipment, pkt) + case Some(obj: SpawnTube) => + ops.handleUseSpawnTube(obj, equipment) + case Some(obj: SensorDeployable) => + ops.handleUseGeneralEntity(obj, equipment) + case Some(obj: TurretDeployable) => + ops.handleUseGeneralEntity(obj, equipment) + case Some(obj: TrapDeployable) => + ops.handleUseGeneralEntity(obj, equipment) + case Some(obj: ShieldGeneratorDeployable) => + ops.handleUseGeneralEntity(obj, equipment) + case Some(obj: TelepadDeployable) => + ops.handleUseTelepadDeployable(obj, equipment, pkt, ops.useRouterTelepadSystem) + case Some(obj: Utility.InternalTelepad) => + ops.handleUseInternalTelepad(obj, pkt, ops.useRouterTelepadSystem) + case Some(obj: CaptureFlag) => + ops.handleUseCaptureFlag(obj) + case Some(_: WarpGate) => + ops.handleUseWarpGate(equipment) + case Some(obj) => + ops.handleUseDefaultEntity(obj, equipment) + case None => () + } } def handleUnuseItem(pkt: UnuseItemMessage): Unit = { @@ -411,7 +406,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") - ops.handleDeployObject(continent, ammoType, pos, orient, player.WhichSide, player.Faction, Some((player, obj))) + ops.handleDeployObject(continent, ammoType, pos, orient, player.WhichSide, player.Faction, player, obj) case Some(obj) => log.warn(s"DeployObject: what is $obj, ${player.Name}? It's not a construction tool!") case None => @@ -976,14 +971,4 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex ) ) } - - private def cancelZoningWhenGeneralHandled(results: GeneralOperations.UseItem.Behavior): Unit = { - results match { - case GeneralOperations.UseItem.Handled => - sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use") - case GeneralOperations.UseItem.HandledPassive => - sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel") - case _ => () - } - } } diff --git a/src/main/scala/net/psforever/actors/session/normal/MountHandlerLogic.scala b/src/main/scala/net/psforever/actors/session/normal/MountHandlerLogic.scala index e7c6c238c..2a6249d62 100644 --- a/src/main/scala/net/psforever/actors/session/normal/MountHandlerLogic.scala +++ b/src/main/scala/net/psforever/actors/session/normal/MountHandlerLogic.scala @@ -63,7 +63,7 @@ class MountHandlerLogic(val ops: SessionMountHandlers, implicit val context: Act sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use") sessionLogic.terminals.CancelAllProximityUnits() ops.MountingAction(tplayer, obj, seatNumber) - sessionLogic.keepAliveFunc = sessionLogic.keepAlivePersistence + sessionLogic.keepAliveFunc = sessionLogic.keepAlivePersistenceFunc case Mountable.CanMount(obj: Vehicle, seatNumber, _) if obj.Definition == GlobalDefinitions.orbital_shuttle => @@ -71,7 +71,7 @@ class MountHandlerLogic(val ops: SessionMountHandlers, implicit val context: Act sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_mount") sessionLogic.terminals.CancelAllProximityUnits() ops.MountingAction(tplayer, obj, seatNumber) - sessionLogic.keepAliveFunc = sessionLogic.keepAlivePersistence + sessionLogic.keepAliveFunc = sessionLogic.keepAlivePersistenceFunc case Mountable.CanMount(obj: Vehicle, seatNumber, _) if obj.Definition == GlobalDefinitions.ant => @@ -148,7 +148,7 @@ class MountHandlerLogic(val ops: SessionMountHandlers, implicit val context: Act sendResponse(PlanetsideAttributeMessage(obj_guid, attribute_type=113, obj.Capacitor)) sessionLogic.general.accessContainer(obj) ops.updateWeaponAtSeatPosition(obj, seatNumber) - sessionLogic.keepAliveFunc = sessionLogic.keepAlivePersistence + sessionLogic.keepAliveFunc = sessionLogic.keepAlivePersistenceFunc tplayer.Actor ! ResetAllEnvironmentInteractions ops.MountingAction(tplayer, obj, seatNumber) @@ -166,7 +166,7 @@ class MountHandlerLogic(val ops: SessionMountHandlers, implicit val context: Act sendResponse(PlanetsideAttributeMessage(obj_guid, obj.Definition.shieldUiAttribute, obj.Shields)) sessionLogic.general.accessContainer(obj) ops.updateWeaponAtSeatPosition(obj, seatNumber) - sessionLogic.keepAliveFunc = sessionLogic.keepAlivePersistence + sessionLogic.keepAliveFunc = sessionLogic.keepAlivePersistenceFunc tplayer.Actor ! ResetAllEnvironmentInteractions ops.MountingAction(tplayer, obj, seatNumber) diff --git a/src/main/scala/net/psforever/actors/session/normal/TerminalHandlerLogic.scala b/src/main/scala/net/psforever/actors/session/normal/TerminalHandlerLogic.scala index 28eb63913..f42e82c3d 100644 --- a/src/main/scala/net/psforever/actors/session/normal/TerminalHandlerLogic.scala +++ b/src/main/scala/net/psforever/actors/session/normal/TerminalHandlerLogic.scala @@ -5,14 +5,11 @@ import akka.actor.{ActorContext, typed} import net.psforever.actors.session.AvatarActor import net.psforever.actors.session.support.{SessionData, SessionTerminalHandlers, TerminalHandlerFunctions} import net.psforever.login.WorldSession.{BuyNewEquipmentPutInInventory, SellEquipmentFromInventory} -import net.psforever.objects.{GlobalDefinitions, Player, Vehicle} +import net.psforever.objects.{Player, Vehicle} import net.psforever.objects.guid.TaskWorkflow -import net.psforever.objects.serverobject.pad.VehicleSpawnPad import net.psforever.objects.serverobject.terminals.Terminal -import net.psforever.objects.sourcing.AmenitySource -import net.psforever.objects.vital.TerminalUsedActivity -import net.psforever.packet.game.{FavoritesRequest, ItemTransactionMessage, ItemTransactionResultMessage, ProximityTerminalUseMessage, UnuseItemMessage} -import net.psforever.types.{TransactionType, Vector3} +import net.psforever.packet.game.{FavoritesRequest, ItemTransactionMessage, ItemTransactionResultMessage, ProximityTerminalUseMessage} +import net.psforever.types.TransactionType object TerminalHandlerLogic { def apply(ops: SessionTerminalHandlers): TerminalHandlerLogic = { @@ -89,6 +86,10 @@ class TerminalHandlerLogic(val ops: SessionTerminalHandlers, implicit val contex case Terminal.BuyVehicle(vehicle, weapons, trunk) => ops.buyVehicle(msg.terminal_guid, msg.transaction_type, vehicle, weapons, trunk) + .collect { + case _: Vehicle => + avatarActor ! AvatarActor.UpdatePurchaseTime(vehicle.Definition) + } ops.lastTerminalOrderFulfillment = true case Terminal.NoDeal() if msg != null => diff --git a/src/main/scala/net/psforever/actors/session/normal/VehicleLogic.scala b/src/main/scala/net/psforever/actors/session/normal/VehicleLogic.scala index 5b8ab62d1..ebb354d06 100644 --- a/src/main/scala/net/psforever/actors/session/normal/VehicleLogic.scala +++ b/src/main/scala/net/psforever/actors/session/normal/VehicleLogic.scala @@ -5,10 +5,11 @@ import akka.actor.{ActorContext, typed} import net.psforever.actors.session.AvatarActor import net.psforever.actors.session.support.{SessionData, VehicleFunctions, VehicleOperations} import net.psforever.objects.serverobject.PlanetSideServerObject -import net.psforever.objects.{Vehicle, Vehicles} +import net.psforever.objects.{PlanetSideGameObject, Vehicle, Vehicles} import net.psforever.objects.serverobject.deploy.Deployment import net.psforever.objects.serverobject.mount.Mountable import net.psforever.objects.vehicles.control.BfrFlight +import net.psforever.objects.vital.Vitality import net.psforever.objects.zones.Zone import net.psforever.packet.game.{ChatMsg, ChildObjectStateMessage, DeployRequestMessage, FrameVehicleStateMessage, VehicleStateMessage, VehicleSubStateMessage} import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage} @@ -214,7 +215,8 @@ class VehicleLogic(val ops: VehicleOperations, implicit val context: ActorContex case Some(mount: Mountable) => (o, mount.PassengerInSeat(player)) case _ => (None, None) }) match { - case (None, None) | (_, None) | (Some(_: Vehicle), Some(0)) => () + case (None, None) | (_, None) | (Some(_: Vehicle), Some(0)) => + () case _ => sessionLogic.persist() sessionLogic.turnCounterFunc(player.GUID) diff --git a/src/main/scala/net/psforever/actors/session/support/GeneralOperations.scala b/src/main/scala/net/psforever/actors/session/support/GeneralOperations.scala index 44e92efe6..2d0532395 100644 --- a/src/main/scala/net/psforever/actors/session/support/GeneralOperations.scala +++ b/src/main/scala/net/psforever/actors/session/support/GeneralOperations.scala @@ -1058,30 +1058,53 @@ class GeneralOperations( orientation: Vector3, side: Sidedness, faction: PlanetSideEmpire.Value, - optionalOwnerBuiltWith: Option[(Player, ConstructionItem)] + owner: Player, + builtWith: 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)) + val (deployableEntity, tasking) = commonHandleDeployObjectSetup(zone, deployableType, position, orientation, side, faction) + deployableEntity.AssignOwnership(owner) //execute - TaskWorkflow.execute(CallBackForTask(tasking, zone.Deployables, zoneBuildCommand, context.self)) + TaskWorkflow.execute(CallBackForTask( + tasking, + zone.Deployables, + Zone.Deployable.BuildByOwner(deployableEntity, owner, builtWith), + context.self + )) } - def handleUseDoor(door: Door, equipment: Option[Equipment]): GeneralOperations.UseItem.Behavior = { + def handleDeployObject( + zone: Zone, + deployableType: DeployedItem.Value, + position: Vector3, + orientation: Vector3, + side: Sidedness, + faction: PlanetSideEmpire.Value + ): Unit = { + val (deployableEntity, tasking) = commonHandleDeployObjectSetup(zone, deployableType, position, orientation, side, faction) + //execute + TaskWorkflow.execute(CallBackForTask( + tasking, + zone.Deployables, + Zone.Deployable.Build(deployableEntity), + context.self + )).onComplete { + _ => + Players.buildCooldownReset(zone, player.Name, deployableEntity.GUID) + deployableEntity.Actor ! Deployable.Deconstruct(Some(20.minutes)) + if (deployableType == DeployedItem.boomer) { + val trigger = new BoomerTrigger + trigger.Companion = deployableEntity.GUID + deployableEntity.asInstanceOf[BoomerDeployable].Trigger = trigger + TaskWorkflow.execute(CallBackForTask( + GUIDTask.registerEquipment(zone.GUID, trigger), + zone.Ground, + Zone.Ground.DropItem(trigger, position + Vector3.z(value = 0.5f), Vector3.z(orientation.z)) + )) + } + } + } + + def handleUseDoor(door: Door, equipment: Option[Equipment]): Unit = { equipment match { case Some(tool: Tool) if tool.Definition == GlobalDefinitions.medicalapplicator => val distance: Float = math.max( @@ -1092,88 +1115,72 @@ class GeneralOperations( case _ => door.Actor ! CommonMessages.Use(player) } - GeneralOperations.UseItem.Handled } - def handleUseResourceSilo(resourceSilo: ResourceSilo, equipment: Option[Equipment]): GeneralOperations.UseItem.Behavior = { + 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)) - GeneralOperations.UseItem.Handled 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)) - GeneralOperations.UseItem.Handled - case _ => - GeneralOperations.UseItem.Unhandled + case _ => () } } - def handleUsePlayer(obj: Player, equipment: Option[Equipment], msg: UseItemMessage): GeneralOperations.UseItem.Behavior = { + 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) - GeneralOperations.UseItem.Handled - } else { - GeneralOperations.UseItem.Unhandled } } 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)) - GeneralOperations.UseItem.Handled 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)) - GeneralOperations.UseItem.Unhandled case (Some(item), _) => log.error(s"UseItem: ${player.Name} looking for Kit to use, but found $item instead") - GeneralOperations.UseItem.Unhandled case (None, None) => - log.warn(s"UseItem: anticipated a Kit ${msg.item_used_guid} for ${player.Name}, but can't find it") - GeneralOperations.UseItem.Unhandled - } + 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) - GeneralOperations.UseItem.Handled + case Some(tool: Tool) if tool.Definition == GlobalDefinitions.medicalapplicator => obj.Actor ! CommonMessages.Use(player, equipment) - GeneralOperations.UseItem.Handled - case _ => - GeneralOperations.UseItem.Unhandled + case _ => () } - } else { - GeneralOperations.UseItem.Unhandled } } - def handleUseLocker(locker: Locker, equipment: Option[Equipment], msg: UseItemMessage): GeneralOperations.UseItem.Behavior = { + def handleUseLocker(locker: Locker, equipment: Option[Equipment], msg: UseItemMessage): Unit = { equipment match { case Some(item) => sendUseGeneralEntityMessage(locker, item) - GeneralOperations.UseItem.Unhandled 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) - GeneralOperations.UseItem.Handled - case _ => - GeneralOperations.UseItem.Unhandled + case _ => () } } - def handleUseCaptureTerminal(captureTerminal: CaptureTerminal, equipment: Option[Equipment]): GeneralOperations.UseItem.Behavior = { + def handleUseCaptureTerminal(captureTerminal: CaptureTerminal, equipment: Option[Equipment]): Unit = { equipment match { case Some(item) => sendUseGeneralEntityMessage(captureTerminal, item) @@ -1182,33 +1189,25 @@ class GeneralOperations( case Some(llu: CaptureFlag) => if (llu.Target.GUID == captureTerminal.Owner.GUID) { continent.LocalEvents ! LocalServiceMessage(continent.id, LocalAction.LluCaptured(llu)) - GeneralOperations.UseItem.Handled } else { log.info( s"LLU target is not this base. Target GUID: ${llu.Target.GUID} This base: ${captureTerminal.Owner.GUID}" ) } - GeneralOperations.UseItem.Unhandled - case _ => - log.warn("Item in specialItemSlotGuid is not registered with continent or is not a LLU") - GeneralOperations.UseItem.Unhandled + case _ => log.warn("Item in specialItemSlotGuid is not registered with continent or is not a LLU") } - case _ => - GeneralOperations.UseItem.Unhandled + case _ => () } } - def handleUseFacilityTurret(obj: FacilityTurret, equipment: Option[Equipment], msg: UseItemMessage): GeneralOperations.UseItem.Behavior = { - equipment - .collect { item => - sendUseGeneralEntityMessage(obj, item) - obj.Actor ! CommonMessages.Use(player, Some((item, msg.unk2.toInt))) //try upgrade path - GeneralOperations.UseItem.Handled - } - .getOrElse(GeneralOperations.UseItem.Unhandled) + 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): GeneralOperations.UseItem.Behavior = { + def handleUseVehicle(obj: Vehicle, equipment: Option[Equipment], msg: UseItemMessage): Unit = { equipment match { case Some(item) => sendUseGeneralEntityMessage(obj, item) @@ -1220,33 +1219,29 @@ class GeneralOperations( .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) - GeneralOperations.UseItem.Handled - } else { - GeneralOperations.UseItem.Unhandled } - case _ => - GeneralOperations.UseItem.Unhandled + case _ => () } } - def handleUseTerminal(terminal: Terminal, equipment: Option[Equipment], msg: UseItemMessage): GeneralOperations.UseItem.Behavior = { + def handleUseTerminal(terminal: Terminal, equipment: Option[Equipment], msg: UseItemMessage): Unit = { equipment match { case Some(item) => sendUseGeneralEntityMessage(terminal, item) - GeneralOperations.UseItem.Handled 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) ) - GeneralOperations.UseItem.Handled } else if ( tdef == GlobalDefinitions.multivehicle_rearm_terminal || tdef == GlobalDefinitions.bfr_rearm_terminal || tdef == GlobalDefinitions.air_rearm_terminal || tdef == GlobalDefinitions.ground_rearm_terminal @@ -1258,49 +1253,45 @@ class GeneralOperations( ) sendResponse(msg) sendResponse(msg.copy(object_guid = vehicle.GUID, object_id = vehicle.Definition.ObjectId)) - GeneralOperations.UseItem.Handled case None => log.error(s"UseItem: Expecting a seated vehicle, ${player.Name} found none") - GeneralOperations.UseItem.Unhandled } } 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)) ) - GeneralOperations.UseItem.Handled } 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)) ) - GeneralOperations.UseItem.Handled } else { log.info(s"${player.Name} is accessing a ${terminal.Definition.Name}") + sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use") sendResponse(msg) - GeneralOperations.UseItem.Handled } - case _ => - GeneralOperations.UseItem.Unhandled + case _ => () } } - def handleUseSpawnTube(obj: SpawnTube, equipment: Option[Equipment]): GeneralOperations.UseItem.Behavior = { + 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) - GeneralOperations.UseItem.Handled - case _ => - GeneralOperations.UseItem.Unhandled + case _ => () } } @@ -1309,7 +1300,7 @@ class GeneralOperations( equipment: Option[Equipment], msg: UseItemMessage, useTelepadFunc: (Vehicle, InternalTelepad, TelepadDeployable, PlanetSideGameObject with TelepadLike, PlanetSideGameObject with TelepadLike) => Unit - ): GeneralOperations.UseItem.Behavior = { + ): Unit = { if (equipment.isEmpty) { (continent.GUID(obj.Router) match { case Some(vehicle: Vehicle) => Some((vehicle, vehicle.Utility(UtilityType.internal_router_telepad_deployable))) @@ -1317,25 +1308,20 @@ class GeneralOperations( 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) - GeneralOperations.UseItem.HandledPassive case Some((vehicle: Vehicle, None)) => log.error( s"telepad@${msg.object_guid.guid} is not linked to a router - ${vehicle.Definition.Name}" ) - GeneralOperations.UseItem.Unhandled 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() - GeneralOperations.UseItem.Unhandled - case _ => - GeneralOperations.UseItem.Unhandled + case _ => () } - } else { - GeneralOperations.UseItem.Unhandled } } @@ -1343,19 +1329,16 @@ class GeneralOperations( obj: InternalTelepad, msg: UseItemMessage, useTelepadFunc: (Vehicle, InternalTelepad, TelepadDeployable, PlanetSideGameObject with TelepadLike, PlanetSideGameObject with TelepadLike) => Unit - ): GeneralOperations.UseItem.Behavior = { + ): Unit = { continent.GUID(obj.Telepad) match { case Some(pad: TelepadDeployable) => player.WhichSide = pad.WhichSide useTelepadFunc(obj.Owner.asInstanceOf[Vehicle], obj, pad, obj, pad) - GeneralOperations.UseItem.HandledPassive 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}" ) - GeneralOperations.UseItem.Unhandled - case None => - GeneralOperations.UseItem.Unhandled + case None => () } } @@ -1433,63 +1416,55 @@ class GeneralOperations( recentTeleportAttempt = time } - def handleUseCaptureFlag(obj: CaptureFlag): GeneralOperations.UseItem.Behavior = { + 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) - GeneralOperations.UseItem.Handled case None => log.warn(s"${player.Faction} player ${player.toString} tried to pick up a ${obj.Faction} LLU - ${obj.GUID}") - GeneralOperations.UseItem.Unhandled 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" ) - GeneralOperations.UseItem.Unhandled - case _ => - GeneralOperations.UseItem.Unhandled + case _ => () } } - def handleUseWarpGate(equipment: Option[Equipment]): GeneralOperations.UseItem.Behavior = { + 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) - GeneralOperations.UseItem.Handled - case _ => - GeneralOperations.UseItem.Unhandled + case _ => () } } - def handleUseGeneralEntity(obj: PlanetSideServerObject, equipment: Option[Equipment]): GeneralOperations.UseItem.Behavior = { - equipment - .collect { item => - obj.Actor ! CommonMessages.Use(player, Some(item)) - GeneralOperations.UseItem.Handled - } - .getOrElse(GeneralOperations.UseItem.Unhandled) + 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): GeneralOperations.UseItem.Behavior = { + def sendUseGeneralEntityMessage(obj: PlanetSideServerObject, equipment: Equipment): Unit = { + sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use") obj.Actor ! CommonMessages.Use(player, Some(equipment)) - GeneralOperations.UseItem.Handled } - def handleUseDefaultEntity(obj: PlanetSideGameObject, equipment: Option[Equipment]): GeneralOperations.UseItem.Behavior = { + 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) => () - GeneralOperations.UseItem.Handled case _ => log.warn(s"UseItem: ${player.Name} does not know how to handle $obj") - GeneralOperations.UseItem.Unhandled } } @@ -1500,6 +1475,28 @@ class GeneralOperations( ) } + private def commonHandleDeployObjectSetup( + zone: Zone, + deployableType: DeployedItem.Value, + position: Vector3, + orientation: Vector3, + side: Sidedness, + faction: PlanetSideEmpire.Value + ): (Deployable, TaskBundle) = { + 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) + } + (deployableEntity, tasking) + } + override protected[session] def actionsToCancel(): Unit = { progressBarValue = None kitToBeUsed = None @@ -1519,11 +1516,12 @@ class GeneralOperations( } } + case Some(o) if player.isAlive => + unaccessContainer(o) + sendResponse(UnuseItemMessage(player.GUID, o.GUID)) + case Some(o) => unaccessContainer(o) - if (player.isAlive) { - sendResponse(UnuseItemMessage(player.GUID, o.GUID)) - } case None => () } diff --git a/src/main/scala/net/psforever/actors/session/support/SessionData.scala b/src/main/scala/net/psforever/actors/session/support/SessionData.scala index 8821f6cd9..68fcf9f0d 100644 --- a/src/main/scala/net/psforever/actors/session/support/SessionData.scala +++ b/src/main/scala/net/psforever/actors/session/support/SessionData.scala @@ -89,6 +89,7 @@ class SessionData( private[session] var persistFunc: () => Unit = noPersistence private[session] var persist: () => Unit = updatePersistenceOnly private[session] var keepAliveFunc: () => Unit = keepAlivePersistenceInitial + private[session] var keepAlivePersistenceFunc: () => Unit = keepAlivePersistence private[session] var turnCounterFunc: PlanetSideGUID => Unit = SessionData.NoTurnCounterYet private[session] val oldRefsMap: mutable.HashMap[PlanetSideGUID, String] = new mutable.HashMap[PlanetSideGUID, String]() private var contextSafeEntity: PlanetSideGUID = PlanetSideGUID(0) @@ -471,7 +472,7 @@ class SessionData( def keepAlivePersistenceInitial(): Unit = { persist() if (player != null && player.HasGUID) { - keepAliveFunc = keepAlivePersistence + keepAliveFunc = keepAlivePersistenceFunc } } diff --git a/src/main/scala/net/psforever/actors/session/support/SessionLocalHandlers.scala b/src/main/scala/net/psforever/actors/session/support/SessionLocalHandlers.scala index f09a432bc..8480a1d55 100644 --- a/src/main/scala/net/psforever/actors/session/support/SessionLocalHandlers.scala +++ b/src/main/scala/net/psforever/actors/session/support/SessionLocalHandlers.scala @@ -30,12 +30,12 @@ class SessionLocalHandlers( } def handleTurretDeployableIsDismissed(obj: TurretDeployable): Unit = { - Players.buildCooldownReset(continent, player.Name, obj) + Players.buildCooldownReset(continent, player.Name, obj.GUID) TaskWorkflow.execute(GUIDTask.unregisterDeployableTurret(continent.GUID, obj)) } def handleDeployableIsDismissed(obj: Deployable): Unit = { - Players.buildCooldownReset(continent, player.Name, obj) + Players.buildCooldownReset(continent, player.Name, obj.GUID) TaskWorkflow.execute(GUIDTask.unregisterObject(continent.GUID, obj)) } diff --git a/src/main/scala/net/psforever/actors/session/support/SessionTerminalHandlers.scala b/src/main/scala/net/psforever/actors/session/support/SessionTerminalHandlers.scala index bb8770a48..835ae7f6d 100644 --- a/src/main/scala/net/psforever/actors/session/support/SessionTerminalHandlers.scala +++ b/src/main/scala/net/psforever/actors/session/support/SessionTerminalHandlers.scala @@ -90,12 +90,11 @@ class SessionTerminalHandlers( vehicle: Vehicle, weapons: List[InventoryItem], trunk: List[InventoryItem] - ): Unit = { + ): Option[Vehicle] = { 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) @@ -126,6 +125,7 @@ class SessionTerminalHandlers( sendResponse(UnuseItemMessage(player.GUID, terminalGuid)) } player.LogActivity(TerminalUsedActivity(AmenitySource(term), transactionType)) + vehicle } .orElse { log.error( diff --git a/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala b/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala index 0aa00bba0..8f66d3078 100644 --- a/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala +++ b/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala @@ -202,6 +202,7 @@ class ZoningOperations( private var zoningChatMessageType: ChatMessageType = ChatMessageType.CMT_QUIT private var zoningCounter: Int = 0 private var zoningTimer: Cancellable = Default.Cancellable + var displayZoningMessageWhenCancelled: Boolean = true /* packets */ @@ -906,7 +907,7 @@ class ZoningOperations( * defaults to `None` */ def CancelZoningProcessWithReason(msg: String, msgType: Option[ChatMessageType] = None): Unit = { - if (zoningStatus != Zoning.Status.None) { + if (displayZoningMessageWhenCancelled && zoningStatus != Zoning.Status.None) { sendResponse(ChatMsg(msgType.getOrElse(zoningChatMessageType), wideContents=false, "", msg, None)) } CancelZoningProcess() @@ -2298,7 +2299,7 @@ class ZoningOperations( * @param zone na */ def HandleReleaseAvatar(tplayer: Player, zone: Zone): Unit = { - sessionLogic.keepAliveFunc = sessionLogic.keepAlivePersistence + sessionLogic.keepAliveFunc = sessionLogic.keepAlivePersistenceFunc tplayer.Release tplayer.VehicleSeated match { case None => @@ -3023,7 +3024,7 @@ class ZoningOperations( sessionLogic.keepAliveFunc = sessionLogic.vehicles.GetMountableAndSeat(None, player, continent) match { case (Some(v: Vehicle), Some(seatNumber)) if seatNumber > 0 && v.WeaponControlledFromSeat(seatNumber).isEmpty => - sessionLogic.keepAlivePersistence + sessionLogic.keepAlivePersistenceFunc case _ => NormalKeepAlive } @@ -3476,7 +3477,7 @@ class ZoningOperations( //sit down sendResponse(ObjectAttachMessage(vguid, pguid, seat)) sessionLogic.general.accessContainer(vehicle) - sessionLogic.keepAliveFunc = sessionLogic.keepAlivePersistence + sessionLogic.keepAliveFunc = sessionLogic.keepAlivePersistenceFunc case _ => () //we can't find a vehicle? and we're still here? that's bad player.VehicleSeated = None diff --git a/src/main/scala/net/psforever/login/WorldSession.scala b/src/main/scala/net/psforever/login/WorldSession.scala index a83a9df00..e24360396 100644 --- a/src/main/scala/net/psforever/login/WorldSession.scala +++ b/src/main/scala/net/psforever/login/WorldSession.scala @@ -1067,6 +1067,13 @@ object WorldSession { } } + /** + * Perform a task and, upon completion, dispatch a message. + * @param task task to be completed first + * @param sendTo where to send the message + * @param pass message + * @return a `TaskBundle` object + */ def CallBackForTask(task: TaskBundle, sendTo: ActorRef, pass: Any): TaskBundle = { TaskBundle( new StraightforwardTask() { @@ -1085,6 +1092,14 @@ object WorldSession { ) } + /** + * Perform a task and, upon completion, dispatch a message whose origin is specific and different from normal. + * @param task task to be completed first + * @param sendTo where to send the message + * @param pass message + * @param replyTo whom to attribute the message being passed (e.g., `tell`) + * @return a `TaskBundle` object + */ def CallBackForTask(task: TaskBundle, sendTo: ActorRef, pass: Any, replyTo: ActorRef): TaskBundle = { TaskBundle( new StraightforwardTask() { diff --git a/src/main/scala/net/psforever/objects/Players.scala b/src/main/scala/net/psforever/objects/Players.scala index d5140c0d5..7ec07f3c2 100644 --- a/src/main/scala/net/psforever/objects/Players.scala +++ b/src/main/scala/net/psforever/objects/Players.scala @@ -18,7 +18,7 @@ import net.psforever.objects.vital.projectile.ProjectileReason import net.psforever.objects.vital.{InGameActivity, InGameHistory, RevivingActivity} import net.psforever.objects.zones.Zone import net.psforever.packet.game._ -import net.psforever.types.{ChatMessageType, ExoSuitType, Vector3} +import net.psforever.types.{ChatMessageType, ExoSuitType, PlanetSideGUID, Vector3} import net.psforever.services.Service import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage} import net.psforever.services.local.{LocalAction, LocalServiceMessage} @@ -327,7 +327,7 @@ object Players { */ def successfulBuildActivity(zone: Zone, channel: String, obj: Deployable): Unit = { //sent to avatar event bus to preempt additional tool management - buildCooldownReset(zone, channel, obj) + buildCooldownReset(zone, channel, obj.GUID) //sent to local event bus to cooperate with deployable management zone.LocalEvents ! LocalServiceMessage( channel, @@ -339,13 +339,13 @@ object Players { * Common actions related to constructing a new `Deployable` object in the game environment. * @param zone in which zone these messages apply * @param channel to whom to send the messages - * @param obj the `Deployable` object + * @param guid `Deployable` object */ - def buildCooldownReset(zone: Zone, channel: String, obj: Deployable): Unit = { + def buildCooldownReset(zone: Zone, channel: String, guid: PlanetSideGUID): Unit = { //sent to avatar event bus to preempt additional tool management zone.AvatarEvents ! AvatarServiceMessage( channel, - AvatarAction.SendResponse(Service.defaultPlayerGUID, GenericObjectActionMessage(obj.GUID, 21)) + AvatarAction.SendResponse(Service.defaultPlayerGUID, GenericObjectActionMessage(guid, 21)) ) } diff --git a/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala b/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala index 186fe1ef5..f0e14b768 100644 --- a/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala +++ b/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala @@ -385,15 +385,13 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm } if (Players.CertificationToUseExoSuit(player, exosuit, subtype)) { if (exosuit == ExoSuitType.MAX) { - val weapon = GlobalDefinitions.MAXArms(subtype, player.Faction) - val cooldown = player.avatar.purchaseCooldown(weapon) + val cooldown = player.avatar.purchaseCooldown(GlobalDefinitions.MAXArms(subtype, player.Faction)) if (originalSubtype == subtype) { - (exosuit, subtype) //same MAX subtype is free + (exosuit, subtype) //same MAX subtype } else if (cooldown.nonEmpty) { - fallbackSuit //different MAX subtype can not have cooldown + fallbackSuit //different MAX subtype } else { - avatarActor ! AvatarActor.UpdatePurchaseTime(weapon) - (exosuit, subtype) //switching for first time causes cooldown + (exosuit, subtype) //switching } } else { (exosuit, subtype) @@ -475,8 +473,6 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm case InventoryItem(citem: ConstructionItem, _) => Deployables.initializeConstructionItem(player.avatar.certifications, citem) } - //deactivate non-passive implants - avatarActor ! AvatarActor.DeactivateActiveImplants val zone = player.Zone zone.AvatarEvents ! AvatarServiceMessage( Players.ZoneChannelIfSpectating(player), @@ -556,7 +552,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm //don't know where boomer trigger "should" go TaskWorkflow.execute(PutNewEquipmentInInventoryOrDrop(player)(trigger)) } - Players.buildCooldownReset(zone, player.Name, obj) + Players.buildCooldownReset(zone, player.Name, obj.GUID) case _ => () } deployablePair = None @@ -573,7 +569,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm player.Actor ! Player.LoseDeployable(obj) TelepadControl.TelepadError(zone, player.Name, msg = "@Telepad_NoDeploy_RouterLost") } - Players.buildCooldownReset(zone, player.Name, obj) + Players.buildCooldownReset(zone, player.Name, obj.GUID) case _ => () } deployablePair = None @@ -581,7 +577,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm case Zone.Deployable.IsBuilt(obj) => deployablePair match { case Some((deployable, tool)) if deployable eq obj => - Players.buildCooldownReset(player.Zone, player.Name, obj) + Players.buildCooldownReset(player.Zone, player.Name, obj.GUID) player.Find(tool) match { case Some(index) => Players.commonDestroyConstructionItem(player, tool, index) @@ -613,10 +609,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm val weapon = GlobalDefinitions.MAXArms(subtype, player.Faction) player.avatar.purchaseCooldown(weapon) .collect(_ => false) - .getOrElse { - avatarActor ! AvatarActor.UpdatePurchaseTime(weapon) - true - } + .getOrElse(true) } else { true }) @@ -671,8 +664,6 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm //insert afterHolsters.foreach(elem => player.Slot(elem.start).Equipment = elem.obj) afterInventory.foreach(elem => player.Inventory.InsertQuickly(elem.start, elem.obj)) - //deactivate non-passive implants - avatarActor ! AvatarActor.DeactivateActiveImplants player.Zone.AvatarEvents ! AvatarServiceMessage( Players.ZoneChannelIfSpectating(player), AvatarAction.ChangeExosuit( @@ -744,7 +735,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm else { log.warn(s"cannot build a ${obj.Definition.Name}") DropEquipmentFromInventory(player)(tool, Some(obj.Position)) - Players.buildCooldownReset(zone, player.Name, obj) + Players.buildCooldownReset(zone, player.Name, obj.GUID) obj.Position = Vector3.Zero obj.AssignOwnership(None) zone.Deployables ! Zone.Deployable.Dismiss(obj) @@ -755,7 +746,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm obj.AssignOwnership(None) val zone = player.Zone zone.Deployables ! Zone.Deployable.Dismiss(obj) - Players.buildCooldownReset(zone, player.Name, obj) + Players.buildCooldownReset(zone, player.Name, obj.GUID) } }