diff --git a/src/main/scala/net/psforever/actors/session/AvatarActor.scala b/src/main/scala/net/psforever/actors/session/AvatarActor.scala index 91230d19b..2852315d6 100644 --- a/src/main/scala/net/psforever/actors/session/AvatarActor.scala +++ b/src/main/scala/net/psforever/actors/session/AvatarActor.scala @@ -1061,19 +1061,17 @@ object AvatarActor { val result = ctx.run(query[persistence.Avatarmodepermission].filter(_.avatarId == lift(avatarId))) result.onComplete { case Success(res) => - val isDevServer = Config.app.world.serverType == ServerType.Development res.headOption .collect { case perms: persistence.Avatarmodepermission => - out.completeWith(Future(ModePermissions(perms.canSpectate || isDevServer, perms.canGm || isDevServer))) + out.completeWith(Future(ModePermissions(perms.canSpectate, perms.canGm))) } .orElse { - out.completeWith(Future(ModePermissions(isDevServer, isDevServer))) + out.completeWith(Future(ModePermissions())) None } case _ => - val isDevServer = Config.app.world.serverType == ServerType.Development - out.completeWith(Future(ModePermissions(isDevServer, isDevServer))) + out.completeWith(Future(ModePermissions())) } out.future } @@ -1159,6 +1157,7 @@ class AvatarActor( case SetSession(newSession) => session = Some(newSession) + _avatar = Option(newSession.avatar) postLoginBehaviour() case other => diff --git a/src/main/scala/net/psforever/actors/session/SessionActor.scala b/src/main/scala/net/psforever/actors/session/SessionActor.scala index d24b0cee3..899d32651 100644 --- a/src/main/scala/net/psforever/actors/session/SessionActor.scala +++ b/src/main/scala/net/psforever/actors/session/SessionActor.scala @@ -160,7 +160,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con if (mode != newMode) { logic.switchFrom(data.session) mode = newMode - logic = mode.setup(data) + logic = newMode.setup(data) } logic.switchTo(data.session) } 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 9d716b3ba..aaac406c8 100644 --- a/src/main/scala/net/psforever/actors/session/csr/AvatarHandlerLogic.scala +++ b/src/main/scala/net/psforever/actors/session/csr/AvatarHandlerLogic.scala @@ -4,6 +4,7 @@ package net.psforever.actors.session.csr import akka.actor.{ActorContext, typed} import net.psforever.actors.session.support.AvatarHandlerFunctions import net.psforever.objects.definition.converter.OCM +import net.psforever.objects.serverobject.containable.ContainableBehavior import net.psforever.packet.game.{AvatarImplantMessage, CreateShortcutMessage, ImplantAction} import net.psforever.types.ImplantType @@ -17,8 +18,8 @@ import net.psforever.objects.inventory.InventoryItem import net.psforever.objects.serverobject.terminals.{ProximityUnit, Terminal} import net.psforever.objects.zones.Zoning import net.psforever.packet.game.objectcreate.ObjectCreateMessageParent -import net.psforever.packet.game.{ArmorChangedMessage, AvatarDeadStateMessage, ChangeAmmoMessage, ChangeFireModeMessage, ChangeFireStateMessage_Start, ChangeFireStateMessage_Stop, ChatMsg, DeadState, DestroyMessage, DrowningTarget, GenericActionMessage, GenericObjectActionMessage, HitHint, ItemTransactionResultMessage, ObjectCreateDetailedMessage, ObjectCreateMessage, ObjectDeleteMessage, ObjectHeldMessage, OxygenStateMessage, PlanetsideAttributeMessage, PlayerStateMessage, ProjectileStateMessage, ReloadMessage, SetEmpireMessage, UseItemMessage, WeaponDryFireMessage} -import net.psforever.services.avatar.{AvatarAction, AvatarResponse, AvatarServiceMessage} +import net.psforever.packet.game.{ArmorChangedMessage, ChangeAmmoMessage, ChangeFireModeMessage, ChangeFireStateMessage_Start, ChangeFireStateMessage_Stop, ChatMsg, DestroyMessage, DrowningTarget, GenericActionMessage, GenericObjectActionMessage, HitHint, ItemTransactionResultMessage, ObjectCreateDetailedMessage, ObjectCreateMessage, ObjectDeleteMessage, ObjectHeldMessage, OxygenStateMessage, PlanetsideAttributeMessage, PlayerStateMessage, ProjectileStateMessage, ReloadMessage, SetEmpireMessage, UseItemMessage, WeaponDryFireMessage} +import net.psforever.services.avatar.AvatarResponse import net.psforever.services.Service import net.psforever.types.{ChatMessageType, PlanetSideGUID, TransactionType, Vector3} import net.psforever.util.Config @@ -247,7 +248,7 @@ class AvatarHandlerLogic(val ops: SessionAvatarHandlers, implicit val context: A case AvatarResponse.HitHint(sourceGuid) if player.isAlive => sendResponse(HitHint(sourceGuid, guid)) - sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_dmg") + sessionLogic.zoning.CancelZoningProcess() case AvatarResponse.Destroy(victim, killer, weapon, pos) => // guid = victim // killer = killer @@ -324,13 +325,17 @@ class AvatarHandlerLogic(val ops: SessionAvatarHandlers, implicit val context: A } DropLeftovers(player)(drop) - case AvatarResponse.ChangeExosuit(target, armor, exosuit, subtype, slot, _, oldHolsters, holsters, _, _, _, delete) => + case AvatarResponse.ChangeExosuit(target, armor, exosuit, subtype, slot, _, oldHolsters, holsters, _, _, drop, delete) => sendResponse(ArmorChangedMessage(target, exosuit, subtype)) sendResponse(PlanetsideAttributeMessage(target, attribute_type=4, armor)) //happening to some other player sendResponse(ObjectHeldMessage(target, slot, unk1 = false)) //cleanup - (oldHolsters ++ delete).foreach { case (_, guid) => sendResponse(ObjectDeleteMessage(guid, unk1=0)) } + val dropPred = ContainableBehavior.DropPredicate(player) + val deleteFromDrop = drop.filterNot(dropPred) + (oldHolsters ++ delete ++ deleteFromDrop.map(f =>(f.obj, f.GUID))) + .distinctBy(_._2) + .foreach { case (_, guid) => sendResponse(ObjectDeleteMessage(guid, unk1=0)) } //draw holsters holsters.foreach { case InventoryItem(obj, index) => @@ -359,7 +364,7 @@ class AvatarHandlerLogic(val ops: SessionAvatarHandlers, implicit val context: A drops ) if resolvedPlayerGuid == target => sendResponse(ArmorChangedMessage(target, exosuit, subtype)) - sendResponse(PlanetsideAttributeMessage(target, attribute_type = 4, armor)) + sendResponse(PlanetsideAttributeMessage(target, attribute_type=4, armor)) //happening to this player sendResponse(ObjectHeldMessage(target, Player.HandsDownSlot, unk1=true)) //cleanup @@ -371,6 +376,7 @@ class AvatarHandlerLogic(val ops: SessionAvatarHandlers, implicit val context: A drops.foreach(item => sendResponse(ObjectDeleteMessage(item.obj.GUID, unk1=0))) //redraw if (maxhand) { + sendResponse(PlanetsideAttributeMessage(target, attribute_type=7, player.Capacitor.toLong)) TaskWorkflow.execute(HoldNewEquipmentUp(player)( Tool(GlobalDefinitions.MAXArms(subtype, player.Faction)), slot = 0 @@ -413,24 +419,6 @@ class AvatarHandlerLogic(val ops: SessionAvatarHandlers, implicit val context: A sessionLogic.general.kitToBeUsed = None sendResponse(ChatMsg(ChatMessageType.UNK_225, msg)) - case AvatarResponse.UpdateKillsDeathsAssists(_, kda) => - avatarActor ! AvatarActor.UpdateKillsDeathsAssists(kda) - - case AvatarResponse.AwardBep(charId, bep, expType) => - //if the target player, always award (some) BEP - if (charId == player.CharId) { - avatarActor ! AvatarActor.AwardBep(bep, expType) - } - - case AvatarResponse.AwardCep(charId, cep) => - //if the target player, always award (some) CEP - if (charId == player.CharId) { - avatarActor ! AvatarActor.AwardCep(cep) - } - - case AvatarResponse.FacilityCaptureRewards(buildingId, zoneNumber, cep) => - ops.facilityCaptureRewards(buildingId, zoneNumber, cep) - case AvatarResponse.SendResponse(msg) => sendResponse(msg) @@ -445,17 +433,19 @@ class AvatarHandlerLogic(val ops: SessionAvatarHandlers, implicit val context: A case AvatarResponse.Killed(mount) => //pure logic sessionLogic.shooting.shotsWhileDead = 0 + sessionLogic.zoning.CancelZoningProcess() //player state changes - sessionLogic.zoning.spawn.reviveTimer.cancel() - player.Revive - val health = player.Health - sendResponse(PlanetsideAttributeMessage(player.GUID, attribute_type=0, health)) - sendResponse(AvatarDeadStateMessage(DeadState.Alive, timer_max=0, timer=0, player.Position, player.Faction, unk5=true)) - continent.AvatarEvents ! AvatarServiceMessage( - continent.id, - AvatarAction.PlanetsideAttributeToAll(player.GUID, attribute_type=0, health) - ) + AvatarActor.updateToolDischargeFor(avatar) + player.FreeHand.Equipment.foreach { item => + DropEquipmentFromInventory(player)(item) + } + sessionLogic.general.dropSpecialSlotItem() + sessionLogic.general.toggleMaxSpecialState(enable = false) + sessionLogic.keepAliveFunc = sessionLogic.zoning.NormalKeepAlive + sessionLogic.zoning.zoningStatus = Zoning.Status.None + ops.revive(player.GUID) + AvatarActor.savePlayerLocation(player) avatarActor ! AvatarActor.InitializeImplants AvatarActor.updateToolDischargeFor(avatar) player.FreeHand.Equipment.foreach { item => @@ -479,17 +469,7 @@ class AvatarHandlerLogic(val ops: SessionAvatarHandlers, implicit val context: A case AvatarResponse.Revive(revivalTargetGuid) if resolvedPlayerGuid == revivalTargetGuid => - log.info(s"No time for rest, ${player.Name}. Back on your feet!") - sessionLogic.zoning.spawn.reviveTimer.cancel() - sessionLogic.zoning.spawn.deadState = DeadState.Alive - player.Revive - val health = player.Health - sendResponse(PlanetsideAttributeMessage(revivalTargetGuid, attribute_type=0, health)) - sendResponse(AvatarDeadStateMessage(DeadState.Alive, timer_max=0, timer=0, player.Position, player.Faction, unk5=true)) - continent.AvatarEvents ! AvatarServiceMessage( - continent.id, - AvatarAction.PlanetsideAttributeToAll(revivalTargetGuid, attribute_type=0, health) - ) + ops.revive(revivalTargetGuid) /* uncommon messages (utility, or once in a while) */ case AvatarResponse.ChangeAmmo(weapon_guid, weapon_slot, previous_guid, ammo_id, ammo_guid, ammo_data) @@ -509,7 +489,7 @@ class AvatarHandlerLogic(val ops: SessionAvatarHandlers, implicit val context: A case AvatarResponse.EnvironmentalDamage(_, _, _) => //TODO damage marker? - sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_dmg") + sessionLogic.zoning.CancelZoningProcess() case AvatarResponse.DropItem(pkt) if isNotSameTarget => sendResponse(pkt) 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 864e9738b..33abb72f3 100644 --- a/src/main/scala/net/psforever/actors/session/csr/ChatLogic.scala +++ b/src/main/scala/net/psforever/actors/session/csr/ChatLogic.scala @@ -6,7 +6,6 @@ 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 @@ -216,18 +215,17 @@ 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 + private def commandToggleSpectatorMode(contents: String): Unit = { contents.toLowerCase() match { - case "on" | "o" | "" if currentSpectatorActivation && player.spectator => + case "on" | "o" | "" if !player.spectator => context.self ! SessionActor.SetMode(SpectateAsCustomerServiceRepresentativeMode) - case "off" | "of" if currentSpectatorActivation && !player.spectator => + case "off" | "of" if player.spectator => context.self ! SessionActor.SetMode(CustomerServiceRepresentativeMode) case _ => () } } - def customCommandModerator(contents : String): Boolean = { + private def customCommandModerator(contents: String): Boolean = { if (sessionLogic.zoning.maintainInitialGmState) { sessionLogic.zoning.maintainInitialGmState = false } else { 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 60d4933ed..67dd120d7 100644 --- a/src/main/scala/net/psforever/actors/session/csr/CustomerServiceRepresentativeMode.scala +++ b/src/main/scala/net/psforever/actors/session/csr/CustomerServiceRepresentativeMode.scala @@ -1,12 +1,16 @@ // Copyright (c) 2024 PSForever package net.psforever.actors.session.csr +import net.psforever.actors.session.SessionActor import net.psforever.actors.session.support.{ChatFunctions, GalaxyHandlerFunctions, GeneralFunctions, LocalHandlerFunctions, ModeLogic, MountHandlerFunctions, PlayerMode, SessionData, SquadHandlerFunctions, TerminalHandlerFunctions, VehicleFunctions, VehicleHandlerFunctions, WeaponAndProjectileFunctions} -import net.psforever.actors.zone.ZoneActor -import net.psforever.objects.Session +import net.psforever.objects.{Deployables, Session, Vehicle} +import net.psforever.objects.avatar.Certification import net.psforever.packet.PlanetSidePacket -import net.psforever.packet.game.ChatMsg -import net.psforever.types.ChatMessageType +import net.psforever.packet.game.{ChatMsg, ObjectCreateDetailedMessage} +import net.psforever.packet.game.objectcreate.{ObjectCreateMessageParent, RibbonBars} +import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage} +import net.psforever.services.chat.{CustomerServiceChannel, SpectatorChannel} +import net.psforever.types.{ChatMessageType, MeritCommendation} class CustomerServiceRepresentativeMode(data: SessionData) extends ModeLogic { val avatarResponse: AvatarHandlerLogic = AvatarHandlerLogic(data.avatarResponse) @@ -21,20 +25,108 @@ class CustomerServiceRepresentativeMode(data: SessionData) extends ModeLogic { val vehicles: VehicleFunctions = VehicleLogic(data.vehicles) val vehicleResponse: VehicleHandlerFunctions = net.psforever.actors.session.normal.VehicleHandlerLogic(data.vehicleResponseOperations) + private var oldRibbons: RibbonBars = RibbonBars() + private var oldCertifications : Set[Certification] = Set() + override def switchTo(session: Session): Unit = { val player = session.player + val avatar = session.avatar val continent = session.zone val sendResponse: PlanetSidePacket=>Unit = data.sendResponse // - continent.actor ! ZoneActor.RemoveFromBlockMap(player) + if (oldCertifications.isEmpty) { + oldCertifications = avatar.certifications + oldRibbons = avatar.decoration.ribbonBars + val newAvatar = avatar.copy( + certifications = Certification.values.toSet, + decoration = avatar.decoration.copy(ribbonBars = RibbonBars( + MeritCommendation.CSAppreciation, + MeritCommendation.Loser, + MeritCommendation.Loser, + MeritCommendation.CSAppreciation + )) + ) + player.avatar = newAvatar + data.context.self ! SessionActor.SetAvatar(newAvatar) + Deployables.InitializeDeployableQuantities(newAvatar) + } + val vehicleAndSeat = data.vehicles.GetMountableAndSeat(None, player, continent) match { + case (Some(obj: Vehicle), Some(seatNum)) => + Some(ObjectCreateMessageParent(obj.GUID, seatNum)) + case _ => + None + } + // + val pguid = player.GUID + val definition = player.Definition + val objectClass = definition.ObjectId + val packet = definition.Packet + sendResponse(ObjectCreateDetailedMessage( + 0L, + objectClass, + pguid, + vehicleAndSeat, + packet.DetailedConstructorData(player).get + )) + data.zoning.spawn.HandleSetCurrentAvatar(player) + continent.AvatarEvents ! AvatarServiceMessage(continent.id, AvatarAction.LoadPlayer( + pguid, + objectClass, + pguid, + packet.ConstructorData(player).get, + vehicleAndSeat + )) + if (player.silenced) { + data.chat.commandIncomingSilence(session, ChatMsg(ChatMessageType.CMT_SILENCE, "player 0")) + } + data.chat.JoinChannel(SpectatorChannel) + data.chat.JoinChannel(CustomerServiceChannel) sendResponse(ChatMsg(ChatMessageType.UNK_225, "CSR MODE ON")) } override def switchFrom(session: Session): Unit = { - val player = data.player + val player = session.player + val avatar = session.avatar + val continent = session.zone val sendResponse: PlanetSidePacket => Unit = data.sendResponse // - data.continent.actor ! ZoneActor.AddToBlockMap(player, player.Position) + val newAvatar = avatar.copy( + certifications = oldCertifications, + decoration = avatar.decoration.copy(ribbonBars = oldRibbons) + ) + oldCertifications = Set() + oldRibbons = RibbonBars() + player.avatar = newAvatar + data.context.self ! SessionActor.SetAvatar(newAvatar) + val vehicleAndSeat = data.vehicles.GetMountableAndSeat(None, player, continent) match { + case (Some(obj: Vehicle), Some(seatNum)) => + Some(ObjectCreateMessageParent(obj.GUID, seatNum)) + case _ => + None + } + Deployables.InitializeDeployableQuantities(newAvatar) + // + val pguid = player.GUID + val definition = player.Definition + val objectClass = definition.ObjectId + val packet = definition.Packet + sendResponse(ObjectCreateDetailedMessage( + 0L, + objectClass, + pguid, + vehicleAndSeat, + packet.DetailedConstructorData(player).get + )) + data.zoning.spawn.HandleSetCurrentAvatar(player) + continent.AvatarEvents ! AvatarServiceMessage(continent.id, AvatarAction.LoadPlayer( + pguid, + objectClass, + pguid, + packet.ConstructorData(player).get, + vehicleAndSeat + )) + data.chat.LeaveChannel(SpectatorChannel) + data.chat.LeaveChannel(CustomerServiceChannel) sendResponse(ChatMsg(ChatMessageType.UNK_225, "CSR MODE OFF")) } } 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 a6732f51e..08b409543 100644 --- a/src/main/scala/net/psforever/actors/session/csr/GeneralLogic.scala +++ b/src/main/scala/net/psforever/actors/session/csr/GeneralLogic.scala @@ -4,7 +4,6 @@ package net.psforever.actors.session.csr import akka.actor.{ActorContext, ActorRef, typed} import net.psforever.actors.session.AvatarActor import net.psforever.actors.session.support.{GeneralFunctions, GeneralOperations, SessionData} -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 @@ -13,7 +12,7 @@ import net.psforever.objects.definition.{BasicDefinition, KitDefinition, Special import net.psforever.objects.entity.WorldEntity import net.psforever.objects.equipment.Equipment import net.psforever.objects.inventory.Container -import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject, ServerObject} +import net.psforever.objects.serverobject.{CommonMessages, ServerObject} import net.psforever.objects.serverobject.containable.Containable import net.psforever.objects.serverobject.doors.Door import net.psforever.objects.serverobject.generator.Generator @@ -31,9 +30,8 @@ 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.{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.packet.game.{ActionCancelMessage, 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, InvalidTerrainMessage, LootItemMessage, MoveItemMessage, ObjectDetectedMessage, ObjectHeldMessage, PickupItemMessage, PlanetsideAttributeMessage, PlayerStateMessageUpstream, RequestDestroyMessage, TargetingImplantRequest, TerrainCondition, TradeMessage, UnuseItemMessage, UseItemMessage, VoiceHostInfo, VoiceHostRequest, ZipLineMessage} import net.psforever.services.RemoverActor -import net.psforever.services.account.AccountPersistenceService import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage} import net.psforever.types.{CapacitorStateType, Cosmetic, ExoSuitType, PlanetSideEmpire, PlanetSideGUID, Vector3} @@ -44,6 +42,8 @@ object GeneralLogic { } class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContext) extends GeneralFunctions { + private var openDoor: Option[Door] = None + def sessionLogic: SessionData = ops.sessionLogic private val avatarActor: typed.ActorRef[AvatarActor.Command] = ops.avatarActor @@ -75,7 +75,7 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex sessionLogic.turnCounterFunc(avatarGuid) //below half health, full heal val maxHealth = player.MaxHealth.toLong - if (player.Health < maxHealth * 0.5f) { + if (player.Health < maxHealth) { player.Health = maxHealth.toInt player.LogActivity(player.ClearHistory().head) sendResponse(PlanetsideAttributeMessage(avatarGuid, 0, maxHealth)) @@ -84,10 +84,29 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex //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)) + if (avatar.stamina < maxStamina) { + avatarActor ! AvatarActor.RestoreStamina(maxStamina) sendResponse(PlanetsideAttributeMessage(player.GUID, 2, maxStamina.toLong)) } + //below half armor, full armor + val maxArmor = player.MaxArmor.toLong + if (player.Armor < maxArmor) { + player.Armor = maxArmor.toInt + sendResponse(PlanetsideAttributeMessage(avatarGuid, 4, maxArmor)) + continent.AvatarEvents ! AvatarServiceMessage(continent.id, AvatarAction.PlanetsideAttribute(avatarGuid, 4, maxArmor)) + } + //doors + openDoor + .collect { + case door + if Vector3.DistanceSquared(door.Position.xy, player.Position.xy) > math.pow(door.Definition.continuousOpenDistance, 2).toFloat => + if (!door.isOpen) { + sendResponse(GenericObjectStateMsg(door.GUID, state = 17)) + } + openDoor = None + case door if !door.isOpen => + sendResponse(GenericObjectStateMsg(door.GUID, state = 16)) + } //expected val isMoving = WorldEntity.isMoving(vel) val isMovingPlus = isMoving || isJumping || jumpThrust @@ -130,7 +149,7 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex // 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. sendResponse(UnuseItemMessage(guid, guid)) ops.unaccessContainer(container) - case None => () + case _ => () } if (!player.spectator) { sessionLogic.updateBlockMap(player, pos) @@ -173,34 +192,18 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex } def handleDropItem(pkt: DropItemMessage): Unit = { - val DropItemMessage(itemGuid) = pkt - (sessionLogic.validObject(itemGuid, decorator = "DropItem"), player.FreeHand.Equipment) match { - case (Some(anItem: Equipment), Some(heldItem)) - if (anItem eq heldItem) && continent.GUID(player.VehicleSeated).nonEmpty => + ops.handleDropItem(pkt) match { + case GeneralOperations.ItemDropState.Dropped => sessionLogic.zoning.CancelZoningProcess() - RemoveOldEquipmentFromInventory(player)(heldItem) - case (Some(anItem: Equipment), Some(heldItem)) - if anItem eq heldItem => - sessionLogic.zoning.CancelZoningProcess() - DropEquipmentFromInventory(player)(heldItem) - case (Some(anItem: Equipment), _) - if continent.GUID(player.VehicleSeated).isEmpty => - //suppress the warning message if in a vehicle - log.warn(s"DropItem: ${player.Name} wanted to drop a ${anItem.Definition.Name}, but it wasn't at hand") - case (Some(obj), _) => - log.warn(s"DropItem: ${player.Name} wanted to drop a ${obj.Definition.Name}, but it was not equipment") case _ => () } } def handlePickupItem(pkt: PickupItemMessage): Unit = { - val PickupItemMessage(itemGuid, _, _, _) = pkt - sessionLogic.validObject(itemGuid, decorator = "PickupItem").collect { - case item: Equipment if player.Fit(item).nonEmpty => - sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use") - PickUpEquipmentFromGround(player)(item) - case _: Equipment => - sendResponse(ActionResultMessage.Fail(16)) //error code? + ops.handlePickupItem(pkt) match { + case GeneralOperations.ItemPickupState.PickedUp => + sessionLogic.zoning.CancelZoningProcess() + case _ => () } } @@ -212,33 +215,11 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex def handleAvatarJump(pkt: AvatarJumpMessage): Unit = { /* no stamina loss */ } def handleZipLine(pkt: ZipLineMessage): Unit = { - val ZipLineMessage(playerGuid, forwards, action, pathId, pos) = pkt - continent.zipLinePaths.find(x => x.PathId == pathId) match { - case Some(path) if path.IsTeleporter => - sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel") - val endPoint = path.ZipLinePoints.last - sendResponse(ZipLineMessage(PlanetSideGUID(0), forwards, 0, pathId, pos)) - //todo: send to zone to show teleport animation to all clients - sendResponse(PlayerStateShiftMessage(ShiftState(0, endPoint, (player.Orientation.z + player.FacingYawUpper) % 360f, None))) - case Some(_) => - sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_motion") - action match { - case 0 => - //travel along the zipline in the direction specified - sendResponse(ZipLineMessage(playerGuid, forwards, action, pathId, pos)) - case 1 => - //disembark from zipline at destination - sendResponse(ZipLineMessage(playerGuid, forwards, action, 0, pos)) - case 2 => - //get off by force - sendResponse(ZipLineMessage(playerGuid, forwards, action, 0, pos)) - case _ => - log.warn( - s"${player.Name} tried to do something with a zipline but can't handle it. forwards: $forwards action: $action pathId: $pathId zone: ${continent.Number} / ${continent.id}" - ) - } + ops.handleZipLine(pkt) match { + case GeneralOperations.ZiplineBehavior.Teleporter | GeneralOperations.ZiplineBehavior.Zipline => + sessionLogic.zoning.CancelZoningProcess() case _ => - log.warn(s"${player.Name} couldn't find a zipline path $pathId in zone ${continent.id}") + () } } @@ -287,99 +268,20 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex } def handleMoveItem(pkt: MoveItemMessage): Unit = { - val MoveItemMessage(itemGuid, sourceGuid, destinationGuid, dest, _) = pkt - ( - continent.GUID(sourceGuid), - continent.GUID(destinationGuid), - sessionLogic.validObject(itemGuid, decorator = "MoveItem") - ) match { - case ( - Some(source: PlanetSideServerObject with Container), - Some(destination: PlanetSideServerObject with Container), - Some(item: Equipment) - ) => - ContainableMoveItem(player.Name, source, destination, item, destination.SlotMapResolution(dest)) - case (None, _, _) => - log.error( - s"MoveItem: ${player.Name} wanted to move $itemGuid from $sourceGuid, but could not find source object" - ) - case (_, None, _) => - log.error( - s"MoveItem: ${player.Name} wanted to move $itemGuid to $destinationGuid, but could not find destination object" - ) - case (_, _, None) => () - case _ => - log.error( - s"MoveItem: ${player.Name} wanted to move $itemGuid from $sourceGuid to $destinationGuid, but multiple problems were encountered" - ) - } + ops.handleMoveItem(pkt) } def handleLootItem(pkt: LootItemMessage): Unit = { - val LootItemMessage(itemGuid, targetGuid) = pkt - (sessionLogic.validObject(itemGuid, decorator = "LootItem"), continent.GUID(targetGuid)) match { - case (Some(item: Equipment), Some(destination: PlanetSideServerObject with Container)) => - //figure out the source - ( - { - val findFunc: PlanetSideServerObject with Container => Option[ - (PlanetSideServerObject with Container, Option[Int]) - ] = ops.findInLocalContainer(itemGuid) - findFunc(player.avatar.locker) - .orElse(findFunc(player)) - .orElse(ops.accessedContainer match { - case Some(parent: PlanetSideServerObject) => - findFunc(parent) - case _ => - None - }) - }, - destination.Fit(item) - ) match { - case (Some((source, Some(_))), Some(dest)) => - ContainableMoveItem(player.Name, source, destination, item, dest) - case (None, _) => - log.error(s"LootItem: ${player.Name} can not find where $item is put currently") - case (_, None) => - log.error(s"LootItem: ${player.Name} can not find anywhere to put $item in $destination") - case _ => - log.error( - s"LootItem: ${player.Name}wanted to move $itemGuid to $targetGuid, but multiple problems were encountered" - ) - } - case (Some(obj), _) => - log.error(s"LootItem: item $obj is (probably) not lootable to ${player.Name}") - case (None, _) => () - case (_, None) => - log.error(s"LootItem: ${player.Name} can not find where to put $itemGuid") - } + ops.handleLootItem(pkt) } def handleAvatarImplant(pkt: AvatarImplantMessage): Unit = { - val AvatarImplantMessage(_, action, slot, status) = pkt - if (action == ImplantAction.Activation) { - if (sessionLogic.zoning.zoningStatus == Zoning.Status.Deconstructing) { - //do not activate; play deactivation sound instead - sessionLogic.zoning.spawn.stopDeconstructing() - avatar.implants(slot).collect { - case implant if implant.active => - avatarActor ! AvatarActor.DeactivateImplant(implant.definition.implantType) - case implant => - sendResponse(PlanetsideAttributeMessage(player.GUID, 28, implant.definition.implantType.value * 2)) - } - } else { + ops.handleAvatarImplant(pkt) match { + case GeneralOperations.ImplantActivationBehavior.Activate | GeneralOperations.ImplantActivationBehavior.Deactivate => sessionLogic.zoning.CancelZoningProcess() - avatar.implants(slot) match { - case Some(implant) => - if (status == 1) { - avatarActor ! AvatarActor.ActivateImplant(implant.definition.implantType) - } else { - avatarActor ! AvatarActor.DeactivateImplant(implant.definition.implantType) - } - case _ => - log.error(s"AvatarImplantMessage: ${player.Name} has an unknown implant in $slot") - } - } + case GeneralOperations.ImplantActivationBehavior.NotFound => + log.error(s"AvatarImplantMessage: ${player.Name} has an unknown implant in ${pkt.implantSlot}") + case _ => () } } @@ -460,19 +362,21 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex } def handleDeployObject(pkt: DeployObjectMessage): Unit = { - val DeployObjectMessage(guid, _, pos, orient, _) = pkt - player.Holsters().find(slot => slot.Equipment.nonEmpty && slot.Equipment.get.GUID == guid).flatMap { slot => slot.Equipment } match { - case Some(obj: ConstructionItem) => - val ammoType = obj.AmmoType match { - case DeployedItem.portable_manned_turret => GlobalDefinitions.PortableMannedTurret(player.Faction).Item - case dtype => dtype - } - 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 => - log.error(s"DeployObject: nothing, ${player.Name}? It's not a construction tool!") + if (!player.spectator) { + val DeployObjectMessage(guid, _, pos, orient, _) = pkt + player.Holsters().find(slot => slot.Equipment.nonEmpty && slot.Equipment.get.GUID == guid).flatMap { slot => slot.Equipment } match { + case Some(obj: ConstructionItem) => + val ammoType = obj.AmmoType match { + case DeployedItem.portable_manned_turret => GlobalDefinitions.PortableMannedTurret(player.Faction).Item + case dtype => dtype + } + 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 => + log.error(s"DeployObject: nothing, ${player.Name}? It's not a construction tool!") + } } } @@ -622,7 +526,10 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex } } + + def handleGenericCollision(pkt: GenericCollisionMsg): Unit = { + player.BailProtection = false val GenericCollisionMsg(ctype, p, _, _, pv, _, _, _, _, _, _, _) = pkt if (pv.z * pv.z >= (pv.x * pv.x + pv.y * pv.y) * 0.5f) { if (ops.heightTrend) { @@ -634,8 +541,7 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex } (ctype, sessionLogic.validObject(p, decorator = "GenericCollision/Primary")) match { case (CollisionIs.OfInfantry, Some(user: Player)) - if user == player => - player.BailProtection = false + if user == player => () case (CollisionIs.OfGroundVehicle, Some(v: Vehicle)) if v.Seats(0).occupant.contains(player) => v.BailProtection = false @@ -647,7 +553,10 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex } } - def handleAvatarFirstTimeEvent(pkt: AvatarFirstTimeEventMessage): Unit = { /* no speedrunning fte's */ } + def handleAvatarFirstTimeEvent(pkt: AvatarFirstTimeEventMessage): Unit = { + val AvatarFirstTimeEventMessage(_, _, _, eventName) = pkt + avatarActor ! AvatarActor.AddFirstTimeEvent(eventName) + } def handleBugReport(pkt: PlanetSideGamePacket): Unit = { val BugReportMessage( @@ -709,7 +618,25 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex avatarActor ! AvatarActor.MemberListRequest(action, name) } - def handleInvalidTerrain(pkt: InvalidTerrainMessage): Unit = { /* csr does not have to worry about invalid terrain */ } + 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 handleActionCancel(pkt: ActionCancelMessage): Unit = { val ActionCancelMessage(_, _, _) = pkt @@ -722,46 +649,14 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex log.trace(s"${player.Name} wants to trade for some reason - $trade") } - def handleDisplayedAward(pkt: DisplayedAwardMessage): Unit = { - val DisplayedAwardMessage(_, ribbon, bar) = pkt - log.trace(s"${player.Name} changed the $bar displayed award ribbon to $ribbon") - avatarActor ! AvatarActor.SetRibbon(ribbon, bar) - } + def handleDisplayedAward(pkt: DisplayedAwardMessage): Unit = { /* intentionally blank */ } def handleObjectDetected(pkt: ObjectDetectedMessage): Unit = { - val ObjectDetectedMessage(_, _, _, targets) = pkt - sessionLogic.shooting.FindWeapon.foreach { - case weapon if weapon.Projectile.AutoLock => - //projectile with auto-lock instigates a warning on the target - val detectedTargets = sessionLogic.shooting.FindDetectedProjectileTargets(targets) - val mode = 7 + (if (weapon.Projectile == GlobalDefinitions.wasp_rocket_projectile) 1 else 0) - detectedTargets.foreach { target => - continent.AvatarEvents ! AvatarServiceMessage(target, AvatarAction.ProjectileAutoLockAwareness(mode)) - } - case _ => () - } + ops.handleObjectDetected(pkt) } def handleTargetingImplantRequest(pkt: TargetingImplantRequest): Unit = { - val TargetingImplantRequest(list) = pkt - val targetInfo: List[TargetInfo] = list.flatMap { x => - continent.GUID(x.target_guid) match { - case Some(player: Player) => - val health = player.Health.toFloat / player.MaxHealth - val armor = if (player.MaxArmor > 0) { - player.Armor.toFloat / player.MaxArmor - } else { - 0 - } - Some(TargetInfo(player.GUID, health, armor)) - case _ => - log.warn( - s"TargetingImplantRequest: the info that ${player.Name} requested for target ${x.target_guid} is not for a player" - ) - None - } - } - sendResponse(TargetingInfoMessage(targetInfo)) + ops.handleTargetingImplantRequest(pkt) } def handleHitHint(pkt: HitHint): Unit = { @@ -804,8 +699,7 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex } def handleKick(player: Player, time: Option[Long]): Unit = { - ops.administrativeKick(player) - sessionLogic.accountPersistence ! AccountPersistenceService.Kick(player.Name, time) + ops.administrativeKick(player, time) } def handleSilenced(isSilenced: Boolean): Unit = { /* can not be silenced */ } @@ -825,20 +719,31 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex /* supporting functions */ def handleUseDoor(door: Door, equipment: Option[Equipment]): Unit = { - equipment match { - case Some(tool: Tool) if tool.Definition == GlobalDefinitions.medicalapplicator => - door.Actor ! CommonMessages.Use(player, Some(Float.MaxValue)) - case _ => - door.Actor ! CommonMessages.Use(player) + if (player.spectator) { + //opens just for us + val guid = door.GUID + openDoor.collect { + case oldDoor if guid != oldDoor.GUID => + sendResponse(GenericObjectStateMsg(oldDoor.GUID, state=17)) + } + sendResponse(GenericObjectStateMsg(guid, state=16)) + openDoor = Some(door) + } else { + //opens for everyone + equipment match { + case Some(tool: Tool) if tool.Definition == GlobalDefinitions.medicalapplicator => + door.Actor ! CommonMessages.Use(player, Some(Float.MaxValue)) + case _ => + door.Actor ! CommonMessages.Use(player) + } } } private def maxCapacitorTick(): Unit = { if (player.ExoSuit == ExoSuitType.MAX) { player.CapacitorState match { - case CapacitorStateType.ChargeDelay => maxCapacitorTickChargeDelay() - case CapacitorStateType.Charging => maxCapacitorTickCharging() - case _ => maxCapacitorTickIdle() + case CapacitorStateType.Idle => maxCapacitorTickIdle() + case _ => maxCapacitorTickCharging() } } else if (player.CapacitorState != CapacitorStateType.Idle) { player.CapacitorState = CapacitorStateType.Idle @@ -847,16 +752,8 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex private def maxCapacitorTickIdle(): Unit = { if (player.Capacitor < player.ExoSuitDef.MaxCapacitor) { - player.CapacitorState = CapacitorStateType.ChargeDelay - maxCapacitorTickChargeDelay() - } - } - - 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() } } 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 602375a4c..0cff4d5da 100644 --- a/src/main/scala/net/psforever/actors/session/csr/SpectateAsCustomerServiceRepresentativeMode.scala +++ b/src/main/scala/net/psforever/actors/session/csr/SpectateAsCustomerServiceRepresentativeMode.scala @@ -7,7 +7,6 @@ import net.psforever.objects.serverobject.ServerObject import net.psforever.objects.{Session, Vehicle} import net.psforever.packet.PlanetSidePacket import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage} -import net.psforever.services.chat.SpectatorChannel import net.psforever.services.teamwork.{SquadAction, SquadServiceMessage} import net.psforever.types.{ChatMessageType, SquadRequestType} // @@ -33,7 +32,6 @@ class SpectatorCSRModeLogic(data: SessionData) extends ModeLogic { val pguid = player.GUID val sendResponse: PlanetSidePacket=>Unit = data.sendResponse // - continent.actor ! ZoneActor.RemoveFromBlockMap(player) data.vehicles.GetMountableAndSeat(None, player, continent) match { case (Some(obj: Vehicle), Some(seatNum)) if seatNum == 0 => data.vehicles.ServerVehicleOverrideStop(obj) @@ -50,15 +48,12 @@ class SpectatorCSRModeLogic(data: SessionData) extends ModeLogic { continent, SquadAction.Membership(SquadRequestType.Leave, player.CharId, Some(player.CharId), player.Name, None) ) - if (player.silenced) { - data.chat.commandIncomingSilence(session, ChatMsg(ChatMessageType.CMT_SILENCE, "player 0")) - } // player.spectator = true - player.bops = true + //player.bops = true + player.allowInteraction = false + continent.actor ! ZoneActor.RemoveFromBlockMap(player) 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 ON")) } @@ -67,18 +62,16 @@ class SpectatorCSRModeLogic(data: SessionData) extends ModeLogic { val pguid = player.GUID val continent = data.continent val avatarId = player.Definition.ObjectId - val sendResponse: PlanetSidePacket => Unit = data.sendResponse + //val sendResponse: PlanetSidePacket => Unit = data.sendResponse // - data.chat.LeaveChannel(SpectatorChannel) player.spectator = false player.bops = false + player.allowInteraction = true + data.continent.actor ! ZoneActor.AddToBlockMap(player, player.Position) 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_225, "CSR SPECTATOR MODE OFF")) } } 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 f5b42446e..8f6ee7605 100644 --- a/src/main/scala/net/psforever/actors/session/csr/TerminalHandlerLogic.scala +++ b/src/main/scala/net/psforever/actors/session/csr/TerminalHandlerLogic.scala @@ -9,7 +9,7 @@ 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.Terminal +import net.psforever.objects.serverobject.terminals.{OrderTerminalDefinition, Terminal} import net.psforever.objects.sourcing.AmenitySource import net.psforever.objects.vital.TerminalUsedActivity import net.psforever.packet.game.{FavoritesRequest, ItemTransactionMessage, ItemTransactionResultMessage, ProximityTerminalUseMessage, UnuseItemMessage} @@ -28,13 +28,25 @@ class TerminalHandlerLogic(val ops: SessionTerminalHandlers, implicit val contex private val avatarActor: typed.ActorRef[AvatarActor.Command] = ops.avatarActor def handleItemTransaction(pkt: ItemTransactionMessage): Unit = { - 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 _ => - sessionLogic.zoning.CancelZoningProcess() - ops.handleItemTransaction(pkt) + if ( player.spectator) { + val ItemTransactionMessage(terminal_guid, _, _, _, _, _) = pkt + sessionLogic.zoning.CancelZoningProcess() + continent + .GUID(terminal_guid) + .collect { case t: Terminal => t.Definition } + .collect { case t: OrderTerminalDefinition => t } + .map(t => t.Request(player, pkt)) + .collect { + case order: Terminal.BuyVehicle => + //do not handle transaction + order + } + .orElse { + ops.handleItemTransaction(pkt) + None + } + } else { + ops.handleItemTransaction(pkt) } } 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 afd433b85..27c0baeb2 100644 --- a/src/main/scala/net/psforever/actors/session/csr/VehicleLogic.scala +++ b/src/main/scala/net/psforever/actors/session/csr/VehicleLogic.scala @@ -13,7 +13,7 @@ import net.psforever.objects.zones.Zone 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} +import net.psforever.types.{DriveState, PlanetSideGUID, Vector3} object VehicleLogic { def apply(ops: VehicleOperations): VehicleLogic = { @@ -347,11 +347,26 @@ class VehicleLogic(val ops: VehicleOperations, implicit val context: ActorContex private def topOffHealth(vehicle: Vehicle): Unit = { topOffHealthOfPlayer() //vehicle below half health, full heal + val guid = vehicle.GUID val maxHealthOfVehicle = vehicle.MaxHealth.toLong - if (vehicle.Health < maxHealthOfVehicle * 0.5f) { + if (vehicle.Health < maxHealthOfVehicle) { vehicle.Health = maxHealthOfVehicle.toInt - sendResponse(PlanetsideAttributeMessage(vehicle.GUID, 0, maxHealthOfVehicle)) - continent.AvatarEvents ! AvatarServiceMessage(continent.id, AvatarAction.PlanetsideAttribute(vehicle.GUID, 0, maxHealthOfVehicle)) + sendResponse(PlanetsideAttributeMessage(guid, 0, maxHealthOfVehicle)) + continent.VehicleEvents ! VehicleServiceMessage( + continent.id, + VehicleAction.PlanetsideAttribute(PlanetSideGUID(0), guid, 0, maxHealthOfVehicle) + ) + } + //vehicle shields below half, full shields + val maxShieldsOfVehicle = vehicle.MaxShields.toLong + val shieldsUi = vehicle.Definition.shieldUiAttribute + if (vehicle.Shields < maxShieldsOfVehicle) { + vehicle.Shields = maxShieldsOfVehicle.toInt + sendResponse(PlanetsideAttributeMessage(guid, shieldsUi, maxShieldsOfVehicle)) + continent.VehicleEvents ! VehicleServiceMessage( + continent.id, + VehicleAction.PlanetsideAttribute(PlanetSideGUID(0), guid, shieldsUi, 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 a99067d89..cf01a73d0 100644 --- a/src/main/scala/net/psforever/actors/session/csr/WeaponAndProjectileLogic.scala +++ b/src/main/scala/net/psforever/actors/session/csr/WeaponAndProjectileLogic.scala @@ -126,7 +126,7 @@ class WeaponAndProjectileLogic(val ops: WeaponAndProjectileOperations, implicit val LongRangeProjectileInfoMessage(guid, _, _) = pkt ops.FindContainedWeapon match { case (Some(_: Vehicle), weapons) - if weapons.exists { _.GUID == guid } => () //now what? + if weapons.exists(_.GUID == guid) => () //now what? case _ => () } } @@ -140,26 +140,19 @@ class WeaponAndProjectileLogic(val ops: WeaponAndProjectileOperations, implicit } //... 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)) - } + ops.handleProxyDamage(pkt.projectile_guid, pkt.hit_info.map(_.hit_pos).getOrElse(Vector3.Zero)) } } } def handleSplashHit(pkt: SplashHitMessage): Unit = { 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) { + if (!player.spectator) { + 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) @@ -177,11 +170,6 @@ class WeaponAndProjectileLogic(val ops: WeaponAndProjectileOperations, implicit 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 ( profile.HasJammedEffectDuration || profile.JammerProjectile || @@ -197,10 +185,12 @@ class WeaponAndProjectileLogic(val ops: WeaponAndProjectileOperations, implicit SpecialEmp.findAllBoomers(profile.DamageRadius) ) } + if (profile.ExistsOnRemoteClients && projectile.HasGUID) { + continent.Projectile ! ZoneProjectile.Remove(projectileGuid) + } } - if (profile.ExistsOnRemoteClients && projectile.HasGUID) { - continent.Projectile ! ZoneProjectile.Remove(projectileGuid) - } + //... + ops.handleProxyDamage(pkt.projectile_uid, pkt.projectile_pos) } } diff --git a/src/main/scala/net/psforever/actors/session/normal/AvatarHandlerLogic.scala b/src/main/scala/net/psforever/actors/session/normal/AvatarHandlerLogic.scala index 890cd27be..0a1c863f8 100644 --- a/src/main/scala/net/psforever/actors/session/normal/AvatarHandlerLogic.scala +++ b/src/main/scala/net/psforever/actors/session/normal/AvatarHandlerLogic.scala @@ -3,6 +3,7 @@ package net.psforever.actors.session.normal import akka.actor.{ActorContext, typed} import net.psforever.actors.session.support.AvatarHandlerFunctions +import net.psforever.objects.Default import net.psforever.objects.serverobject.containable.ContainableBehavior import net.psforever.packet.game.{AvatarImplantMessage, CreateShortcutMessage, ImplantAction} import net.psforever.types.ImplantType @@ -21,7 +22,7 @@ import net.psforever.objects.vital.etc.ExplodingEntityReason import net.psforever.objects.zones.Zoning import net.psforever.packet.game.objectcreate.ObjectCreateMessageParent import net.psforever.packet.game.{ArmorChangedMessage, AvatarDeadStateMessage, ChangeAmmoMessage, ChangeFireModeMessage, ChangeFireStateMessage_Start, ChangeFireStateMessage_Stop, ChatMsg, DeadState, DestroyMessage, DrowningTarget, GenericActionMessage, GenericObjectActionMessage, HitHint, ItemTransactionResultMessage, ObjectCreateDetailedMessage, ObjectCreateMessage, ObjectDeleteMessage, ObjectHeldMessage, OxygenStateMessage, PlanetsideAttributeMessage, PlayerStateMessage, ProjectileStateMessage, ReloadMessage, SetEmpireMessage, UseItemMessage, WeaponDryFireMessage} -import net.psforever.services.avatar.{AvatarAction, AvatarResponse, AvatarServiceMessage} +import net.psforever.services.avatar.AvatarResponse import net.psforever.services.Service import net.psforever.types.{ChatMessageType, PlanetSideGUID, TransactionType, Vector3} import net.psforever.util.Config @@ -510,7 +511,10 @@ class AvatarHandlerLogic(val ops: SessionAvatarHandlers, implicit val context: A sessionLogic.zoning.spawn.shiftPosition = Some(player.Position) //respawn + val respawnTimer = 300000 //milliseconds + sendResponse(AvatarDeadStateMessage(DeadState.Dead, respawnTimer, respawnTimer, player.Position, player.Faction, unk5=true)) sessionLogic.zoning.spawn.reviveTimer.cancel() + sessionLogic.zoning.spawn.reviveTimer = Default.Cancellable if (player.death_by == 0) { sessionLogic.zoning.spawn.randomRespawn(300.seconds) } else { @@ -523,16 +527,7 @@ class AvatarHandlerLogic(val ops: SessionAvatarHandlers, implicit val context: A case AvatarResponse.Revive(revivalTargetGuid) if resolvedPlayerGuid == revivalTargetGuid => log.info(s"No time for rest, ${player.Name}. Back on your feet!") - sessionLogic.zoning.spawn.reviveTimer.cancel() - sessionLogic.zoning.spawn.deadState = DeadState.Alive - player.Revive - val health = player.Health - sendResponse(PlanetsideAttributeMessage(revivalTargetGuid, attribute_type=0, health)) - sendResponse(AvatarDeadStateMessage(DeadState.Alive, timer_max=0, timer=0, player.Position, player.Faction, unk5=true)) - continent.AvatarEvents ! AvatarServiceMessage( - continent.id, - AvatarAction.PlanetsideAttributeToAll(revivalTargetGuid, attribute_type=0, health) - ) + ops.revive(revivalTargetGuid) /* uncommon messages (utility, or once in a while) */ case AvatarResponse.ChangeAmmo(weapon_guid, weapon_slot, previous_guid, ammo_id, ammo_guid, ammo_data) 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 4d74b4e08..9c402a6fd 100644 --- a/src/main/scala/net/psforever/actors/session/normal/ChatLogic.scala +++ b/src/main/scala/net/psforever/actors/session/normal/ChatLogic.scala @@ -6,10 +6,10 @@ 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.packet.game.{ChatMsg, ServerType, SetChatFilterMessage} import net.psforever.services.chat.DefaultChannel import net.psforever.types.ChatMessageType +import net.psforever.util.Config object ChatLogic { def apply(ops: ChatOperations): ChatLogic = { @@ -155,7 +155,13 @@ 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 + val currentSpectatorActivation = { + if (avatar != null) { + avatar.permissions.canSpectate || avatar.permissions.canGM + } else { + false + } + } || Config.app.world.serverType == ServerType.Development contents.toLowerCase() match { case "on" | "o" | "" if currentSpectatorActivation && !player.spectator => context.self ! SessionActor.SetMode(ops.SpectatorMode) @@ -164,12 +170,20 @@ class ChatLogic(val ops: ChatOperations, implicit val context: ActorContext) ext } 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 _ => () + if (sessionLogic.zoning.maintainInitialGmState) { + sessionLogic.zoning.maintainInitialGmState = false + } else { + val currentCsrActivation = (if (avatar != null) { + avatar.permissions.canGM + } else { + false + }) || Config.app.world.serverType == ServerType.Development + 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 c89179572..627cf62d3 100644 --- a/src/main/scala/net/psforever/actors/session/normal/GeneralLogic.scala +++ b/src/main/scala/net/psforever/actors/session/normal/GeneralLogic.scala @@ -5,7 +5,6 @@ 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.{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 @@ -37,7 +36,7 @@ import net.psforever.objects.vital.etc.SuicideReason import net.psforever.objects.vital.interaction.DamageInteraction import net.psforever.objects.zones.{ZoneProjectile, Zoning} import net.psforever.packet.PlanetSideGamePacket -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.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, InvalidTerrainMessage, LootItemMessage, MoveItemMessage, ObjectDetectedMessage, ObjectHeldMessage, PickupItemMessage, PlanetsideAttributeMessage, PlayerStateMessageUpstream, RequestDestroyMessage, TargetingImplantRequest, 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.support.CaptureFlagManager @@ -203,34 +202,25 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex } def handleDropItem(pkt: DropItemMessage): Unit = { - val DropItemMessage(itemGuid) = pkt - (sessionLogic.validObject(itemGuid, decorator = "DropItem"), player.FreeHand.Equipment) match { - case (Some(anItem: Equipment), Some(heldItem)) - if (anItem eq heldItem) && continent.GUID(player.VehicleSeated).nonEmpty => + ops.handleDropItem(pkt) match { + case GeneralOperations.ItemDropState.Dropped => sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use") - RemoveOldEquipmentFromInventory(player)(heldItem) - case (Some(anItem: Equipment), Some(heldItem)) - if anItem eq heldItem => - sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use") - DropEquipmentFromInventory(player)(heldItem) - case (Some(anItem: Equipment), _) + case GeneralOperations.ItemDropState.NotDropped if continent.GUID(player.VehicleSeated).isEmpty => - //suppress the warning message if in a vehicle - log.warn(s"DropItem: ${player.Name} wanted to drop a ${anItem.Definition.Name}, but it wasn't at hand") - case (Some(obj), _) => - log.warn(s"DropItem: ${player.Name} wanted to drop a ${obj.Definition.Name}, but it was not equipment") + log.warn(s"DropItem: ${player.Name} wanted to drop an item, but it wasn't at hand") + case GeneralOperations.ItemDropState.NotDropped => + log.warn(s"DropItem: ${player.Name} wanted to drop an item, but it was not equipment") case _ => () } } def handlePickupItem(pkt: PickupItemMessage): Unit = { - val PickupItemMessage(itemGuid, _, _, _) = pkt - sessionLogic.validObject(itemGuid, decorator = "PickupItem").collect { - case item: Equipment if player.Fit(item).nonEmpty => + ops.handlePickupItem(pkt) match { + case GeneralOperations.ItemPickupState.PickedUp => sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_use") - PickUpEquipmentFromGround(player)(item) - case _: Equipment => + case GeneralOperations.ItemPickupState.Dropped => sendResponse(ActionResultMessage.Fail(16)) //error code? + case _ => () } } @@ -246,33 +236,17 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex } def handleZipLine(pkt: ZipLineMessage): Unit = { - val ZipLineMessage(playerGuid, forwards, action, pathId, pos) = pkt - continent.zipLinePaths.find(x => x.PathId == pathId) match { - case Some(path) if path.IsTeleporter => + ops.handleZipLine(pkt) match { + case GeneralOperations.ZiplineBehavior.Teleporter => sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel") - val endPoint = path.ZipLinePoints.last - sendResponse(ZipLineMessage(PlanetSideGUID(0), forwards, 0, pathId, pos)) - //todo: send to zone to show teleport animation to all clients - sendResponse(PlayerStateShiftMessage(ShiftState(0, endPoint, (player.Orientation.z + player.FacingYawUpper) % 360f, None))) - case Some(_) => + case GeneralOperations.ZiplineBehavior.Zipline => sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_motion") - action match { - case 0 => - //travel along the zipline in the direction specified - sendResponse(ZipLineMessage(playerGuid, forwards, action, pathId, pos)) - case 1 => - //disembark from zipline at destination - sendResponse(ZipLineMessage(playerGuid, forwards, action, 0, pos)) - case 2 => - //get off by force - sendResponse(ZipLineMessage(playerGuid, forwards, action, 0, pos)) - case _ => - log.warn( - s"${player.Name} tried to do something with a zipline but can't handle it. forwards: $forwards action: $action pathId: $pathId zone: ${continent.Number} / ${continent.id}" - ) - } - case _ => - log.warn(s"${player.Name} couldn't find a zipline path $pathId in zone ${continent.id}") + case GeneralOperations.ZiplineBehavior.Unsupported => + log.warn( + s"${player.Name} tried to do something with a zipline but can't handle it. action: ${pkt.action}, pathId: ${pkt.path_id}, zone: ${continent.id}" + ) + case GeneralOperations.ZiplineBehavior.NotFound => + log.warn(s"${player.Name} couldn't find a zipline path ${pkt.path_id} in zone ${continent.id}") } } @@ -281,9 +255,8 @@ 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) => - /* line 1a: player is admin (and overrules other access requirements) */ - /* line 1b: vehicle and player (as the owner) acknowledge each other */ - /* line 1c: vehicle is the same faction as player, is ownable, and either the owner is absent or the vehicle is destroyed */ + /* line 1a: vehicle and player (as the owner) acknowledge each other */ + /* line 1b: vehicle is the same faction as player, is ownable, and either the owner is absent or the vehicle is destroyed */ /* line 2: vehicle is not mounted in anything or, if it is, its seats are empty */ if ( ((avatar.vehicle.contains(objectGuid) && vehicle.OwnerGuid.contains(player.GUID)) || @@ -334,99 +307,20 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex } def handleMoveItem(pkt: MoveItemMessage): Unit = { - val MoveItemMessage(itemGuid, sourceGuid, destinationGuid, dest, _) = pkt - ( - continent.GUID(sourceGuid), - continent.GUID(destinationGuid), - sessionLogic.validObject(itemGuid, decorator = "MoveItem") - ) match { - case ( - Some(source: PlanetSideServerObject with Container), - Some(destination: PlanetSideServerObject with Container), - Some(item: Equipment) - ) => - ContainableMoveItem(player.Name, source, destination, item, destination.SlotMapResolution(dest)) - case (None, _, _) => - log.error( - s"MoveItem: ${player.Name} wanted to move $itemGuid from $sourceGuid, but could not find source object" - ) - case (_, None, _) => - log.error( - s"MoveItem: ${player.Name} wanted to move $itemGuid to $destinationGuid, but could not find destination object" - ) - case (_, _, None) => () - case _ => - log.error( - s"MoveItem: ${player.Name} wanted to move $itemGuid from $sourceGuid to $destinationGuid, but multiple problems were encountered" - ) - } + ops.handleMoveItem(pkt) } def handleLootItem(pkt: LootItemMessage): Unit = { - val LootItemMessage(itemGuid, targetGuid) = pkt - (sessionLogic.validObject(itemGuid, decorator = "LootItem"), continent.GUID(targetGuid)) match { - case (Some(item: Equipment), Some(destination: PlanetSideServerObject with Container)) => - //figure out the source - ( - { - val findFunc: PlanetSideServerObject with Container => Option[ - (PlanetSideServerObject with Container, Option[Int]) - ] = ops.findInLocalContainer(itemGuid) - findFunc(player.avatar.locker) - .orElse(findFunc(player)) - .orElse(ops.accessedContainer match { - case Some(parent: PlanetSideServerObject) => - findFunc(parent) - case _ => - None - }) - }, - destination.Fit(item) - ) match { - case (Some((source, Some(_))), Some(dest)) => - ContainableMoveItem(player.Name, source, destination, item, dest) - case (None, _) => - log.error(s"LootItem: ${player.Name} can not find where $item is put currently") - case (_, None) => - log.error(s"LootItem: ${player.Name} can not find anywhere to put $item in $destination") - case _ => - log.error( - s"LootItem: ${player.Name}wanted to move $itemGuid to $targetGuid, but multiple problems were encountered" - ) - } - case (Some(obj), _) => - log.error(s"LootItem: item $obj is (probably) not lootable to ${player.Name}") - case (None, _) => () - case (_, None) => - log.error(s"LootItem: ${player.Name} can not find where to put $itemGuid") - } + ops.handleLootItem(pkt) } def handleAvatarImplant(pkt: AvatarImplantMessage): Unit = { - val AvatarImplantMessage(_, action, slot, status) = pkt - if (action == ImplantAction.Activation) { - if (sessionLogic.zoning.zoningStatus == Zoning.Status.Deconstructing) { - //do not activate; play deactivation sound instead - sessionLogic.zoning.spawn.stopDeconstructing() - avatar.implants(slot).collect { - case implant if implant.active => - avatarActor ! AvatarActor.DeactivateImplant(implant.definition.implantType) - case implant => - sendResponse(PlanetsideAttributeMessage(player.GUID, 28, implant.definition.implantType.value * 2)) - } - } else { + ops.handleAvatarImplant(pkt) match { + case GeneralOperations.ImplantActivationBehavior.Activate | GeneralOperations.ImplantActivationBehavior.Deactivate => sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_implant") - avatar.implants(slot) match { - case Some(implant) => - if (status == 1) { - avatarActor ! AvatarActor.ActivateImplant(implant.definition.implantType) - } else { - avatarActor ! AvatarActor.DeactivateImplant(implant.definition.implantType) - } - case _ => - log.error(s"AvatarImplantMessage: ${player.Name} has an unknown implant in $slot") - } - } + case GeneralOperations.ImplantActivationBehavior.NotFound => + log.error(s"AvatarImplantMessage: ${player.Name} has an unknown implant in ${pkt.implantSlot}") + case _ => () } } @@ -729,7 +623,8 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex } val curr = System.currentTimeMillis() (target1, t, target2) match { - case (None, _, _) => () + case (None, _, _) => + () case (Some(us: PlanetSideServerObject with Vitality with FactionAffinity), PlanetSideGUID(0), _) => if (updateCollisionHistoryForTarget(us, curr)) { @@ -824,13 +719,7 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex } def handleCreateShortcut(pkt: CreateShortcutMessage): Unit = { - val CreateShortcutMessage(_, slot, shortcutOpt) = pkt - shortcutOpt match { - case Some(shortcut) => - avatarActor ! AvatarActor.AddShortcut(slot - 1, shortcut) - case None => - avatarActor ! AvatarActor.RemoveShortcut(slot - 1) - } + ops.handleCreateShortcut(pkt) } def handleChangeShortcutBank(pkt: ChangeShortcutBankMessage): Unit = { @@ -880,39 +769,11 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex } def handleObjectDetected(pkt: ObjectDetectedMessage): Unit = { - val ObjectDetectedMessage(_, _, _, targets) = pkt - sessionLogic.shooting.FindWeapon.foreach { - case weapon if weapon.Projectile.AutoLock => - //projectile with auto-lock instigates a warning on the target - val detectedTargets = sessionLogic.shooting.FindDetectedProjectileTargets(targets) - val mode = 7 + (if (weapon.Projectile == GlobalDefinitions.wasp_rocket_projectile) 1 else 0) - detectedTargets.foreach { target => - continent.AvatarEvents ! AvatarServiceMessage(target, AvatarAction.ProjectileAutoLockAwareness(mode)) - } - case _ => () - } + ops.handleObjectDetected(pkt) } def handleTargetingImplantRequest(pkt: TargetingImplantRequest): Unit = { - val TargetingImplantRequest(list) = pkt - val targetInfo: List[TargetInfo] = list.flatMap { x => - continent.GUID(x.target_guid) match { - case Some(player: Player) => - val health = player.Health.toFloat / player.MaxHealth - val armor = if (player.MaxArmor > 0) { - player.Armor.toFloat / player.MaxArmor - } else { - 0 - } - Some(TargetInfo(player.GUID, health, armor)) - case _ => - log.warn( - s"TargetingImplantRequest: the info that ${player.Name} requested for target ${x.target_guid} is not for a player" - ) - None - } - } - sendResponse(TargetingInfoMessage(targetInfo)) + ops.handleTargetingImplantRequest(pkt) } def handleHitHint(pkt: HitHint): Unit = { @@ -967,13 +828,10 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex session = session.copy(flying = flying) } - def handleSetSpectator(spectator: Boolean): Unit = { - session.player.spectator = spectator - } + def handleSetSpectator(spectator: Boolean): Unit = { /* normal players can not flag spectate */ } def handleKick(player: Player, time: Option[Long]): Unit = { - ops.administrativeKick(player) - sessionLogic.accountPersistence ! AccountPersistenceService.Kick(player.Name, time) + ops.administrativeKick(player, time) } def handleSilenced(isSilenced: Boolean): Unit = { 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 295da46fb..69824b026 100644 --- a/src/main/scala/net/psforever/actors/session/normal/WeaponAndProjectileLogic.scala +++ b/src/main/scala/net/psforever/actors/session/normal/WeaponAndProjectileLogic.scala @@ -145,7 +145,7 @@ class WeaponAndProjectileLogic(val ops: WeaponAndProjectileOperations, implicit val LongRangeProjectileInfoMessage(guid, _, _) = pkt ops.FindContainedWeapon match { case (Some(_: Vehicle), weapons) - if weapons.exists { _.GUID == guid } => () //now what? + if weapons.exists(_.GUID == guid) => () //now what? case _ => () } } @@ -160,16 +160,8 @@ class WeaponAndProjectileLogic(val ops: WeaponAndProjectileOperations, implicit } //... 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.handleProxyDamage(pkt.projectile_guid, pkt.hit_info.map(_.hit_pos).getOrElse(Vector3.Zero)).foreach { + case (target, proxy, hitPos, _) => ops.checkForHitPositionDiscrepancy(proxy.GUID, hitPos, target) } } @@ -192,25 +184,15 @@ class WeaponAndProjectileLogic(val ops: WeaponAndProjectileOperations, implicit val (direct, others) = list.partition { case (_, _, hitPos, targetPos) => hitPos == targetPos } direct.foreach { case (target, _, hitPos, _) => - ops.checkForHitPositionDiscrepancy(projectile.GUID, hitPos, target) + ops.checkForHitPositionDiscrepancy(projectileGuid, hitPos, target) ops.resolveProjectileInteraction(target, projectile, resolution1, hitPos) } others.foreach { case (target, _, hitPos, _) => - ops.checkForHitPositionDiscrepancy(projectile.GUID, hitPos, target) + ops.checkForHitPositionDiscrepancy(projectileGuid, 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) - } - if (profile == GlobalDefinitions.oicw_projectile) { - ops.performLittleBuddyExplosion(proxyList.map(_._2)) - } - } - //... if ( profile.HasJammedEffectDuration || profile.JammerProjectile || @@ -231,6 +213,11 @@ class WeaponAndProjectileLogic(val ops: WeaponAndProjectileOperations, implicit continent.Projectile ! ZoneProjectile.Remove(projectile.GUID) } } + //... + ops.handleProxyDamage(pkt.projectile_uid, pkt.projectile_pos).foreach { + case (target, proxy, hitPos, _) => + ops.checkForHitPositionDiscrepancy(proxy.GUID, hitPos, target) + } } def handleLashHit(pkt: LashMessage): Unit = { 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 4f5fbe3d3..ce287f6fc 100644 --- a/src/main/scala/net/psforever/actors/session/spectator/ChatLogic.scala +++ b/src/main/scala/net/psforever/actors/session/spectator/ChatLogic.scala @@ -34,6 +34,7 @@ class ChatLogic(val ops: ChatOperations, implicit val context: ActorContext) ext case (CMT_ANONYMOUS, _, _) => () case (CMT_TOGGLE_GM, _, _) => () + sessionLogic.zoning.maintainInitialGmState = false case (CMT_CULLWATERMARK, _, contents) => ops.commandWatermark(contents) 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 c522b2f3c..28e89a6c3 100644 --- a/src/main/scala/net/psforever/actors/session/spectator/GeneralLogic.scala +++ b/src/main/scala/net/psforever/actors/session/spectator/GeneralLogic.scala @@ -13,10 +13,10 @@ import net.psforever.objects.serverobject.doors.Door 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.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, RequestDestroyMessage, TargetingImplantRequest, TradeMessage, UnuseItemMessage, UseItemMessage, VoiceHostInfo, VoiceHostRequest, ZipLineMessage} import net.psforever.services.account.AccountPersistenceService import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage} -import net.psforever.types.{ExoSuitType, PlanetSideGUID, Vector3} +import net.psforever.types.{ExoSuitType, Vector3} object GeneralLogic { def apply(ops: GeneralOperations): GeneralLogic = { @@ -101,31 +101,15 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex def handleAvatarJump(pkt: AvatarJumpMessage): Unit = { /* intentionally blank */ } def handleZipLine(pkt: ZipLineMessage): Unit = { - val ZipLineMessage(playerGuid, forwards, action, pathId, pos) = pkt - continent.zipLinePaths.find(x => x.PathId == pathId) match { - case Some(path) if path.IsTeleporter => - val endPoint = path.ZipLinePoints.last - sendResponse(ZipLineMessage(PlanetSideGUID(0), forwards, 0, pathId, pos)) - //todo: send to zone to show teleport animation to all clients - sendResponse(PlayerStateShiftMessage(ShiftState(0, endPoint, (player.Orientation.z + player.FacingYawUpper) % 360f, None))) - case Some(_) => - action match { - case 0 => - //travel along the zipline in the direction specified - sendResponse(ZipLineMessage(playerGuid, forwards, action, pathId, pos)) - case 1 => - //disembark from zipline at destination! - sendResponse(ZipLineMessage(playerGuid, forwards, action, 0, pos)) - case 2 => - //get off by force - sendResponse(ZipLineMessage(playerGuid, forwards, action, 0, pos)) - case _ => - log.warn( - s"${player.Name} tried to do something with a zipline but can't handle it. forwards: $forwards action: $action pathId: $pathId zone: ${continent.Number} / ${continent.id}" - ) - } - case _ => - log.warn(s"${player.Name} couldn't find a zipline path $pathId in zone ${continent.id}") + ops.handleZipLine(pkt) match { + case GeneralOperations.ZiplineBehavior.Teleporter | GeneralOperations.ZiplineBehavior.Zipline => + sessionLogic.zoning.CancelZoningProcess() + case GeneralOperations.ZiplineBehavior.Unsupported => + log.warn( + s"${player.Name} tried to do something with a zipline but can't handle it. action: ${pkt.action}, pathId: ${pkt.path_id}, zone: ${continent.id}" + ) + case GeneralOperations.ZiplineBehavior.NotFound => + log.warn(s"${player.Name} couldn't find a zipline path ${pkt.path_id} in zone ${continent.id}") } } @@ -359,34 +343,14 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex def handleTrade(pkt: TradeMessage): Unit = { /* intentionally blank */ } - def handleDisplayedAward(pkt: DisplayedAwardMessage): Unit = { - val DisplayedAwardMessage(_, ribbon, bar) = pkt - log.trace(s"${player.Name} changed the $bar displayed award ribbon to $ribbon") - avatarActor ! AvatarActor.SetRibbon(ribbon, bar) + def handleDisplayedAward(pkt: DisplayedAwardMessage): Unit = { /* intentionally blank */ } + + def handleObjectDetected(pkt: ObjectDetectedMessage): Unit = { + ops.handleObjectDetected(pkt) } - def handleObjectDetected(pkt: ObjectDetectedMessage): Unit = { /* intentionally blank */ } - def handleTargetingImplantRequest(pkt: TargetingImplantRequest): Unit = { - val TargetingImplantRequest(list) = pkt - val targetInfo: List[TargetInfo] = list.flatMap { x => - continent.GUID(x.target_guid) match { - case Some(player: Player) => - val health = player.Health.toFloat / player.MaxHealth - val armor = if (player.MaxArmor > 0) { - player.Armor.toFloat / player.MaxArmor - } else { - 0 - } - Some(TargetInfo(player.GUID, health, armor)) - case _ => - log.warn( - s"TargetingImplantRequest: the info that ${player.Name} requested for target ${x.target_guid} is not for a player" - ) - None - } - } - sendResponse(TargetingInfoMessage(targetInfo)) + ops.handleTargetingImplantRequest(pkt) } def handleHitHint(pkt: HitHint): Unit = { /* intentionally blank */ } @@ -434,8 +398,7 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex } def handleKick(player: Player, time: Option[Long]): Unit = { - administrativeKick(player) - sessionLogic.accountPersistence ! AccountPersistenceService.Kick(player.Name, time) + ops.administrativeKick(player, None) } def handleSilenced(isSilenced: Boolean): Unit = { @@ -450,12 +413,6 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex /* supporting functions */ - private def administrativeKick(tplayer: Player): Unit = { - log.warn(s"${tplayer.Name} has been kicked by ${player.Name}") - tplayer.death_by = -1 - sessionLogic.accountPersistence ! AccountPersistenceService.Kick(tplayer.Name) - } - private def customImplantOff(slot: Int, implant: Implant): Unit = { customImplants = customImplants.updated(slot, implant.copy(active = false)) sendResponse(AvatarImplantMessage(player.GUID, ImplantAction.Activation, slot, 0)) 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 95bfdf352..536529dbd 100644 --- a/src/main/scala/net/psforever/actors/session/spectator/VehicleHandlerLogic.scala +++ b/src/main/scala/net/psforever/actors/session/spectator/VehicleHandlerLogic.scala @@ -4,10 +4,9 @@ package net.psforever.actors.session.spectator 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.{GlobalDefinitions, 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} @@ -151,9 +150,6 @@ class VehicleHandlerLogic(val ops: SessionVehicleHandlers, implicit val context: 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 @@ -169,14 +165,11 @@ class VehicleHandlerLogic(val ops: SessionVehicleHandlers, implicit val context: //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 { + 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" + case _ => () } - 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 @@ -194,11 +187,6 @@ class VehicleHandlerLogic(val ops: SessionVehicleHandlers, implicit val context: 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)) @@ -211,10 +199,6 @@ class VehicleHandlerLogic(val ops: SessionVehicleHandlers, implicit val context: 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)) @@ -226,13 +210,6 @@ class VehicleHandlerLogic(val ops: SessionVehicleHandlers, implicit val context: 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) @@ -263,100 +240,9 @@ class VehicleHandlerLogic(val ops: SessionVehicleHandlers, implicit val context: 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) => - 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 { - 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 => @@ -384,16 +270,4 @@ class VehicleHandlerLogic(val ops: SessionVehicleHandlers, implicit val context: 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/support/GeneralOperations.scala b/src/main/scala/net/psforever/actors/session/support/GeneralOperations.scala index 24e68571a..f0d313547 100644 --- a/src/main/scala/net/psforever/actors/session/support/GeneralOperations.scala +++ b/src/main/scala/net/psforever/actors/session/support/GeneralOperations.scala @@ -183,6 +183,217 @@ class GeneralOperations( private[session] var progressBarUpdate: Cancellable = Default.Cancellable private var charSavedTimer: Cancellable = Default.Cancellable + def handleDropItem(pkt: DropItemMessage): GeneralOperations.ItemDropState.Behavior = { + val DropItemMessage(itemGuid) = pkt + (sessionLogic.validObject(itemGuid, decorator = "DropItem"), player.FreeHand.Equipment) match { + case (Some(anItem: Equipment), Some(heldItem)) + if (anItem eq heldItem) && continent.GUID(player.VehicleSeated).nonEmpty => + RemoveOldEquipmentFromInventory(player)(heldItem) + GeneralOperations.ItemDropState.Dropped + case (Some(anItem: Equipment), Some(heldItem)) + if anItem eq heldItem => + DropEquipmentFromInventory(player)(heldItem) + GeneralOperations.ItemDropState.Dropped + case (Some(_), _) => + GeneralOperations.ItemDropState.NotDropped + case _ => + GeneralOperations.ItemDropState.NotFound + } + } + + def handlePickupItem(pkt: PickupItemMessage): GeneralOperations.ItemPickupState.Behavior = { + val PickupItemMessage(itemGuid, _, _, _) = pkt + sessionLogic.validObject(itemGuid, decorator = "PickupItem") match { + case Some(item: Equipment) + if player.Fit(item).nonEmpty => + PickUpEquipmentFromGround(player)(item) + GeneralOperations.ItemPickupState.PickedUp + case Some(_: Equipment) => + GeneralOperations.ItemPickupState.Dropped + case _ => + GeneralOperations.ItemPickupState.NotFound + } + } + + def handleZipLine(pkt: ZipLineMessage): GeneralOperations.ZiplineBehavior.Behavior = { + val ZipLineMessage(playerGuid, forwards, action, pathId, pos) = pkt + continent.zipLinePaths.find(x => x.PathId == pathId) match { + case Some(path) if path.IsTeleporter => + val endPoint = path.ZipLinePoints.last + sendResponse(ZipLineMessage(PlanetSideGUID(0), forwards, 0, pathId, pos)) + //todo: send to zone to show teleport animation to all clients + sendResponse(PlayerStateShiftMessage(ShiftState(0, endPoint, (player.Orientation.z + player.FacingYawUpper) % 360f, None))) + GeneralOperations.ZiplineBehavior.Teleporter + case Some(_) => + //todo: send to zone to show zipline animation to all clients + action match { + case 0 => + //travel along the zipline in the direction specified + sendResponse(ZipLineMessage(playerGuid, forwards, action, pathId, pos)) + GeneralOperations.ZiplineBehavior.Zipline + case 1 => + //disembark from zipline at destination + sendResponse(ZipLineMessage(playerGuid, forwards, action, 0, pos)) + GeneralOperations.ZiplineBehavior.Zipline + case 2 => + //get off by force + sendResponse(ZipLineMessage(playerGuid, forwards, action, 0, pos)) + GeneralOperations.ZiplineBehavior.Zipline + case _ => + GeneralOperations.ZiplineBehavior.Unsupported + } + case _ => + GeneralOperations.ZiplineBehavior.NotFound + } + } + + def handleMoveItem(pkt: MoveItemMessage): Unit = { + val MoveItemMessage(itemGuid, sourceGuid, destinationGuid, dest, _) = pkt + ( + continent.GUID(sourceGuid), + continent.GUID(destinationGuid), + sessionLogic.validObject(itemGuid, decorator = "MoveItem") + ) match { + case ( + Some(source: PlanetSideServerObject with Container), + Some(destination: PlanetSideServerObject with Container), + Some(item: Equipment) + ) => + ContainableMoveItem(player.Name, source, destination, item, destination.SlotMapResolution(dest)) + case (None, _, _) => + log.error( + s"MoveItem: ${player.Name} wanted to move $itemGuid from $sourceGuid, but could not find source object" + ) + case (_, None, _) => + log.error( + s"MoveItem: ${player.Name} wanted to move $itemGuid to $destinationGuid, but could not find destination object" + ) + case (_, _, None) => () + case _ => + log.error( + s"MoveItem: ${player.Name} wanted to move $itemGuid from $sourceGuid to $destinationGuid, but multiple problems were encountered" + ) + } + } + + def handleLootItem(pkt: LootItemMessage): Unit = { + val LootItemMessage(itemGuid, targetGuid) = pkt + (sessionLogic.validObject(itemGuid, decorator = "LootItem"), continent.GUID(targetGuid)) match { + case (Some(item: Equipment), Some(destination: PlanetSideServerObject with Container)) => + //figure out the source + ( + { + val findFunc: PlanetSideServerObject with Container => Option[ + (PlanetSideServerObject with Container, Option[Int]) + ] = findInLocalContainer(itemGuid) + findFunc(player.avatar.locker) + .orElse(findFunc(player)) + .orElse(accessedContainer match { + case Some(parent: PlanetSideServerObject) => + findFunc(parent) + case _ => + None + }) + }, + destination.Fit(item) + ) match { + case (Some((source, Some(_))), Some(dest)) => + ContainableMoveItem(player.Name, source, destination, item, dest) + case (None, _) => + log.error(s"LootItem: ${player.Name} can not find where $item is put currently") + case (_, None) => + log.error(s"LootItem: ${player.Name} can not find anywhere to put $item in $destination") + case _ => + log.error( + s"LootItem: ${player.Name}wanted to move $itemGuid to $targetGuid, but multiple problems were encountered" + ) + } + case (Some(obj), _) => + log.error(s"LootItem: item $obj is (probably) not lootable to ${player.Name}") + case (None, _) => () + case (_, None) => + log.error(s"LootItem: ${player.Name} can not find where to put $itemGuid") + } + } + + def handleAvatarImplant(pkt: AvatarImplantMessage): GeneralOperations.ImplantActivationBehavior.Behavior = { + val AvatarImplantMessage(_, action, slot, status) = pkt + if (action == ImplantAction.Activation) { + if (sessionLogic.zoning.zoningStatus == Zoning.Status.Deconstructing) { + //do not activate; play deactivation sound instead + sessionLogic.zoning.spawn.stopDeconstructing() + avatar.implants(slot).collect { + case implant if implant.active => + avatarActor ! AvatarActor.DeactivateImplant(implant.definition.implantType) + case implant => + sendResponse(PlanetsideAttributeMessage(player.GUID, 28, implant.definition.implantType.value * 2)) + } + GeneralOperations.ImplantActivationBehavior.Failed + } else { + avatar.implants(slot) match { + case Some(implant) => + if (status == 1) { + avatarActor ! AvatarActor.ActivateImplant(implant.definition.implantType) + GeneralOperations.ImplantActivationBehavior.Activate + } else { + avatarActor ! AvatarActor.DeactivateImplant(implant.definition.implantType) + GeneralOperations.ImplantActivationBehavior.Deactivate + } + case _ => + GeneralOperations.ImplantActivationBehavior.NotFound + } + } + } else { + GeneralOperations.ImplantActivationBehavior.Failed + } + } + + def handleCreateShortcut(pkt: CreateShortcutMessage): Unit = { + val CreateShortcutMessage(_, slot, shortcutOpt) = pkt + shortcutOpt match { + case Some(shortcut) => + avatarActor ! AvatarActor.AddShortcut(slot - 1, shortcut) + case None => + avatarActor ! AvatarActor.RemoveShortcut(slot - 1) + } + } + + def handleObjectDetected(pkt: ObjectDetectedMessage): Unit = { + val ObjectDetectedMessage(_, _, _, targets) = pkt + sessionLogic.shooting.FindWeapon.foreach { + case weapon if weapon.Projectile.AutoLock => + //projectile with auto-lock instigates a warning on the target + val detectedTargets = sessionLogic.shooting.FindDetectedProjectileTargets(targets) + val mode = 7 + (if (weapon.Projectile == GlobalDefinitions.wasp_rocket_projectile) 1 else 0) + detectedTargets.foreach { target => + continent.AvatarEvents ! AvatarServiceMessage(target, AvatarAction.ProjectileAutoLockAwareness(mode)) + } + case _ => () + } + } + + def handleTargetingImplantRequest(pkt: TargetingImplantRequest): Unit = { + val TargetingImplantRequest(list) = pkt + val targetInfo: List[TargetInfo] = list.flatMap { x => + continent.GUID(x.target_guid) match { + case Some(player: Player) => + val health = player.Health.toFloat / player.MaxHealth + val armor = if (player.MaxArmor > 0) { + player.Armor.toFloat / player.MaxArmor + } else { + 0 + } + Some(TargetInfo(player.GUID, health, armor)) + case _ => + log.warn( + s"TargetingImplantRequest: the info that ${player.Name} requested for target ${x.target_guid} is not for a player" + ) + None + } + } + sendResponse(TargetingInfoMessage(targetInfo)) + } + /** * Enforce constraints on bulk purchases as determined by a given player's previous purchase times and hard acquisition delays. * Intended to assist in sanitizing loadout information from the perspective of the player, or target owner. @@ -765,10 +976,10 @@ class GeneralOperations( ) } - def administrativeKick(tplayer: Player): Unit = { + def administrativeKick(tplayer: Player, time: Option[Long]): Unit = { log.warn(s"${tplayer.Name} has been kicked by ${player.Name}") tplayer.death_by = -1 - sessionLogic.accountPersistence ! AccountPersistenceService.Kick(tplayer.Name) + sessionLogic.accountPersistence ! AccountPersistenceService.Kick(tplayer.Name, time) //get out of that vehicle sessionLogic.vehicles.GetMountableAndSeat(None, tplayer, continent) match { case (Some(obj), Some(seatNum)) => @@ -1275,3 +1486,35 @@ class GeneralOperations( charSavedTimer.cancel() } } + +object GeneralOperations { + object ItemDropState { + sealed trait Behavior + case object Dropped extends Behavior + case object NotDropped extends Behavior + case object NotFound extends Behavior + } + + object ItemPickupState { + sealed trait Behavior + case object PickedUp extends Behavior + case object Dropped extends Behavior + case object NotFound extends Behavior + } + + object ZiplineBehavior { + sealed trait Behavior + case object Teleporter extends Behavior + case object Zipline extends Behavior + case object Unsupported extends Behavior + case object NotFound extends Behavior + } + + object ImplantActivationBehavior { + sealed trait Behavior + case object Activate extends Behavior + case object Deactivate extends Behavior + case object Failed extends Behavior + case object NotFound extends Behavior + } +} diff --git a/src/main/scala/net/psforever/actors/session/support/SessionAvatarHandlers.scala b/src/main/scala/net/psforever/actors/session/support/SessionAvatarHandlers.scala index 3e0e7f9d8..c753e1bbb 100644 --- a/src/main/scala/net/psforever/actors/session/support/SessionAvatarHandlers.scala +++ b/src/main/scala/net/psforever/actors/session/support/SessionAvatarHandlers.scala @@ -2,9 +2,11 @@ package net.psforever.actors.session.support import akka.actor.{ActorContext, typed} +import net.psforever.objects.Default import net.psforever.objects.sourcing.{PlayerSource, SourceEntry} import net.psforever.packet.game.objectcreate.ConstructorData import net.psforever.objects.zones.exp +import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage} import scala.collection.mutable // @@ -154,6 +156,22 @@ class SessionAvatarHandlers( victimSeated ) } + + def revive(revivalTargetGuid: PlanetSideGUID): Unit = { + val spawn = sessionLogic.zoning.spawn + spawn.reviveTimer.cancel() + spawn.reviveTimer = Default.Cancellable + spawn.respawnTimer.cancel() + spawn.respawnTimer = Default.Cancellable + player.Revive + val health = player.Health + sendResponse(PlanetsideAttributeMessage(revivalTargetGuid, attribute_type=0, health)) + sendResponse(AvatarDeadStateMessage(DeadState.Alive, timer_max=0, timer=0, player.Position, player.Faction, unk5=true)) + continent.AvatarEvents ! AvatarServiceMessage( + continent.id, + AvatarAction.PlanetsideAttributeToAll(revivalTargetGuid, attribute_type=0, health) + ) + } } object SessionAvatarHandlers { 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 e5b1db25f..a21a2e18e 100644 --- a/src/main/scala/net/psforever/actors/session/support/WeaponAndProjectileOperations.scala +++ b/src/main/scala/net/psforever/actors/session/support/WeaponAndProjectileOperations.scala @@ -175,6 +175,7 @@ class WeaponAndProjectileOperations( angle, shotVelocity ) + projectile.GUID = projectileGUID val initialQuality = tool.FireMode match { case mode: ChargeFireModeDefinition => ProjectileQuality.Modified( @@ -193,6 +194,7 @@ class WeaponAndProjectileOperations( log.trace( s"WeaponFireMessage: ${player.Name}'s ${projectile_info.Name} is a remote projectile" ) + qualityprojectile.Invalidate() continent.Projectile ! ZoneProjectile.Add(player.GUID, qualityprojectile) } } else { @@ -550,6 +552,20 @@ class WeaponAndProjectileOperations( .getOrElse(false) } + def handleProxyDamage( + projectileGuid: PlanetSideGUID, + explosionPosition: Vector3 + ): List[(PlanetSideGameObject with FactionAffinity with Vitality, Projectile, Vector3, Vector3)] = { + val proxyList = FindProjectileEntry(projectileGuid) + .map(projectile => resolveDamageProxy(projectile, projectile.GUID, explosionPosition)) + .getOrElse(Nil) + proxyList.collectFirst { + case (_, proxy, _, _) if proxy.profile == GlobalDefinitions.oicw_little_buddy => + performLittleBuddyExplosion(proxyList.map(_._2)) + } + proxyList + } + /** * Take a projectile that was introduced into the game world and * determine if it generates a secondary damage projectile or @@ -560,11 +576,11 @@ class WeaponAndProjectileOperations( * @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)] = { + private 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 @@ -662,7 +678,7 @@ class WeaponAndProjectileOperations( } } - def performLittleBuddyExplosion(listOfProjectiles: List[Projectile]): Boolean = { + private def performLittleBuddyExplosion(listOfProjectiles: List[Projectile]): Boolean = { val listOfLittleBuddies: List[Projectile] = listOfProjectiles.filter { _.tool_def == GlobalDefinitions.oicw } val size: Int = listOfLittleBuddies.size if (size > 0) { 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 46ea275fc..edd630c9d 100644 --- a/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala +++ b/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala @@ -2440,6 +2440,10 @@ class ZoningOperations( log.debug(s"AvatarCreate (vehicle): ${player.Name}'s ${vdef.Name}") AvatarCreateInVehicle(player, vehicle, seat) + case _ if player.spectator => + player.VehicleSeated = None + sendResponse(OCM.detailed(player)) + case _ => player.VehicleSeated = None val definition = player.avatar.definition @@ -2588,8 +2592,6 @@ class ZoningOperations( zones.exp.ToDatabase.reportRespawns(tplayer.CharId, ScoreCard.reviveCount(player.avatar.scorecard.CurrentLife)) val obj = Player.Respawn(tplayer) DefinitionUtil.applyDefaultLoadout(obj) - obj.death_by = tplayer.death_by - obj.silenced = tplayer.silenced obj } diff --git a/src/main/scala/net/psforever/objects/Player.scala b/src/main/scala/net/psforever/objects/Player.scala index 1ac54eba1..8415e3b6e 100644 --- a/src/main/scala/net/psforever/objects/Player.scala +++ b/src/main/scala/net/psforever/objects/Player.scala @@ -635,13 +635,16 @@ object Player { player.Inventory.Resize(eSuit.InventoryScale.Width, eSuit.InventoryScale.Height) player.Inventory.Offset = eSuit.InventoryOffset //holsters - (0 until 5).foreach(index => { player.Slot(index).Size = eSuit.Holster(index) }) + (0 until 5).foreach { index => player.Slot(index).Size = eSuit.Holster(index) } } def Respawn(player: Player): Player = { if (player.Release) { val obj = new Player(player.avatar) obj.Continent = player.Continent + obj.death_by = player.death_by + obj.silenced = player.silenced + obj.allowInteraction = player.allowInteraction obj.avatar.scorecard.respawn() obj } else { diff --git a/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala b/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala index f0b36413a..4fb846813 100644 --- a/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala +++ b/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala @@ -954,7 +954,6 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm def DestructionAwareness(target: Player, cause: DamageResult): Unit = { val player_guid = target.GUID val pos = target.Position - val respawnTimer = 300000 //milliseconds val zone = target.Zone val events = zone.AvatarEvents val nameChannel = target.Name @@ -1023,13 +1022,6 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm DestroyMessage(player_guid, attribute, Service.defaultPlayerGUID, pos) ) //how many players get this message? ) - events ! AvatarServiceMessage( - nameChannel, - AvatarAction.SendResponse( - Service.defaultPlayerGUID, - AvatarDeadStateMessage(DeadState.Dead, respawnTimer, respawnTimer, pos, target.Faction, unk5=true) - ) - ) //TODO other methods of death? val pentry = PlayerSource(target) cause diff --git a/src/main/scala/net/psforever/objects/ballistics/Projectile.scala b/src/main/scala/net/psforever/objects/ballistics/Projectile.scala index 4cf27207d..13c53d3a4 100644 --- a/src/main/scala/net/psforever/objects/ballistics/Projectile.scala +++ b/src/main/scala/net/psforever/objects/ballistics/Projectile.scala @@ -81,24 +81,7 @@ final case class Projectile( * @return a new `Projectile` entity */ def quality(value: ProjectileQuality): Projectile = { - val projectile = new Projectile( - profile, - tool_def, - fire_mode, - mounted_in, - owner, - attribute_to, - shot_origin, - shot_angle, - shot_velocity, - value, - id, - fire_time - ) - if(isMiss) projectile.Miss() - else if(isResolved) projectile.Resolve() - projectile.WhichSide = this.WhichSide - projectile + Projectile.copy(original=this, copy(quality = value)) } /** @@ -186,4 +169,15 @@ object Projectile { ): Projectile = { Projectile(profile, tool_def, fire_mode, None, owner, attribute_to, shot_origin, shot_angle, None) } + + def copy(original: Projectile, dirtyCopy: Projectile): Projectile = { + val properCopy = dirtyCopy.copy(fire_time = original.fire_time, id = original.id) + properCopy.GUID = original.GUID + properCopy.WhichSide = original.WhichSide + if (original.isMiss) + properCopy.Miss() + else if (original.isResolved) + properCopy.Resolve() + properCopy + } } diff --git a/src/main/scala/net/psforever/services/chat/ChatChannel.scala b/src/main/scala/net/psforever/services/chat/ChatChannel.scala index 94515f0a3..3fbea3e64 100644 --- a/src/main/scala/net/psforever/services/chat/ChatChannel.scala +++ b/src/main/scala/net/psforever/services/chat/ChatChannel.scala @@ -10,3 +10,5 @@ case object DefaultChannel extends ChatChannel final case class SquadChannel(guid: PlanetSideGUID) extends ChatChannel case object SpectatorChannel extends ChatChannel + +case object CustomerServiceChannel extends ChatChannel diff --git a/src/test/scala/objects/PlayerControlTest.scala b/src/test/scala/objects/PlayerControlTest.scala index aad7d21ca..e464288ae 100644 --- a/src/test/scala/objects/PlayerControlTest.scala +++ b/src/test/scala/objects/PlayerControlTest.scala @@ -533,7 +533,7 @@ class PlayerControlDeathStandingTest extends ActorTest { assert(player2.isAlive) player2.Actor ! Vitality.Damage(applyDamageTo) - val msg_avatar = avatarProbe.receiveN(8, 500 milliseconds) + val msg_avatar = avatarProbe.receiveN(7, 500 milliseconds) val msg_stamina = probe.receiveOne(500 milliseconds) activityProbe.expectNoMessage(200 milliseconds) assert( @@ -585,19 +585,6 @@ class PlayerControlDeathStandingTest extends ActorTest { ) assert( msg_avatar(6) match { - case AvatarServiceMessage( - "TestCharacter2", - AvatarAction.SendResponse( - _, - AvatarDeadStateMessage(DeadState.Dead, 300000, 300000, Vector3.Zero, PlanetSideEmpire.NC, true) - ) - ) => - true - case _ => false - } - ) - assert( - msg_avatar(7) match { case AvatarServiceMessage("test", AvatarAction.DestroyDisplay(killer, victim, _, _)) if killer.Name.equals(player1.Name) && victim.Name.equals(player2.Name) => true @@ -680,7 +667,7 @@ class PlayerControlDeathStandingTest extends ActorTest { // assert(player2.isAlive) // // player2.Actor ! Vitality.Damage(applyDamageTo) -// val msg_avatar = avatarProbe.receiveN(9, 1500 milliseconds) +// val msg_avatar = avatarProbe.receiveN(8, 1500 milliseconds) // val msg_stamina = probe.receiveOne(500 milliseconds) // activityProbe.expectNoMessage(200 milliseconds) // assert( @@ -749,19 +736,6 @@ class PlayerControlDeathStandingTest extends ActorTest { // ) // assert( // msg_avatar(7) match { -// case AvatarServiceMessage( -// "TestCharacter2", -// AvatarAction.SendResponse( -// _, -// AvatarDeadStateMessage(DeadState.Dead, 300000, 300000, Vector3.Zero, PlanetSideEmpire.NC, true) -// ) -// ) => -// true -// case _ => false -// } -// ) -// assert( -// msg_avatar(8) match { // case AvatarServiceMessage("test", AvatarAction.DestroyDisplay(killer, victim, _, _)) // if killer.Name.equals(player1.Name) && victim.Name.equals(player2.Name) => // true