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 4833ce14e..9d716b3ba 100644
--- a/src/main/scala/net/psforever/actors/session/csr/AvatarHandlerLogic.scala
+++ b/src/main/scala/net/psforever/actors/session/csr/AvatarHandlerLogic.scala
@@ -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))
diff --git a/src/main/scala/net/psforever/actors/session/csr/ChatLogic.scala b/src/main/scala/net/psforever/actors/session/csr/ChatLogic.scala
index 781ea7e50..864e9738b 100644
--- a/src/main/scala/net/psforever/actors/session/csr/ChatLogic.scala
+++ b/src/main/scala/net/psforever/actors/session/csr/ChatLogic.scala
@@ -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
}
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 41c530b4c..60d4933ed 100644
--- a/src/main/scala/net/psforever/actors/session/csr/CustomerServiceRepresentativeMode.scala
+++ b/src/main/scala/net/psforever/actors/session/csr/CustomerServiceRepresentativeMode.scala
@@ -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
diff --git a/src/main/scala/net/psforever/actors/session/csr/GalaxyHandlerLogic.scala b/src/main/scala/net/psforever/actors/session/csr/GalaxyHandlerLogic.scala
deleted file mode 100644
index cf9b9932b..000000000
--- a/src/main/scala/net/psforever/actors/session/csr/GalaxyHandlerLogic.scala
+++ /dev/null
@@ -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 _ => ()
- }
- }
-}
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 61c5e942a..a6732f51e 100644
--- a/src/main/scala/net/psforever/actors/session/csr/GeneralLogic.scala
+++ b/src/main/scala/net/psforever/actors/session/csr/GeneralLogic.scala
@@ -1,22 +1,19 @@
// Copyright (c) 2024 PSForever
package net.psforever.actors.session.csr
-import akka.actor.typed.scaladsl.adapter._
import akka.actor.{ActorContext, ActorRef, typed}
-import net.psforever.actors.session.{AvatarActor, SessionActor}
+import net.psforever.actors.session.AvatarActor
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.objects.avatar.{Avatar, PlayerControl, SpecialCarry}
+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}
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.affinity.FactionAffinity
import net.psforever.objects.serverobject.containable.Containable
import net.psforever.objects.serverobject.doors.Door
import net.psforever.objects.serverobject.generator.Generator
@@ -24,33 +21,21 @@ 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.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.vehicles.Utility
+import net.psforever.objects.vital.Vitality
+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.packet.game.{ActionCancelMessage, ActionResultMessage, AvatarFirstTimeEventMessage, AvatarImplantMessage, AvatarJumpMessage, BattleplanMessage, BindPlayerMessage, BugReportMessage, ChangeFireModeMessage, ChangeShortcutBankMessage, CharacterCreateRequestMessage, CharacterRequestMessage, 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, TradeMessage, UnuseItemMessage, UseItemMessage, VoiceHostInfo, VoiceHostRequest, ZipLineMessage}
import net.psforever.services.RemoverActor
-import net.psforever.services.account.{AccountPersistenceService, RetrieveAccountData}
+import net.psforever.services.account.AccountPersistenceService
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.util.Config
-
-import scala.concurrent.duration._
+import net.psforever.types.{CapacitorStateType, Cosmetic, ExoSuitType, PlanetSideEmpire, PlanetSideGUID, Vector3}
object GeneralLogic {
def apply(ops: GeneralOperations): GeneralLogic = {
@@ -63,30 +48,11 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
private val avatarActor: typed.ActorRef[AvatarActor.Command] = ops.avatarActor
- def handleConnectToWorldRequest(pkt: ConnectToWorldRequestMessage): Unit = {
- val ConnectToWorldRequestMessage(_, token, majorVersion, minorVersion, revision, buildDate, _, _) = pkt
- log.trace(
- s"ConnectToWorldRequestMessage: client with versioning $majorVersion.$minorVersion.$revision, $buildDate has sent a token to the server"
- )
- sendResponse(ChatMsg(ChatMessageType.CMT_CULLWATERMARK, wideContents=false, "", "", None))
- context.self ! SessionActor.StartHeartbeat
- sessionLogic.accountIntermediary ! RetrieveAccountData(token)
- }
+ def handleConnectToWorldRequest(pkt: ConnectToWorldRequestMessage): Unit = { /* intentionally blank */ }
- def handleCharacterCreateRequest(pkt: CharacterCreateRequestMessage): Unit = {
- val CharacterCreateRequestMessage(name, head, voice, gender, empire) = pkt
- avatarActor ! AvatarActor.CreateAvatar(name, head, voice, gender, empire)
- }
+ def handleCharacterCreateRequest(pkt: CharacterCreateRequestMessage): Unit = { /* intentionally blank */ }
- def handleCharacterRequest(pkt: CharacterRequestMessage): Unit = {
- val CharacterRequestMessage(charId, action) = pkt
- action match {
- case CharacterRequestAction.Delete =>
- avatarActor ! AvatarActor.DeleteAvatar(charId.toInt)
- case CharacterRequestAction.Select =>
- avatarActor ! AvatarActor.SelectAvatar(charId.toInt, context.self)
- }
- }
+ def handleCharacterRequest(pkt: CharacterRequestMessage): Unit = { /* intentionally blank */ }
def handlePlayerStateUpstream(pkt: PlayerStateMessageUpstream): Unit = {
val PlayerStateMessageUpstream(
@@ -107,14 +73,21 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
) = pkt
sessionLogic.persist()
sessionLogic.turnCounterFunc(avatarGuid)
- sessionLogic.updateBlockMap(player, pos)
- //below half health, fully heal
+ //below half health, full heal
val maxHealth = player.MaxHealth.toLong
- if (player.Health < maxHealth * 0.5) {
+ if (player.Health < maxHealth * 0.5f) {
player.Health = maxHealth.toInt
+ player.LogActivity(player.ClearHistory().head)
sendResponse(PlanetsideAttributeMessage(avatarGuid, 0, maxHealth))
continent.AvatarEvents ! AvatarServiceMessage(continent.id, AvatarAction.PlanetsideAttribute(avatarGuid, 0, maxHealth))
}
+ //below half stamina, full stamina
+ val avatar = player.avatar
+ val maxStamina = avatar.maxStamina
+ if (avatar.stamina < maxStamina * 0.5f) {
+ session = session.copy(avatar = avatar.copy(stamina = maxStamina))
+ sendResponse(PlanetsideAttributeMessage(player.GUID, 2, maxStamina.toLong))
+ }
//expected
val isMoving = WorldEntity.isMoving(vel)
val isMovingPlus = isMoving || isJumping || jumpThrust
@@ -122,7 +95,7 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
if (sessionLogic.zoning.zoningStatus == Zoning.Status.Deconstructing) {
sessionLogic.zoning.spawn.stopDeconstructing()
} else if (sessionLogic.zoning.zoningStatus != Zoning.Status.None) {
- sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_motion")
+ sessionLogic.zoning.CancelZoningProcess()
}
}
ops.fallHeightTracker(pos.z)
@@ -136,10 +109,10 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
player.Crouching = isCrouching
player.Jumping = isJumping
if (isCloaking && !player.Cloaked) {
- sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_cloak")
+ sessionLogic.zoning.CancelZoningProcess()
}
player.Cloaked = player.ExoSuit == ExoSuitType.Infiltration && isCloaking
- maxCapacitorTick(jumpThrust)
+ maxCapacitorTick()
if (isMovingPlus && sessionLogic.terminals.usingMedicalTerminal.isDefined) {
continent.GUID(sessionLogic.terminals.usingMedicalTerminal) match {
case Some(term: Terminal with ProximityUnit) =>
@@ -150,52 +123,40 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
ops.accessedContainer match {
// Ensure we don't unload the contents of the vehicle trunk for players seated in the vehicle.
// This can happen if PSUM arrives during the mounting process
- case Some(veh: Vehicle) if player.VehicleSeated.isEmpty || player.VehicleSeated.get != veh.GUID =>
- if (isMoving || veh.isMoving(test = 1) || Vector3.DistanceSquared(player.Position, veh.TrunkLocation) > 9) {
- val guid = player.GUID
- sendResponse(UnuseItemMessage(guid, veh.GUID))
- sendResponse(UnuseItemMessage(guid, guid))
- ops.unaccessContainer(veh)
- }
- case Some(container) => //just in case
- if (isMovingPlus && (player.VehicleSeated.isEmpty || player.VehicleSeated.get != container.GUID)) {
+ case Some(container)
+ if !container.HasGUID && (player.VehicleSeated.isEmpty || player.VehicleSeated.get != container.GUID) => //just in case
// Ensure we don't close the container if the player is seated in it
val guid = player.GUID
// 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(guid, container.GUID))
- }
sendResponse(UnuseItemMessage(guid, guid))
ops.unaccessContainer(container)
- }
case None => ()
}
- val eagleEye: Boolean = ops.canSeeReallyFar
- val isNotVisible: Boolean = sessionLogic.zoning.zoningStatus == Zoning.Status.Deconstructing ||
- (player.isAlive && sessionLogic.zoning.spawn.deadState == DeadState.RespawnTime)
- continent.AvatarEvents ! AvatarServiceMessage(
- continent.id,
- AvatarAction.PlayerState(
- avatarGuid,
- player.Position,
- player.Velocity,
- yaw,
- pitch,
- yawUpper,
- seqTime,
- isCrouching,
- isJumping,
- jumpThrust,
- isCloaking,
- isNotVisible,
- eagleEye
+ if (!player.spectator) {
+ sessionLogic.updateBlockMap(player, pos)
+ val eagleEye: Boolean = ops.canSeeReallyFar
+ val isNotVisible: Boolean = sessionLogic.zoning.zoningStatus == Zoning.Status.Deconstructing ||
+ (player.isAlive && sessionLogic.zoning.spawn.deadState == DeadState.RespawnTime)
+ continent.AvatarEvents ! AvatarServiceMessage(
+ continent.id,
+ AvatarAction.PlayerState(
+ avatarGuid,
+ player.Position,
+ player.Velocity,
+ yaw,
+ pitch,
+ yawUpper,
+ seqTime,
+ isCrouching,
+ isJumping,
+ jumpThrust,
+ isCloaking,
+ isNotVisible,
+ eagleEye
+ )
)
- )
- sessionLogic.squad.updateSquad()
- if (player.death_by == -1) {
- sessionLogic.kickedByAdministration()
}
- player.zoneInteractions()
+ sessionLogic.squad.updateSquad()
}
def handleVoiceHostRequest(pkt: VoiceHostRequest): Unit = {
@@ -216,11 +177,11 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
(sessionLogic.validObject(itemGuid, decorator = "DropItem"), player.FreeHand.Equipment) match {
case (Some(anItem: Equipment), Some(heldItem))
if (anItem eq heldItem) && continent.GUID(player.VehicleSeated).nonEmpty =>
- sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
+ sessionLogic.zoning.CancelZoningProcess()
RemoveOldEquipmentFromInventory(player)(heldItem)
case (Some(anItem: Equipment), Some(heldItem))
if anItem eq heldItem =>
- sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use")
+ sessionLogic.zoning.CancelZoningProcess()
DropEquipmentFromInventory(player)(heldItem)
case (Some(anItem: Equipment), _)
if continent.GUID(player.VehicleSeated).isEmpty =>
@@ -248,11 +209,7 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
player.Actor ! PlayerControl.ObjectHeld(heldHolsters)
}
- def handleAvatarJump(pkt: AvatarJumpMessage): Unit = {
- val AvatarJumpMessage(_) = pkt
- avatarActor ! AvatarActor.ConsumeStamina(10)
- avatarActor ! AvatarActor.SuspendStaminaRegeneration(2.5 seconds)
- }
+ def handleAvatarJump(pkt: AvatarJumpMessage): Unit = { /* no stamina loss */ }
def handleZipLine(pkt: ZipLineMessage): Unit = {
val ZipLineMessage(playerGuid, forwards, action, pathId, pos) = pkt
@@ -290,13 +247,7 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
//make sure this is the correct response for all cases
sessionLogic.validObject(objectGuid, decorator = "RequestDestroy") match {
case Some(vehicle: Vehicle) =>
- /* vehicle is not mounted in anything or, if it is, its seats are empty */
- if (vehicle.MountedIn.isEmpty || !vehicle.Seats.values.exists(_.isOccupied)) { //todo kick out to delete?
- vehicle.Actor ! Vehicle.Deconstruct()
- //log.info(s"RequestDestroy: vehicle $vehicle")
- } else {
- log.warn(s"RequestDestroy: ${player.Name} must own vehicle in order to deconstruct it")
- }
+ vehicle.Actor ! Vehicle.Deconstruct()
case Some(obj: Projectile) =>
if (!obj.isResolved) {
@@ -305,7 +256,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
@@ -320,10 +271,16 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
obj.Actor ! Deployable.Deconstruct()
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}")
+ case Some(obj: Player) if obj.isBackpack =>
+ obj.Position = Vector3.Zero
+ continent.AvatarEvents ! AvatarServiceMessage.Corpse(RemoverActor.ClearSpecific(List(obj), continent))
+
+ case Some(obj: Player) =>
+ sessionLogic.general.suicide(obj)
+
+ case Some(_) => ()
case None => ()
}
@@ -411,7 +368,7 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
sendResponse(PlanetsideAttributeMessage(player.GUID, 28, implant.definition.implantType.value * 2))
}
} else {
- sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_implant")
+ sessionLogic.zoning.CancelZoningProcess()
avatar.implants(slot) match {
case Some(implant) =>
if (status == 1) {
@@ -439,45 +396,49 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
case Some(door: Door) =>
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) 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) =>
- 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 => ()
}
}
@@ -506,21 +467,8 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
case DeployedItem.portable_manned_turret => GlobalDefinitions.PortableMannedTurret(player.Faction).Item
case dtype => dtype
}
- 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))
+ sessionLogic.zoning.CancelZoningProcess()
+ ops.handleDeployObject(continent, ammoType, pos, orient, player.WhichSide, PlanetSideEmpire.NEUTRAL, None)
case Some(obj) =>
log.warn(s"DeployObject: what is $obj, ${player.Name}? It's not a construction tool!")
case None =>
@@ -531,10 +479,8 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
def handlePlanetsideAttribute(pkt: PlanetsideAttributeMessage): Unit = {
val PlanetsideAttributeMessage(objectGuid, attributeType, attributeValue) = pkt
sessionLogic.validObject(objectGuid, decorator = "PlanetsideAttribute") match {
- case Some(vehicle: Vehicle) if player.avatar.vehicle.contains(vehicle.GUID) =>
- vehicle.Actor ! ServerObject.AttributeMsg(attributeType, attributeValue)
case Some(vehicle: Vehicle) =>
- log.warn(s"PlanetsideAttribute: ${player.Name} does not own vehicle ${vehicle.GUID} and can not change it")
+ vehicle.Actor ! ServerObject.AttributeMsg(attributeType, attributeValue)
// Cosmetics options
case Some(_: Player) if attributeType == 106 =>
avatarActor ! AvatarActor.SetCosmetics(Cosmetic.valuesFromAttributeValue(attributeValue))
@@ -557,7 +503,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
@@ -591,11 +537,7 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
def handleGenericAction(pkt: GenericActionMessage): Unit = {
val GenericActionMessage(action) = pkt
- if (player == null) {
- if (action == GenericAction.AwayFromKeyboard_RCV) {
- log.debug("GenericObjectState: AFK state reported during login")
- }
- } else {
+ if (player != null) {
val (toolOpt, definition) = player.Slot(0).Equipment match {
case Some(tool: Tool) =>
(Some(tool), tool.Definition)
@@ -606,7 +548,6 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
case GenericAction.DropSpecialItem =>
ops.dropSpecialSlotItem()
case GenericAction.MaxAnchorsExtend_RCV =>
- log.info(s"${player.Name} has anchored ${player.Sex.pronounObject}self to the ground")
player.UsingSpecial = SpecialExoSuitDefinition.Mode.Anchored
continent.AvatarEvents ! AvatarServiceMessage(
continent.id,
@@ -627,7 +568,6 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
log.warn(s"GenericObject: ${player.Name} is a MAX with an unexpected attachment - ${definition.Name}")
}
case GenericAction.MaxAnchorsRelease_RCV =>
- log.info(s"${player.Name} has released the anchors")
player.UsingSpecial = SpecialExoSuitDefinition.Mode.Normal
continent.AvatarEvents ! AvatarServiceMessage(
continent.id,
@@ -664,23 +604,16 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
log.warn(s"GenericActionMessage: ${player.Name} can't stop MAX special effect")
}
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)
+ if (!avatar.lookingForSquad) {
+ avatarActor ! AvatarActor.SetLookingForSquad(false)
}
case GenericAction.NotLookingForSquad_RCV => //Looking For Squad OFF
- if (avatar.lookingForSquad && (sessionLogic.squad.squadUI.isEmpty || sessionLogic.squad.squadUI(player.CharId).index == 0)) {
+ if (avatar.lookingForSquad) {
avatarActor ! AvatarActor.SetLookingForSquad(false)
}
case _ =>
@@ -690,99 +623,31 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
}
def handleGenericCollision(pkt: GenericCollisionMsg): Unit = {
- val GenericCollisionMsg(ctype, p, _, ppos, pv, t, _, tpos, tv, _, _, _) = pkt
- val fallHeight = {
- if (pv.z * pv.z >= (pv.x * pv.x + pv.y * pv.y) * 0.5f) {
- if (ops.heightTrend) {
- val fall = ops.heightLast - ops.heightHistory
- ops.heightHistory = ops.heightLast
- fall
- }
- else {
- val fall = ops.heightHistory - ops.heightLast
- ops.heightLast = ops.heightHistory
- fall
- }
- } else {
- 0f
+ val GenericCollisionMsg(ctype, p, _, _, pv, _, _, _, _, _, _, _) = pkt
+ if (pv.z * pv.z >= (pv.x * pv.x + pv.y * pv.y) * 0.5f) {
+ if (ops.heightTrend) {
+ ops.heightHistory = ops.heightLast
+ }
+ else {
+ ops.heightLast = ops.heightHistory
}
}
- val (target1, target2, bailProtectStatus, velocity) = (ctype, sessionLogic.validObject(p, decorator = "GenericCollision/Primary")) match {
- case (CollisionIs.OfInfantry, out @ Some(user: Player))
+ (ctype, sessionLogic.validObject(p, decorator = "GenericCollision/Primary")) match {
+ case (CollisionIs.OfInfantry, Some(user: Player))
if user == player =>
- val bailStatus = session.flying || session.speed > 1f || player.BailProtection
player.BailProtection = false
- val v = if (player.avatar.implants.exists {
- case Some(implant) => implant.definition.implantType == ImplantType.Surge && implant.active
- case _ => false
- }) {
- Vector3.Zero
- } else {
- pv
- }
- (out, None, bailStatus, v)
- case (CollisionIs.OfGroundVehicle, out @ Some(v: Vehicle))
+ case (CollisionIs.OfGroundVehicle, Some(v: Vehicle))
if v.Seats(0).occupant.contains(player) =>
- val bailStatus = v.BailProtection
v.BailProtection = false
- (out, sessionLogic.validObject(t, decorator = "GenericCollision/GroundVehicle"), bailStatus, pv)
- case (CollisionIs.OfAircraft, out @ Some(v: Vehicle))
- if v.Definition.CanFly && v.Seats(0).occupant.contains(player) =>
- (out, sessionLogic.validObject(t, decorator = "GenericCollision/Aircraft"), false, pv)
+ case (CollisionIs.OfAircraft, Some(v: Vehicle))
+ if v.Definition.CanFly && v.Seats(0).occupant.contains(player) => ()
case (CollisionIs.BetweenThings, _) =>
log.warn("GenericCollision: CollisionIs.BetweenThings detected - no handling case")
- (None, None, false, Vector3.Zero)
- case _ =>
- (None, None, false, Vector3.Zero)
- }
- val curr = System.currentTimeMillis()
- (target1, t, target2) match {
- case (None, _, _) => ()
-
- case (Some(us: PlanetSideServerObject with Vitality with FactionAffinity), PlanetSideGUID(0), _) =>
- if (updateCollisionHistoryForTarget(us, curr)) {
- if (!bailProtectStatus) {
- sessionLogic.handleDealingDamage(
- us,
- DamageInteraction(
- SourceEntry(us),
- CollisionReason(velocity, fallHeight, us.DamageModel),
- ppos
- )
- )
- }
- }
-
- case (Some(us: Vehicle), _, Some(victim: SensorDeployable)) =>
- collisionBetweenVehicleAndFragileDeployable(us, ppos, victim, tpos, velocity - tv, fallHeight, curr)
-
- case (Some(us: Vehicle), _, Some(victim: TurretDeployable)) if victim.Seats.isEmpty =>
- collisionBetweenVehicleAndFragileDeployable(us, ppos, victim, tpos, velocity - tv, fallHeight, curr)
-
- case (
- Some(us: PlanetSideServerObject with Vitality with FactionAffinity), _,
- Some(victim: PlanetSideServerObject with Vitality with FactionAffinity)
- ) =>
- if (updateCollisionHistoryForTarget(victim, curr)) {
- val usSource = SourceEntry(us)
- val victimSource = SourceEntry(victim)
- //we take damage from the collision
- if (!bailProtectStatus) {
- performCollisionWithSomethingDamage(us, usSource, ppos, victimSource, fallHeight, velocity - tv)
- }
- //get dealt damage from our own collision (no protection)
- ops.collisionHistory.put(us.Actor, curr)
- performCollisionWithSomethingDamage(victim, victimSource, tpos, usSource, fallHeight = 0f, tv - velocity)
- }
-
case _ => ()
}
}
- def handleAvatarFirstTimeEvent(pkt: AvatarFirstTimeEventMessage): Unit = {
- val AvatarFirstTimeEventMessage(_, _, _, eventName) = pkt
- avatarActor ! AvatarActor.AddFirstTimeEvent(eventName)
- }
+ def handleAvatarFirstTimeEvent(pkt: AvatarFirstTimeEventMessage): Unit = { /* no speedrunning fte's */ }
def handleBugReport(pkt: PlanetSideGamePacket): Unit = {
val BugReportMessage(
@@ -809,24 +674,16 @@ 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 _ if vehicleGuid.nonEmpty =>
- log.warn(
- s"FacilityBenefitShieldChargeRequest: ${player.Name} can not find chargeable entity ${vehicleGuid.get.guid} in ${continent.id}"
- )
- case _ =>
- log.warn(s"FacilityBenefitShieldChargeRequest: ${player.Name} is not seated in anything")
+ case obj: Vehicle => ops.commonFacilityShieldCharging(obj)
+ case obj: TurretDeployable => ops.commonFacilityShieldCharging(obj)
+ case _ if vehicleGuid.nonEmpty => ()
+ case _ => ()
}
}
def handleBattleplan(pkt: BattleplanMessage): Unit = {
- val BattleplanMessage(_, name, _, _) = pkt
- val lament: String = s"$name has a brilliant idea that no one will ever see"
- log.info(lament)
- log.debug(s"Battleplan: $lament - $pkt")
+ /* can not draw battleplan */
+ //todo csr exclusive battleplan channel
}
def handleBindPlayer(pkt: BindPlayerMessage): Unit = {
@@ -852,25 +709,7 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
avatarActor ! AvatarActor.MemberListRequest(action, name)
}
- def handleInvalidTerrain(pkt: InvalidTerrainMessage): Unit = {
- val InvalidTerrainMessage(_, vehicleGuid, alert, _) = pkt
- (continent.GUID(vehicleGuid), continent.GUID(player.VehicleSeated)) match {
- case (Some(packetVehicle: Vehicle), Some(playerVehicle: Vehicle)) if packetVehicle eq playerVehicle =>
- if (alert == TerrainCondition.Unsafe) {
- log.info(s"${player.Name}'s ${packetVehicle.Definition.Name} is approaching terrain unsuitable for idling")
- }
- case (Some(packetVehicle: Vehicle), Some(_: Vehicle)) =>
- if (alert == TerrainCondition.Unsafe) {
- log.info(s"${packetVehicle.Definition.Name}@${packetVehicle.GUID} is approaching terrain unsuitable for idling, but is not ${player.Name}'s vehicle")
- }
- case (Some(_: Vehicle), _) =>
- log.warn(s"InvalidTerrain: ${player.Name} is not seated in a(ny) vehicle near unsuitable terrain")
- case (Some(packetThing), _) =>
- log.warn(s"InvalidTerrain: ${player.Name} thinks that ${packetThing.Definition.Name}@${packetThing.GUID} is near unsuitable terrain")
- case _ =>
- log.error(s"InvalidTerrain: ${player.Name} is complaining about a thing@$vehicleGuid that can not be found")
- }
- }
+ def handleInvalidTerrain(pkt: InvalidTerrainMessage): Unit = { /* csr does not have to worry about invalid terrain */ }
def handleActionCancel(pkt: ActionCancelMessage): Unit = {
val ActionCancelMessage(_, _, _) = pkt
@@ -931,19 +770,9 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
/* messages */
- def handleRenewCharSavedTimer(): Unit = {
- ops.renewCharSavedTimer(
- Config.app.game.savedMsg.interruptedByAction.fixed,
- Config.app.game.savedMsg.interruptedByAction.variable
- )
- }
+ def handleRenewCharSavedTimer(): Unit = { /* */ }
- def handleRenewCharSavedTimerMsg(): Unit = {
- ops.displayCharSavedMsgThenRenewTimer(
- Config.app.game.savedMsg.interruptedByAction.fixed,
- Config.app.game.savedMsg.interruptedByAction.variable
- )
- }
+ def handleRenewCharSavedTimerMsg(): Unit = { /* */ }
def handleSetAvatar(avatar: Avatar): Unit = {
session = session.copy(avatar = avatar)
@@ -953,21 +782,14 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
LivePlayerList.Update(avatar.id, avatar)
}
- def handleReceiveAccountData(account: Account): Unit = {
- log.trace(s"ReceiveAccountData $account")
- session = session.copy(account = account)
- avatarActor ! AvatarActor.SetAccount(account)
- }
+ def handleReceiveAccountData(account: Account): Unit = { /* no need */ }
def handleUseCooldownRenew: BasicDefinition => Unit = {
case _: KitDefinition => ops.kitToBeUsed = None
case _ => ()
}
- def handleAvatarResponse(avatar: Avatar): Unit = {
- session = session.copy(avatar = avatar)
- sessionLogic.accountPersistence ! AccountPersistenceService.Login(avatar.name, avatar.id)
- }
+ def handleAvatarResponse(avatar: Avatar): Unit = { /* no need */ }
def handleSetSpeed(speed: Float): Unit = {
session = session.copy(speed = speed)
@@ -986,9 +808,7 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
sessionLogic.accountPersistence ! AccountPersistenceService.Kick(player.Name, time)
}
- def handleSilenced(isSilenced: Boolean): Unit = {
- player.silenced = isSilenced
- }
+ def handleSilenced(isSilenced: Boolean): Unit = { /* can not be silenced */ }
def handleItemPutInSlot(msg: Containable.ItemPutInSlot): Unit = {
log.debug(s"ItemPutInSlot: $msg")
@@ -1004,471 +824,44 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
/* supporting functions */
- private def handleUseDoor(door: Door, equipment: Option[Equipment]): Unit = {
+ 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))
+ door.Actor ! CommonMessages.Use(player, Some(Float.MaxValue))
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 = {
+ private def maxCapacitorTick(): Unit = {
if (player.ExoSuit == ExoSuitType.MAX) {
- val activate = (jumpThrust || player.isOverdrived || player.isShielded) && player.Capacitor > 0
player.CapacitorState match {
- case CapacitorStateType.Idle => maxCapacitorTickIdle(activate)
- case CapacitorStateType.Discharging => maxCapacitorTickDischarging(activate)
- case CapacitorStateType.ChargeDelay => maxCapacitorTickChargeDelay(activate)
- case CapacitorStateType.Charging => maxCapacitorTickCharging(activate)
+ case CapacitorStateType.ChargeDelay => maxCapacitorTickChargeDelay()
+ case CapacitorStateType.Charging => maxCapacitorTickCharging()
+ case _ => maxCapacitorTickIdle()
}
} else if (player.CapacitorState != CapacitorStateType.Idle) {
player.CapacitorState = CapacitorStateType.Idle
}
}
- private def maxCapacitorTickIdle(activate: Boolean): Unit = {
- if (activate) {
- player.CapacitorState = CapacitorStateType.Discharging
- //maxCapacitorTickDischarging(activate)
- } else if (player.Capacitor < player.ExoSuitDef.MaxCapacitor) {
+ private def maxCapacitorTickIdle(): Unit = {
+ if (player.Capacitor < player.ExoSuitDef.MaxCapacitor) {
player.CapacitorState = CapacitorStateType.ChargeDelay
- maxCapacitorTickChargeDelay(activate)
+ maxCapacitorTickChargeDelay()
}
}
- 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 < player.ExoSuitDef.MaxCapacitor) {
- if (player.Faction != PlanetSideEmpire.VS) {
- ops.toggleMaxSpecialState(enable = false)
- }
- player.CapacitorState = CapacitorStateType.ChargeDelay
- maxCapacitorTickChargeDelay(activate)
- } else {
- player.CapacitorState = CapacitorStateType.Idle
- }
- }
-
- private def maxCapacitorTickChargeDelay(activate: Boolean): Unit = {
- if (activate) {
- player.CapacitorState = CapacitorStateType.Discharging
- //maxCapacitorTickDischarging(activate)
- } else if (player.Capacitor == player.ExoSuitDef.MaxCapacitor) {
+ private def maxCapacitorTickChargeDelay(): Unit = {
+ if (player.Capacitor == player.ExoSuitDef.MaxCapacitor) {
player.CapacitorState = CapacitorStateType.Idle
} else if (System.currentTimeMillis() - player.CapacitorLastUsedMillis > player.ExoSuitDef.CapacitorRechargeDelayMillis) {
player.CapacitorState = CapacitorStateType.Charging
- //maxCapacitorTickCharging(activate)
}
}
- private def maxCapacitorTickCharging(activate: Boolean): Unit = {
- if (activate) {
- player.CapacitorState = CapacitorStateType.Discharging
- //maxCapacitorTickDischarging(activate)
- } else if (player.Capacitor < player.ExoSuitDef.MaxCapacitor) {
+ 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
@@ -1477,64 +870,4 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
player.CapacitorState = CapacitorStateType.Idle
}
}
-
- private def updateCollisionHistoryForTarget(
- target: PlanetSideServerObject with Vitality with FactionAffinity,
- curr: Long
- ): Boolean = {
- ops.collisionHistory.get(target.Actor) match {
- case Some(lastCollision) if curr - lastCollision <= 1000L =>
- false
- case _ =>
- ops.collisionHistory.put(target.Actor, curr)
- true
- }
- }
-
- private def collisionBetweenVehicleAndFragileDeployable(
- vehicle: Vehicle,
- vehiclePosition: Vector3,
- smallDeployable: Deployable,
- smallDeployablePosition: Vector3,
- velocity: Vector3,
- fallHeight: Float,
- collisionTime: Long
- ): Unit = {
- if (updateCollisionHistoryForTarget(smallDeployable, collisionTime)) {
- val smallDeployableSource = SourceEntry(smallDeployable)
- //vehicle takes damage from the collision (ignore bail protection in this case)
- performCollisionWithSomethingDamage(vehicle, SourceEntry(vehicle), vehiclePosition, smallDeployableSource, fallHeight, velocity)
- //deployable gets absolutely destroyed
- ops.collisionHistory.put(vehicle.Actor, collisionTime)
- sessionLogic.handleDealingDamage(
- smallDeployable,
- DamageInteraction(smallDeployableSource, SuicideReason(), smallDeployablePosition)
- )
- }
- }
-
- private def performCollisionWithSomethingDamage(
- target: PlanetSideServerObject with Vitality with FactionAffinity,
- targetSource: SourceEntry,
- targetPosition: Vector3,
- victimSource: SourceEntry,
- fallHeight: Float,
- velocity: Vector3
- ): Unit = {
- sessionLogic.handleDealingDamage(
- target,
- DamageInteraction(
- targetSource,
- CollisionWithReason(CollisionReason(velocity, fallHeight, target.DamageModel), victimSource),
- targetPosition
- )
- )
- }
-
- private def commonFacilityShieldCharging(obj: PlanetSideServerObject with BlockMapEntity): Unit = {
- obj.Actor ! CommonMessages.ChargeShields(
- 15,
- Some(continent.blockMap.sector(obj).buildingList.maxBy(_.Definition.SOIRadius))
- )
- }
}
diff --git a/src/main/scala/net/psforever/actors/session/csr/LocalHandlerLogic.scala b/src/main/scala/net/psforever/actors/session/csr/LocalHandlerLogic.scala
deleted file mode 100644
index 37d7c2f62..000000000
--- a/src/main/scala/net/psforever/actors/session/csr/LocalHandlerLogic.scala
+++ /dev/null
@@ -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))
- }
-}
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 91e470beb..94ba103a7 100644
--- a/src/main/scala/net/psforever/actors/session/csr/MountHandlerLogic.scala
+++ b/src/main/scala/net/psforever/actors/session/csr/MountHandlerLogic.scala
@@ -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)
- )
- }
}
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 665e60cf9..602375a4c 100644
--- a/src/main/scala/net/psforever/actors/session/csr/SpectateAsCustomerServiceRepresentativeMode.scala
+++ b/src/main/scala/net/psforever/actors/session/csr/SpectateAsCustomerServiceRepresentativeMode.scala
@@ -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"))
}
}
diff --git a/src/main/scala/net/psforever/actors/session/csr/SquadHandlerLogic.scala b/src/main/scala/net/psforever/actors/session/csr/SquadHandlerLogic.scala
index fae51f9f7..f6be56232 100644
--- a/src/main/scala/net/psforever/actors/session/csr/SquadHandlerLogic.scala
+++ b/src/main/scala/net/psforever/actors/session/csr/SquadHandlerLogic.scala
@@ -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))
+ }
}
}
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 ba6843ceb..f5b42446e 100644
--- a/src/main/scala/net/psforever/actors/session/csr/TerminalHandlerLogic.scala
+++ b/src/main/scala/net/psforever/actors/session/csr/TerminalHandlerLogic.scala
@@ -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 =>
diff --git a/src/main/scala/net/psforever/actors/session/csr/VehicleHandlerLogic.scala b/src/main/scala/net/psforever/actors/session/csr/VehicleHandlerLogic.scala
deleted file mode 100644
index 7bacfbda7..000000000
--- a/src/main/scala/net/psforever/actors/session/csr/VehicleHandlerLogic.scala
+++ /dev/null
@@ -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)
- }
- }
-}
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 c9eac4b88..afd433b85 100644
--- a/src/main/scala/net/psforever/actors/session/csr/VehicleLogic.scala
+++ b/src/main/scala/net/psforever/actors/session/csr/VehicleLogic.scala
@@ -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))
+ }
}
}
diff --git a/src/main/scala/net/psforever/actors/session/csr/WeaponAndProjectileLogic.scala b/src/main/scala/net/psforever/actors/session/csr/WeaponAndProjectileLogic.scala
index 22cf517e3..a99067d89 100644
--- a/src/main/scala/net/psforever/actors/session/csr/WeaponAndProjectileLogic.scala
+++ b/src/main/scala/net/psforever/actors/session/csr/WeaponAndProjectileLogic.scala
@@ -1,217 +1,54 @@
// 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.{SessionData, WeaponAndProjectileFunctions, WeaponAndProjectileOperations}
-import net.psforever.login.WorldSession.{CountAmmunition, CountGrenades, FindAmmoBoxThatUses, FindEquipmentStock, FindToolThatUses, PutEquipmentInInventoryOrDrop, PutNewEquipmentInInventoryOrDrop, RemoveOldEquipmentFromInventory}
-import net.psforever.objects.ballistics.{Projectile, ProjectileQuality}
-import net.psforever.objects.definition.ProjectileDefinition
-import net.psforever.objects.entity.SimpleWorldEntity
-import net.psforever.objects.equipment.{ChargeFireModeDefinition, Equipment, EquipmentSize, FireModeSwitch}
-import net.psforever.objects.guid.{GUIDTask, TaskBundle, TaskWorkflow}
+import net.psforever.login.WorldSession.{CountGrenades, FindEquipmentStock, FindToolThatUses, RemoveOldEquipmentFromInventory}
+import net.psforever.objects.equipment.ChargeFireModeDefinition
import net.psforever.objects.inventory.Container
-import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject}
-import net.psforever.objects.serverobject.doors.InteriorDoorPassage
-import net.psforever.objects.{AmmoBox, BoomerDeployable, BoomerTrigger, ConstructionItem, Deployables, DummyExplodingEntity, GlobalDefinitions, OwnableByPlayer, PlanetSideGameObject, Player, SpecialEmp, Tool, Tools, Vehicle}
-import net.psforever.objects.serverobject.interior.Sidedness
-import net.psforever.objects.serverobject.mount.Mountable
-import net.psforever.objects.serverobject.structures.Amenity
+import net.psforever.objects.{BoomerDeployable, BoomerTrigger, GlobalDefinitions, Player, SpecialEmp, Tool, Tools, Vehicle}
import net.psforever.objects.serverobject.turret.{FacilityTurret, VanuSentry}
-import net.psforever.objects.serverobject.turret.auto.{AutomatedTurret, AutomatedTurretBehavior}
-import net.psforever.objects.sourcing.{PlayerSource, SourceEntry}
-import net.psforever.objects.vital.Vitality
import net.psforever.objects.vital.base.{DamageResolution, DamageType}
-import net.psforever.objects.vital.etc.OicwLilBuddyReason
-import net.psforever.objects.vital.interaction.DamageInteraction
-import net.psforever.objects.vital.projectile.ProjectileReason
import net.psforever.objects.zones.{Zone, ZoneProjectile}
-import net.psforever.packet.game.{AIDamage, AvatarGrenadeStateMessage, ChainLashMessage, ChangeAmmoMessage, ChangeFireModeMessage, ChangeFireStateMessage_Start, ChangeFireStateMessage_Stop, HitMessage, InventoryStateMessage, LashMessage, LongRangeProjectileInfoMessage, ObjectAttachMessage, ObjectDeleteMessage, ObjectDetachMessage, ProjectileStateMessage, QuantityUpdateMessage, ReloadMessage, SplashHitMessage, UplinkRequest, WeaponDelayFireMessage, WeaponDryFireMessage, WeaponFireMessage, WeaponLazeTargetPositionMessage}
+import net.psforever.packet.game.{AIDamage, AvatarGrenadeStateMessage, ChangeAmmoMessage, ChangeFireModeMessage, ChangeFireStateMessage_Start, ChangeFireStateMessage_Stop, HitMessage, LashMessage, LongRangeProjectileInfoMessage, ProjectileStateMessage, QuantityUpdateMessage, ReloadMessage, SplashHitMessage, UplinkRequest, WeaponDelayFireMessage, WeaponDryFireMessage, WeaponFireMessage, WeaponLazeTargetPositionMessage}
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
import net.psforever.types.{PlanetSideGUID, Vector3}
-import net.psforever.util.Config
import scala.concurrent.ExecutionContext.Implicits.global
-import scala.concurrent.Future
import scala.concurrent.duration._
object WeaponAndProjectileLogic {
def apply(ops: WeaponAndProjectileOperations): WeaponAndProjectileLogic = {
new WeaponAndProjectileLogic(ops, ops.context)
}
-
- /**
- * Does a line segment line intersect with a sphere?
- * This most likely belongs in `Geometry` or `GeometryForm` or somehow in association with the `\objects\geometry\` package.
- * @param start first point of the line segment
- * @param end second point of the line segment
- * @param center center of the sphere
- * @param radius radius of the sphere
- * @return list of all points of intersection, if any
- * @see `Vector3.DistanceSquared`
- * @see `Vector3.MagnitudeSquared`
- */
- private def quickLineSphereIntersectionPoints(
- start: Vector3,
- end: Vector3,
- center: Vector3,
- radius: Float
- ): Iterable[Vector3] = {
- /*
- Algorithm adapted from code found on https://paulbourke.net/geometry/circlesphere/index.html#linesphere,
- because I kept messing up proper substitution of the line formula and the circle formula into the quadratic equation.
- */
- val Vector3(cx, cy, cz) = center
- val Vector3(sx, sy, sz) = start
- val vector = end - start
- //speed our way through a quadratic equation
- val (a, b) = {
- val Vector3(dx, dy, dz) = vector
- (
- dx * dx + dy * dy + dz * dz,
- 2f * (dx * (sx - cx) + dy * (sy - cy) + dz * (sz - cz))
- )
- }
- val c = Vector3.MagnitudeSquared(center) + Vector3.MagnitudeSquared(start) - 2f * (cx * sx + cy * sy + cz * sz) - radius * radius
- val result = b * b - 4 * a * c
- if (result < 0f) {
- //negative, no intersection
- Seq()
- } else if (result < 0.00001f) {
- //zero-ish, one intersection point
- Seq(start - vector * (b / (2f * a)))
- } else {
- //positive, two intersection points
- val sqrt = math.sqrt(result).toFloat
- val endStart = vector / (2f * a)
- Seq(start + endStart * (sqrt - b), start + endStart * (b + sqrt) * -1f)
- }.filter(p => Vector3.DistanceSquared(start, p) <= a)
- }
-
- /**
- * Preparation for explosion damage that utilizes the Scorpion's little buddy sub-projectiles.
- * The main difference from "normal" server-side explosion
- * is that the owner of the projectile must be clarified explicitly.
- * @see `Zone::serverSideDamage`
- * @param zone where the explosion is taking place
- * (`source` contains the coordinate location)
- * @param source a game object that represents the source of the explosion
- * @param owner who or what to accredit damage from the explosion to;
- * clarifies a normal `SourceEntry(source)` accreditation
- */
- private def detonateLittleBuddy(
- zone: Zone,
- source: PlanetSideGameObject with FactionAffinity with Vitality,
- proxy: Projectile,
- owner: SourceEntry
- )(): Unit = {
- Zone.serverSideDamage(zone, source, littleBuddyExplosionDamage(owner, proxy.id, source.Position))
- }
-
- /**
- * Preparation for explosion damage that utilizes the Scorpion's little buddy sub-projectiles.
- * The main difference from "normal" server-side explosion
- * is that the owner of the projectile must be clarified explicitly.
- * The sub-projectiles will be the product of a normal projectile rather than a standard game object
- * so a custom `source` entity must wrap around it and fulfill the requirements of the field.
- * @see `Zone::explosionDamage`
- * @param owner who or what to accredit damage from the explosion to
- * @param explosionPosition where the explosion will be positioned in the game world
- * @param source a game object that represents the source of the explosion
- * @param target a game object that is affected by the explosion
- * @return a `DamageInteraction` object
- */
- private def littleBuddyExplosionDamage(
- owner: SourceEntry,
- projectileId: Long,
- explosionPosition: Vector3
- )
- (
- source: PlanetSideGameObject with FactionAffinity with Vitality,
- target: PlanetSideGameObject with FactionAffinity with Vitality
- ): DamageInteraction = {
- DamageInteraction(SourceEntry(target), OicwLilBuddyReason(owner, projectileId, target.DamageModel), explosionPosition)
- }
}
class WeaponAndProjectileLogic(val ops: WeaponAndProjectileOperations, implicit val context: ActorContext) extends WeaponAndProjectileFunctions {
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 handleWeaponFire(pkt: WeaponFireMessage): Unit = {
- val WeaponFireMessage(
- _,
- weapon_guid,
- projectile_guid,
- shot_origin,
- _,
- _,
- _,
- _/*max_distance,*/,
- _,
- _/*projectile_type,*/,
- thrown_projectile_vel
- ) = pkt
- HandleWeaponFireOperations(weapon_guid, projectile_guid, shot_origin, thrown_projectile_vel.flatten)
+ ops.handleWeaponFireOperations(pkt)
}
def handleWeaponDelayFire(pkt: WeaponDelayFireMessage): Unit = {
val WeaponDelayFireMessage(_, _) = pkt
- log.info(s"${player.Name} - $pkt")
}
def handleWeaponDryFire(pkt: WeaponDryFireMessage): Unit = {
- val WeaponDryFireMessage(weapon_guid) = pkt
- val (containerOpt, tools) = ops.FindContainedWeapon
- tools
- .find { _.GUID == weapon_guid }
- .orElse { continent.GUID(weapon_guid) }
- .collect {
- case _: Equipment if containerOpt.exists(_.isInstanceOf[Player]) =>
- continent.AvatarEvents ! AvatarServiceMessage(
- continent.id,
- AvatarAction.WeaponDryFire(player.GUID, weapon_guid)
- )
- case _: Equipment =>
- continent.VehicleEvents ! VehicleServiceMessage(
- continent.id,
- VehicleAction.WeaponDryFire(player.GUID, weapon_guid)
- )
- }
- .orElse {
- log.warn(
- s"WeaponDryFire: ${player.Name}'s weapon ${weapon_guid.guid} is either not a weapon or does not exist"
- )
- None
- }
+ ops.handleWeaponDryFire(pkt)
}
- def handleWeaponLazeTargetPosition(pkt: WeaponLazeTargetPositionMessage): Unit = {
- val WeaponLazeTargetPositionMessage(_, _, _) = pkt
- //do not need to handle the progress bar animation/state on the server
- //laze waypoint is requested by client upon completion (see SquadWaypointRequest)
- val purpose = if (sessionLogic.squad.squad_supplement_id > 0) {
- s" for ${player.Sex.possessive} squad (#${sessionLogic.squad.squad_supplement_id -1})"
- } else {
- " ..."
- }
- log.info(s"${player.Name} is lazing a position$purpose")
- }
+ def handleWeaponLazeTargetPosition(pkt: WeaponLazeTargetPositionMessage): Unit = { /* laze is handled elsewhere */ }
- def handleUplinkRequest(packet: UplinkRequest): Unit = {
- sessionLogic.administrativeKick(player)
- }
+ def handleUplinkRequest(packet: UplinkRequest): Unit = { /* CUD not implemented yet */ }
- def handleAvatarGrenadeState(pkt: AvatarGrenadeStateMessage): Unit = {
- val AvatarGrenadeStateMessage(_, state) = pkt
- //TODO I thought I had this working?
- log.info(s"${player.Name} has $state ${player.Sex.possessive} grenade")
- }
+ def handleAvatarGrenadeState(pkt: AvatarGrenadeStateMessage): Unit = { /* grenades are handled elsewhere */ }
def handleChangeFireStateStart(pkt: ChangeFireStateMessage_Start): Unit = {
val ChangeFireStateMessage_Start(item_guid) = pkt
@@ -274,98 +111,15 @@ class WeaponAndProjectileLogic(val ops: WeaponAndProjectileOperations, implicit
}
def handleChangeAmmo(pkt: ChangeAmmoMessage): Unit = {
- val ChangeAmmoMessage(item_guid, _) = pkt
- val (thing, equipment) = sessionLogic.findContainedEquipment()
- if (equipment.isEmpty) {
- log.warn(s"ChangeAmmo: either can not find $item_guid or the object found was not Equipment")
- } else {
- equipment foreach {
- case obj: ConstructionItem =>
- if (Deployables.performConstructionItemAmmoChange(player.avatar.certifications, obj, obj.AmmoTypeIndex)) {
- log.info(
- s"${player.Name} switched ${player.Sex.possessive} ${obj.Definition.Name} to construct ${obj.AmmoType} (option #${obj.FireModeIndex})"
- )
- sendResponse(ChangeAmmoMessage(obj.GUID, obj.AmmoTypeIndex))
- }
- case tool: Tool =>
- thing match {
- case Some(player: Player) =>
- PerformToolAmmoChange(tool, player, ModifyAmmunition(player))
- case Some(mountable: PlanetSideServerObject with Container) =>
- PerformToolAmmoChange(tool, mountable, ModifyAmmunitionInMountable(mountable))
- case _ =>
- log.warn(s"ChangeAmmo: the ${thing.get.Definition.Name} in ${player.Name}'s is not the correct type")
- }
- case obj =>
- log.warn(s"ChangeAmmo: the ${obj.Definition.Name} in ${player.Name}'s hands does not contain ammunition")
- }
- }
+ ops.handleChangeAmmo(pkt)
}
def handleChangeFireMode(pkt: ChangeFireModeMessage): Unit = {
- val ChangeFireModeMessage(item_guid, _/*fire_mode*/) = pkt
- sessionLogic.findEquipment(item_guid) match {
- case Some(obj: PlanetSideGameObject with FireModeSwitch[_]) =>
- val originalModeIndex = obj.FireModeIndex
- if (obj match {
- case citem: ConstructionItem =>
- val modeChanged = Deployables.performConstructionItemFireModeChange(
- player.avatar.certifications,
- citem,
- originalModeIndex
- )
- modeChanged
- case _ =>
- obj.NextFireMode
- obj.FireModeIndex != originalModeIndex
- }) {
- val modeIndex = obj.FireModeIndex
- obj match {
- case citem: ConstructionItem =>
- log.info(s"${player.Name} switched ${player.Sex.possessive} ${obj.Definition.Name} to construct ${citem.AmmoType} (mode #$modeIndex)")
- case _ =>
- log.info(s"${player.Name} changed ${player.Sex.possessive} her ${obj.Definition.Name}'s fire mode to #$modeIndex")
- }
- sendResponse(ChangeFireModeMessage(item_guid, modeIndex))
- continent.AvatarEvents ! AvatarServiceMessage(
- continent.id,
- AvatarAction.ChangeFireMode(player.GUID, item_guid, modeIndex)
- )
- }
- case Some(_) =>
- log.warn(s"ChangeFireMode: the object that was found for $item_guid does not possess fire modes")
- case None =>
- log.warn(s"ChangeFireMode: can not find $item_guid")
- }
+ ops.handleChangeFireMode(pkt)
}
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 = {
@@ -378,296 +132,100 @@ class WeaponAndProjectileLogic(val ops: WeaponAndProjectileOperations, implicit
}
def handleDirectHit(pkt: HitMessage): Unit = {
- val HitMessage(
- _,
- projectile_guid,
- _,
- hit_info,
- _,
- _,
- _
- ) = pkt
- //find defined projectile
- ops.FindProjectileEntry(projectile_guid) match {
- case Some(projectile) =>
- //find target(s)
- (hit_info match {
- case Some(hitInfo) =>
- val hitPos = hitInfo.hit_pos
- sessionLogic.validObject(hitInfo.hitobject_guid, decorator = "Hit/hitInfo") match {
- case _ if projectile.profile == GlobalDefinitions.flail_projectile =>
- val radius = projectile.profile.DamageRadius * projectile.profile.DamageRadius
- val targets = Zone.findAllTargets(continent, player, hitPos, projectile.profile)
- .filter { target =>
- Vector3.DistanceSquared(target.Position, hitPos) <= radius
- }
- targets.map { target =>
- CheckForHitPositionDiscrepancy(projectile_guid, hitPos, target)
- (target, projectile, hitPos, target.Position)
- }
-
- case Some(target: PlanetSideGameObject with FactionAffinity with Vitality) =>
- CheckForHitPositionDiscrepancy(projectile_guid, hitPos, target)
- List((target, projectile, hitInfo.shot_origin, hitPos))
-
- case None =>
- HandleDamageProxy(projectile, projectile_guid, hitPos)
-
- case _ =>
- Nil
- }
- case None =>
- Nil
- })
- .foreach {
- case (
- target: PlanetSideGameObject with FactionAffinity with Vitality,
- proj: Projectile,
- _: Vector3,
- hitPos: Vector3
- ) =>
- ResolveProjectileInteraction(proj, DamageResolution.Hit, target, hitPos).collect { resprojectile =>
- addShotsLanded(resprojectile.cause.attribution, shots = 1)
- sessionLogic.handleDealingDamage(target, resprojectile)
- }
- case _ => ()
- }
- case None =>
- log.warn(s"ResolveProjectile: expected projectile, but ${projectile_guid.guid} not found")
+ val list = ops.composeDirectDamageInformation(pkt)
+ if (!player.spectator) {
+ list.foreach {
+ case (target, projectile, _, _) =>
+ ops.resolveProjectileInteraction(target, projectile, DamageResolution.Hit, target.Position)
+ }
+ //...
+ if (list.isEmpty) {
+ val proxyList = ops
+ .FindProjectileEntry(pkt.projectile_guid)
+ .map(projectile => ops.resolveDamageProxy(projectile, projectile.GUID, pkt.hit_info.map(_.hit_pos).getOrElse(Vector3.Zero)))
+ .getOrElse(Nil)
+ proxyList.collectFirst {
+ case (_, proxy, _, _) if proxy.tool_def == GlobalDefinitions.oicw =>
+ ops.performLittleBuddyExplosion(proxyList.map(_._2))
+ }
+ }
}
}
def handleSplashHit(pkt: SplashHitMessage): Unit = {
- val SplashHitMessage(
- _,
- projectile_guid,
- explosion_pos,
- direct_victim_uid,
- _,
- projectile_vel,
- _,
- targets
- ) = pkt
- ops.FindProjectileEntry(projectile_guid) match {
- case Some(projectile) =>
- val profile = projectile.profile
- projectile.Velocity = projectile_vel
+ val list = ops.composeSplashDamageInformation(pkt)
+ if (list.nonEmpty) {
+ val projectile = list.head._2
+ val explosionPosition = projectile.Position
+ val projectileGuid = projectile.GUID
+ val profile = projectile.profile
+ if (!player.spectator) {
val (resolution1, resolution2) = profile.Aggravated match {
case Some(_) if profile.ProjectileDamageTypes.contains(DamageType.Aggravated) =>
(DamageResolution.AggravatedDirect, DamageResolution.AggravatedSplash)
case _ =>
(DamageResolution.Splash, DamageResolution.Splash)
}
- //direct_victim_uid
- sessionLogic.validObject(direct_victim_uid, decorator = "SplashHit/direct_victim") match {
- case Some(target: PlanetSideGameObject with FactionAffinity with Vitality) =>
- CheckForHitPositionDiscrepancy(projectile_guid, explosion_pos, target)
- ResolveProjectileInteraction(projectile, resolution1, target, target.Position).collect { resprojectile =>
- addShotsLanded(resprojectile.cause.attribution, shots = 1)
- sessionLogic.handleDealingDamage(target, resprojectile)
- }
- case _ => ()
- }
- //other victims
- targets.foreach(elem => {
- sessionLogic.validObject(elem.uid, decorator = "SplashHit/other_victims") match {
- case Some(target: PlanetSideGameObject with FactionAffinity with Vitality) =>
- CheckForHitPositionDiscrepancy(projectile_guid, explosion_pos, target)
- ResolveProjectileInteraction(projectile, resolution2, target, explosion_pos).collect { resprojectile =>
- addShotsLanded(resprojectile.cause.attribution, shots = 1)
- sessionLogic.handleDealingDamage(target, resprojectile)
- }
- case _ => ()
- }
- })
//...
- HandleDamageProxy(projectile, projectile_guid, explosion_pos)
+ val (direct, others) = list.partition { case (_, _, hitPos, targetPos) => hitPos == targetPos }
+ direct.foreach {
+ case (target, _, _, _) =>
+ ops.resolveProjectileInteraction(target, projectile, resolution1, target.Position)
+ }
+ others.foreach {
+ case (target, _, _, _) =>
+ ops.resolveProjectileInteraction(target, projectile, resolution2, target.Position)
+ }
+ //...
+ val proxyList = ops.resolveDamageProxy(projectile, projectileGuid, explosionPosition).map(_._2)
+ if (profile == GlobalDefinitions.oicw_projectile) {
+ ops.performLittleBuddyExplosion(proxyList) //normal damage radius
+ }
+ //...
if (
- projectile.profile.HasJammedEffectDuration ||
- projectile.profile.JammerProjectile ||
- projectile.profile.SympatheticExplosion
+ profile.HasJammedEffectDuration ||
+ profile.JammerProjectile ||
+ profile.SympatheticExplosion
) {
- //can also substitute 'projectile.profile' for 'SpecialEmp.emp'
+ //can also substitute 'profile' for 'SpecialEmp.emp'
Zone.serverSideDamage(
continent,
player,
SpecialEmp.emp,
- SpecialEmp.createEmpInteraction(SpecialEmp.emp, explosion_pos),
- SpecialEmp.prepareDistanceCheck(player, explosion_pos, player.Faction),
+ SpecialEmp.createEmpInteraction(SpecialEmp.emp, explosionPosition),
+ SpecialEmp.prepareDistanceCheck(player, explosionPosition, player.Faction),
SpecialEmp.findAllBoomers(profile.DamageRadius)
)
}
- if (profile.ExistsOnRemoteClients && projectile.HasGUID) {
- //cleanup
- continent.Projectile ! ZoneProjectile.Remove(projectile.GUID)
- }
- case None => ()
+ }
+ if (profile.ExistsOnRemoteClients && projectile.HasGUID) {
+ continent.Projectile ! ZoneProjectile.Remove(projectileGuid)
+ }
}
}
def handleLashHit(pkt: LashMessage): Unit = {
- val LashMessage(_, _, victim_guid, projectile_guid, hit_pos, _) = pkt
- sessionLogic.validObject(victim_guid, decorator = "Lash") match {
- case Some(target: PlanetSideGameObject with FactionAffinity with Vitality) =>
- CheckForHitPositionDiscrepancy(projectile_guid, hit_pos, target)
- ResolveProjectileInteraction(projectile_guid, DamageResolution.Lash, target, hit_pos).foreach {
- resprojectile =>
- addShotsLanded(resprojectile.cause.attribution, shots = 1)
- sessionLogic.handleDealingDamage(target, resprojectile)
- }
- case _ => ()
+ val list = ops.composeLashDamageInformation(pkt)
+ if (!player.spectator) {
+ list.foreach {
+ case (target, projectile, _, targetPosition) =>
+ ops.resolveProjectileInteraction(target, projectile, DamageResolution.Lash, targetPosition)
+ }
}
}
def handleAIDamage(pkt: AIDamage): Unit = {
- val AIDamage(targetGuid, attackerGuid, projectileTypeId, _, _) = pkt
- (continent.GUID(player.VehicleSeated) match {
- case Some(tobj: PlanetSideServerObject with FactionAffinity with Vitality with OwnableByPlayer)
- if tobj.GUID == targetGuid &&
- tobj.OwnerGuid.contains(player.GUID) =>
- //deployable turrets
- Some(tobj)
- case Some(tobj: PlanetSideServerObject with FactionAffinity with Vitality with Mountable)
- if tobj.GUID == targetGuid &&
- tobj.Seats.values.flatMap(_.occupants.map(_.GUID)).toSeq.contains(player.GUID) =>
- //facility turrets, etc.
- Some(tobj)
- case _
- if player.GUID == targetGuid =>
- //player avatars
- Some(player)
- case _ =>
- None
- }).collect {
- case target: AutomatedTurret.Target =>
- sessionLogic.validObject(attackerGuid, decorator = "AIDamage/AutomatedTurret")
- .collect {
- case turret: AutomatedTurret if turret.Target.isEmpty =>
- turret.Actor ! AutomatedTurretBehavior.ConfirmShot(target)
- Some(target)
-
- case turret: AutomatedTurret =>
- turret.Actor ! AutomatedTurretBehavior.ConfirmShot(target)
- HandleAIDamage(target, CompileAutomatedTurretDamageData(turret, projectileTypeId))
- Some(target)
- }
- }
- .orElse {
- //occasionally, something that is not technically a turret's natural target may be attacked
- sessionLogic.validObject(targetGuid, decorator = "AIDamage/Target")
- .collect {
- case target: PlanetSideServerObject with FactionAffinity with Vitality =>
- sessionLogic.validObject(attackerGuid, decorator = "AIDamage/Attacker")
- .collect {
- case turret: AutomatedTurret if turret.Target.nonEmpty =>
- //the turret must be shooting at something (else) first
- HandleAIDamage(target, CompileAutomatedTurretDamageData(turret, projectileTypeId))
- }
- Some(target)
- }
+ val list = ops.composeAIDamageInformation(pkt)
+ if (!player.spectator && ops.confirmAIDamageTarget(pkt, list.map(_._1))) {
+ list.foreach {
+ case (target, projectile, _, targetPosition) =>
+ ops.resolveProjectileInteraction(target, projectile, DamageResolution.Hit, targetPosition)
}
+ }
}
/* support code */
- private def HandleWeaponFireOperations(
- weaponGUID: PlanetSideGUID,
- projectileGUID: PlanetSideGUID,
- shotOrigin: Vector3,
- shotVelocity: Option[Vector3]
- ): Unit = {
- ops.HandleWeaponFireAccountability(weaponGUID, projectileGUID) match {
- case (Some(obj), Some(tool)) =>
- val projectileIndex = projectileGUID.guid - Projectile.baseUID
- val projectilePlace = ops.projectiles(projectileIndex)
- if (
- projectilePlace match {
- case Some(projectile) =>
- !projectile.isResolved && System.currentTimeMillis() - projectile.fire_time < projectile.profile.Lifespan.toLong
- case None =>
- false
- }
- ) {
- log.debug(
- s"WeaponFireMessage: overwriting unresolved projectile ${projectileGUID.guid}, known to ${player.Name}"
- )
- }
- val (angle, attribution, acceptableDistanceToOwner) = obj match {
- case p: Player =>
- (
- SimpleWorldEntity.validateOrientationEntry(
- p.Orientation + Vector3.z(p.FacingYawUpper)
- ),
- tool.Definition.ObjectId,
- 10f + (if (p.Velocity.nonEmpty) {
- 5f
- } else {
- 0f
- })
- )
- case v: Vehicle if v.Definition.CanFly =>
- (tool.Orientation, obj.Definition.ObjectId, 1000f) //TODO this is too simplistic to find proper angle
- case _: Vehicle =>
- (tool.Orientation, obj.Definition.ObjectId, 225f) //TODO this is too simplistic to find proper angle
- case _ =>
- (obj.Orientation, obj.Definition.ObjectId, 300f)
- }
- val distanceToOwner = Vector3.DistanceSquared(shotOrigin, player.Position)
- if (distanceToOwner <= acceptableDistanceToOwner) {
- val projectile_info = tool.Projectile
- val wguid = weaponGUID.guid
- val mountedIn = (continent.turretToWeapon
- .find { case (guid, _) => guid == wguid } match {
- case Some((_, turretGuid)) => Some((
- turretGuid,
- continent.GUID(turretGuid).collect { case o: PlanetSideGameObject with FactionAffinity => SourceEntry(o) }
- ))
- case _ => None
- }) match {
- case Some((guid, Some(entity))) => Some((guid, entity))
- case _ => None
- }
- val projectile = new Projectile(
- projectile_info,
- tool.Definition,
- tool.FireMode,
- mountedIn,
- PlayerSource(player),
- attribution,
- shotOrigin,
- angle,
- shotVelocity
- )
- val initialQuality = tool.FireMode match {
- case mode: ChargeFireModeDefinition =>
- ProjectileQuality.Modified(
- {
- val timeInterval = projectile.fire_time - ops.shootingStart.getOrElse(tool.GUID, System.currentTimeMillis())
- timeInterval.toFloat / mode.Time.toFloat
- }
- )
- case _ =>
- ProjectileQuality.Normal
- }
- val qualityprojectile = projectile.quality(initialQuality)
- qualityprojectile.WhichSide = player.WhichSide
- ops.projectiles(projectileIndex) = Some(qualityprojectile)
- if (projectile_info.ExistsOnRemoteClients) {
- log.trace(
- s"WeaponFireMessage: ${player.Name}'s ${projectile_info.Name} is a remote projectile"
- )
- continent.Projectile ! ZoneProjectile.Add(player.GUID, qualityprojectile)
- }
- } else {
- log.warn(
- s"WeaponFireMessage: ${player.Name}'s ${tool.Definition.Name} projectile is too far from owner position at time of discharge ($distanceToOwner > $acceptableDistanceToOwner); suspect"
- )
- }
-
- case _ => ()
- }
- }
-
/**
* After a weapon has finished shooting, determine if it needs to be sorted in a special way.
* @param tool a weapon
@@ -679,7 +237,6 @@ class WeaponAndProjectileLogic(val ops: WeaponAndProjectileOperations, implicit
val ammoType = tool.AmmoType
FindEquipmentStock(player, FindToolThatUses(ammoType), 3, CountGrenades).reverse match { //do not search sidearm holsters
case Nil =>
- log.info(s"${player.Name} has no more $ammoType grenades to throw")
RemoveOldEquipmentFromInventory(player)(tool)
case x :: xs => //this is similar to ReloadMessage
@@ -691,11 +248,10 @@ 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)
@@ -706,354 +262,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))
- }
-
- /**
- * Given a vehicle that contains a box of ammunition in its `Trunk` 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 ModifyAmmunitionInMountable(obj: PlanetSideServerObject with Container)(box: AmmoBox, reloadValue: Int): Unit = {
- ModifyAmmunition(obj)(box, reloadValue)
- obj.Find(box).collect { index =>
- continent.VehicleEvents ! VehicleServiceMessage(
- s"${obj.Actor}",
- VehicleAction.InventoryState(
- player.GUID,
- box,
- obj.GUID,
- index,
- box.Definition.Packet.DetailedConstructorData(box).get
- )
- )
- }
- }
-
- /**
- * na
- * @param tool na
- * @param obj na
- */
- private def PerformToolAmmoChange(
- tool: Tool,
- obj: PlanetSideServerObject with Container,
- modifyFunc: (AmmoBox, Int) => Unit
- ): Unit = {
- val originalAmmoType = tool.AmmoType
- do {
- val requestedAmmoType = tool.NextAmmoType
- val fullMagazine = tool.MaxMagazine
- if (requestedAmmoType != tool.AmmoSlot.Box.AmmoType) {
- FindEquipmentStock(obj, FindAmmoBoxThatUses(requestedAmmoType), fullMagazine, CountAmmunition).reverse match {
- case Nil => ()
- case x :: xs =>
- val stowNewFunc: Equipment => TaskBundle = PutNewEquipmentInInventoryOrDrop(obj)
- val stowFunc: Equipment => Future[Any] = PutEquipmentInInventoryOrDrop(obj)
-
- xs.foreach(item => {
- obj.Inventory -= item.start
- sendResponse(ObjectDeleteMessage(item.obj.GUID, 0))
- TaskWorkflow.execute(GUIDTask.unregisterObject(continent.GUID, item.obj))
- })
-
- //box will be the replacement ammo; give it the discovered magazine and load it into the weapon
- val box = x.obj.asInstanceOf[AmmoBox]
- //previousBox is the current magazine in tool; it will be removed from the weapon
- val previousBox = tool.AmmoSlot.Box
- val originalBoxCapacity = box.Capacity
- val tailReloadValue: Int = if (xs.isEmpty) {
- 0
- } else {
- xs.map(_.obj.asInstanceOf[AmmoBox].Capacity).sum
- }
- val sumReloadValue: Int = originalBoxCapacity + tailReloadValue
- val ammoSlotIndex = tool.FireMode.AmmoSlotIndex
- val box_guid = box.GUID
- val tool_guid = tool.GUID
- obj.Inventory -= x.start //remove replacement ammo from inventory
- tool.AmmoSlots(ammoSlotIndex).Box = box //put replacement ammo in tool
- sendResponse(ObjectDetachMessage(tool_guid, previousBox.GUID, Vector3.Zero, 0f))
- sendResponse(ObjectDetachMessage(obj.GUID, box_guid, Vector3.Zero, 0f))
- sendResponse(ObjectAttachMessage(tool_guid, box_guid, ammoSlotIndex))
-
- //announce swapped ammunition box in weapon
- val previous_box_guid = previousBox.GUID
- val boxDef = box.Definition
- sendResponse(ChangeAmmoMessage(tool_guid, box.Capacity))
- continent.AvatarEvents ! AvatarServiceMessage(
- continent.id,
- AvatarAction.ChangeAmmo(
- player.GUID,
- tool_guid,
- ammoSlotIndex,
- previous_box_guid,
- boxDef.ObjectId,
- box.GUID,
- boxDef.Packet.ConstructorData(box).get
- )
- )
-
- //handle inventory contents
- box.Capacity = if (sumReloadValue <= fullMagazine) {
- sumReloadValue
- } else {
- val splitReloadAmmo: Int = sumReloadValue - fullMagazine
- log.trace(
- s"PerformToolAmmoChange: ${player.Name} takes ${originalBoxCapacity - splitReloadAmmo} from a box of $originalBoxCapacity $requestedAmmoType ammo"
- )
- val boxForInventory = AmmoBox(box.Definition, splitReloadAmmo)
- TaskWorkflow.execute(stowNewFunc(boxForInventory))
- fullMagazine
- }
- sendResponse(
- InventoryStateMessage(box.GUID, tool.GUID, box.Capacity)
- ) //should work for both players and vehicles
- log.info(s"${player.Name} loads ${box.Capacity} $requestedAmmoType into the ${tool.Definition.Name}")
- if (previousBox.Capacity > 0) {
- //divide capacity across other existing and not full boxes of that ammo type
- var capacity = previousBox.Capacity
- val iter = obj.Inventory.Items
- .filter(entry => {
- entry.obj match {
- case item: AmmoBox =>
- item.AmmoType == originalAmmoType && item.FullCapacity != item.Capacity
- case _ =>
- false
- }
- })
- .sortBy(_.start)
- .iterator
- while (capacity > 0 && iter.hasNext) {
- val entry = iter.next()
- val item: AmmoBox = entry.obj.asInstanceOf[AmmoBox]
- val ammoAllocated = math.min(item.FullCapacity - item.Capacity, capacity)
- log.info(s"${player.Name} put $ammoAllocated back into a box of ${item.Capacity} $originalAmmoType")
- capacity -= ammoAllocated
- modifyFunc(item, -ammoAllocated)
- }
- previousBox.Capacity = capacity
- }
-
- if (previousBox.Capacity > 0) {
- //split previousBox into AmmoBox objects of appropriate max capacity, e.g., 100 9mm -> 2 x 50 9mm
- obj.Inventory.Fit(previousBox) match {
- case Some(_) =>
- stowFunc(previousBox)
- case None =>
- sessionLogic.general.normalItemDrop(player, continent)(previousBox)
- }
- AmmoBox.Split(previousBox) match {
- case Nil | List(_) => () //done (the former case is technically not possible)
- case _ :: toUpdate =>
- modifyFunc(previousBox, 0) //update to changed capacity value
- toUpdate.foreach(box => { TaskWorkflow.execute(stowNewFunc(box)) })
- }
- } else {
- TaskWorkflow.execute(GUIDTask.unregisterObject(continent.GUID, previousBox))
- }
- }
- }
- } while (tool.AmmoType != originalAmmoType && tool.AmmoType != tool.AmmoSlot.Box.AmmoType)
- }
-
- private def CheckForHitPositionDiscrepancy(
- projectile_guid: PlanetSideGUID,
- hitPos: Vector3,
- target: PlanetSideGameObject with FactionAffinity with Vitality
- ): Unit = {
- val hitPositionDiscrepancy = Vector3.DistanceSquared(hitPos, target.Position)
- if (hitPositionDiscrepancy > Config.app.antiCheat.hitPositionDiscrepancyThreshold) {
- // If the target position on the server does not match the position where the projectile landed within reason there may be foul play
- log.warn(
- s"${player.Name}'s shot #${projectile_guid.guid} has hit discrepancy with target. Target: ${target.Position}, Reported: $hitPos, Distance: $hitPositionDiscrepancy / ${math.sqrt(hitPositionDiscrepancy).toFloat}; suspect"
- )
- }
- }
-
- /**
- * Find a projectile with the given globally unique identifier and mark it as a resolved shot.
- * A `Resolved` shot has either encountered an obstacle or is being cleaned up for not finding an obstacle.
- * @param projectile_guid the projectile GUID
- * @param resolution the resolution status to promote the projectile
- * @return the projectile
- */
- private def ResolveProjectileInteraction(
- projectile_guid: PlanetSideGUID,
- resolution: DamageResolution.Value,
- target: PlanetSideGameObject with FactionAffinity with Vitality,
- pos: Vector3
- ): Option[DamageInteraction] = {
- ops.FindProjectileEntry(projectile_guid) match {
- case Some(projectile) =>
- ResolveProjectileInteraction(projectile, resolution, target, pos)
- case None =>
- log.trace(s"ResolveProjectile: ${player.Name} expected projectile, but ${projectile_guid.guid} not found")
- None
- }
- }
-
- /**
- * na
- * @param projectile the projectile object
- * @param resolution the resolution status to promote the projectile
- * @return a copy of the projectile
- */
- private def ResolveProjectileInteraction(
- projectile: Projectile,
- resolution: DamageResolution.Value,
- target: PlanetSideGameObject with FactionAffinity with Vitality,
- pos: Vector3
- ): Option[DamageInteraction] = {
- if (projectile.isMiss) {
- log.warn("expected projectile was already counted as a missed shot; can not resolve any further")
- None
- } else {
- val outProjectile = ProjectileQuality.modifiers(projectile, resolution, target, pos, Some(player))
- if (projectile.tool_def.Size == EquipmentSize.Melee && outProjectile.quality == ProjectileQuality.Modified(25)) {
- avatarActor ! AvatarActor.ConsumeStamina(10)
- }
- Some(DamageInteraction(SourceEntry(target), ProjectileReason(resolution, outProjectile, target.DamageModel), pos))
- }
- }
-
- /**
- * Take a projectile that was introduced into the game world and
- * determine if it generates a secondary damage projectile or
- * an method of damage causation that requires additional management.
- * @param projectile the projectile
- * @param pguid the client-local projectile identifier
- * @param hitPos the game world position where the projectile is being recorded
- * @return a for all affected targets, a combination of projectiles, projectile location, and the target's location;
- * nothing if no targets were affected
- */
- private def HandleDamageProxy(
- projectile: Projectile,
- pguid: PlanetSideGUID,
- hitPos: Vector3
- ): List[(PlanetSideGameObject with FactionAffinity with Vitality, Projectile, Vector3, Vector3)] = {
- GlobalDefinitions.getDamageProxy(projectile, hitPos) match {
- case Nil =>
- Nil
- case list if list.isEmpty =>
- Nil
- case list =>
- HandleDamageProxySetupLittleBuddy(list, hitPos)
- UpdateProjectileSidednessAfterHit(projectile, hitPos)
- val projectileSide = projectile.WhichSide
- list.flatMap { proxy =>
- if (proxy.profile.ExistsOnRemoteClients) {
- proxy.Position = hitPos
- proxy.WhichSide = projectileSide
- continent.Projectile ! ZoneProjectile.Add(player.GUID, proxy)
- Nil
- } else if (proxy.tool_def == GlobalDefinitions.maelstrom) {
- //server-side maelstrom grenade target selection
- val radius = proxy.profile.LashRadius * proxy.profile.LashRadius
- val targets = Zone.findAllTargets(continent, hitPos, proxy.profile.LashRadius, { _.livePlayerList })
- .filter { target =>
- Vector3.DistanceSquared(target.Position, hitPos) <= radius
- }
- //chainlash is separated from the actual damage application for convenience
- continent.AvatarEvents ! AvatarServiceMessage(
- continent.id,
- AvatarAction.SendResponse(
- PlanetSideGUID(0),
- ChainLashMessage(
- hitPos,
- projectile.profile.ObjectId,
- targets.map { _.GUID }
- )
- )
- )
- targets.map { target =>
- CheckForHitPositionDiscrepancy(pguid, hitPos, target)
- (target, proxy, hitPos, target.Position)
- }
- } else {
- Nil
- }
- }
- }
- }
-
- private def HandleDamageProxySetupLittleBuddy(listOfProjectiles: List[Projectile], detonationPosition: Vector3): Boolean = {
- val listOfLittleBuddies: List[Projectile] = listOfProjectiles.filter { _.tool_def == GlobalDefinitions.oicw }
- val size: Int = listOfLittleBuddies.size
- if (size > 0) {
- val desiredDownwardsProjectiles: Int = 2
- val firstHalf: Int = math.min(size, desiredDownwardsProjectiles) //number that fly straight down
- val secondHalf: Int = math.max(size - firstHalf, 0) //number that are flared out
- val z: Float = player.Orientation.z //player's standing direction
- val north: Vector3 = Vector3(0,1,0) //map North
- val speed: Float = 144f //speed (packet discovered)
- val dist: Float = 25 //distance (client defined)
- val downwardsAngle: Float = -85f
- val flaredAngle: Float = -70f
- //angle of separation for downwards, degrees from vertical for flared out
- val (smallStep, smallAngle): (Float, Float) = if (firstHalf > 1) {
- (360f / firstHalf, downwardsAngle)
- } else {
- (0f, 0f)
- }
- val (largeStep, largeAngle): (Float, Float) = if (secondHalf > 1) {
- (360f / secondHalf, flaredAngle)
- } else {
- (0f, 0f)
- }
- val smallRotOffset: Float = z + 90f
- val largeRotOffset: Float = z + math.random().toFloat * 45f
- val verticalCorrection = Vector3.z(dist - dist * math.sin(math.toRadians(90 - smallAngle + largeAngle)).toFloat)
- //downwards projectiles
- var i: Int = 0
- listOfLittleBuddies.take(firstHalf).foreach { proxy =>
- val facing = (smallRotOffset + smallStep * i.toFloat) % 360
- val dir = north.Rx(smallAngle).Rz(facing)
- proxy.Position = detonationPosition + dir.xy + verticalCorrection
- proxy.Velocity = dir * speed
- proxy.Orientation = Vector3(0, (360f + smallAngle) % 360, facing)
- HandleDamageProxyLittleBuddyExplosion(proxy, dir, dist)
- i += 1
- }
- //flared out projectiles
- i = 0
- listOfLittleBuddies.drop(firstHalf).foreach { proxy =>
- val facing = (largeRotOffset + largeStep * i.toFloat) % 360
- val dir = north.Rx(largeAngle).Rz(facing)
- proxy.Position = detonationPosition + dir
- proxy.Velocity = dir * speed
- proxy.Orientation = Vector3(0, (360f + largeAngle) % 360, facing)
- HandleDamageProxyLittleBuddyExplosion(proxy, dir, dist)
- i += 1
- }
- true
- } else {
- false
- }
- }
-
- private def HandleDamageProxyLittleBuddyExplosion(proxy: Projectile, orientation: Vector3, distance: Float): Unit = {
- //explosion
- val obj = new DummyExplodingEntity(proxy, proxy.owner.Faction)
- obj.Position = obj.Position + orientation * distance
- val explosionFunc: ()=>Unit = WeaponAndProjectileLogic.detonateLittleBuddy(continent, obj, proxy, proxy.owner)
- context.system.scheduler.scheduleOnce(500.milliseconds) { explosionFunc() }
- }
-
/*
used by ChangeFireStateMessage_Start handling
*/
@@ -1078,10 +286,12 @@ class WeaponAndProjectileLogic(val ops: WeaponAndProjectileOperations, implicit
}
private def fireStateStartPlayerMessages(itemGuid: PlanetSideGUID): Unit = {
- continent.AvatarEvents ! AvatarServiceMessage(
- continent.id,
- AvatarAction.ChangeFireState_Start(player.GUID, itemGuid)
- )
+ if (!player.spectator) {
+ continent.AvatarEvents ! AvatarServiceMessage(
+ continent.id,
+ AvatarAction.ChangeFireState_Start(player.GUID, itemGuid)
+ )
+ }
}
private def fireStateStartMountedMessages(itemGuid: PlanetSideGUID): Unit = {
@@ -1103,7 +313,7 @@ class WeaponAndProjectileLogic(val ops: WeaponAndProjectileOperations, implicit
log.warn(
s"ChangeFireState_Start: ${player.Name}'s ${tool.Definition.Name} magazine was empty before trying to shoot"
)
- ops.EmptyMagazine(itemGuid, tool)
+ ops.emptyMagazine(itemGuid, tool)
}
private def fireStateStartWhenPlayer(tool: Tool, itemGuid: PlanetSideGUID): Unit = {
@@ -1165,10 +375,12 @@ class WeaponAndProjectileLogic(val ops: WeaponAndProjectileOperations, implicit
used by ReloadMessage handling
*/
private def reloadPlayerMessages(itemGuid: PlanetSideGUID): Unit = {
- continent.AvatarEvents ! AvatarServiceMessage(
- continent.id,
- AvatarAction.Reload(player.GUID, itemGuid)
- )
+ if (!player.spectator) {
+ continent.AvatarEvents ! AvatarServiceMessage(
+ continent.id,
+ AvatarAction.Reload(player.GUID, itemGuid)
+ )
+ }
}
private def reloadVehicleMessages(itemGuid: PlanetSideGUID): Unit = {
@@ -1178,70 +390,19 @@ class WeaponAndProjectileLogic(val ops: WeaponAndProjectileOperations, implicit
)
}
- private def handleReloadProcedure(
- itemGuid: PlanetSideGUID,
- obj: PlanetSideGameObject with Container,
- tools: Set[Tool],
- unk1: Int,
- deleteFunc: Equipment => Future[Any],
- modifyFunc: (AmmoBox, Int) => Unit,
- messageFunc: PlanetSideGUID => Unit
- ): Unit = {
- tools
- .filter { _.GUID == itemGuid }
- .foreach { tool =>
- val currentMagazine : Int = tool.Magazine
- val magazineSize : Int = tool.MaxMagazine
- val reloadValue : Int = magazineSize - currentMagazine
- if (magazineSize > 0 && reloadValue > 0) {
- FindEquipmentStock(obj, FindAmmoBoxThatUses(tool.AmmoType), reloadValue, CountAmmunition).reverse match {
- case Nil => ()
- case x :: xs =>
- xs.foreach { item => deleteFunc(item.obj) }
- val box = x.obj.asInstanceOf[AmmoBox]
- val tailReloadValue : Int = if (xs.isEmpty) {
- 0
- }
- else {
- xs.map(_.obj.asInstanceOf[AmmoBox].Capacity).sum
- }
- val sumReloadValue : Int = box.Capacity + tailReloadValue
- val actualReloadValue = if (sumReloadValue <= reloadValue) {
- deleteFunc(box)
- sumReloadValue
- }
- else {
- modifyFunc(box, reloadValue - tailReloadValue)
- reloadValue
- }
- val finalReloadValue = actualReloadValue + currentMagazine
- log.info(
- s"${player.Name} successfully reloaded $reloadValue ${tool.AmmoType} into ${tool.Definition.Name}"
- )
- tool.Magazine = finalReloadValue
- sendResponse(ReloadMessage(itemGuid, finalReloadValue, unk1))
- messageFunc(itemGuid)
- }
- } else {
- //the weapon can not reload due to full magazine; the UI for the magazine is obvious bugged, so fix it
- sendResponse(QuantityUpdateMessage(tool.AmmoSlot.Box.GUID, magazineSize))
- }
- }
- }
-
private def handleReloadWhenPlayer(
itemGuid: PlanetSideGUID,
obj: Player,
tools: Set[Tool],
unk1: Int
): Unit = {
- handleReloadProcedure(
+ ops.handleReloadProcedure(
itemGuid,
obj,
tools,
unk1,
RemoveOldEquipmentFromInventory(obj)(_),
- ModifyAmmunition(obj)(_, _),
+ ops.modifyAmmunition(obj)(_, _),
reloadPlayerMessages
)
}
@@ -1252,128 +413,14 @@ class WeaponAndProjectileLogic(val ops: WeaponAndProjectileOperations, implicit
tools: Set[Tool],
unk1: Int
): Unit = {
- handleReloadProcedure(
+ ops.handleReloadProcedure(
itemGuid,
obj,
tools,
unk1,
RemoveOldEquipmentFromInventory(obj)(_),
- ModifyAmmunitionInMountable(obj)(_, _),
+ ops.modifyAmmunitionInMountable(obj)(_, _),
reloadVehicleMessages
)
}
-
- //noinspection SameParameterValue
- private def addShotsLanded(weaponId: Int, shots: Int): Unit = {
- ops.addShotsToMap(ops.shotsLanded, weaponId, shots)
- }
-
- private def CompileAutomatedTurretDamageData(
- turret: AutomatedTurret,
- projectileTypeId: Long
- ): Option[(AutomatedTurret, Tool, SourceEntry, ProjectileDefinition)] = {
- turret match {
- case tOwner: OwnableByPlayer =>
- CompileAutomatedTurretDamageData(
- turret,
- CompileAutomatedTurretOwnableBlame(tOwner),
- projectileTypeId
- )
- case tAmenity: Amenity =>
- CompileAutomatedTurretDamageData(
- turret,
- CompileAutomatedTurretAmenityBlame(tAmenity),
- projectileTypeId
- )
- case _ =>
- None
- }
- }
-
- private def CompileAutomatedTurretOwnableBlame(turret: AutomatedTurret with OwnableByPlayer): SourceEntry = {
- Deployables.AssignBlameTo(continent, turret.OwnerName, SourceEntry(turret))
- }
-
- private def CompileAutomatedTurretAmenityBlame(turret: AutomatedTurret with Amenity): SourceEntry = {
- turret
- .Seats
- .values
- .flatMap(_.occupant)
- .collectFirst(SourceEntry(_))
- .getOrElse(SourceEntry(turret.Owner))
- }
-
- private def CompileAutomatedTurretDamageData(
- turret: AutomatedTurret,
- blame: SourceEntry,
- projectileTypeId: Long
- ): Option[(AutomatedTurret, Tool, SourceEntry, ProjectileDefinition)] = {
- turret.Weapons
- .values
- .flatMap { _.Equipment }
- .collect {
- case weapon: Tool => (turret, weapon, blame, weapon.Projectile)
- }
- .find { case (_, _, _, p) => p.ObjectId == projectileTypeId }
- }
-
- private def HandleAIDamage(
- target: PlanetSideServerObject with FactionAffinity with Vitality,
- results: Option[(AutomatedTurret, Tool, SourceEntry, ProjectileDefinition)]
- ): Unit = {
- results.collect {
- case (obj, tool, owner, projectileInfo) =>
- val angle = Vector3.Unit(target.Position - obj.Position)
- val proj = new Projectile(
- projectileInfo,
- tool.Definition,
- tool.FireMode,
- None,
- owner,
- obj.Definition.ObjectId,
- obj.Position + Vector3.z(value = 1f),
- angle,
- Some(angle * projectileInfo.FinalVelocity)
- )
- val hitPos = target.Position + Vector3.z(value = 1f)
- ResolveProjectileInteraction(proj, DamageResolution.Hit, target, hitPos).collect { resprojectile =>
- addShotsLanded(resprojectile.cause.attribution, shots = 1)
- sessionLogic.handleDealingDamage(target, resprojectile)
- }
- }
- }
-
- private def UpdateProjectileSidednessAfterHit(projectile: Projectile, hitPosition: Vector3): Unit = {
- val origin = projectile.Position
- val distance = Vector3.Magnitude(hitPosition - origin)
- continent.blockMap
- .sector(hitPosition, distance)
- .environmentList
- .collect { case o: InteriorDoorPassage =>
- val door = o.door
- val intersectTest = WeaponAndProjectileLogic.quickLineSphereIntersectionPoints(
- origin,
- hitPosition,
- door.Position,
- door.Definition.UseRadius + 0.1f
- )
- (door, intersectTest)
- }
- .collect { case (door, intersectionTest) if intersectionTest.nonEmpty =>
- (door, Vector3.Magnitude(hitPosition - door.Position), intersectionTest)
- }
- .minByOption { case (_, dist, _) => dist }
- .foreach { case (door, _, intersects) =>
- val strictly = if (Vector3.DotProduct(Vector3.Unit(hitPosition - door.Position), door.Outwards) > 0f) {
- Sidedness.OutsideOf
- } else {
- Sidedness.InsideOf
- }
- projectile.WhichSide = if (intersects.size == 1) {
- Sidedness.InBetweenSides(door, strictly)
- } else {
- strictly
- }
- }
- }
}
diff --git a/src/main/scala/net/psforever/actors/session/normal/ChatLogic.scala b/src/main/scala/net/psforever/actors/session/normal/ChatLogic.scala
index b4fb47ed2..4d74b4e08 100644
--- a/src/main/scala/net/psforever/actors/session/normal/ChatLogic.scala
+++ b/src/main/scala/net/psforever/actors/session/normal/ChatLogic.scala
@@ -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
+ }
}
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 a19a401a0..c89179572 100644
--- a/src/main/scala/net/psforever/actors/session/normal/GeneralLogic.scala
+++ b/src/main/scala/net/psforever/actors/session/normal/GeneralLogic.scala
@@ -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))
- )
- }
}
diff --git a/src/main/scala/net/psforever/actors/session/normal/LocalHandlerLogic.scala b/src/main/scala/net/psforever/actors/session/normal/LocalHandlerLogic.scala
index 09bd78579..3b7538a5a 100644
--- a/src/main/scala/net/psforever/actors/session/normal/LocalHandlerLogic.scala
+++ b/src/main/scala/net/psforever/actors/session/normal/LocalHandlerLogic.scala
@@ -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))
- }
}
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 c2f1a8969..e7c6c238c 100644
--- a/src/main/scala/net/psforever/actors/session/normal/MountHandlerLogic.scala
+++ b/src/main/scala/net/psforever/actors/session/normal/MountHandlerLogic.scala
@@ -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)
- )
- }
}
diff --git a/src/main/scala/net/psforever/actors/session/normal/NormalMode.scala b/src/main/scala/net/psforever/actors/session/normal/NormalMode.scala
index 1a88bf9f3..60d0012aa 100644
--- a/src/main/scala/net/psforever/actors/session/normal/NormalMode.scala
+++ b/src/main/scala/net/psforever/actors/session/normal/NormalMode.scala
@@ -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)
diff --git a/src/main/scala/net/psforever/actors/session/normal/SquadHandlerLogic.scala b/src/main/scala/net/psforever/actors/session/normal/SquadHandlerLogic.scala
index 9197211f8..71bbc20a0 100644
--- a/src/main/scala/net/psforever/actors/session/normal/SquadHandlerLogic.scala
+++ b/src/main/scala/net/psforever/actors/session/normal/SquadHandlerLogic.scala
@@ -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 = {
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 bf9eb1dc7..28eb63913 100644
--- a/src/main/scala/net/psforever/actors/session/normal/TerminalHandlerLogic.scala
+++ b/src/main/scala/net/psforever/actors/session/normal/TerminalHandlerLogic.scala
@@ -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 =>
diff --git a/src/main/scala/net/psforever/actors/session/normal/WeaponAndProjectileLogic.scala b/src/main/scala/net/psforever/actors/session/normal/WeaponAndProjectileLogic.scala
index 6d202dc8b..295da46fb 100644
--- a/src/main/scala/net/psforever/actors/session/normal/WeaponAndProjectileLogic.scala
+++ b/src/main/scala/net/psforever/actors/session/normal/WeaponAndProjectileLogic.scala
@@ -1,163 +1,41 @@
// 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 akka.actor.typed
+//import net.psforever.actors.session.AvatarActor
import net.psforever.actors.session.support.{SessionData, WeaponAndProjectileFunctions, WeaponAndProjectileOperations}
-import net.psforever.login.WorldSession.{CountAmmunition, CountGrenades, FindAmmoBoxThatUses, FindEquipmentStock, FindToolThatUses, PutEquipmentInInventoryOrDrop, PutNewEquipmentInInventoryOrDrop, RemoveOldEquipmentFromInventory}
-import net.psforever.objects.ballistics.{Projectile, ProjectileQuality}
-import net.psforever.objects.definition.ProjectileDefinition
-import net.psforever.objects.entity.SimpleWorldEntity
-import net.psforever.objects.equipment.{ChargeFireModeDefinition, Equipment, EquipmentSize, FireModeSwitch}
-import net.psforever.objects.guid.{GUIDTask, TaskBundle, TaskWorkflow}
+import net.psforever.login.WorldSession.{CountGrenades, FindEquipmentStock, FindToolThatUses, RemoveOldEquipmentFromInventory}
+import net.psforever.objects.equipment.ChargeFireModeDefinition
import net.psforever.objects.inventory.Container
-import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject}
-import net.psforever.objects.serverobject.doors.InteriorDoorPassage
-import net.psforever.objects.{AmmoBox, BoomerDeployable, BoomerTrigger, ConstructionItem, Deployables, DummyExplodingEntity, GlobalDefinitions, OwnableByPlayer, PlanetSideGameObject, Player, SpecialEmp, Tool, Tools, Vehicle}
-import net.psforever.objects.serverobject.interior.Sidedness
-import net.psforever.objects.serverobject.mount.Mountable
-import net.psforever.objects.serverobject.structures.Amenity
+import net.psforever.objects.{BoomerDeployable, BoomerTrigger, GlobalDefinitions, Player, SpecialEmp, Tool, Tools, Vehicle}
import net.psforever.objects.serverobject.turret.{FacilityTurret, VanuSentry}
-import net.psforever.objects.serverobject.turret.auto.{AutomatedTurret, AutomatedTurretBehavior}
-import net.psforever.objects.sourcing.{PlayerSource, SourceEntry}
-import net.psforever.objects.vital.Vitality
import net.psforever.objects.vital.base.{DamageResolution, DamageType}
-import net.psforever.objects.vital.etc.OicwLilBuddyReason
-import net.psforever.objects.vital.interaction.DamageInteraction
-import net.psforever.objects.vital.projectile.ProjectileReason
import net.psforever.objects.zones.{Zone, ZoneProjectile}
-import net.psforever.packet.game.{AIDamage, AvatarGrenadeStateMessage, ChainLashMessage, ChangeAmmoMessage, ChangeFireModeMessage, ChangeFireStateMessage_Start, ChangeFireStateMessage_Stop, HitMessage, InventoryStateMessage, LashMessage, LongRangeProjectileInfoMessage, ObjectAttachMessage, ObjectDeleteMessage, ObjectDetachMessage, ProjectileStateMessage, QuantityUpdateMessage, ReloadMessage, SplashHitMessage, UplinkRequest, WeaponDelayFireMessage, WeaponDryFireMessage, WeaponFireMessage, WeaponLazeTargetPositionMessage}
+import net.psforever.packet.game.{AIDamage, AvatarGrenadeStateMessage, ChangeAmmoMessage, ChangeFireModeMessage, ChangeFireStateMessage_Start, ChangeFireStateMessage_Stop, HitMessage, LashMessage, LongRangeProjectileInfoMessage, ProjectileStateMessage, QuantityUpdateMessage, ReloadMessage, SplashHitMessage, UplinkRequest, WeaponDelayFireMessage, WeaponDryFireMessage, WeaponFireMessage, WeaponLazeTargetPositionMessage}
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
import net.psforever.types.{PlanetSideGUID, Vector3}
-import net.psforever.util.Config
import scala.concurrent.ExecutionContext.Implicits.global
-import scala.concurrent.Future
import scala.concurrent.duration._
object WeaponAndProjectileLogic {
def apply(ops: WeaponAndProjectileOperations): WeaponAndProjectileLogic = {
new WeaponAndProjectileLogic(ops, ops.context)
}
-
- /**
- * Does a line segment line intersect with a sphere?
- * This most likely belongs in `Geometry` or `GeometryForm` or somehow in association with the `\objects\geometry\` package.
- * @param start first point of the line segment
- * @param end second point of the line segment
- * @param center center of the sphere
- * @param radius radius of the sphere
- * @return list of all points of intersection, if any
- * @see `Vector3.DistanceSquared`
- * @see `Vector3.MagnitudeSquared`
- */
- private def quickLineSphereIntersectionPoints(
- start: Vector3,
- end: Vector3,
- center: Vector3,
- radius: Float
- ): Iterable[Vector3] = {
- /*
- Algorithm adapted from code found on https://paulbourke.net/geometry/circlesphere/index.html#linesphere,
- because I kept messing up proper substitution of the line formula and the circle formula into the quadratic equation.
- */
- val Vector3(cx, cy, cz) = center
- val Vector3(sx, sy, sz) = start
- val vector = end - start
- //speed our way through a quadratic equation
- val (a, b) = {
- val Vector3(dx, dy, dz) = vector
- (
- dx * dx + dy * dy + dz * dz,
- 2f * (dx * (sx - cx) + dy * (sy - cy) + dz * (sz - cz))
- )
- }
- val c = Vector3.MagnitudeSquared(center) + Vector3.MagnitudeSquared(start) - 2f * (cx * sx + cy * sy + cz * sz) - radius * radius
- val result = b * b - 4 * a * c
- if (result < 0f) {
- //negative, no intersection
- Seq()
- } else if (result < 0.00001f) {
- //zero-ish, one intersection point
- Seq(start - vector * (b / (2f * a)))
- } else {
- //positive, two intersection points
- val sqrt = math.sqrt(result).toFloat
- val endStart = vector / (2f * a)
- Seq(start + endStart * (sqrt - b), start + endStart * (b + sqrt) * -1f)
- }.filter(p => Vector3.DistanceSquared(start, p) <= a)
- }
-
- /**
- * Preparation for explosion damage that utilizes the Scorpion's little buddy sub-projectiles.
- * The main difference from "normal" server-side explosion
- * is that the owner of the projectile must be clarified explicitly.
- * @see `Zone::serverSideDamage`
- * @param zone where the explosion is taking place
- * (`source` contains the coordinate location)
- * @param source a game object that represents the source of the explosion
- * @param owner who or what to accredit damage from the explosion to;
- * clarifies a normal `SourceEntry(source)` accreditation
- */
- private def detonateLittleBuddy(
- zone: Zone,
- source: PlanetSideGameObject with FactionAffinity with Vitality,
- proxy: Projectile,
- owner: SourceEntry
- )(): Unit = {
- Zone.serverSideDamage(zone, source, littleBuddyExplosionDamage(owner, proxy.id, source.Position))
- }
-
- /**
- * Preparation for explosion damage that utilizes the Scorpion's little buddy sub-projectiles.
- * The main difference from "normal" server-side explosion
- * is that the owner of the projectile must be clarified explicitly.
- * The sub-projectiles will be the product of a normal projectile rather than a standard game object
- * so a custom `source` entity must wrap around it and fulfill the requirements of the field.
- * @see `Zone::explosionDamage`
- * @param owner who or what to accredit damage from the explosion to
- * @param explosionPosition where the explosion will be positioned in the game world
- * @param source a game object that represents the source of the explosion
- * @param target a game object that is affected by the explosion
- * @return a `DamageInteraction` object
- */
- private def littleBuddyExplosionDamage(
- owner: SourceEntry,
- projectileId: Long,
- explosionPosition: Vector3
- )
- (
- source: PlanetSideGameObject with FactionAffinity with Vitality,
- target: PlanetSideGameObject with FactionAffinity with Vitality
- ): DamageInteraction = {
- DamageInteraction(SourceEntry(target), OicwLilBuddyReason(owner, projectileId, target.DamageModel), explosionPosition)
- }
}
class WeaponAndProjectileLogic(val ops: WeaponAndProjectileOperations, implicit val context: ActorContext) extends WeaponAndProjectileFunctions {
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 handleWeaponFire(pkt: WeaponFireMessage): Unit = {
- val WeaponFireMessage(
- _,
- weapon_guid,
- projectile_guid,
- shot_origin,
- _,
- _,
- _,
- _/*max_distance,*/,
- _,
- _/*projectile_type,*/,
- thrown_projectile_vel
- ) = pkt
- HandleWeaponFireOperations(weapon_guid, projectile_guid, shot_origin, thrown_projectile_vel.flatten)
+ ops.handleWeaponFireOperations(pkt)
}
def handleWeaponDelayFire(pkt: WeaponDelayFireMessage): Unit = {
@@ -166,29 +44,7 @@ class WeaponAndProjectileLogic(val ops: WeaponAndProjectileOperations, implicit
}
def handleWeaponDryFire(pkt: WeaponDryFireMessage): Unit = {
- val WeaponDryFireMessage(weapon_guid) = pkt
- val (containerOpt, tools) = ops.FindContainedWeapon
- tools
- .find { _.GUID == weapon_guid }
- .orElse { continent.GUID(weapon_guid) }
- .collect {
- case _: Equipment if containerOpt.exists(_.isInstanceOf[Player]) =>
- continent.AvatarEvents ! AvatarServiceMessage(
- continent.id,
- AvatarAction.WeaponDryFire(player.GUID, weapon_guid)
- )
- case _: Equipment =>
- continent.VehicleEvents ! VehicleServiceMessage(
- continent.id,
- VehicleAction.WeaponDryFire(player.GUID, weapon_guid)
- )
- }
- .orElse {
- log.warn(
- s"WeaponDryFire: ${player.Name}'s weapon ${weapon_guid.guid} is either not a weapon or does not exist"
- )
- None
- }
+ ops.handleWeaponDryFire(pkt)
}
def handleWeaponLazeTargetPosition(pkt: WeaponLazeTargetPositionMessage): Unit = {
@@ -208,8 +64,8 @@ class WeaponAndProjectileLogic(val ops: WeaponAndProjectileOperations, implicit
}
def handleAvatarGrenadeState(pkt: AvatarGrenadeStateMessage): Unit = {
+ //grenades are handled elsewhere
val AvatarGrenadeStateMessage(_, state) = pkt
- //TODO I thought I had this working?
log.info(s"${player.Name} has $state ${player.Sex.possessive} grenade")
}
@@ -274,98 +130,15 @@ class WeaponAndProjectileLogic(val ops: WeaponAndProjectileOperations, implicit
}
def handleChangeAmmo(pkt: ChangeAmmoMessage): Unit = {
- val ChangeAmmoMessage(item_guid, _) = pkt
- val (thing, equipment) = sessionLogic.findContainedEquipment()
- if (equipment.isEmpty) {
- log.warn(s"ChangeAmmo: either can not find $item_guid or the object found was not Equipment")
- } else {
- equipment foreach {
- case obj: ConstructionItem =>
- if (Deployables.performConstructionItemAmmoChange(player.avatar.certifications, obj, obj.AmmoTypeIndex)) {
- log.info(
- s"${player.Name} switched ${player.Sex.possessive} ${obj.Definition.Name} to construct ${obj.AmmoType} (option #${obj.FireModeIndex})"
- )
- sendResponse(ChangeAmmoMessage(obj.GUID, obj.AmmoTypeIndex))
- }
- case tool: Tool =>
- thing match {
- case Some(player: Player) =>
- PerformToolAmmoChange(tool, player, ModifyAmmunition(player))
- case Some(mountable: PlanetSideServerObject with Container) =>
- PerformToolAmmoChange(tool, mountable, ModifyAmmunitionInMountable(mountable))
- case _ =>
- log.warn(s"ChangeAmmo: the ${thing.get.Definition.Name} in ${player.Name}'s is not the correct type")
- }
- case obj =>
- log.warn(s"ChangeAmmo: the ${obj.Definition.Name} in ${player.Name}'s hands does not contain ammunition")
- }
- }
+ ops.handleChangeAmmo(pkt)
}
def handleChangeFireMode(pkt: ChangeFireModeMessage): Unit = {
- val ChangeFireModeMessage(item_guid, _/*fire_mode*/) = pkt
- sessionLogic.findEquipment(item_guid) match {
- case Some(obj: PlanetSideGameObject with FireModeSwitch[_]) =>
- val originalModeIndex = obj.FireModeIndex
- if (obj match {
- case citem: ConstructionItem =>
- val modeChanged = Deployables.performConstructionItemFireModeChange(
- player.avatar.certifications,
- citem,
- originalModeIndex
- )
- modeChanged
- case _ =>
- obj.NextFireMode
- obj.FireModeIndex != originalModeIndex
- }) {
- val modeIndex = obj.FireModeIndex
- obj match {
- case citem: ConstructionItem =>
- log.info(s"${player.Name} switched ${player.Sex.possessive} ${obj.Definition.Name} to construct ${citem.AmmoType} (mode #$modeIndex)")
- case _ =>
- log.info(s"${player.Name} changed ${player.Sex.possessive} her ${obj.Definition.Name}'s fire mode to #$modeIndex")
- }
- sendResponse(ChangeFireModeMessage(item_guid, modeIndex))
- continent.AvatarEvents ! AvatarServiceMessage(
- continent.id,
- AvatarAction.ChangeFireMode(player.GUID, item_guid, modeIndex)
- )
- }
- case Some(_) =>
- log.warn(s"ChangeFireMode: the object that was found for $item_guid does not possess fire modes")
- case None =>
- log.warn(s"ChangeFireMode: can not find $item_guid")
- }
+ ops.handleChangeFireMode(pkt)
}
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 = {
@@ -378,296 +151,110 @@ class WeaponAndProjectileLogic(val ops: WeaponAndProjectileOperations, implicit
}
def handleDirectHit(pkt: HitMessage): Unit = {
- val HitMessage(
- _,
- projectile_guid,
- _,
- hit_info,
- _,
- _,
- _
- ) = pkt
- //find defined projectile
- ops.FindProjectileEntry(projectile_guid) match {
- case Some(projectile) =>
- //find target(s)
- (hit_info match {
- case Some(hitInfo) =>
- val hitPos = hitInfo.hit_pos
- sessionLogic.validObject(hitInfo.hitobject_guid, decorator = "Hit/hitInfo") match {
- case _ if projectile.profile == GlobalDefinitions.flail_projectile =>
- val radius = projectile.profile.DamageRadius * projectile.profile.DamageRadius
- val targets = Zone.findAllTargets(continent, player, hitPos, projectile.profile)
- .filter { target =>
- Vector3.DistanceSquared(target.Position, hitPos) <= radius
- }
- targets.map { target =>
- CheckForHitPositionDiscrepancy(projectile_guid, hitPos, target)
- (target, projectile, hitPos, target.Position)
- }
-
- case Some(target: PlanetSideGameObject with FactionAffinity with Vitality) =>
- CheckForHitPositionDiscrepancy(projectile_guid, hitPos, target)
- List((target, projectile, hitInfo.shot_origin, hitPos))
-
- case None =>
- HandleDamageProxy(projectile, projectile_guid, hitPos)
-
- case _ =>
- Nil
- }
- case None =>
- Nil
- })
- .foreach {
- case (
- target: PlanetSideGameObject with FactionAffinity with Vitality,
- proj: Projectile,
- _: Vector3,
- hitPos: Vector3
- ) =>
- ResolveProjectileInteraction(proj, DamageResolution.Hit, target, hitPos).collect { resprojectile =>
- addShotsLanded(resprojectile.cause.attribution, shots = 1)
- sessionLogic.handleDealingDamage(target, resprojectile)
- }
- case _ => ()
- }
- case None =>
- log.warn(s"ResolveProjectile: expected projectile, but ${projectile_guid.guid} not found")
+ val list = ops.composeDirectDamageInformation(pkt)
+ .collect {
+ case (target, projectile, hitPos, _) =>
+ ops.checkForHitPositionDiscrepancy(projectile.GUID, hitPos, target)
+ ops.resolveProjectileInteraction(target, projectile, DamageResolution.Hit, hitPos)
+ projectile
+ }
+ //...
+ if (list.isEmpty) {
+ val proxyList = ops
+ .FindProjectileEntry(pkt.projectile_guid)
+ .map(projectile => ops.resolveDamageProxy(projectile, projectile.GUID, pkt.hit_info.map(_.hit_pos).getOrElse(Vector3.Zero)))
+ .getOrElse(Nil)
+ proxyList.collectFirst {
+ case (_, proxy, _, _) if proxy.tool_def == GlobalDefinitions.oicw =>
+ ops.performLittleBuddyExplosion(proxyList.map(_._2))
+ }
+ proxyList.foreach {
+ case (target, proxy, hitPos, _) if proxy.tool_def == GlobalDefinitions.oicw =>
+ ops.checkForHitPositionDiscrepancy(proxy.GUID, hitPos, target)
+ }
}
}
def handleSplashHit(pkt: SplashHitMessage): Unit = {
- val SplashHitMessage(
- _,
- projectile_guid,
- explosion_pos,
- direct_victim_uid,
- _,
- projectile_vel,
- _,
- targets
- ) = pkt
- ops.FindProjectileEntry(projectile_guid) match {
- case Some(projectile) =>
- val profile = projectile.profile
- projectile.Velocity = projectile_vel
- val (resolution1, resolution2) = profile.Aggravated match {
- case Some(_) if profile.ProjectileDamageTypes.contains(DamageType.Aggravated) =>
- (DamageResolution.AggravatedDirect, DamageResolution.AggravatedSplash)
- case _ =>
- (DamageResolution.Splash, DamageResolution.Splash)
+ val list = ops.composeSplashDamageInformation(pkt)
+ if (list.nonEmpty) {
+ val projectile = list.head._2
+ val explosionPosition = projectile.Position
+ val projectileGuid = projectile.GUID
+ val profile = projectile.profile
+ val (resolution1, resolution2) = profile.Aggravated match {
+ case Some(_) if profile.ProjectileDamageTypes.contains(DamageType.Aggravated) =>
+ (DamageResolution.AggravatedDirect, DamageResolution.AggravatedSplash)
+ case _ =>
+ (DamageResolution.Splash, DamageResolution.Splash)
+ }
+ //...
+ val (direct, others) = list.partition { case (_, _, hitPos, targetPos) => hitPos == targetPos }
+ direct.foreach {
+ case (target, _, hitPos, _) =>
+ ops.checkForHitPositionDiscrepancy(projectile.GUID, hitPos, target)
+ ops.resolveProjectileInteraction(target, projectile, resolution1, hitPos)
+ }
+ others.foreach {
+ case (target, _, hitPos, _) =>
+ ops.checkForHitPositionDiscrepancy(projectile.GUID, hitPos, target)
+ ops.resolveProjectileInteraction(target, projectile, resolution2, hitPos)
+ }
+ //...
+ val proxyList = ops.resolveDamageProxy(projectile, projectileGuid, explosionPosition)
+ if (proxyList.nonEmpty) {
+ proxyList.foreach {
+ case (target, _, hitPos, _) => ops.checkForHitPositionDiscrepancy(projectileGuid, hitPos, target)
}
- //direct_victim_uid
- sessionLogic.validObject(direct_victim_uid, decorator = "SplashHit/direct_victim") match {
- case Some(target: PlanetSideGameObject with FactionAffinity with Vitality) =>
- CheckForHitPositionDiscrepancy(projectile_guid, explosion_pos, target)
- ResolveProjectileInteraction(projectile, resolution1, target, target.Position).collect { resprojectile =>
- addShotsLanded(resprojectile.cause.attribution, shots = 1)
- sessionLogic.handleDealingDamage(target, resprojectile)
- }
- case _ => ()
+ if (profile == GlobalDefinitions.oicw_projectile) {
+ ops.performLittleBuddyExplosion(proxyList.map(_._2))
}
- //other victims
- targets.foreach(elem => {
- sessionLogic.validObject(elem.uid, decorator = "SplashHit/other_victims") match {
- case Some(target: PlanetSideGameObject with FactionAffinity with Vitality) =>
- CheckForHitPositionDiscrepancy(projectile_guid, explosion_pos, target)
- ResolveProjectileInteraction(projectile, resolution2, target, explosion_pos).collect { resprojectile =>
- addShotsLanded(resprojectile.cause.attribution, shots = 1)
- sessionLogic.handleDealingDamage(target, resprojectile)
- }
- case _ => ()
- }
- })
- //...
- HandleDamageProxy(projectile, projectile_guid, explosion_pos)
- if (
- projectile.profile.HasJammedEffectDuration ||
- projectile.profile.JammerProjectile ||
- projectile.profile.SympatheticExplosion
- ) {
- //can also substitute 'projectile.profile' for 'SpecialEmp.emp'
- Zone.serverSideDamage(
- continent,
- player,
- SpecialEmp.emp,
- SpecialEmp.createEmpInteraction(SpecialEmp.emp, explosion_pos),
- SpecialEmp.prepareDistanceCheck(player, explosion_pos, player.Faction),
- SpecialEmp.findAllBoomers(profile.DamageRadius)
- )
- }
- if (profile.ExistsOnRemoteClients && projectile.HasGUID) {
- //cleanup
- continent.Projectile ! ZoneProjectile.Remove(projectile.GUID)
- }
- case None => ()
+ }
+ //...
+ if (
+ profile.HasJammedEffectDuration ||
+ profile.JammerProjectile ||
+ profile.SympatheticExplosion
+ ) {
+ //can also substitute 'profile' for 'SpecialEmp.emp'
+ Zone.serverSideDamage(
+ continent,
+ player,
+ SpecialEmp.emp,
+ SpecialEmp.createEmpInteraction(SpecialEmp.emp, explosionPosition),
+ SpecialEmp.prepareDistanceCheck(player, explosionPosition, player.Faction),
+ SpecialEmp.findAllBoomers(profile.DamageRadius)
+ )
+ }
+ if (profile.ExistsOnRemoteClients && projectile.HasGUID) {
+ //cleanup
+ continent.Projectile ! ZoneProjectile.Remove(projectile.GUID)
+ }
}
}
def handleLashHit(pkt: LashMessage): Unit = {
- val LashMessage(_, _, victim_guid, projectile_guid, hit_pos, _) = pkt
- sessionLogic.validObject(victim_guid, decorator = "Lash") match {
- case Some(target: PlanetSideGameObject with FactionAffinity with Vitality) =>
- CheckForHitPositionDiscrepancy(projectile_guid, hit_pos, target)
- ResolveProjectileInteraction(projectile_guid, DamageResolution.Lash, target, hit_pos).foreach {
- resprojectile =>
- addShotsLanded(resprojectile.cause.attribution, shots = 1)
- sessionLogic.handleDealingDamage(target, resprojectile)
- }
- case _ => ()
+ val list = ops.composeLashDamageInformation(pkt)
+ list.foreach {
+ case (target, projectile, hitPos, _) =>
+ ops.checkForHitPositionDiscrepancy(projectile.GUID, hitPos, target)
+ ops.resolveProjectileInteraction(target, projectile, DamageResolution.Lash, hitPos)
}
}
def handleAIDamage(pkt: AIDamage): Unit = {
- val AIDamage(targetGuid, attackerGuid, projectileTypeId, _, _) = pkt
- (continent.GUID(player.VehicleSeated) match {
- case Some(tobj: PlanetSideServerObject with FactionAffinity with Vitality with OwnableByPlayer)
- if tobj.GUID == targetGuid &&
- tobj.OwnerGuid.contains(player.GUID) =>
- //deployable turrets
- Some(tobj)
- case Some(tobj: PlanetSideServerObject with FactionAffinity with Vitality with Mountable)
- if tobj.GUID == targetGuid &&
- tobj.Seats.values.flatMap(_.occupants.map(_.GUID)).toSeq.contains(player.GUID) =>
- //facility turrets, etc.
- Some(tobj)
- case _
- if player.GUID == targetGuid =>
- //player avatars
- Some(player)
- case _ =>
- None
- }).collect {
- case target: AutomatedTurret.Target =>
- sessionLogic.validObject(attackerGuid, decorator = "AIDamage/AutomatedTurret")
- .collect {
- case turret: AutomatedTurret if turret.Target.isEmpty =>
- turret.Actor ! AutomatedTurretBehavior.ConfirmShot(target)
- Some(target)
-
- case turret: AutomatedTurret =>
- turret.Actor ! AutomatedTurretBehavior.ConfirmShot(target)
- HandleAIDamage(target, CompileAutomatedTurretDamageData(turret, projectileTypeId))
- Some(target)
- }
- }
- .orElse {
- //occasionally, something that is not technically a turret's natural target may be attacked
- continent.GUID(targetGuid) //AIDamage/Attacker
- .collect {
- case target: PlanetSideServerObject with FactionAffinity with Vitality =>
- sessionLogic.validObject(attackerGuid, decorator = "AIDamage/Attacker")
- .collect {
- case turret: AutomatedTurret if turret.Target.nonEmpty =>
- //the turret must be shooting at something (else) first
- HandleAIDamage(target, CompileAutomatedTurretDamageData(turret, projectileTypeId))
- }
- Some(target)
- }
+ val list = ops.composeAIDamageInformation(pkt)
+ if (ops.confirmAIDamageTarget(pkt, list.map(_._1))) {
+ list.foreach {
+ case (target, projectile, hitPos, _) =>
+ ops.checkForHitPositionDiscrepancy(projectile.GUID, hitPos, target)
+ ops.resolveProjectileInteraction(target, projectile, DamageResolution.Hit, hitPos)
}
+ }
}
/* support code */
- private def HandleWeaponFireOperations(
- weaponGUID: PlanetSideGUID,
- projectileGUID: PlanetSideGUID,
- shotOrigin: Vector3,
- shotVelocity: Option[Vector3]
- ): Unit = {
- ops.HandleWeaponFireAccountability(weaponGUID, projectileGUID) match {
- case (Some(obj), Some(tool)) =>
- val projectileIndex = projectileGUID.guid - Projectile.baseUID
- val projectilePlace = ops.projectiles(projectileIndex)
- if (
- projectilePlace match {
- case Some(projectile) =>
- !projectile.isResolved && System.currentTimeMillis() - projectile.fire_time < projectile.profile.Lifespan.toLong
- case None =>
- false
- }
- ) {
- log.debug(
- s"WeaponFireMessage: overwriting unresolved projectile ${projectileGUID.guid}, known to ${player.Name}"
- )
- }
- val (angle, attribution, acceptableDistanceToOwner) = obj match {
- case p: Player =>
- (
- SimpleWorldEntity.validateOrientationEntry(
- p.Orientation + Vector3.z(p.FacingYawUpper)
- ),
- tool.Definition.ObjectId,
- 10f + (if (p.Velocity.nonEmpty) {
- 5f
- } else {
- 0f
- })
- )
- case v: Vehicle if v.Definition.CanFly =>
- (tool.Orientation, obj.Definition.ObjectId, 1000f) //TODO this is too simplistic to find proper angle
- case _: Vehicle =>
- (tool.Orientation, obj.Definition.ObjectId, 225f) //TODO this is too simplistic to find proper angle
- case _ =>
- (obj.Orientation, obj.Definition.ObjectId, 300f)
- }
- val distanceToOwner = Vector3.DistanceSquared(shotOrigin, player.Position)
- if (distanceToOwner <= acceptableDistanceToOwner) {
- val projectile_info = tool.Projectile
- val wguid = weaponGUID.guid
- val mountedIn = (continent.turretToWeapon
- .find { case (guid, _) => guid == wguid } match {
- case Some((_, turretGuid)) => Some((
- turretGuid,
- continent.GUID(turretGuid).collect { case o: PlanetSideGameObject with FactionAffinity => SourceEntry(o) }
- ))
- case _ => None
- }) match {
- case Some((guid, Some(entity))) => Some((guid, entity))
- case _ => None
- }
- val projectile = new Projectile(
- projectile_info,
- tool.Definition,
- tool.FireMode,
- mountedIn,
- PlayerSource(player),
- attribution,
- shotOrigin,
- angle,
- shotVelocity
- )
- val initialQuality = tool.FireMode match {
- case mode: ChargeFireModeDefinition =>
- ProjectileQuality.Modified(
- {
- val timeInterval = projectile.fire_time - ops.shootingStart.getOrElse(tool.GUID, System.currentTimeMillis())
- timeInterval.toFloat / mode.Time.toFloat
- }
- )
- case _ =>
- ProjectileQuality.Normal
- }
- val qualityprojectile = projectile.quality(initialQuality)
- qualityprojectile.WhichSide = player.WhichSide
- ops.projectiles(projectileIndex) = Some(qualityprojectile)
- if (projectile_info.ExistsOnRemoteClients) {
- log.trace(
- s"WeaponFireMessage: ${player.Name}'s ${projectile_info.Name} is a remote projectile"
- )
- continent.Projectile ! ZoneProjectile.Add(player.GUID, qualityprojectile)
- }
- } else {
- log.warn(
- s"WeaponFireMessage: ${player.Name}'s ${tool.Definition.Name} projectile is too far from owner position at time of discharge ($distanceToOwner > $acceptableDistanceToOwner); suspect"
- )
- }
-
- case _ => ()
- }
- }
-
/**
* After a weapon has finished shooting, determine if it needs to be sorted in a special way.
* @param tool a weapon
@@ -691,11 +278,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)
@@ -706,354 +293,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))
- }
-
- /**
- * Given a vehicle that contains a box of ammunition in its `Trunk` 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 ModifyAmmunitionInMountable(obj: PlanetSideServerObject with Container)(box: AmmoBox, reloadValue: Int): Unit = {
- ModifyAmmunition(obj)(box, reloadValue)
- obj.Find(box).collect { index =>
- continent.VehicleEvents ! VehicleServiceMessage(
- s"${obj.Actor}",
- VehicleAction.InventoryState(
- player.GUID,
- box,
- obj.GUID,
- index,
- box.Definition.Packet.DetailedConstructorData(box).get
- )
- )
- }
- }
-
- /**
- * na
- * @param tool na
- * @param obj na
- */
- private def PerformToolAmmoChange(
- tool: Tool,
- obj: PlanetSideServerObject with Container,
- modifyFunc: (AmmoBox, Int) => Unit
- ): Unit = {
- val originalAmmoType = tool.AmmoType
- do {
- val requestedAmmoType = tool.NextAmmoType
- val fullMagazine = tool.MaxMagazine
- if (requestedAmmoType != tool.AmmoSlot.Box.AmmoType) {
- FindEquipmentStock(obj, FindAmmoBoxThatUses(requestedAmmoType), fullMagazine, CountAmmunition).reverse match {
- case Nil => ()
- case x :: xs =>
- val stowNewFunc: Equipment => TaskBundle = PutNewEquipmentInInventoryOrDrop(obj)
- val stowFunc: Equipment => Future[Any] = PutEquipmentInInventoryOrDrop(obj)
-
- xs.foreach(item => {
- obj.Inventory -= item.start
- sendResponse(ObjectDeleteMessage(item.obj.GUID, 0))
- TaskWorkflow.execute(GUIDTask.unregisterObject(continent.GUID, item.obj))
- })
-
- //box will be the replacement ammo; give it the discovered magazine and load it into the weapon
- val box = x.obj.asInstanceOf[AmmoBox]
- //previousBox is the current magazine in tool; it will be removed from the weapon
- val previousBox = tool.AmmoSlot.Box
- val originalBoxCapacity = box.Capacity
- val tailReloadValue: Int = if (xs.isEmpty) {
- 0
- } else {
- xs.map(_.obj.asInstanceOf[AmmoBox].Capacity).sum
- }
- val sumReloadValue: Int = originalBoxCapacity + tailReloadValue
- val ammoSlotIndex = tool.FireMode.AmmoSlotIndex
- val box_guid = box.GUID
- val tool_guid = tool.GUID
- obj.Inventory -= x.start //remove replacement ammo from inventory
- tool.AmmoSlots(ammoSlotIndex).Box = box //put replacement ammo in tool
- sendResponse(ObjectDetachMessage(tool_guid, previousBox.GUID, Vector3.Zero, 0f))
- sendResponse(ObjectDetachMessage(obj.GUID, box_guid, Vector3.Zero, 0f))
- sendResponse(ObjectAttachMessage(tool_guid, box_guid, ammoSlotIndex))
-
- //announce swapped ammunition box in weapon
- val previous_box_guid = previousBox.GUID
- val boxDef = box.Definition
- sendResponse(ChangeAmmoMessage(tool_guid, box.Capacity))
- continent.AvatarEvents ! AvatarServiceMessage(
- continent.id,
- AvatarAction.ChangeAmmo(
- player.GUID,
- tool_guid,
- ammoSlotIndex,
- previous_box_guid,
- boxDef.ObjectId,
- box.GUID,
- boxDef.Packet.ConstructorData(box).get
- )
- )
-
- //handle inventory contents
- box.Capacity = if (sumReloadValue <= fullMagazine) {
- sumReloadValue
- } else {
- val splitReloadAmmo: Int = sumReloadValue - fullMagazine
- log.trace(
- s"PerformToolAmmoChange: ${player.Name} takes ${originalBoxCapacity - splitReloadAmmo} from a box of $originalBoxCapacity $requestedAmmoType ammo"
- )
- val boxForInventory = AmmoBox(box.Definition, splitReloadAmmo)
- TaskWorkflow.execute(stowNewFunc(boxForInventory))
- fullMagazine
- }
- sendResponse(
- InventoryStateMessage(box.GUID, tool.GUID, box.Capacity)
- ) //should work for both players and vehicles
- log.info(s"${player.Name} loads ${box.Capacity} $requestedAmmoType into the ${tool.Definition.Name}")
- if (previousBox.Capacity > 0) {
- //divide capacity across other existing and not full boxes of that ammo type
- var capacity = previousBox.Capacity
- val iter = obj.Inventory.Items
- .filter(entry => {
- entry.obj match {
- case item: AmmoBox =>
- item.AmmoType == originalAmmoType && item.FullCapacity != item.Capacity
- case _ =>
- false
- }
- })
- .sortBy(_.start)
- .iterator
- while (capacity > 0 && iter.hasNext) {
- val entry = iter.next()
- val item: AmmoBox = entry.obj.asInstanceOf[AmmoBox]
- val ammoAllocated = math.min(item.FullCapacity - item.Capacity, capacity)
- log.info(s"${player.Name} put $ammoAllocated back into a box of ${item.Capacity} $originalAmmoType")
- capacity -= ammoAllocated
- modifyFunc(item, -ammoAllocated)
- }
- previousBox.Capacity = capacity
- }
-
- if (previousBox.Capacity > 0) {
- //split previousBox into AmmoBox objects of appropriate max capacity, e.g., 100 9mm -> 2 x 50 9mm
- obj.Inventory.Fit(previousBox) match {
- case Some(_) =>
- stowFunc(previousBox)
- case None =>
- sessionLogic.general.normalItemDrop(player, continent)(previousBox)
- }
- AmmoBox.Split(previousBox) match {
- case Nil | List(_) => () //done (the former case is technically not possible)
- case _ :: toUpdate =>
- modifyFunc(previousBox, 0) //update to changed capacity value
- toUpdate.foreach(box => { TaskWorkflow.execute(stowNewFunc(box)) })
- }
- } else {
- TaskWorkflow.execute(GUIDTask.unregisterObject(continent.GUID, previousBox))
- }
- }
- }
- } while (tool.AmmoType != originalAmmoType && tool.AmmoType != tool.AmmoSlot.Box.AmmoType)
- }
-
- private def CheckForHitPositionDiscrepancy(
- projectile_guid: PlanetSideGUID,
- hitPos: Vector3,
- target: PlanetSideGameObject with FactionAffinity with Vitality
- ): Unit = {
- val hitPositionDiscrepancy = Vector3.DistanceSquared(hitPos, target.Position)
- if (hitPositionDiscrepancy > Config.app.antiCheat.hitPositionDiscrepancyThreshold) {
- // If the target position on the server does not match the position where the projectile landed within reason there may be foul play
- log.warn(
- s"${player.Name}'s shot #${projectile_guid.guid} has hit discrepancy with target. Target: ${target.Position}, Reported: $hitPos, Distance: $hitPositionDiscrepancy / ${math.sqrt(hitPositionDiscrepancy).toFloat}; suspect"
- )
- }
- }
-
- /**
- * Find a projectile with the given globally unique identifier and mark it as a resolved shot.
- * A `Resolved` shot has either encountered an obstacle or is being cleaned up for not finding an obstacle.
- * @param projectile_guid the projectile GUID
- * @param resolution the resolution status to promote the projectile
- * @return the projectile
- */
- private def ResolveProjectileInteraction(
- projectile_guid: PlanetSideGUID,
- resolution: DamageResolution.Value,
- target: PlanetSideGameObject with FactionAffinity with Vitality,
- pos: Vector3
- ): Option[DamageInteraction] = {
- ops.FindProjectileEntry(projectile_guid) match {
- case Some(projectile) =>
- ResolveProjectileInteraction(projectile, resolution, target, pos)
- case None =>
- log.trace(s"ResolveProjectile: ${player.Name} expected projectile, but ${projectile_guid.guid} not found")
- None
- }
- }
-
- /**
- * na
- * @param projectile the projectile object
- * @param resolution the resolution status to promote the projectile
- * @return a copy of the projectile
- */
- private def ResolveProjectileInteraction(
- projectile: Projectile,
- resolution: DamageResolution.Value,
- target: PlanetSideGameObject with FactionAffinity with Vitality,
- pos: Vector3
- ): Option[DamageInteraction] = {
- if (projectile.isMiss) {
- log.warn("expected projectile was already counted as a missed shot; can not resolve any further")
- None
- } else {
- val outProjectile = ProjectileQuality.modifiers(projectile, resolution, target, pos, Some(player))
- if (projectile.tool_def.Size == EquipmentSize.Melee && outProjectile.quality == ProjectileQuality.Modified(25)) {
- avatarActor ! AvatarActor.ConsumeStamina(10)
- }
- Some(DamageInteraction(SourceEntry(target), ProjectileReason(resolution, outProjectile, target.DamageModel), pos))
- }
- }
-
- /**
- * Take a projectile that was introduced into the game world and
- * determine if it generates a secondary damage projectile or
- * an method of damage causation that requires additional management.
- * @param projectile the projectile
- * @param pguid the client-local projectile identifier
- * @param hitPos the game world position where the projectile is being recorded
- * @return a for all affected targets, a combination of projectiles, projectile location, and the target's location;
- * nothing if no targets were affected
- */
- private def HandleDamageProxy(
- projectile: Projectile,
- pguid: PlanetSideGUID,
- hitPos: Vector3
- ): List[(PlanetSideGameObject with FactionAffinity with Vitality, Projectile, Vector3, Vector3)] = {
- GlobalDefinitions.getDamageProxy(projectile, hitPos) match {
- case Nil =>
- Nil
- case list if list.isEmpty =>
- Nil
- case list =>
- HandleDamageProxySetupLittleBuddy(list, hitPos)
- UpdateProjectileSidednessAfterHit(projectile, hitPos)
- val projectileSide = projectile.WhichSide
- list.flatMap { proxy =>
- if (proxy.profile.ExistsOnRemoteClients) {
- proxy.Position = hitPos
- proxy.WhichSide = projectileSide
- continent.Projectile ! ZoneProjectile.Add(player.GUID, proxy)
- Nil
- } else if (proxy.tool_def == GlobalDefinitions.maelstrom) {
- //server-side maelstrom grenade target selection
- val radius = proxy.profile.LashRadius * proxy.profile.LashRadius
- val targets = Zone.findAllTargets(continent, hitPos, proxy.profile.LashRadius, { _.livePlayerList })
- .filter { target =>
- Vector3.DistanceSquared(target.Position, hitPos) <= radius
- }
- //chainlash is separated from the actual damage application for convenience
- continent.AvatarEvents ! AvatarServiceMessage(
- continent.id,
- AvatarAction.SendResponse(
- PlanetSideGUID(0),
- ChainLashMessage(
- hitPos,
- projectile.profile.ObjectId,
- targets.map { _.GUID }
- )
- )
- )
- targets.map { target =>
- CheckForHitPositionDiscrepancy(pguid, hitPos, target)
- (target, proxy, hitPos, target.Position)
- }
- } else {
- Nil
- }
- }
- }
- }
-
- private def HandleDamageProxySetupLittleBuddy(listOfProjectiles: List[Projectile], detonationPosition: Vector3): Boolean = {
- val listOfLittleBuddies: List[Projectile] = listOfProjectiles.filter { _.tool_def == GlobalDefinitions.oicw }
- val size: Int = listOfLittleBuddies.size
- if (size > 0) {
- val desiredDownwardsProjectiles: Int = 2
- val firstHalf: Int = math.min(size, desiredDownwardsProjectiles) //number that fly straight down
- val secondHalf: Int = math.max(size - firstHalf, 0) //number that are flared out
- val z: Float = player.Orientation.z //player's standing direction
- val north: Vector3 = Vector3(0,1,0) //map North
- val speed: Float = 144f //speed (packet discovered)
- val dist: Float = 25 //distance (client defined)
- val downwardsAngle: Float = -85f
- val flaredAngle: Float = -70f
- //angle of separation for downwards, degrees from vertical for flared out
- val (smallStep, smallAngle): (Float, Float) = if (firstHalf > 1) {
- (360f / firstHalf, downwardsAngle)
- } else {
- (0f, 0f)
- }
- val (largeStep, largeAngle): (Float, Float) = if (secondHalf > 1) {
- (360f / secondHalf, flaredAngle)
- } else {
- (0f, 0f)
- }
- val smallRotOffset: Float = z + 90f
- val largeRotOffset: Float = z + math.random().toFloat * 45f
- val verticalCorrection = Vector3.z(dist - dist * math.sin(math.toRadians(90 - smallAngle + largeAngle)).toFloat)
- //downwards projectiles
- var i: Int = 0
- listOfLittleBuddies.take(firstHalf).foreach { proxy =>
- val facing = (smallRotOffset + smallStep * i.toFloat) % 360
- val dir = north.Rx(smallAngle).Rz(facing)
- proxy.Position = detonationPosition + dir.xy + verticalCorrection
- proxy.Velocity = dir * speed
- proxy.Orientation = Vector3(0, (360f + smallAngle) % 360, facing)
- HandleDamageProxyLittleBuddyExplosion(proxy, dir, dist)
- i += 1
- }
- //flared out projectiles
- i = 0
- listOfLittleBuddies.drop(firstHalf).foreach { proxy =>
- val facing = (largeRotOffset + largeStep * i.toFloat) % 360
- val dir = north.Rx(largeAngle).Rz(facing)
- proxy.Position = detonationPosition + dir
- proxy.Velocity = dir * speed
- proxy.Orientation = Vector3(0, (360f + largeAngle) % 360, facing)
- HandleDamageProxyLittleBuddyExplosion(proxy, dir, dist)
- i += 1
- }
- true
- } else {
- false
- }
- }
-
- private def HandleDamageProxyLittleBuddyExplosion(proxy: Projectile, orientation: Vector3, distance: Float): Unit = {
- //explosion
- val obj = new DummyExplodingEntity(proxy, proxy.owner.Faction)
- obj.Position = obj.Position + orientation * distance
- val explosionFunc: ()=>Unit = WeaponAndProjectileLogic.detonateLittleBuddy(continent, obj, proxy, proxy.owner)
- context.system.scheduler.scheduleOnce(500.milliseconds) { explosionFunc() }
- }
-
/*
used by ChangeFireStateMessage_Start handling
*/
@@ -1095,19 +334,15 @@ class WeaponAndProjectileLogic(val ops: WeaponAndProjectileOperations, implicit
)
}
- private def allowFireStateChangeStart(tool: Tool, itemGuid: PlanetSideGUID): Boolean = {
- tool.FireMode.RoundsPerShot == 0 || tool.Magazine > 0 || ops.prefire.contains(itemGuid)
- }
-
private def enforceEmptyMagazine(tool: Tool, itemGuid: PlanetSideGUID): Unit = {
log.warn(
s"ChangeFireState_Start: ${player.Name}'s ${tool.Definition.Name} magazine was empty before trying to shoot"
)
- ops.EmptyMagazine(itemGuid, tool)
+ ops.emptyMagazine(itemGuid, tool)
}
private def fireStateStartWhenPlayer(tool: Tool, itemGuid: PlanetSideGUID): Unit = {
- if (allowFireStateChangeStart(tool, itemGuid)) {
+ if (ops.allowFireStateChangeStart(tool, itemGuid)) {
fireStateStartSetup(itemGuid)
//special case - suppress the decimator's alternate fire mode, by projectile
if (tool.Projectile != GlobalDefinitions.phoenix_missile_guided_projectile) {
@@ -1120,7 +355,7 @@ class WeaponAndProjectileLogic(val ops: WeaponAndProjectileOperations, implicit
}
private def fireStateStartWhenMounted(tool: Tool, itemGuid: PlanetSideGUID): Unit = {
- if (allowFireStateChangeStart(tool, itemGuid)) {
+ if (ops.allowFireStateChangeStart(tool, itemGuid)) {
fireStateStartSetup(itemGuid)
fireStateStartMountedMessages(itemGuid)
fireStateStartChargeMode(tool)
@@ -1178,70 +413,19 @@ class WeaponAndProjectileLogic(val ops: WeaponAndProjectileOperations, implicit
)
}
- private def handleReloadProcedure(
- itemGuid: PlanetSideGUID,
- obj: PlanetSideGameObject with Container,
- tools: Set[Tool],
- unk1: Int,
- deleteFunc: Equipment => Future[Any],
- modifyFunc: (AmmoBox, Int) => Unit,
- messageFunc: PlanetSideGUID => Unit
- ): Unit = {
- tools
- .filter { _.GUID == itemGuid }
- .foreach { tool =>
- val currentMagazine : Int = tool.Magazine
- val magazineSize : Int = tool.MaxMagazine
- val reloadValue : Int = magazineSize - currentMagazine
- if (magazineSize > 0 && reloadValue > 0) {
- FindEquipmentStock(obj, FindAmmoBoxThatUses(tool.AmmoType), reloadValue, CountAmmunition).reverse match {
- case Nil => ()
- case x :: xs =>
- xs.foreach { item => deleteFunc(item.obj) }
- val box = x.obj.asInstanceOf[AmmoBox]
- val tailReloadValue : Int = if (xs.isEmpty) {
- 0
- }
- else {
- xs.map(_.obj.asInstanceOf[AmmoBox].Capacity).sum
- }
- val sumReloadValue : Int = box.Capacity + tailReloadValue
- val actualReloadValue = if (sumReloadValue <= reloadValue) {
- deleteFunc(box)
- sumReloadValue
- }
- else {
- modifyFunc(box, reloadValue - tailReloadValue)
- reloadValue
- }
- val finalReloadValue = actualReloadValue + currentMagazine
- log.info(
- s"${player.Name} successfully reloaded $reloadValue ${tool.AmmoType} into ${tool.Definition.Name}"
- )
- tool.Magazine = finalReloadValue
- sendResponse(ReloadMessage(itemGuid, finalReloadValue, unk1))
- messageFunc(itemGuid)
- }
- } else {
- //the weapon can not reload due to full magazine; the UI for the magazine is obvious bugged, so fix it
- sendResponse(QuantityUpdateMessage(tool.AmmoSlot.Box.GUID, magazineSize))
- }
- }
- }
-
private def handleReloadWhenPlayer(
itemGuid: PlanetSideGUID,
obj: Player,
tools: Set[Tool],
unk1: Int
): Unit = {
- handleReloadProcedure(
+ ops.handleReloadProcedure(
itemGuid,
obj,
tools,
unk1,
RemoveOldEquipmentFromInventory(obj)(_),
- ModifyAmmunition(obj)(_, _),
+ ops.modifyAmmunition(obj)(_, _),
reloadPlayerMessages
)
}
@@ -1252,128 +436,14 @@ class WeaponAndProjectileLogic(val ops: WeaponAndProjectileOperations, implicit
tools: Set[Tool],
unk1: Int
): Unit = {
- handleReloadProcedure(
+ ops.handleReloadProcedure(
itemGuid,
obj,
tools,
unk1,
RemoveOldEquipmentFromInventory(obj)(_),
- ModifyAmmunitionInMountable(obj)(_, _),
+ ops.modifyAmmunitionInMountable(obj)(_, _),
reloadVehicleMessages
)
}
-
- //noinspection SameParameterValue
- private def addShotsLanded(weaponId: Int, shots: Int): Unit = {
- ops.addShotsToMap(ops.shotsLanded, weaponId, shots)
- }
-
- private def CompileAutomatedTurretDamageData(
- turret: AutomatedTurret,
- projectileTypeId: Long
- ): Option[(AutomatedTurret, Tool, SourceEntry, ProjectileDefinition)] = {
- turret match {
- case tOwner: OwnableByPlayer =>
- CompileAutomatedTurretDamageData(
- turret,
- CompileAutomatedTurretOwnableBlame(tOwner),
- projectileTypeId
- )
- case tAmenity: Amenity =>
- CompileAutomatedTurretDamageData(
- turret,
- CompileAutomatedTurretAmenityBlame(tAmenity),
- projectileTypeId
- )
- case _ =>
- None
- }
- }
-
- private def CompileAutomatedTurretOwnableBlame(turret: AutomatedTurret with OwnableByPlayer): SourceEntry = {
- Deployables.AssignBlameTo(continent, turret.OwnerName, SourceEntry(turret))
- }
-
- private def CompileAutomatedTurretAmenityBlame(turret: AutomatedTurret with Amenity): SourceEntry = {
- turret
- .Seats
- .values
- .flatMap(_.occupant)
- .collectFirst(SourceEntry(_))
- .getOrElse(SourceEntry(turret.Owner))
- }
-
- private def CompileAutomatedTurretDamageData(
- turret: AutomatedTurret,
- blame: SourceEntry,
- projectileTypeId: Long
- ): Option[(AutomatedTurret, Tool, SourceEntry, ProjectileDefinition)] = {
- turret.Weapons
- .values
- .flatMap { _.Equipment }
- .collect {
- case weapon: Tool => (turret, weapon, blame, weapon.Projectile)
- }
- .find { case (_, _, _, p) => p.ObjectId == projectileTypeId }
- }
-
- private def HandleAIDamage(
- target: PlanetSideServerObject with FactionAffinity with Vitality,
- results: Option[(AutomatedTurret, Tool, SourceEntry, ProjectileDefinition)]
- ): Unit = {
- results.collect {
- case (obj, tool, owner, projectileInfo) =>
- val angle = Vector3.Unit(target.Position - obj.Position)
- val proj = new Projectile(
- projectileInfo,
- tool.Definition,
- tool.FireMode,
- None,
- owner,
- obj.Definition.ObjectId,
- obj.Position + Vector3.z(value = 1f),
- angle,
- Some(angle * projectileInfo.FinalVelocity)
- )
- val hitPos = target.Position + Vector3.z(value = 1f)
- ResolveProjectileInteraction(proj, DamageResolution.Hit, target, hitPos).collect { resprojectile =>
- addShotsLanded(resprojectile.cause.attribution, shots = 1)
- sessionLogic.handleDealingDamage(target, resprojectile)
- }
- }
- }
-
- private def UpdateProjectileSidednessAfterHit(projectile: Projectile, hitPosition: Vector3): Unit = {
- val origin = projectile.Position
- val distance = Vector3.Magnitude(hitPosition - origin)
- continent.blockMap
- .sector(hitPosition, distance)
- .environmentList
- .collect { case o: InteriorDoorPassage =>
- val door = o.door
- val intersectTest = WeaponAndProjectileLogic.quickLineSphereIntersectionPoints(
- origin,
- hitPosition,
- door.Position,
- door.Definition.UseRadius + 0.1f
- )
- (door, intersectTest)
- }
- .collect { case (door, intersectionTest) if intersectionTest.nonEmpty =>
- (door, Vector3.Magnitude(hitPosition - door.Position), intersectionTest)
- }
- .minByOption { case (_, dist, _) => dist }
- .foreach { case (door, _, intersects) =>
- val strictly = if (Vector3.DotProduct(Vector3.Unit(hitPosition - door.Position), door.Outwards) > 0f) {
- Sidedness.OutsideOf
- } else {
- Sidedness.InsideOf
- }
- projectile.WhichSide = if (intersects.size == 1) {
- Sidedness.InBetweenSides(door, strictly)
- } else {
- strictly
- }
- }
- }
}
diff --git a/src/main/scala/net/psforever/actors/session/spectator/AvatarHandlerLogic.scala b/src/main/scala/net/psforever/actors/session/spectator/AvatarHandlerLogic.scala
index e6ef12b6d..67a569c23 100644
--- a/src/main/scala/net/psforever/actors/session/spectator/AvatarHandlerLogic.scala
+++ b/src/main/scala/net/psforever/actors/session/spectator/AvatarHandlerLogic.scala
@@ -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)
diff --git a/src/main/scala/net/psforever/actors/session/spectator/ChatLogic.scala b/src/main/scala/net/psforever/actors/session/spectator/ChatLogic.scala
index a7e5884e2..4f5fbe3d3 100644
--- a/src/main/scala/net/psforever/actors/session/spectator/ChatLogic.scala
+++ b/src/main/scala/net/psforever/actors/session/spectator/ChatLogic.scala
@@ -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)
diff --git a/src/main/scala/net/psforever/actors/session/spectator/GalaxyHandlerLogic.scala b/src/main/scala/net/psforever/actors/session/spectator/GalaxyHandlerLogic.scala
deleted file mode 100644
index d3b6abf63..000000000
--- a/src/main/scala/net/psforever/actors/session/spectator/GalaxyHandlerLogic.scala
+++ /dev/null
@@ -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 _ => ()
- }
- }
-}
diff --git a/src/main/scala/net/psforever/actors/session/spectator/GeneralLogic.scala b/src/main/scala/net/psforever/actors/session/spectator/GeneralLogic.scala
index 806747a5a..c522b2f3c 100644
--- a/src/main/scala/net/psforever/actors/session/spectator/GeneralLogic.scala
+++ b/src/main/scala/net/psforever/actors/session/spectator/GeneralLogic.scala
@@ -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
diff --git a/src/main/scala/net/psforever/actors/session/spectator/LocalHandlerLogic.scala b/src/main/scala/net/psforever/actors/session/spectator/LocalHandlerLogic.scala
deleted file mode 100644
index f28ab0f38..000000000
--- a/src/main/scala/net/psforever/actors/session/spectator/LocalHandlerLogic.scala
+++ /dev/null
@@ -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))
- }
-}
diff --git a/src/main/scala/net/psforever/actors/session/spectator/MountHandlerLogic.scala b/src/main/scala/net/psforever/actors/session/spectator/MountHandlerLogic.scala
index 2cf441e0f..42214bbd4 100644
--- a/src/main/scala/net/psforever/actors/session/spectator/MountHandlerLogic.scala
+++ b/src/main/scala/net/psforever/actors/session/spectator/MountHandlerLogic.scala
@@ -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)
- )
- }
}
diff --git a/src/main/scala/net/psforever/actors/session/spectator/SpectatorMode.scala b/src/main/scala/net/psforever/actors/session/spectator/SpectatorMode.scala
index b950274db..89c0321e4 100644
--- a/src/main/scala/net/psforever/actors/session/spectator/SpectatorMode.scala
+++ b/src/main/scala/net/psforever/actors/session/spectator/SpectatorMode.scala
@@ -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
}
diff --git a/src/main/scala/net/psforever/actors/session/spectator/VehicleHandlerLogic.scala b/src/main/scala/net/psforever/actors/session/spectator/VehicleHandlerLogic.scala
index ee66f69e9..95bfdf352 100644
--- a/src/main/scala/net/psforever/actors/session/spectator/VehicleHandlerLogic.scala
+++ b/src/main/scala/net/psforever/actors/session/spectator/VehicleHandlerLogic.scala
@@ -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 {
diff --git a/src/main/scala/net/psforever/actors/session/spectator/VehicleLogic.scala b/src/main/scala/net/psforever/actors/session/spectator/VehicleLogic.scala
index 7ba9cca0b..c61499dd8 100644
--- a/src/main/scala/net/psforever/actors/session/spectator/VehicleLogic.scala
+++ b/src/main/scala/net/psforever/actors/session/spectator/VehicleLogic.scala
@@ -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 */
diff --git a/src/main/scala/net/psforever/actors/session/spectator/WeaponAndProjectileLogic.scala b/src/main/scala/net/psforever/actors/session/spectator/WeaponAndProjectileLogic.scala
index 1ebcc1fdd..c6992abb2 100644
--- a/src/main/scala/net/psforever/actors/session/spectator/WeaponAndProjectileLogic.scala
+++ b/src/main/scala/net/psforever/actors/session/spectator/WeaponAndProjectileLogic.scala
@@ -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,
diff --git a/src/main/scala/net/psforever/actors/session/support/ChatOperations.scala b/src/main/scala/net/psforever/actors/session/support/ChatOperations.scala
index 2536eac69..381b80f84 100644
--- a/src/main/scala/net/psforever/actors/session/support/ChatOperations.scala
+++ b/src/main/scala/net/psforever/actors/session/support/ChatOperations.scala
@@ -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],
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 67bf09d4e..24e68571a 100644
--- a/src/main/scala/net/psforever/actors/session/support/GeneralOperations.scala
+++ b/src/main/scala/net/psforever/actors/session/support/GeneralOperations.scala
@@ -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
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 7fc6a893b..f09a432bc 100644
--- a/src/main/scala/net/psforever/actors/session/support/SessionLocalHandlers.scala
+++ b/src/main/scala/net/psforever/actors/session/support/SessionLocalHandlers.scala
@@ -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))
+ }
}
diff --git a/src/main/scala/net/psforever/actors/session/support/SessionMountHandlers.scala b/src/main/scala/net/psforever/actors/session/support/SessionMountHandlers.scala
index c124bd182..bc9f30b12 100644
--- a/src/main/scala/net/psforever/actors/session/support/SessionMountHandlers.scala
+++ b/src/main/scala/net/psforever/actors/session/support/SessionMountHandlers.scala
@@ -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)
diff --git a/src/main/scala/net/psforever/actors/session/support/SessionSquadHandlers.scala b/src/main/scala/net/psforever/actors/session/support/SessionSquadHandlers.scala
index 1ccb6bf3f..1aaea60c8 100644
--- a/src/main/scala/net/psforever/actors/session/support/SessionSquadHandlers.scala
+++ b/src/main/scala/net/psforever/actors/session/support/SessionSquadHandlers.scala
@@ -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
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 347139e6a..bb8770a48 100644
--- a/src/main/scala/net/psforever/actors/session/support/SessionTerminalHandlers.scala
+++ b/src/main/scala/net/psforever/actors/session/support/SessionTerminalHandlers.scala
@@ -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
diff --git a/src/main/scala/net/psforever/actors/session/support/VehicleOperations.scala b/src/main/scala/net/psforever/actors/session/support/VehicleOperations.scala
index f23c61d38..a9f4dc2e8 100644
--- a/src/main/scala/net/psforever/actors/session/support/VehicleOperations.scala
+++ b/src/main/scala/net/psforever/actors/session/support/VehicleOperations.scala
@@ -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`.
diff --git a/src/main/scala/net/psforever/actors/session/support/WeaponAndProjectileOperations.scala b/src/main/scala/net/psforever/actors/session/support/WeaponAndProjectileOperations.scala
index 55753dff9..e5b1db25f 100644
--- a/src/main/scala/net/psforever/actors/session/support/WeaponAndProjectileOperations.scala
+++ b/src/main/scala/net/psforever/actors/session/support/WeaponAndProjectileOperations.scala
@@ -2,14 +2,34 @@
package net.psforever.actors.session.support
import akka.actor.{ActorContext, typed}
-import net.psforever.objects.definition.SpecialExoSuitDefinition
-import net.psforever.objects.zones.Zoning
+import net.psforever.login.WorldSession.{CountAmmunition, FindAmmoBoxThatUses, FindEquipmentStock, PutEquipmentInInventoryOrDrop, PutNewEquipmentInInventoryOrDrop}
+import net.psforever.objects.ballistics.ProjectileQuality
+import net.psforever.objects.definition.{ProjectileDefinition, SpecialExoSuitDefinition}
+import net.psforever.objects.entity.SimpleWorldEntity
+import net.psforever.objects.equipment.{ChargeFireModeDefinition, Equipment, FireModeSwitch}
+import net.psforever.objects.guid.{GUIDTask, TaskBundle, TaskWorkflow}
+import net.psforever.objects.serverobject.PlanetSideServerObject
+import net.psforever.objects.serverobject.affinity.FactionAffinity
+import net.psforever.objects.serverobject.doors.InteriorDoorPassage
+import net.psforever.objects.serverobject.interior.Sidedness
+import net.psforever.objects.serverobject.structures.Amenity
+import net.psforever.objects.zones.{Zone, ZoneProjectile, Zoning}
import net.psforever.objects.serverobject.turret.VanuSentry
+import net.psforever.objects.serverobject.turret.auto.{AutomatedTurret, AutomatedTurretBehavior}
+import net.psforever.objects.sourcing.{PlayerSource, SourceEntry}
+import net.psforever.objects.vital.Vitality
+import net.psforever.objects.vital.base.DamageResolution
+import net.psforever.objects.vital.etc.OicwLilBuddyReason
+import net.psforever.objects.vital.interaction.DamageInteraction
+import net.psforever.objects.vital.projectile.ProjectileReason
import net.psforever.objects.zones.exp.ToDatabase
-import net.psforever.types.ChatMessageType
+import net.psforever.types.{ChatMessageType, Vector3}
+import net.psforever.util.Config
import scala.collection.mutable
+import scala.concurrent.Future
import scala.concurrent.duration._
+import scala.concurrent.ExecutionContext.Implicits.global
//
import net.psforever.actors.session.AvatarActor
import net.psforever.objects.avatar.scoring.EquipmentStat
@@ -78,7 +98,114 @@ class WeaponAndProjectileOperations(
Array.fill[Option[Projectile]](Projectile.rangeUID - Projectile.baseUID)(None)
}
- def HandleWeaponFireAccountability(
+ def handleWeaponFireOperations(pkt: WeaponFireMessage): Unit = {
+ val WeaponFireMessage(
+ _,
+ weaponGUID,
+ projectileGUID,
+ shotOrigin,
+ _,
+ _,
+ _,
+ _/*max_distance,*/,
+ _,
+ _/*projectile_type,*/,
+ thrown_projectile_vel
+ ) = pkt
+ val shotVelocity = thrown_projectile_vel.flatten
+ handleWeaponFireAccountability(weaponGUID, projectileGUID) match {
+ case (Some(obj), Some(tool)) =>
+ val projectileIndex = projectileGUID.guid - Projectile.baseUID
+ val projectilePlace = projectiles(projectileIndex)
+ if (
+ projectilePlace match {
+ case Some(projectile) =>
+ !projectile.isResolved && System.currentTimeMillis() - projectile.fire_time < projectile.profile.Lifespan.toLong
+ case None =>
+ false
+ }
+ ) {
+ log.debug(
+ s"WeaponFireMessage: overwriting unresolved projectile ${projectileGUID.guid}, known to ${player.Name}"
+ )
+ }
+ val (angle, attribution, acceptableDistanceToOwner) = obj match {
+ case p: Player =>
+ (
+ SimpleWorldEntity.validateOrientationEntry(
+ p.Orientation + Vector3.z(p.FacingYawUpper)
+ ),
+ tool.Definition.ObjectId,
+ 10f + (if (p.Velocity.nonEmpty) {
+ 5f
+ } else {
+ 0f
+ })
+ )
+ case v: Vehicle if v.Definition.CanFly =>
+ (tool.Orientation, obj.Definition.ObjectId, 1000f) //TODO this is too simplistic to find proper angle
+ case _: Vehicle =>
+ (tool.Orientation, obj.Definition.ObjectId, 225f) //TODO this is too simplistic to find proper angle
+ case _ =>
+ (obj.Orientation, obj.Definition.ObjectId, 300f)
+ }
+ val distanceToOwner = Vector3.DistanceSquared(shotOrigin, player.Position)
+ if (distanceToOwner <= acceptableDistanceToOwner) {
+ val projectile_info = tool.Projectile
+ val wguid = weaponGUID.guid
+ val mountedIn = (continent.turretToWeapon
+ .find { case (guid, _) => guid == wguid } match {
+ case Some((_, turretGuid)) => Some((
+ turretGuid,
+ continent.GUID(turretGuid).collect { case o: PlanetSideGameObject with FactionAffinity => SourceEntry(o) }
+ ))
+ case _ => None
+ }) match {
+ case Some((guid, Some(entity))) => Some((guid, entity))
+ case _ => None
+ }
+ val projectile = new Projectile(
+ projectile_info,
+ tool.Definition,
+ tool.FireMode,
+ mountedIn,
+ PlayerSource(player),
+ attribution,
+ shotOrigin,
+ angle,
+ shotVelocity
+ )
+ val initialQuality = tool.FireMode match {
+ case mode: ChargeFireModeDefinition =>
+ ProjectileQuality.Modified(
+ {
+ val timeInterval = projectile.fire_time - shootingStart.getOrElse(tool.GUID, System.currentTimeMillis())
+ timeInterval.toFloat / mode.Time.toFloat
+ }
+ )
+ case _ =>
+ ProjectileQuality.Normal
+ }
+ val qualityprojectile = projectile.quality(initialQuality)
+ qualityprojectile.WhichSide = player.WhichSide
+ projectiles(projectileIndex) = Some(qualityprojectile)
+ if (projectile_info.ExistsOnRemoteClients) {
+ log.trace(
+ s"WeaponFireMessage: ${player.Name}'s ${projectile_info.Name} is a remote projectile"
+ )
+ continent.Projectile ! ZoneProjectile.Add(player.GUID, qualityprojectile)
+ }
+ } else {
+ log.warn(
+ s"WeaponFireMessage: ${player.Name}'s ${tool.Definition.Name} projectile is too far from owner position at time of discharge ($distanceToOwner > $acceptableDistanceToOwner); suspect"
+ )
+ }
+
+ case _ => ()
+ }
+ }
+
+ def handleWeaponFireAccountability(
weaponGUID: PlanetSideGUID,
projectileGUID: PlanetSideGUID
): (Option[PlanetSideGameObject with Container], Option[Tool]) = {
@@ -105,7 +232,7 @@ class WeaponAndProjectileOperations(
case tool: Tool if tool.GUID == weaponGUID =>
if (tool.Magazine <= 0) { //safety: enforce ammunition depletion
prefire -= weaponGUID
- EmptyMagazine(weaponGUID, tool)
+ emptyMagazine(weaponGUID, tool)
projectiles(projectileGUID.guid - Projectile.baseUID) = None
(None, None)
} else if (!player.isAlive) { //proper internal accounting, but no projectile
@@ -137,6 +264,471 @@ class WeaponAndProjectileOperations(
}
}
+ def handleWeaponDryFire(pkt: WeaponDryFireMessage): Unit = {
+ val WeaponDryFireMessage(weapon_guid) = pkt
+ val (containerOpt, tools) = FindContainedWeapon
+ tools
+ .find { _.GUID == weapon_guid }
+ .orElse { continent.GUID(weapon_guid) }
+ .collect {
+ case _: Equipment if containerOpt.exists(_.isInstanceOf[Player]) =>
+ continent.AvatarEvents ! AvatarServiceMessage(
+ continent.id,
+ AvatarAction.WeaponDryFire(player.GUID, weapon_guid)
+ )
+ case _: Equipment =>
+ continent.VehicleEvents ! VehicleServiceMessage(
+ continent.id,
+ VehicleAction.WeaponDryFire(player.GUID, weapon_guid)
+ )
+ }
+ .orElse {
+ log.warn(
+ s"WeaponDryFire: ${player.Name}'s weapon ${weapon_guid.guid} is either not a weapon or does not exist"
+ )
+ None
+ }
+ }
+
+ def handleChangeAmmo(pkt: ChangeAmmoMessage): Unit = {
+ val ChangeAmmoMessage(item_guid, _) = pkt
+ val (thing, equipment) = sessionLogic.findContainedEquipment()
+ if (equipment.isEmpty) {
+ log.warn(s"ChangeAmmo: either can not find $item_guid or the object found was not Equipment")
+ } else {
+ equipment foreach {
+ case obj: ConstructionItem =>
+ if (Deployables.performConstructionItemAmmoChange(player.avatar.certifications, obj, obj.AmmoTypeIndex)) {
+ log.info(
+ s"${player.Name} switched ${player.Sex.possessive} ${obj.Definition.Name} to construct ${obj.AmmoType} (option #${obj.FireModeIndex})"
+ )
+ sendResponse(ChangeAmmoMessage(obj.GUID, obj.AmmoTypeIndex))
+ }
+ case tool: Tool =>
+ thing match {
+ case Some(player: Player) =>
+ performToolAmmoChange(tool, player, modifyAmmunition(player))
+ case Some(mountable: PlanetSideServerObject with Container) =>
+ performToolAmmoChange(tool, mountable, modifyAmmunitionInMountable(mountable))
+ case _ =>
+ log.warn(s"ChangeAmmo: the ${thing.get.Definition.Name} in ${player.Name}'s is not the correct type")
+ }
+ case obj =>
+ log.warn(s"ChangeAmmo: the ${obj.Definition.Name} in ${player.Name}'s hands does not contain ammunition")
+ }
+ }
+ }
+
+ def handleChangeFireMode(pkt: ChangeFireModeMessage): Unit = {
+ val ChangeFireModeMessage(item_guid, _/*fire_mode*/) = pkt
+ sessionLogic.findEquipment(item_guid) match {
+ case Some(obj: PlanetSideGameObject with FireModeSwitch[_]) =>
+ val originalModeIndex = obj.FireModeIndex
+ if (obj match {
+ case citem: ConstructionItem =>
+ val modeChanged = Deployables.performConstructionItemFireModeChange(
+ player.avatar.certifications,
+ citem,
+ originalModeIndex
+ )
+ modeChanged
+ case _ =>
+ obj.NextFireMode
+ obj.FireModeIndex != originalModeIndex
+ }) {
+ val modeIndex = obj.FireModeIndex
+ obj match {
+ case citem: ConstructionItem =>
+ log.info(s"${player.Name} switched ${player.Sex.possessive} ${obj.Definition.Name} to construct ${citem.AmmoType} (mode #$modeIndex)")
+ case _ =>
+ log.info(s"${player.Name} changed ${player.Sex.possessive} her ${obj.Definition.Name}'s fire mode to #$modeIndex")
+ }
+ sendResponse(ChangeFireModeMessage(item_guid, modeIndex))
+ continent.AvatarEvents ! AvatarServiceMessage(
+ continent.id,
+ AvatarAction.ChangeFireMode(player.GUID, item_guid, modeIndex)
+ )
+ }
+ case Some(_) =>
+ log.warn(s"ChangeFireMode: the object that was found for $item_guid does not possess fire modes")
+ case None =>
+ log.warn(s"ChangeFireMode: can not find $item_guid")
+ }
+ }
+
+ 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
+ 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")
+ }
+ }
+
+ def composeDirectDamageInformation(pkt: HitMessage): List[(PlanetSideGameObject with FactionAffinity with Vitality, Projectile, Vector3, Vector3)] = {
+ val HitMessage(
+ _,
+ projectile_guid,
+ _,
+ hit_info,
+ _,
+ _,
+ _
+ ) = pkt
+ //find defined projectile
+ FindProjectileEntry(projectile_guid)
+ .collect {
+ case projectile =>
+ hit_info match {
+ case Some(hitInfo) =>
+ val hitPos = hitInfo.hit_pos
+ sessionLogic.validObject(hitInfo.hitobject_guid, decorator = "Hit/hitInfo") match {
+ case _ if projectile.profile == GlobalDefinitions.flail_projectile =>
+ val radius = projectile.profile.DamageRadius * projectile.profile.DamageRadius
+ Zone
+ .findAllTargets(continent, player, hitPos, projectile.profile)
+ .filter(target => Vector3.DistanceSquared(target.Position, hitPos) <= radius)
+ .map(target => (target, projectile, hitPos, target.Position))
+
+ case Some(target: PlanetSideGameObject with FactionAffinity with Vitality) =>
+ List((target, projectile, hitInfo.shot_origin, hitPos))
+
+ case None =>
+ Nil
+
+ case _ =>
+ Nil
+ }
+ case None =>
+ Nil
+ }
+ }
+ .getOrElse {
+ log.warn(s"ResolveProjectile: expected projectile, but ${projectile_guid.guid} not found")
+ Nil
+ }
+ }
+
+ def composeSplashDamageInformation(pkt: SplashHitMessage): List[(PlanetSideGameObject with FactionAffinity with Vitality, Projectile, Vector3, Vector3)] = {
+ val SplashHitMessage(
+ _,
+ projectile_guid,
+ explosion_pos,
+ direct_victim_uid,
+ _,
+ projectile_vel,
+ _,
+ targets
+ ) = pkt
+ FindProjectileEntry(projectile_guid)
+ .collect {
+ case projectile =>
+ projectile.Position = explosion_pos
+ projectile.Velocity = projectile_vel
+ //direct_victim_uid
+ val direct = sessionLogic
+ .validObject(direct_victim_uid, decorator = "SplashHit/direct_victim")
+ .collect {
+ case target: PlanetSideGameObject with FactionAffinity with Vitality =>
+ val targetPosition = target.Position
+ List((target, projectile, targetPosition, targetPosition))
+ }
+ .getOrElse(Nil)
+ //other victims
+ val others = targets
+ .flatMap(elem => sessionLogic.validObject(elem.uid, decorator = "SplashHit/other_victims"))
+ .collect {
+ case target: PlanetSideGameObject with FactionAffinity with Vitality =>
+ (target, projectile, explosion_pos, target.Position)
+ }
+ direct ++ others
+ }
+ .getOrElse {
+ log.warn(s"ResolveProjectile: expected projectile, but ${projectile_guid.guid} not found")
+ Nil
+ }
+ }
+
+ def composeLashDamageInformation(pkt: LashMessage): List[(PlanetSideGameObject with FactionAffinity with Vitality, Projectile, Vector3, Vector3)] = {
+ val LashMessage(_, _, victim_guid, projectile_guid, hit_pos, _) = pkt
+ FindProjectileEntry(projectile_guid)
+ .flatMap {
+ projectile =>
+ sessionLogic
+ .validObject(victim_guid, decorator = "LashHit/victim_guid")
+ .collect {
+ case target: PlanetSideGameObject with FactionAffinity with Vitality =>
+ List((target, projectile, hit_pos, target.Position))
+ }
+ .orElse(None)
+ }
+ .getOrElse(Nil)
+ }
+
+ def composeAIDamageInformation(pkt: AIDamage): List[(PlanetSideGameObject with FactionAffinity with Vitality, Projectile, Vector3, Vector3)] = {
+ val AIDamage(targetGuid, attackerGuid, projectileTypeId, _, _) = pkt
+ (continent.GUID(player.VehicleSeated) match {
+ case Some(tobj: PlanetSideServerObject with FactionAffinity with Vitality with OwnableByPlayer)
+ if tobj.GUID == targetGuid &&
+ tobj.OwnerGuid.contains(player.GUID) =>
+ //deployable turrets
+ Some(tobj)
+ case Some(tobj: PlanetSideServerObject with FactionAffinity with Vitality with Mountable)
+ if tobj.GUID == targetGuid &&
+ tobj.Seats.values.flatMap(_.occupants.map(_.GUID)).toSeq.contains(player.GUID) =>
+ //facility turrets, etc.
+ Some(tobj)
+ case _
+ if player.GUID == targetGuid =>
+ //player avatars
+ Some(player)
+ case _ =>
+ None
+ }).collect {
+ case target: AutomatedTurret.Target =>
+ sessionLogic.validObject(attackerGuid, decorator = "AIDamage/AutomatedTurret")
+ .collect {
+ case turret: AutomatedTurret if turret.Target.isEmpty =>
+ Nil
+ case turret: AutomatedTurret =>
+ prepareAIProjectile(target, CompileAutomatedTurretDamageData(turret, projectileTypeId))
+ }
+ .getOrElse(Nil)
+ }
+ .orElse {
+ //occasionally, something that is not technically a turret's natural target may be attacked
+ continent.GUID(targetGuid) //AIDamage/Attacker
+ .collect {
+ case target: PlanetSideServerObject with FactionAffinity with Vitality =>
+ sessionLogic.validObject(attackerGuid, decorator = "AIDamage/Attacker")
+ .collect {
+ case turret: AutomatedTurret if turret.Target.nonEmpty =>
+ //the turret must be shooting at something (else) first
+ prepareAIProjectile(target, CompileAutomatedTurretDamageData(turret, projectileTypeId))
+ }
+ }
+ .flatten
+ }
+ .getOrElse(Nil)
+ }
+
+ def confirmAIDamageTarget(
+ pkt: AIDamage,
+ list: List[PlanetSideGameObject with FactionAffinity with Vitality]
+ ): Boolean = {
+ val AIDamage(_, attackerGuid, _, _, _) = pkt
+ sessionLogic
+ .validObject(attackerGuid, decorator = "AIDamage/AutomatedTurret")
+ .collect {
+ case turret: AutomatedTurret =>
+ list.collect {
+ case target: AutomatedTurret.Target =>
+ turret.Actor ! AutomatedTurretBehavior.ConfirmShot(target)
+ }
+ turret.Target.isEmpty
+ }
+ .getOrElse(false)
+ }
+
+ /**
+ * Take a projectile that was introduced into the game world and
+ * determine if it generates a secondary damage projectile or
+ * an method of damage causation that requires additional management.
+ * @param projectile the projectile
+ * @param pguid the client-local projectile identifier
+ * @param hitPos the game world position where the projectile is being recorded
+ * @return a for all affected targets, a combination of projectiles, projectile location, and the target's location;
+ * nothing if no targets were affected
+ */
+ def resolveDamageProxy(
+ projectile: Projectile,
+ pguid: PlanetSideGUID,
+ hitPos: Vector3
+ ): List[(PlanetSideGameObject with FactionAffinity with Vitality, Projectile, Vector3, Vector3)] = {
+ GlobalDefinitions.getDamageProxy(projectile, hitPos) match {
+ case Nil =>
+ Nil
+ case list if list.isEmpty =>
+ Nil
+ case list =>
+ setupDamageProxyLittleBuddy(list, hitPos)
+ WeaponAndProjectileOperations.updateProjectileSidednessAfterHit(continent, projectile, hitPos)
+ val projectileSide = projectile.WhichSide
+ list.flatMap { proxy =>
+ if (proxy.profile.ExistsOnRemoteClients) {
+ proxy.Position = hitPos
+ proxy.WhichSide = projectileSide
+ continent.Projectile ! ZoneProjectile.Add(player.GUID, proxy)
+ Nil
+ } else if (proxy.tool_def == GlobalDefinitions.maelstrom) {
+ //server-side maelstrom grenade target selection
+ val radius = proxy.profile.LashRadius * proxy.profile.LashRadius
+ val targets = Zone.findAllTargets(continent, hitPos, proxy.profile.LashRadius, { _.livePlayerList })
+ .filter { target =>
+ Vector3.DistanceSquared(target.Position, hitPos) <= radius
+ }
+ //chainlash is separated from the actual damage application for convenience
+ continent.AvatarEvents ! AvatarServiceMessage(
+ continent.id,
+ AvatarAction.SendResponse(
+ PlanetSideGUID(0),
+ ChainLashMessage(
+ hitPos,
+ projectile.profile.ObjectId,
+ targets.map { _.GUID }
+ )
+ )
+ )
+ targets.map { target =>
+ (target, proxy, hitPos, target.Position)
+ }
+ } else {
+ Nil
+ }
+ }
+ }
+ }
+
+ private def setupDamageProxyLittleBuddy(listOfProjectiles: List[Projectile], detonationPosition: Vector3): Boolean = {
+ val listOfLittleBuddies: List[Projectile] = listOfProjectiles.filter { _.tool_def == GlobalDefinitions.oicw }
+ val size: Int = listOfLittleBuddies.size
+ if (size > 0) {
+ val desiredDownwardsProjectiles: Int = 2
+ val firstHalf: Int = math.min(size, desiredDownwardsProjectiles) //number that fly straight down
+ val secondHalf: Int = math.max(size - firstHalf, 0) //number that are flared out
+ val z: Float = player.Orientation.z //player's standing direction
+ val north: Vector3 = Vector3(0,1,0) //map North
+ val speed: Float = 144f //speed (packet discovered)
+ val dist: Float = 25 //distance (client defined)
+ val downwardsAngle: Float = -85f
+ val flaredAngle: Float = -70f
+ //angle of separation for downwards, degrees from vertical for flared out
+ val (smallStep, smallAngle): (Float, Float) = if (firstHalf > 1) {
+ (360f / firstHalf, downwardsAngle)
+ } else {
+ (0f, 0f)
+ }
+ val (largeStep, largeAngle): (Float, Float) = if (secondHalf > 1) {
+ (360f / secondHalf, flaredAngle)
+ } else {
+ (0f, 0f)
+ }
+ val smallRotOffset: Float = z + 90f
+ val largeRotOffset: Float = z + math.random().toFloat * 45f
+ val verticalCorrection = Vector3.z(dist - dist * math.sin(math.toRadians(90 - smallAngle + largeAngle)).toFloat)
+ //downwards projectiles
+ var i: Int = 0
+ listOfLittleBuddies.take(firstHalf).foreach { proxy =>
+ val facing = (smallRotOffset + smallStep * i.toFloat) % 360
+ val dir = north.Rx(smallAngle).Rz(facing)
+ proxy.Position = detonationPosition + dir.xy + verticalCorrection
+ proxy.Velocity = dir * speed
+ proxy.Orientation = Vector3(0, (360f + smallAngle) % 360, facing)
+ i += 1
+ }
+ //flared out projectiles
+ i = 0
+ listOfLittleBuddies.drop(firstHalf).foreach { proxy =>
+ val facing = (largeRotOffset + largeStep * i.toFloat) % 360
+ val dir = north.Rx(largeAngle).Rz(facing)
+ proxy.Position = detonationPosition + dir
+ proxy.Velocity = dir * speed
+ proxy.Orientation = Vector3(0, (360f + largeAngle) % 360, facing)
+ i += 1
+ }
+ true
+ } else {
+ false
+ }
+ }
+
+ def performLittleBuddyExplosion(listOfProjectiles: List[Projectile]): Boolean = {
+ val listOfLittleBuddies: List[Projectile] = listOfProjectiles.filter { _.tool_def == GlobalDefinitions.oicw }
+ val size: Int = listOfLittleBuddies.size
+ if (size > 0) {
+ val desiredDownwardsProjectiles: Int = 2
+ val firstHalf: Int = math.min(size, desiredDownwardsProjectiles) //number that fly straight down
+ val speed: Float = 144f //speed (packet discovered)
+ val dist: Float = 25 //distance (client defined)
+ //downwards projectiles
+ var i: Int = 0
+ listOfLittleBuddies.take(firstHalf).foreach { proxy =>
+ val dir = proxy.Velocity.map(_ / speed).getOrElse(Vector3.Zero)
+ queueLittleBuddyDamage(proxy, dir, dist)
+ i += 1
+ }
+ //flared out projectiles
+ i = 0
+ listOfLittleBuddies.drop(firstHalf).foreach { proxy =>
+ val dir = proxy.Velocity.map(_ / speed).getOrElse(Vector3.Zero)
+ queueLittleBuddyDamage(proxy, dir, dist)
+ i += 1
+ }
+ true
+ } else {
+ false
+ }
+ }
+
+ private def queueLittleBuddyDamage(proxy: Projectile, orientation: Vector3, distance: Float): Unit = {
+ //explosion
+ val obj = new DummyExplodingEntity(proxy, proxy.owner.Faction)
+ obj.Position = obj.Position + orientation * distance
+ val explosionFunc: ()=>Unit = WeaponAndProjectileOperations.detonateLittleBuddy(continent, obj, proxy, proxy.owner)
+ context.system.scheduler.scheduleOnce(500.milliseconds) { explosionFunc() }
+ }
+
+ /**
+ * Find a projectile with the given globally unique identifier and mark it as a resolved shot.
+ * A `Resolved` shot has either encountered an obstacle or is being cleaned up for not finding an obstacle.
+ * @param projectile projectile
+ * @param resolution resolution status to promote the projectile
+ * @return package that contains information about the damage
+ */
+ def resolveProjectileInteraction(
+ target: PlanetSideGameObject with FactionAffinity with Vitality,
+ projectile: Projectile,
+ resolution: DamageResolution.Value,
+ hitPosition: Vector3
+ ): Option[DamageInteraction] = {
+ if (projectile.isMiss) {
+ None
+ } else {
+ val outProjectile = ProjectileQuality.modifiers(projectile, resolution, target, hitPosition, Some(player))
+ if (projectile.tool_def.Size == EquipmentSize.Melee && outProjectile.quality == ProjectileQuality.Modified(25)) {
+ avatarActor ! AvatarActor.ConsumeStamina(10)
+ }
+ val resolvedProjectile = DamageInteraction(
+ SourceEntry(target),
+ ProjectileReason(resolution, outProjectile, target.DamageModel),
+ hitPosition
+ )
+ addShotsToMap(shotsLanded, resolvedProjectile.cause.attribution, shots = 1)
+ sessionLogic.handleDealingDamage(target, resolvedProjectile)
+ Some(resolvedProjectile)
+ }
+ }
+
def FindEnabledWeaponsToHandleWeaponFireAccountability(
o: Option[PlanetSideGameObject with Container],
tools: Set[Tool]
@@ -182,7 +774,7 @@ class WeaponAndProjectileOperations(
* @param weapon_guid the weapon (GUID)
* @param tool the weapon (object)
*/
- def EmptyMagazine(weapon_guid: PlanetSideGUID, tool: Tool): Unit = {
+ def emptyMagazine(weapon_guid: PlanetSideGUID, tool: Tool): Unit = {
tool.Magazine = 0
sendResponse(InventoryStateMessage(tool.AmmoSlot.Box.GUID, weapon_guid, 0))
sendResponse(ChangeFireStateMessage_Stop(weapon_guid))
@@ -274,22 +866,30 @@ class WeaponAndProjectileOperations(
*/
def FindWeapon: Set[Tool] = FindContainedWeapon._2
+ def allowFireStateChangeStart(tool: Tool, itemGuid: PlanetSideGUID): Boolean = {
+ tool.FireMode.RoundsPerShot == 0 || tool.Magazine > 0 || prefire.contains(itemGuid)
+ }
+
def fireStateStopPlayerMessages(itemGuid: PlanetSideGUID): Unit = {
- continent.AvatarEvents ! AvatarServiceMessage(
- continent.id,
- AvatarAction.ChangeFireState_Stop(player.GUID, itemGuid)
- )
+ if (!player.spectator) {
+ continent.AvatarEvents ! AvatarServiceMessage(
+ continent.id,
+ AvatarAction.ChangeFireState_Stop(player.GUID, itemGuid)
+ )
+ }
}
def fireStateStopMountedMessages(itemGuid: PlanetSideGUID): Unit = {
- sessionLogic.findContainedEquipment()._1.collect {
- case turret: FacilityTurret if continent.map.cavern =>
- turret.Actor ! VanuSentry.ChangeFireStop
+ if (!player.spectator) {
+ sessionLogic.findContainedEquipment()._1.collect {
+ case turret: FacilityTurret if continent.map.cavern =>
+ turret.Actor ! VanuSentry.ChangeFireStop
+ }
+ continent.VehicleEvents ! VehicleServiceMessage(
+ continent.id,
+ VehicleAction.ChangeFireState_Stop(player.GUID, itemGuid)
+ )
}
- continent.VehicleEvents ! VehicleServiceMessage(
- continent.id,
- VehicleAction.ChangeFireState_Stop(player.GUID, itemGuid)
- )
}
private def addShotsFired(weaponId: Int, shots: Int): Unit = {
@@ -326,6 +926,308 @@ class WeaponAndProjectileOperations(
ToDatabase.reportToolDischarge(avatarId, EquipmentStat(weaponId, fired, landed, 0, 0))
}
+ def handleReloadProcedure(
+ itemGuid: PlanetSideGUID,
+ obj: PlanetSideGameObject with Container,
+ tools: Set[Tool],
+ unk1: Int,
+ deleteFunc: Equipment => Future[Any],
+ modifyFunc: (AmmoBox, Int) => Unit,
+ messageFunc: PlanetSideGUID => Unit
+ ): Unit = {
+ tools
+ .filter { _.GUID == itemGuid }
+ .foreach { tool =>
+ val currentMagazine : Int = tool.Magazine
+ val magazineSize : Int = tool.MaxMagazine
+ val reloadValue : Int = magazineSize - currentMagazine
+ if (magazineSize > 0 && reloadValue > 0) {
+ FindEquipmentStock(obj, FindAmmoBoxThatUses(tool.AmmoType), reloadValue, CountAmmunition).reverse match {
+ case Nil => ()
+ case x :: xs =>
+ xs.foreach { item => deleteFunc(item.obj) }
+ val box = x.obj.asInstanceOf[AmmoBox]
+ val tailReloadValue : Int = if (xs.isEmpty) {
+ 0
+ }
+ else {
+ xs.map(_.obj.asInstanceOf[AmmoBox].Capacity).sum
+ }
+ val sumReloadValue : Int = box.Capacity + tailReloadValue
+ val actualReloadValue = if (sumReloadValue <= reloadValue) {
+ deleteFunc(box)
+ sumReloadValue
+ }
+ else {
+ modifyFunc(box, reloadValue - tailReloadValue)
+ reloadValue
+ }
+ val finalReloadValue = actualReloadValue + currentMagazine
+ log.info(
+ s"${player.Name} successfully reloaded $reloadValue ${tool.AmmoType} into ${tool.Definition.Name}"
+ )
+ tool.Magazine = finalReloadValue
+ sendResponse(ReloadMessage(itemGuid, finalReloadValue, unk1))
+ messageFunc(itemGuid)
+ }
+ } else {
+ //the weapon can not reload due to full magazine; the UI for the magazine is obvious bugged, so fix it
+ sendResponse(QuantityUpdateMessage(tool.AmmoSlot.Box.GUID, magazineSize))
+ }
+ }
+ }
+
+ /**
+ * na
+ * @param tool na
+ * @param obj na
+ */
+ private def performToolAmmoChange(
+ tool: Tool,
+ obj: PlanetSideServerObject with Container,
+ modifyFunc: (AmmoBox, Int) => Unit
+ ): Unit = {
+ val originalAmmoType = tool.AmmoType
+ do {
+ val requestedAmmoType = tool.NextAmmoType
+ val fullMagazine = tool.MaxMagazine
+ if (requestedAmmoType != tool.AmmoSlot.Box.AmmoType) {
+ FindEquipmentStock(obj, FindAmmoBoxThatUses(requestedAmmoType), fullMagazine, CountAmmunition).reverse match {
+ case Nil => ()
+ case x :: xs =>
+ val stowNewFunc: Equipment => TaskBundle = PutNewEquipmentInInventoryOrDrop(obj)
+ val stowFunc: Equipment => Future[Any] = PutEquipmentInInventoryOrDrop(obj)
+
+ xs.foreach(item => {
+ obj.Inventory -= item.start
+ sendResponse(ObjectDeleteMessage(item.obj.GUID, 0))
+ TaskWorkflow.execute(GUIDTask.unregisterObject(continent.GUID, item.obj))
+ })
+
+ //box will be the replacement ammo; give it the discovered magazine and load it into the weapon
+ val box = x.obj.asInstanceOf[AmmoBox]
+ //previousBox is the current magazine in tool; it will be removed from the weapon
+ val previousBox = tool.AmmoSlot.Box
+ val originalBoxCapacity = box.Capacity
+ val tailReloadValue: Int = if (xs.isEmpty) {
+ 0
+ } else {
+ xs.map(_.obj.asInstanceOf[AmmoBox].Capacity).sum
+ }
+ val sumReloadValue: Int = originalBoxCapacity + tailReloadValue
+ val ammoSlotIndex = tool.FireMode.AmmoSlotIndex
+ val box_guid = box.GUID
+ val tool_guid = tool.GUID
+ obj.Inventory -= x.start //remove replacement ammo from inventory
+ tool.AmmoSlots(ammoSlotIndex).Box = box //put replacement ammo in tool
+ sendResponse(ObjectDetachMessage(tool_guid, previousBox.GUID, Vector3.Zero, 0f))
+ sendResponse(ObjectDetachMessage(obj.GUID, box_guid, Vector3.Zero, 0f))
+ sendResponse(ObjectAttachMessage(tool_guid, box_guid, ammoSlotIndex))
+
+ //announce swapped ammunition box in weapon
+ val previous_box_guid = previousBox.GUID
+ val boxDef = box.Definition
+ sendResponse(ChangeAmmoMessage(tool_guid, box.Capacity))
+ continent.AvatarEvents ! AvatarServiceMessage(
+ continent.id,
+ AvatarAction.ChangeAmmo(
+ player.GUID,
+ tool_guid,
+ ammoSlotIndex,
+ previous_box_guid,
+ boxDef.ObjectId,
+ box.GUID,
+ boxDef.Packet.ConstructorData(box).get
+ )
+ )
+
+ //handle inventory contents
+ box.Capacity = if (sumReloadValue <= fullMagazine) {
+ sumReloadValue
+ } else {
+ val splitReloadAmmo: Int = sumReloadValue - fullMagazine
+ log.trace(
+ s"PerformToolAmmoChange: ${player.Name} takes ${originalBoxCapacity - splitReloadAmmo} from a box of $originalBoxCapacity $requestedAmmoType ammo"
+ )
+ val boxForInventory = AmmoBox(box.Definition, splitReloadAmmo)
+ TaskWorkflow.execute(stowNewFunc(boxForInventory))
+ fullMagazine
+ }
+ sendResponse(
+ InventoryStateMessage(box.GUID, tool.GUID, box.Capacity)
+ ) //should work for both players and vehicles
+ log.info(s"${player.Name} loads ${box.Capacity} $requestedAmmoType into the ${tool.Definition.Name}")
+ if (previousBox.Capacity > 0) {
+ //divide capacity across other existing and not full boxes of that ammo type
+ var capacity = previousBox.Capacity
+ val iter = obj.Inventory.Items
+ .filter(entry => {
+ entry.obj match {
+ case item: AmmoBox =>
+ item.AmmoType == originalAmmoType && item.FullCapacity != item.Capacity
+ case _ =>
+ false
+ }
+ })
+ .sortBy(_.start)
+ .iterator
+ while (capacity > 0 && iter.hasNext) {
+ val entry = iter.next()
+ val item: AmmoBox = entry.obj.asInstanceOf[AmmoBox]
+ val ammoAllocated = math.min(item.FullCapacity - item.Capacity, capacity)
+ log.info(s"${player.Name} put $ammoAllocated back into a box of ${item.Capacity} $originalAmmoType")
+ capacity -= ammoAllocated
+ modifyFunc(item, -ammoAllocated)
+ }
+ previousBox.Capacity = capacity
+ }
+
+ if (previousBox.Capacity > 0) {
+ //split previousBox into AmmoBox objects of appropriate max capacity, e.g., 100 9mm -> 2 x 50 9mm
+ obj.Inventory.Fit(previousBox) match {
+ case Some(_) =>
+ stowFunc(previousBox)
+ case None =>
+ sessionLogic.general.normalItemDrop(player, continent)(previousBox)
+ }
+ AmmoBox.Split(previousBox) match {
+ case Nil | List(_) => () //done (the former case is technically not possible)
+ case _ :: toUpdate =>
+ modifyFunc(previousBox, 0) //update to changed capacity value
+ toUpdate.foreach(box => { TaskWorkflow.execute(stowNewFunc(box)) })
+ }
+ } else {
+ TaskWorkflow.execute(GUIDTask.unregisterObject(continent.GUID, previousBox))
+ }
+ }
+ }
+ } while (tool.AmmoType != originalAmmoType && tool.AmmoType != tool.AmmoSlot.Box.AmmoType)
+ }
+
+ /**
+ * 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`
+ */
+ 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))
+ }
+
+ /**
+ * Given a vehicle that contains a box of ammunition in its `Trunk` 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`
+ */
+ def modifyAmmunitionInMountable(obj: PlanetSideServerObject with Container)(box: AmmoBox, reloadValue: Int): Unit = {
+ modifyAmmunition(obj)(box, reloadValue)
+ obj.Find(box).collect { index =>
+ continent.VehicleEvents ! VehicleServiceMessage(
+ s"${obj.Actor}",
+ VehicleAction.InventoryState(
+ player.GUID,
+ box,
+ obj.GUID,
+ index,
+ box.Definition.Packet.DetailedConstructorData(box).get
+ )
+ )
+ }
+ }
+
+ def checkForHitPositionDiscrepancy(
+ projectile_guid: PlanetSideGUID,
+ hitPos: Vector3,
+ target: PlanetSideGameObject with Vitality
+ ): Unit = {
+ val hitPositionDiscrepancy = Vector3.DistanceSquared(hitPos, target.Position)
+ if (hitPositionDiscrepancy > Config.app.antiCheat.hitPositionDiscrepancyThreshold) {
+ // If the target position on the server does not match the position where the projectile landed within reason there may be foul play
+ log.warn(
+ s"${player.Name}'s shot #${projectile_guid.guid} has hit discrepancy with target. Target: ${target.Position}, Reported: $hitPos, Distance: $hitPositionDiscrepancy / ${math.sqrt(hitPositionDiscrepancy).toFloat}; suspect"
+ )
+ }
+ }
+
+ private def CompileAutomatedTurretDamageData(
+ turret: AutomatedTurret,
+ projectileTypeId: Long
+ ): Option[(AutomatedTurret, Tool, SourceEntry, ProjectileDefinition)] = {
+ turret match {
+ case tOwner: OwnableByPlayer =>
+ CompileAutomatedTurretDamageData(
+ turret,
+ CompileAutomatedTurretOwnableBlame(tOwner),
+ projectileTypeId
+ )
+ case tAmenity: Amenity =>
+ CompileAutomatedTurretDamageData(
+ turret,
+ CompileAutomatedTurretAmenityBlame(tAmenity),
+ projectileTypeId
+ )
+ case _ =>
+ None
+ }
+ }
+
+ private def CompileAutomatedTurretOwnableBlame(turret: AutomatedTurret with OwnableByPlayer): SourceEntry = {
+ Deployables.AssignBlameTo(continent, turret.OwnerName, SourceEntry(turret))
+ }
+
+ private def CompileAutomatedTurretAmenityBlame(turret: AutomatedTurret with Amenity): SourceEntry = {
+ turret
+ .Seats
+ .values
+ .flatMap(_.occupant)
+ .collectFirst(SourceEntry(_))
+ .getOrElse(SourceEntry(turret.Owner))
+ }
+
+ private def CompileAutomatedTurretDamageData(
+ turret: AutomatedTurret,
+ blame: SourceEntry,
+ projectileTypeId: Long
+ ): Option[(AutomatedTurret, Tool, SourceEntry, ProjectileDefinition)] = {
+ turret.Weapons
+ .values
+ .flatMap { _.Equipment }
+ .collect {
+ case weapon: Tool => (turret, weapon, blame, weapon.Projectile)
+ }
+ .find { case (_, _, _, p) => p.ObjectId == projectileTypeId }
+ }
+
+ private def prepareAIProjectile(
+ target: PlanetSideServerObject with FactionAffinity with Vitality,
+ results: Option[(AutomatedTurret, Tool, SourceEntry, ProjectileDefinition)]
+ ): List[(PlanetSideGameObject with FactionAffinity with Vitality, Projectile, Vector3, Vector3)] = {
+ val hitPos = target.Position + Vector3.z(value = 1f)
+ results.collect {
+ case (obj, tool, owner, projectileInfo) =>
+ val angle = Vector3.Unit(target.Position - obj.Position)
+ val projectile = new Projectile(
+ projectileInfo,
+ tool.Definition,
+ tool.FireMode,
+ None,
+ owner,
+ obj.Definition.ObjectId,
+ obj.Position + Vector3.z(value = 1f),
+ angle,
+ Some(angle * projectileInfo.FinalVelocity)
+ )
+ (target, projectile, hitPos, target.Position)
+ }.toList
+ }
+
override protected[session] def actionsToCancel(): Unit = {
shootingStart.clear()
shootingStop.clear()
@@ -352,3 +1254,132 @@ class WeaponAndProjectileOperations(
}
}
}
+
+object WeaponAndProjectileOperations {
+ def updateProjectileSidednessAfterHit(zone: Zone, projectile: Projectile, hitPosition: Vector3): Unit = {
+ val origin = projectile.Position
+ val distance = Vector3.Magnitude(hitPosition - origin)
+ zone.blockMap
+ .sector(hitPosition, distance)
+ .environmentList
+ .collect { case o: InteriorDoorPassage =>
+ val door = o.door
+ val intersectTest = quickLineSphereIntersectionPoints(
+ origin,
+ hitPosition,
+ door.Position,
+ door.Definition.UseRadius + 0.1f
+ )
+ (door, intersectTest)
+ }
+ .collect { case (door, intersectionTest) if intersectionTest.nonEmpty =>
+ (door, Vector3.Magnitude(hitPosition - door.Position), intersectionTest)
+ }
+ .minByOption { case (_, dist, _) => dist }
+ .foreach { case (door, _, intersects) =>
+ val strictly = if (Vector3.DotProduct(Vector3.Unit(hitPosition - door.Position), door.Outwards) > 0f) {
+ Sidedness.OutsideOf
+ } else {
+ Sidedness.InsideOf
+ }
+ projectile.WhichSide = if (intersects.size == 1) {
+ Sidedness.InBetweenSides(door, strictly)
+ } else {
+ strictly
+ }
+ }
+ }
+
+ /**
+ * Does a line segment line intersect with a sphere?
+ * This most likely belongs in `Geometry` or `GeometryForm` or somehow in association with the `\objects\geometry\` package.
+ * @param start first point of the line segment
+ * @param end second point of the line segment
+ * @param center center of the sphere
+ * @param radius radius of the sphere
+ * @return list of all points of intersection, if any
+ * @see `Vector3.DistanceSquared`
+ * @see `Vector3.MagnitudeSquared`
+ */
+ def quickLineSphereIntersectionPoints(
+ start: Vector3,
+ end: Vector3,
+ center: Vector3,
+ radius: Float
+ ): Iterable[Vector3] = {
+ /*
+ Algorithm adapted from code found on https://paulbourke.net/geometry/circlesphere/index.html#linesphere,
+ because I kept messing up proper substitution of the line formula and the circle formula into the quadratic equation.
+ */
+ val Vector3(cx, cy, cz) = center
+ val Vector3(sx, sy, sz) = start
+ val vector = end - start
+ //speed our way through a quadratic equation
+ val (a, b) = {
+ val Vector3(dx, dy, dz) = vector
+ (
+ dx * dx + dy * dy + dz * dz,
+ 2f * (dx * (sx - cx) + dy * (sy - cy) + dz * (sz - cz))
+ )
+ }
+ val c = Vector3.MagnitudeSquared(center) + Vector3.MagnitudeSquared(start) - 2f * (cx * sx + cy * sy + cz * sz) - radius * radius
+ val result = b * b - 4 * a * c
+ if (result < 0f) {
+ //negative, no intersection
+ Seq()
+ } else if (result < 0.00001f) {
+ //zero-ish, one intersection point
+ Seq(start - vector * (b / (2f * a)))
+ } else {
+ //positive, two intersection points
+ val sqrt = math.sqrt(result).toFloat
+ val endStart = vector / (2f * a)
+ Seq(start + endStart * (sqrt - b), start + endStart * (b + sqrt) * -1f)
+ }.filter(p => Vector3.DistanceSquared(start, p) <= a)
+ }
+
+ /**
+ * Preparation for explosion damage that utilizes the Scorpion's little buddy sub-projectiles.
+ * The main difference from "normal" server-side explosion
+ * is that the owner of the projectile must be clarified explicitly.
+ * @see `Zone::serverSideDamage`
+ * @param zone where the explosion is taking place
+ * (`source` contains the coordinate location)
+ * @param source a game object that represents the source of the explosion
+ * @param owner who or what to accredit damage from the explosion to;
+ * clarifies a normal `SourceEntry(source)` accreditation
+ */
+ def detonateLittleBuddy(
+ zone: Zone,
+ source: PlanetSideGameObject with FactionAffinity with Vitality,
+ proxy: Projectile,
+ owner: SourceEntry
+ )(): Unit = {
+ Zone.serverSideDamage(zone, source, littleBuddyExplosionDamage(owner, proxy.id, source.Position))
+ }
+
+ /**
+ * Preparation for explosion damage that utilizes the Scorpion's little buddy sub-projectiles.
+ * The main difference from "normal" server-side explosion
+ * is that the owner of the projectile must be clarified explicitly.
+ * The sub-projectiles will be the product of a normal projectile rather than a standard game object
+ * so a custom `source` entity must wrap around it and fulfill the requirements of the field.
+ * @see `Zone::explosionDamage`
+ * @param owner who or what to accredit damage from the explosion to
+ * @param explosionPosition where the explosion will be positioned in the game world
+ * @param source a game object that represents the source of the explosion
+ * @param target a game object that is affected by the explosion
+ * @return a `DamageInteraction` object
+ */
+ private def littleBuddyExplosionDamage(
+ owner: SourceEntry,
+ projectileId: Long,
+ explosionPosition: Vector3
+ )
+ (
+ source: PlanetSideGameObject with FactionAffinity with Vitality,
+ target: PlanetSideGameObject with FactionAffinity with Vitality
+ ): DamageInteraction = {
+ DamageInteraction(SourceEntry(target), OicwLilBuddyReason(owner, projectileId, target.DamageModel), explosionPosition)
+ }
+}
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 9d56fc5f3..46ea275fc 100644
--- a/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala
+++ b/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala
@@ -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()
diff --git a/src/main/scala/net/psforever/objects/Player.scala b/src/main/scala/net/psforever/objects/Player.scala
index 24c277b3a..1ac54eba1 100644
--- a/src/main/scala/net/psforever/objects/Player.scala
+++ b/src/main/scala/net/psforever/objects/Player.scala
@@ -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
diff --git a/src/main/scala/net/psforever/objects/definition/converter/AvatarConverter.scala b/src/main/scala/net/psforever/objects/definition/converter/AvatarConverter.scala
index 31f5f3992..28aafe07c 100644
--- a/src/main/scala/net/psforever/objects/definition/converter/AvatarConverter.scala
+++ b/src/main/scala/net/psforever/objects/definition/converter/AvatarConverter.scala
@@ -69,7 +69,7 @@ object AvatarConverter {
obj.avatar.basic,
CommonFieldData(
obj.Faction,
- bops = obj.spectator,
+ bops = obj.bops,
alt_model_flag,
v1 = false,
None,
diff --git a/src/main/scala/net/psforever/objects/zones/blockmap/Sector.scala b/src/main/scala/net/psforever/objects/zones/blockmap/Sector.scala
index 531d64502..c78cb1b1e 100644
--- a/src/main/scala/net/psforever/objects/zones/blockmap/Sector.scala
+++ b/src/main/scala/net/psforever/objects/zones/blockmap/Sector.scala
@@ -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)
diff --git a/src/main/scala/net/psforever/packet/game/objectcreate/DetailedCharacterData.scala b/src/main/scala/net/psforever/packet/game/objectcreate/DetailedCharacterData.scala
index 27dda936a..b68edb8d6 100644
--- a/src/main/scala/net/psforever/packet/game/objectcreate/DetailedCharacterData.scala
+++ b/src/main/scala/net/psforever/packet/game/objectcreate/DetailedCharacterData.scala
@@ -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
}