From babd455753d28ebec07bb16fe02fc87325a3631c Mon Sep 17 00:00:00 2001 From: Fate-JH Date: Fri, 27 Nov 2020 09:30:56 -0500 Subject: [PATCH] Yet Another Corpse Fix (#637) * numeric session ids now restored; LoginActor knows about connection address; corpses are barren unless searched * session and login id/counter moved under server; function literal definition changed * corpse channel * trying to fix docker as per set-env requirement changes; I don't know what I'm doing --- .github/workflows/publish.yaml | 2 +- build.sbt | 2 +- .../scala/net/psforever/server/Server.scala | 39 ++++- .../actor/objects/VehicleSpawnPadTest.scala | 1 - .../net/psforever/actors/net/LoginActor.scala | 7 +- .../actors/net/MiddlewareActor.scala | 14 +- .../psforever/actors/net/SocketActor.scala | 14 +- .../actors/session/SessionActor.scala | 133 +++++++++++++----- .../scala/net/psforever/objects/Player.scala | 13 ++ .../objects/avatar/PlayerControl.scala | 41 +++--- .../converter/AvatarConverter.scala | 23 ++- .../converter/CorpseConverter.scala | 76 +--------- 12 files changed, 196 insertions(+), 169 deletions(-) diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index c538e0d1..532f00ca 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -36,7 +36,7 @@ jobs: uses: rlespinasse/github-slug-action@v2.x - name: Set variables run: | - echo "::set-env name=REPOSITORY::$(echo $GITHUB_REPOSITORY | tr '[A-Z]' '[a-z]')" + echo "REPOSITORY=$(echo $GITHUB_REPOSITORY | tr '[A-Z]' '[a-z]')" >> $GITHUB_ENV - name: Build and push Docker image uses: docker/build-push-action@v2.2.0 with: diff --git a/build.sbt b/build.sbt index 603851f8..96b9da83 100644 --- a/build.sbt +++ b/build.sbt @@ -3,7 +3,7 @@ import xerial.sbt.pack.PackPlugin._ lazy val psforeverSettings = Seq( organization := "net.psforever", version := "1.0.2-SNAPSHOT", - scalaVersion := "2.13.4", + scalaVersion := "2.13.3", Global / cancelable := false, semanticdbEnabled := true, semanticdbVersion := scalafixSemanticdb.revision, diff --git a/server/src/main/scala/net/psforever/server/Server.scala b/server/src/main/scala/net/psforever/server/Server.scala index defc34bf..5aeae7c6 100644 --- a/server/src/main/scala/net/psforever/server/Server.scala +++ b/server/src/main/scala/net/psforever/server/Server.scala @@ -4,6 +4,7 @@ import java.net.{InetAddress, InetSocketAddress} import java.nio.file.Paths import java.util.Locale import java.util.UUID.randomUUID +import java.util.concurrent.atomic.AtomicLong import akka.actor.ActorSystem import akka.actor.typed.ActorRef @@ -19,7 +20,7 @@ import net.psforever.login.psadmin.PsAdminActor import net.psforever.login._ import net.psforever.objects.Default import net.psforever.objects.zones._ -import net.psforever.services.account.{AccountIntermediaryService, AccountPersistenceService} +import net.psforever.services.account.{AccountIntermediaryService, AccountPersistenceService, ReceiveIPAddress} import net.psforever.services.chat.ChatService import net.psforever.services.galaxy.GalaxyService import net.psforever.services.properties.PropertyOverrideManager @@ -95,19 +96,21 @@ object Server { Default(system) // typed to classic wrappers for login and session actors - val login = (ref: ActorRef[MiddlewareActor.Command], connectionId: String) => { + val login = (ref: ActorRef[MiddlewareActor.Command], info: InetSocketAddress, connectionId: String) => { + import net.psforever.services.account.IPAddress Behaviors.setup[PlanetSidePacket](context => { - val actor = context.actorOf(classic.Props(new LoginActor(ref, connectionId)), "login") + val actor = context.actorOf(classic.Props(new LoginActor(ref, connectionId, Login.getNewId())), "login") + actor ! ReceiveIPAddress(new IPAddress(info)) Behaviors.receiveMessage(message => { actor ! message Behaviors.same }) }) } - val session = (ref: ActorRef[MiddlewareActor.Command], connectionId: String) => { + val session = (ref: ActorRef[MiddlewareActor.Command], info: InetSocketAddress, connectionId: String) => { Behaviors.setup[PlanetSidePacket](context => { val uuid = randomUUID().toString - val actor = context.actorOf(classic.Props(new SessionActor(ref, connectionId)), s"session-${uuid}") + val actor = context.actorOf(classic.Props(new SessionActor(ref, connectionId, Session.getNewId())), s"session-$uuid") Behaviors.receiveMessage(message => { actor ! message Behaviors.same @@ -230,6 +233,30 @@ object Server { case _ => sys.exit(1) } - } + + sealed trait AuthoritativeCounter { + /** the id accumulator */ + private val masterIdKeyRing: AtomicLong = new AtomicLong(0L) + + /** + * Poll the next id. + * @return a id + */ + def nextId(): Long = masterIdKeyRing.get() + + /** + * Get the next available id. + * Increment the counter. + * @return a id + */ + def getNewId(): Long = { + val oldId = masterIdKeyRing.getAndIncrement() + oldId + } + } + + object Session extends AuthoritativeCounter + + object Login extends AuthoritativeCounter } diff --git a/server/src/test/scala/actor/objects/VehicleSpawnPadTest.scala b/server/src/test/scala/actor/objects/VehicleSpawnPadTest.scala index a8233c64..43fafd4f 100644 --- a/server/src/test/scala/actor/objects/VehicleSpawnPadTest.scala +++ b/server/src/test/scala/actor/objects/VehicleSpawnPadTest.scala @@ -9,7 +9,6 @@ import net.psforever.objects.serverobject.structures.StructureType import net.psforever.objects.{GlobalDefinitions, Player, Vehicle} import net.psforever.objects.zones.Zone import net.psforever.types.{PlanetSideGUID, _} -import net.psforever.services.RemoverActor import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage} import akka.actor.typed.scaladsl.adapter._ import net.psforever.actors.zone.ZoneActor diff --git a/src/main/scala/net/psforever/actors/net/LoginActor.scala b/src/main/scala/net/psforever/actors/net/LoginActor.scala index 994e5d03..6632b3f9 100644 --- a/src/main/scala/net/psforever/actors/net/LoginActor.scala +++ b/src/main/scala/net/psforever/actors/net/LoginActor.scala @@ -10,7 +10,7 @@ import net.psforever.packet.game._ import net.psforever.persistence import net.psforever.services.ServiceManager import net.psforever.services.ServiceManager.Lookup -import net.psforever.services.account.{ReceiveIPAddress, RetrieveIPAddress, StoreAccountData} +import net.psforever.services.account.{ReceiveIPAddress, StoreAccountData} import net.psforever.types.PlanetSideEmpire import net.psforever.util.Config import net.psforever.util.Database._ @@ -42,7 +42,7 @@ class LoginActor( */ -class LoginActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], connectionId: String) +class LoginActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], connectionId: String, sessionId: Long) extends Actor with MDCContextAware { private[this] val log = org.log4s.getLogger @@ -53,7 +53,6 @@ class LoginActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], conne val usernameRegex = """[A-Za-z0-9]{3,}""".r - var sessionId: Long = 0 var leftRef: ActorRef = ActorRef.noSender var rightRef: ActorRef = ActorRef.noSender var accountIntermediary: ActorRef = ActorRef.noSender @@ -100,8 +99,6 @@ class LoginActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], conne val clientVersion = s"Client Version: $majorVersion.$minorVersion.$revision, $buildDate" - accountIntermediary ! RetrieveIPAddress(sessionId) - if (token.isDefined) log.info(s"New login UN:$username Token:${token.get}. $clientVersion") else { diff --git a/src/main/scala/net/psforever/actors/net/MiddlewareActor.scala b/src/main/scala/net/psforever/actors/net/MiddlewareActor.scala index 8c1ed25f..e2d36559 100644 --- a/src/main/scala/net/psforever/actors/net/MiddlewareActor.scala +++ b/src/main/scala/net/psforever/actors/net/MiddlewareActor.scala @@ -59,7 +59,7 @@ object MiddlewareActor { def apply( socket: ActorRef[Udp.Command], sender: InetSocketAddress, - next: (ActorRef[Command], String) => Behavior[PlanetSidePacket], + next: (ActorRef[Command], InetSocketAddress, String) => Behavior[PlanetSidePacket], connectionId: String ): Behavior[Command] = Behaviors.setup(context => new MiddlewareActor(context, socket, sender, next, connectionId).start()) @@ -83,7 +83,7 @@ class MiddlewareActor( context: ActorContext[MiddlewareActor.Command], socket: ActorRef[Udp.Command], sender: InetSocketAddress, - next: (ActorRef[MiddlewareActor.Command], String) => Behavior[PlanetSidePacket], + next: (ActorRef[MiddlewareActor.Command], InetSocketAddress, String) => Behavior[PlanetSidePacket], connectionId: String ) { @@ -102,7 +102,7 @@ class MiddlewareActor( var crypto: Option[CryptoCoding] = None val nextActor: ActorRef[PlanetSidePacket] = - context.spawnAnonymous(next(context.self, connectionId), ActorTags(s"id=${connectionId}")) + context.spawnAnonymous(next(context.self, sender, connectionId), ActorTags(s"id=$connectionId")) /** Queue of incoming packets (plus sequence numbers and timestamps) that arrived in the wrong order */ val inReorderQueue: ListBuffer[(PlanetSidePacket, Int, Long)] = ListBuffer() @@ -329,7 +329,7 @@ class MiddlewareActor( connectionClose() } case Failure(e) => - log.error(s"Could not decode packet in cryptoSetup: ${e}") + log.error(s"Could not decode packet in cryptoSetup: $e") connectionClose() } case other => @@ -420,7 +420,7 @@ class MiddlewareActor( inReorderQueue.addOne((packet, sequence, System.currentTimeMillis())) } case Successful((packet, None)) => in(packet) - case Failure(e) => log.error(s"Could not decode packet: ${e}") + case Failure(e) => log.error(s"Could not decode packet: $e") } Behaviors.same @@ -483,7 +483,7 @@ class MiddlewareActor( case RelatedA(slot, subslot) => log.info(s"Client indicated a packet is missing prior to slot '$slot' and subslot '$subslot'") outSlottedMetaPackets.find(_.subslot == subslot - 1) match { - case Some(packet) => outQueueBundled.enqueue(packet) + case Some(_packet) => outQueueBundled.enqueue(_packet) case None => log.warn(s"Client requested unknown subslot '$subslot'") } Behaviors.same @@ -526,7 +526,7 @@ class MiddlewareActor( def in(packet: Attempt[PlanetSidePacket]): Unit = { packet match { - case Successful(packet) => in(packet) + case Successful(_packet) => in(_packet) case Failure(cause) => log.error(cause.message) } } diff --git a/src/main/scala/net/psforever/actors/net/SocketActor.scala b/src/main/scala/net/psforever/actors/net/SocketActor.scala index b4fb976a..65155e3a 100644 --- a/src/main/scala/net/psforever/actors/net/SocketActor.scala +++ b/src/main/scala/net/psforever/actors/net/SocketActor.scala @@ -26,7 +26,7 @@ import scala.util.Random object SocketActor { def apply( address: InetSocketAddress, - next: (ActorRef[MiddlewareActor.Command], String) => Behavior[PlanetSidePacket] + next: (ActorRef[MiddlewareActor.Command], InetSocketAddress, String) => Behavior[PlanetSidePacket] ): Behavior[Command] = Behaviors.setup(context => new SocketActor(context, address, next).start()) @@ -64,7 +64,7 @@ object SocketActor { case _: Udp.Received | _: Udp.Send => simulate(message) Behaviors.same - case other => + case _ => socketActor ! toSocket(message) Behaviors.same } @@ -97,7 +97,7 @@ object SocketActor { class SocketActor( context: ActorContext[SocketActor.Command], address: InetSocketAddress, - next: (ActorRef[MiddlewareActor.Command], String) => Behavior[PlanetSidePacket] + next: (ActorRef[MiddlewareActor.Command], InetSocketAddress, String) => Behavior[PlanetSidePacket] ) { import SocketActor._ import SocketActor.Command @@ -174,13 +174,13 @@ class SocketActor( case Udp.Received(data, remote) => incomingTimes(remote) = System.currentTimeMillis() val ref = packetActors.get(remote) match { - case Some(ref) => ref + case Some(_ref) => _ref case None => val connectionId = randomUUID.toString val ref = context.spawn( MiddlewareActor(udpCommandAdapter, remote, next, connectionId), - s"middleware-${connectionId}", - ActorTags(s"uuid=${connectionId}") + s"middleware-$connectionId", + ActorTags(s"uuid=$connectionId") ) context.watch(ref) packetActors(remote) = ref @@ -218,7 +218,7 @@ class SocketActor( case (_, Terminated(ref)) => packetActors.find(_._2 == ref) match { case Some((remote, _)) => packetActors.remove(remote) - case None => log.warn(s"Received Terminated for unknown actor ${ref}") + case None => log.warn(s"Received Terminated for unknown actor $ref") } Behaviors.same } diff --git a/src/main/scala/net/psforever/actors/session/SessionActor.scala b/src/main/scala/net/psforever/actors/session/SessionActor.scala index 6d97d3f4..adb25ec3 100644 --- a/src/main/scala/net/psforever/actors/session/SessionActor.scala +++ b/src/main/scala/net/psforever/actors/session/SessionActor.scala @@ -158,7 +158,7 @@ object SessionActor { private final case class LoadedRemoteProjectile(projectile_guid: PlanetSideGUID, projectile: Option[Projectile]) } -class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], connectionId: String) +class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], connectionId: String, sessionId: Long) extends Actor with MDCContextAware { @@ -5985,6 +5985,8 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con AccessVehicleContents(v) case o: LockerContainer => AccessGenericContainer(o) + case p: Player if p.isBackpack => + AccessCorpseContents(p) case p: PlanetSideServerObject with Container => accessedContainer = Some(p) case _ => ; @@ -5993,27 +5995,59 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con def AccessGenericContainer(container: PlanetSideServerObject with Container): Unit = { accessedContainer = Some(container) - DisplayContainerContents(container.GUID, container) + DisplayContainerContents(container.GUID, container.Inventory.Items) } /** - * Common preparation for interfacing with a vehicle. + * Common preparation for interfacing with a vehicle trunk. * Join a vehicle-specific group for shared updates. * Construct every object in the vehicle's inventory for shared manipulation updates. + * @see `Container.Inventory` + * @see `GridInventory.Items` * @param vehicle the vehicle */ def AccessVehicleContents(vehicle: Vehicle): Unit = { accessedContainer = Some(vehicle) - AccessVehicleContainerChannel(vehicle) - DisplayContainerContents(vehicle.GUID, vehicle) + AccessContainerChannel(continent.VehicleEvents, vehicle.Actor.toString) + DisplayContainerContents(vehicle.GUID, vehicle.Inventory.Items) } - def AccessVehicleContainerChannel(container: PlanetSideServerObject): Unit = { - continent.VehicleEvents ! Service.Join(s"${container.Actor}") + /** + * Common preparation for interfacing with a corpse (former player's backpack). + * Join a corpse-specific group for shared updates. + * Construct every object in the player's hands and inventory for shared manipulation updates. + * @see `Container.Inventory` + * @see `GridInventory.Items` + * @see `Player.HolsterItems` + * @param tplayer the corpse + */ + def AccessCorpseContents(tplayer: Player): Unit = { + accessedContainer = Some(tplayer) + AccessContainerChannel(continent.AvatarEvents, tplayer.Actor.toString) + DisplayContainerContents(tplayer.GUID, tplayer.HolsterItems()) + DisplayContainerContents(tplayer.GUID, tplayer.Inventory.Items) } - def DisplayContainerContents(containerId: PlanetSideGUID, container: Container): Unit = { - container.Inventory.Items.foreach(entry => { + /** + * Join an entity-specific group for shared updates. + * @param events the event system bus to which to subscribe + * @param channel the channel name + */ + def AccessContainerChannel(events: ActorRef, channel: String): Unit = { + events ! Service.Join(channel) + } + + /** + * Depict the contents of a container by building them in the local client + * in their container as a group of detailed entities. + * @see `ObjectCreateDetailedMessage` + * @see `ObjectCreateMessageParent` + * @see `PacketConverter.DetailedConstructorData` + * @param containerId the container's unique identifier + * @param items a list of the entities to be depicted + */ + def DisplayContainerContents(containerId: PlanetSideGUID, items: Iterable[InventoryItem]): Unit = { + items.foreach(entry => { val obj = entry.obj val objDef = obj.Definition sendResponse( @@ -6027,6 +6061,10 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con }) } + /** + * For whatever conatiner the character considers itself accessing, + * initiate protocol to release it from "access". + */ def UnaccessContainer(): Unit = { accessedContainer match { case Some(container) => UnaccessContainer(container) @@ -6034,12 +6072,17 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con } } + /** + * For the target container, initiate protocol to release it from "access". + */ def UnaccessContainer(container: Container): Unit = { container match { case v: Vehicle => UnaccessVehicleContainer(v) case o: LockerContainer => UnaccessGenericContainer(o) + case p: Player if p.isBackpack => + UnaccessCorpseContainer(p) case _: PlanetSideServerObject with Container => accessedContainer = None case _ => ; @@ -6048,7 +6091,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con def UnaccessGenericContainer(container: Container): Unit = { accessedContainer = None - HideContainerContents(container) + HideContainerContents(container.Inventory.Items) } /** @@ -6062,18 +6105,42 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con if (vehicle.AccessingTrunk.contains(player.GUID)) { vehicle.AccessingTrunk = None } - UnaccessVehicleContainerChannel(vehicle) - HideContainerContents(vehicle) + UnaccessContainerChannel(continent.VehicleEvents, vehicle.Actor.toString) + HideContainerContents(vehicle.Inventory.Items) } - def UnaccessVehicleContainerChannel(container: PlanetSideServerObject): Unit = { - continent.VehicleEvents ! Service.Leave(Some(s"${container.Actor}")) + /** + * Common preparation for disengaging from a corpse. + * Leave the corpse-specific group that was used for shared updates. + * Deconstruct every object in the backpack's inventory. + * @param vehicle the vehicle + */ + def UnaccessCorpseContainer(tplayer: Player): Unit = { + accessedContainer = None + UnaccessContainerChannel(continent.AvatarEvents, tplayer.Actor.toString) + HideContainerContents(tplayer.HolsterItems()) + HideContainerContents(tplayer.Inventory.Items) } - def HideContainerContents(container: Container): Unit = { - container.Inventory.Items.foreach(entry => { + /** + * Leave an entity-specific group for shared updates. + * @param events the event system bus to which to subscribe + * @param channel the channel name + */ + def UnaccessContainerChannel(events: ActorRef, channel: String): Unit = { + events ! Service.Leave(Some(channel)) + } + + /** + * Forget the contents of a container by deleting that content from the local client. + * @see `InventoryItem` + * @see `ObjectDeleteMessage` + * @param items a list of the entities to be depicted + */ + def HideContainerContents(items: List[InventoryItem]): Unit = { + items.foreach { entry => sendResponse(ObjectDeleteMessage(entry.obj.GUID, 0)) - }) + } } /** @@ -7189,6 +7256,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con */ def DepictPlayerAsCorpse(tplayer: Player): Unit = { val guid = tplayer.GUID + //the corpse as a receptacle sendResponse( ObjectCreateDetailedMessage( ObjectClass.avatar, @@ -8241,23 +8309,20 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con * @return all discovered `BoomTrigger` objects */ def RemoveBoomerTriggersFromInventory(): List[BoomerTrigger] = { - val holstersWithIndex = player.Holsters().zipWithIndex - ((player.Inventory.Items.collect({ case InventoryItem(obj: BoomerTrigger, index) => (obj, index) })) ++ - (holstersWithIndex - .map({ case ((slot, index)) => (slot.Equipment, index) }) - .collect { case ((Some(obj: BoomerTrigger), index)) => (obj, index) })) - .map({ - case ((obj, index)) => - player.Slot(index).Equipment = None - sendResponse(ObjectDeleteMessage(obj.GUID, 0)) - if (player.VisibleSlots.contains(index) && player.HasGUID) { - continent.AvatarEvents ! AvatarServiceMessage( - continent.id, - AvatarAction.ObjectDelete(player.GUID, obj.GUID) - ) - } - obj - }) + val events = continent.AvatarEvents + val zoneId = continent.id + (player.Inventory.Items ++ player.HolsterItems()) + .collect { case InventoryItem(obj: BoomerTrigger, index) => + player.Slot(index).Equipment = None + sendResponse(ObjectDeleteMessage(obj.GUID, 0)) + if (player.HasGUID && player.VisibleSlots.contains(index)) { + events ! AvatarServiceMessage( + zoneId, + AvatarAction.ObjectDelete(player.GUID, obj.GUID) + ) + } + obj + } } /** diff --git a/src/main/scala/net/psforever/objects/Player.scala b/src/main/scala/net/psforever/objects/Player.scala index d7585f01..e0da9c91 100644 --- a/src/main/scala/net/psforever/objects/Player.scala +++ b/src/main/scala/net/psforever/objects/Player.scala @@ -190,6 +190,19 @@ class Player(var avatar: Avatar) def Holsters(): Array[EquipmentSlot] = holsters + /** + * Transform the holster equipment slots + * into a list of the kind of item wrapper found in an inventory. + * @see `GridInventory` + * @see `InventoryItem` + * @return a list of items that would be found in a proper inventory + */ + def HolsterItems(): List[InventoryItem] = holsters + .zipWithIndex + .collect { + case out @ (slot: EquipmentSlot, index: Int) if slot.Equipment.nonEmpty => InventoryItem(slot.Equipment.get, index) + }.toList + def Inventory: GridInventory = inventory override def Fit(obj: Equipment): Option[Int] = { diff --git a/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala b/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala index 0943d808..78df5067 100644 --- a/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala +++ b/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala @@ -260,12 +260,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm afterHolsters.foreach({ elem => player.Slot(elem.start).Equipment = elem.obj }) val remainder = Players.fillEmptyHolsters(player.Holsters().iterator, toInventory ++ beforeInventory) ( - player - .Holsters() - .zipWithIndex - .map { case (slot, i) => (slot.Equipment, i) } - .collect { case (Some(obj), index) => InventoryItem(obj, index) } - .toList, + player.HolsterItems(), remainder ) } @@ -408,11 +403,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm player.Holsters().iterator, (curatedHolsters ++ curatedInventory).filterNot(dropPred) ) - val finalHolsters = player - .Holsters() - .zipWithIndex - .collect { case (slot, index) if slot.Equipment.nonEmpty => InventoryItem(slot.Equipment.get, index) } - .toList + val finalHolsters = player.HolsterItems() //inventory val (finalInventory, _) = GridInventory.recoverInventory(leftoversForInventory, player.Inventory) (finalHolsters, finalInventory) @@ -822,14 +813,11 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm def MessageDeferredCallback(msg: Any): Unit = { msg match { case Containable.MoveItem(_, item, _) => - //momentarily put item back where it was originally + //momentarily depict item back where it was originally val obj = ContainerObject obj.Find(item) match { case Some(slot) => - obj.Zone.AvatarEvents ! AvatarServiceMessage( - player.Name, - AvatarAction.SendResponse(Service.defaultPlayerGUID, ObjectAttachMessage(obj.GUID, item.GUID, slot)) - ) + PutItemInSlotCallback(item, slot) case None => ; } case _ => ; @@ -840,7 +828,13 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm val obj = ContainerObject val zone = obj.Zone val events = zone.AvatarEvents - val toChannel = if (obj.VisibleSlots.contains(slot)) zone.id else player.Name + val toChannel = if (player.isBackpack) { + self.toString + } else if (obj.VisibleSlots.contains(slot)) { + zone.id + } else { + player.Name + } item.Faction = PlanetSideEmpire.NEUTRAL if (slot == obj.DrawnSlot) { obj.DrawnSlot = Player.HandsDownSlot @@ -856,9 +850,10 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm val name = player.Name val definition = item.Definition val faction = obj.Faction + val toChannel = if (player.isBackpack) { self.toString } else { name } item.Faction = faction events ! AvatarServiceMessage( - name, + toChannel, AvatarAction.SendResponse( Service.defaultPlayerGUID, ObjectCreateDetailedMessage( @@ -869,7 +864,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm ) ) ) - if (obj.VisibleSlots.contains(slot)) { + if (!player.isBackpack && obj.VisibleSlots.contains(slot)) { events ! AvatarServiceMessage(zone.id, AvatarAction.EquipmentInHand(guid, guid, slot, item)) } //handle specific types of items @@ -924,7 +919,13 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm def SwapItemCallback(item: Equipment, fromSlot: Int): Unit = { val obj = ContainerObject val zone = obj.Zone - val toChannel = if (obj.VisibleSlots.contains(fromSlot)) zone.id else player.Name + val toChannel = if (player.isBackpack) { + self.toString + } else if (obj.VisibleSlots.contains(fromSlot)) { + zone.id + } else { + player.Name + } zone.AvatarEvents ! AvatarServiceMessage( toChannel, AvatarAction.ObjectDelete(Service.defaultPlayerGUID, item.GUID) diff --git a/src/main/scala/net/psforever/objects/definition/converter/AvatarConverter.scala b/src/main/scala/net/psforever/objects/definition/converter/AvatarConverter.scala index 48bb92e3..819f697d 100644 --- a/src/main/scala/net/psforever/objects/definition/converter/AvatarConverter.scala +++ b/src/main/scala/net/psforever/objects/definition/converter/AvatarConverter.scala @@ -70,7 +70,7 @@ object AvatarConverter { obj.Faction, bops = false, alt_model_flag, - false, + v1 = false, None, obj.Jammed, None, @@ -89,11 +89,11 @@ object AvatarConverter { 0L, outfit_name = "", outfit_logo = 0, - false, + unk1 = false, obj.isBackpack, - false, - false, - false, + unk2 = false, + unk3 = false, + unk4 = false, facingPitch = obj.Orientation.y, facingYawUpper = obj.FacingYawUpper, obj.avatar.lookingForSquad, @@ -163,21 +163,21 @@ object AvatarConverter { Some(DCDExtra2(0, 0)), Nil, Nil, - false, + unkC = false, obj.avatar.cosmetics ) pad_length: Option[Int] => DetailedCharacterData(ba, bb(obj.avatar.bep, pad_length))(pad_length) } def MakeInventoryData(obj: Player): InventoryData = { - InventoryData(MakeHolsters(obj, BuildEquipment).sortBy(_.parentSlot)) + InventoryData(MakeHolsters(obj, BuildEquipment)) } def MakeDetailedInventoryData(obj: Player): InventoryData = { InventoryData( - (MakeHolsters(obj, BuildDetailedEquipment) ++ + MakeHolsters(obj, BuildDetailedEquipment) ++ MakeFifthSlot(obj) ++ - MakeInventory(obj)).sortBy(_.parentSlot) + MakeInventory(obj) ) } @@ -210,7 +210,7 @@ object AvatarConverter { * @param builder the function used to transform to the decoded packet form * @return a list of all items that were in the holsters in decoded packet form */ - private def MakeHolsters(obj: Player, builder: (Int, Equipment) => InternalSlot): List[InternalSlot] = { + def MakeHolsters(obj: Player, builder: (Int, Equipment) => InternalSlot): List[InternalSlot] = { recursiveMakeHolsters(obj.Holsters().iterator, builder) } @@ -222,9 +222,8 @@ object AvatarConverter { * @return a list of any item that was in the fifth holster in decoded packet form */ private def MakeFifthSlot(obj: Player): List[InternalSlot] = { - obj.Slot(5).Equipment match { + obj.Slot(slot = 5).Equipment match { case Some(equip) => - //List(BuildDetailedEquipment(5, equip)) List(InternalSlot( equip.Definition.ObjectId, equip.GUID, diff --git a/src/main/scala/net/psforever/objects/definition/converter/CorpseConverter.scala b/src/main/scala/net/psforever/objects/definition/converter/CorpseConverter.scala index 8a1ec259..686e89dd 100644 --- a/src/main/scala/net/psforever/objects/definition/converter/CorpseConverter.scala +++ b/src/main/scala/net/psforever/objects/definition/converter/CorpseConverter.scala @@ -3,11 +3,9 @@ package net.psforever.objects.definition.converter import net.psforever.objects.Player import net.psforever.objects.avatar.Certification -import net.psforever.objects.equipment.{Equipment, EquipmentSlot} import net.psforever.packet.game.objectcreate._ import net.psforever.types.{PlanetSideGUID, _} -import scala.annotation.tailrec import scala.util.{Failure, Success, Try} class CorpseConverter extends AvatarConverter { @@ -20,7 +18,6 @@ class CorpseConverter extends AvatarConverter { PlacementData(obj.Position, Vector3(0, 0, obj.Orientation.z)), MakeAppearanceData(obj), MakeDetailedCharacterData(obj), - InventoryData((MakeHolsters(obj) ++ MakeInventory(obj)).sortBy(_.parentSlot)), DrawnSlot.None ) ) @@ -117,78 +114,7 @@ class CorpseConverter extends AvatarConverter { unkC = false, cosmetics = None ) - (pad_length: Option[Int]) => DetailedCharacterData(ba, bb(0, pad_length))(pad_length) - } - - /** - * Given a player with an inventory, convert the contents of that inventory into converted-decoded packet data. - * The inventory is not represented in a `0x17` `Player`, so the conversion is only valid for `0x18` avatars. - * It will always be "`Detailed`". - * @param obj the `Player` game object - * @return a list of all items that were in the inventory in decoded packet form - */ - private def MakeInventory(obj: Player): List[InternalSlot] = { - obj.Inventory.Items - .map(item => { - val equip: Equipment = item.obj - BuildEquipment(item.start, equip) - }) - } - - /** - * Given a player with equipment holsters, convert the contents of those holsters into converted-decoded packet data. - * The decoded packet form is determined by the function in the parameters as both `0x17` and `0x18` conversions are available, - * with exception to the contents of the fifth slot. - * The fifth slot is only represented if the `Player` is an `0x18` type. - * @param obj the `Player` game object - * @return a list of all items that were in the holsters in decoded packet form - */ - private def MakeHolsters(obj: Player): List[InternalSlot] = { - recursiveMakeHolsters(obj.Holsters().iterator) - } - - /** - * Given some equipment holsters, convert the contents of those holsters into converted-decoded packet data. - * @param iter an `Iterator` of `EquipmentSlot` objects that are a part of the player's holsters - * @param list the current `List` of transformed data - * @param index which holster is currently being explored - * @return the `List` of inventory data created from the holsters - */ - @tailrec private def recursiveMakeHolsters( - iter: Iterator[EquipmentSlot], - list: List[InternalSlot] = Nil, - index: Int = 0 - ): List[InternalSlot] = { - if (!iter.hasNext) { - list - } else { - val slot: EquipmentSlot = iter.next() - if (slot.Equipment.isDefined) { - val equip: Equipment = slot.Equipment.get - recursiveMakeHolsters( - iter, - list :+ BuildEquipment(index, equip), - index + 1 - ) - } else { - recursiveMakeHolsters(iter, list, index + 1) - } - } - } - - /** - * A builder method for turning an object into `0x17` decoded packet form. - * @param index the position of the object - * @param equip the game object - * @return the game object in decoded packet form - */ - private def BuildEquipment(index: Int, equip: Equipment): InternalSlot = { - InternalSlot( - equip.Definition.ObjectId, - equip.GUID, - index, - equip.Definition.Packet.DetailedConstructorData(equip).get - ) + pad_length: Option[Int] => DetailedCharacterData(ba, bb(0, pad_length))(pad_length) } }