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
This commit is contained in:
Fate-JH 2020-11-27 09:30:56 -05:00 committed by GitHub
parent e357663364
commit babd455753
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 196 additions and 169 deletions

View file

@ -36,7 +36,7 @@ jobs:
uses: rlespinasse/github-slug-action@v2.x uses: rlespinasse/github-slug-action@v2.x
- name: Set variables - name: Set variables
run: | 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 - name: Build and push Docker image
uses: docker/build-push-action@v2.2.0 uses: docker/build-push-action@v2.2.0
with: with:

View file

@ -3,7 +3,7 @@ import xerial.sbt.pack.PackPlugin._
lazy val psforeverSettings = Seq( lazy val psforeverSettings = Seq(
organization := "net.psforever", organization := "net.psforever",
version := "1.0.2-SNAPSHOT", version := "1.0.2-SNAPSHOT",
scalaVersion := "2.13.4", scalaVersion := "2.13.3",
Global / cancelable := false, Global / cancelable := false,
semanticdbEnabled := true, semanticdbEnabled := true,
semanticdbVersion := scalafixSemanticdb.revision, semanticdbVersion := scalafixSemanticdb.revision,

View file

@ -4,6 +4,7 @@ import java.net.{InetAddress, InetSocketAddress}
import java.nio.file.Paths import java.nio.file.Paths
import java.util.Locale import java.util.Locale
import java.util.UUID.randomUUID import java.util.UUID.randomUUID
import java.util.concurrent.atomic.AtomicLong
import akka.actor.ActorSystem import akka.actor.ActorSystem
import akka.actor.typed.ActorRef import akka.actor.typed.ActorRef
@ -19,7 +20,7 @@ import net.psforever.login.psadmin.PsAdminActor
import net.psforever.login._ import net.psforever.login._
import net.psforever.objects.Default import net.psforever.objects.Default
import net.psforever.objects.zones._ 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.chat.ChatService
import net.psforever.services.galaxy.GalaxyService import net.psforever.services.galaxy.GalaxyService
import net.psforever.services.properties.PropertyOverrideManager import net.psforever.services.properties.PropertyOverrideManager
@ -95,19 +96,21 @@ object Server {
Default(system) Default(system)
// typed to classic wrappers for login and session actors // 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 => { 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 => { Behaviors.receiveMessage(message => {
actor ! message actor ! message
Behaviors.same Behaviors.same
}) })
}) })
} }
val session = (ref: ActorRef[MiddlewareActor.Command], connectionId: String) => { val session = (ref: ActorRef[MiddlewareActor.Command], info: InetSocketAddress, connectionId: String) => {
Behaviors.setup[PlanetSidePacket](context => { Behaviors.setup[PlanetSidePacket](context => {
val uuid = randomUUID().toString 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 => { Behaviors.receiveMessage(message => {
actor ! message actor ! message
Behaviors.same Behaviors.same
@ -230,6 +233,30 @@ object Server {
case _ => case _ =>
sys.exit(1) 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
} }

View file

@ -9,7 +9,6 @@ import net.psforever.objects.serverobject.structures.StructureType
import net.psforever.objects.{GlobalDefinitions, Player, Vehicle} import net.psforever.objects.{GlobalDefinitions, Player, Vehicle}
import net.psforever.objects.zones.Zone import net.psforever.objects.zones.Zone
import net.psforever.types.{PlanetSideGUID, _} import net.psforever.types.{PlanetSideGUID, _}
import net.psforever.services.RemoverActor
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage} import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
import akka.actor.typed.scaladsl.adapter._ import akka.actor.typed.scaladsl.adapter._
import net.psforever.actors.zone.ZoneActor import net.psforever.actors.zone.ZoneActor

View file

@ -10,7 +10,7 @@ import net.psforever.packet.game._
import net.psforever.persistence import net.psforever.persistence
import net.psforever.services.ServiceManager import net.psforever.services.ServiceManager
import net.psforever.services.ServiceManager.Lookup 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.types.PlanetSideEmpire
import net.psforever.util.Config import net.psforever.util.Config
import net.psforever.util.Database._ 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 extends Actor
with MDCContextAware { with MDCContextAware {
private[this] val log = org.log4s.getLogger 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 val usernameRegex = """[A-Za-z0-9]{3,}""".r
var sessionId: Long = 0
var leftRef: ActorRef = ActorRef.noSender var leftRef: ActorRef = ActorRef.noSender
var rightRef: ActorRef = ActorRef.noSender var rightRef: ActorRef = ActorRef.noSender
var accountIntermediary: 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" val clientVersion = s"Client Version: $majorVersion.$minorVersion.$revision, $buildDate"
accountIntermediary ! RetrieveIPAddress(sessionId)
if (token.isDefined) if (token.isDefined)
log.info(s"New login UN:$username Token:${token.get}. $clientVersion") log.info(s"New login UN:$username Token:${token.get}. $clientVersion")
else { else {

View file

@ -59,7 +59,7 @@ object MiddlewareActor {
def apply( def apply(
socket: ActorRef[Udp.Command], socket: ActorRef[Udp.Command],
sender: InetSocketAddress, sender: InetSocketAddress,
next: (ActorRef[Command], String) => Behavior[PlanetSidePacket], next: (ActorRef[Command], InetSocketAddress, String) => Behavior[PlanetSidePacket],
connectionId: String connectionId: String
): Behavior[Command] = ): Behavior[Command] =
Behaviors.setup(context => new MiddlewareActor(context, socket, sender, next, connectionId).start()) Behaviors.setup(context => new MiddlewareActor(context, socket, sender, next, connectionId).start())
@ -83,7 +83,7 @@ class MiddlewareActor(
context: ActorContext[MiddlewareActor.Command], context: ActorContext[MiddlewareActor.Command],
socket: ActorRef[Udp.Command], socket: ActorRef[Udp.Command],
sender: InetSocketAddress, sender: InetSocketAddress,
next: (ActorRef[MiddlewareActor.Command], String) => Behavior[PlanetSidePacket], next: (ActorRef[MiddlewareActor.Command], InetSocketAddress, String) => Behavior[PlanetSidePacket],
connectionId: String connectionId: String
) { ) {
@ -102,7 +102,7 @@ class MiddlewareActor(
var crypto: Option[CryptoCoding] = None var crypto: Option[CryptoCoding] = None
val nextActor: ActorRef[PlanetSidePacket] = 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 */ /** Queue of incoming packets (plus sequence numbers and timestamps) that arrived in the wrong order */
val inReorderQueue: ListBuffer[(PlanetSidePacket, Int, Long)] = ListBuffer() val inReorderQueue: ListBuffer[(PlanetSidePacket, Int, Long)] = ListBuffer()
@ -329,7 +329,7 @@ class MiddlewareActor(
connectionClose() connectionClose()
} }
case Failure(e) => case Failure(e) =>
log.error(s"Could not decode packet in cryptoSetup: ${e}") log.error(s"Could not decode packet in cryptoSetup: $e")
connectionClose() connectionClose()
} }
case other => case other =>
@ -420,7 +420,7 @@ class MiddlewareActor(
inReorderQueue.addOne((packet, sequence, System.currentTimeMillis())) inReorderQueue.addOne((packet, sequence, System.currentTimeMillis()))
} }
case Successful((packet, None)) => in(packet) 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 Behaviors.same
@ -483,7 +483,7 @@ class MiddlewareActor(
case RelatedA(slot, subslot) => case RelatedA(slot, subslot) =>
log.info(s"Client indicated a packet is missing prior to slot '$slot' and subslot '$subslot'") log.info(s"Client indicated a packet is missing prior to slot '$slot' and subslot '$subslot'")
outSlottedMetaPackets.find(_.subslot == subslot - 1) match { 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'") case None => log.warn(s"Client requested unknown subslot '$subslot'")
} }
Behaviors.same Behaviors.same
@ -526,7 +526,7 @@ class MiddlewareActor(
def in(packet: Attempt[PlanetSidePacket]): Unit = { def in(packet: Attempt[PlanetSidePacket]): Unit = {
packet match { packet match {
case Successful(packet) => in(packet) case Successful(_packet) => in(_packet)
case Failure(cause) => log.error(cause.message) case Failure(cause) => log.error(cause.message)
} }
} }

View file

@ -26,7 +26,7 @@ import scala.util.Random
object SocketActor { object SocketActor {
def apply( def apply(
address: InetSocketAddress, address: InetSocketAddress,
next: (ActorRef[MiddlewareActor.Command], String) => Behavior[PlanetSidePacket] next: (ActorRef[MiddlewareActor.Command], InetSocketAddress, String) => Behavior[PlanetSidePacket]
): Behavior[Command] = ): Behavior[Command] =
Behaviors.setup(context => new SocketActor(context, address, next).start()) Behaviors.setup(context => new SocketActor(context, address, next).start())
@ -64,7 +64,7 @@ object SocketActor {
case _: Udp.Received | _: Udp.Send => case _: Udp.Received | _: Udp.Send =>
simulate(message) simulate(message)
Behaviors.same Behaviors.same
case other => case _ =>
socketActor ! toSocket(message) socketActor ! toSocket(message)
Behaviors.same Behaviors.same
} }
@ -97,7 +97,7 @@ object SocketActor {
class SocketActor( class SocketActor(
context: ActorContext[SocketActor.Command], context: ActorContext[SocketActor.Command],
address: InetSocketAddress, address: InetSocketAddress,
next: (ActorRef[MiddlewareActor.Command], String) => Behavior[PlanetSidePacket] next: (ActorRef[MiddlewareActor.Command], InetSocketAddress, String) => Behavior[PlanetSidePacket]
) { ) {
import SocketActor._ import SocketActor._
import SocketActor.Command import SocketActor.Command
@ -174,13 +174,13 @@ class SocketActor(
case Udp.Received(data, remote) => case Udp.Received(data, remote) =>
incomingTimes(remote) = System.currentTimeMillis() incomingTimes(remote) = System.currentTimeMillis()
val ref = packetActors.get(remote) match { val ref = packetActors.get(remote) match {
case Some(ref) => ref case Some(_ref) => _ref
case None => case None =>
val connectionId = randomUUID.toString val connectionId = randomUUID.toString
val ref = context.spawn( val ref = context.spawn(
MiddlewareActor(udpCommandAdapter, remote, next, connectionId), MiddlewareActor(udpCommandAdapter, remote, next, connectionId),
s"middleware-${connectionId}", s"middleware-$connectionId",
ActorTags(s"uuid=${connectionId}") ActorTags(s"uuid=$connectionId")
) )
context.watch(ref) context.watch(ref)
packetActors(remote) = ref packetActors(remote) = ref
@ -218,7 +218,7 @@ class SocketActor(
case (_, Terminated(ref)) => case (_, Terminated(ref)) =>
packetActors.find(_._2 == ref) match { packetActors.find(_._2 == ref) match {
case Some((remote, _)) => packetActors.remove(remote) 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 Behaviors.same
} }

View file

@ -158,7 +158,7 @@ object SessionActor {
private final case class LoadedRemoteProjectile(projectile_guid: PlanetSideGUID, projectile: Option[Projectile]) 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 extends Actor
with MDCContextAware { with MDCContextAware {
@ -5985,6 +5985,8 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
AccessVehicleContents(v) AccessVehicleContents(v)
case o: LockerContainer => case o: LockerContainer =>
AccessGenericContainer(o) AccessGenericContainer(o)
case p: Player if p.isBackpack =>
AccessCorpseContents(p)
case p: PlanetSideServerObject with Container => case p: PlanetSideServerObject with Container =>
accessedContainer = Some(p) accessedContainer = Some(p)
case _ => ; case _ => ;
@ -5993,27 +5995,59 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
def AccessGenericContainer(container: PlanetSideServerObject with Container): Unit = { def AccessGenericContainer(container: PlanetSideServerObject with Container): Unit = {
accessedContainer = Some(container) 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. * Join a vehicle-specific group for shared updates.
* Construct every object in the vehicle's inventory for shared manipulation updates. * Construct every object in the vehicle's inventory for shared manipulation updates.
* @see `Container.Inventory`
* @see `GridInventory.Items`
* @param vehicle the vehicle * @param vehicle the vehicle
*/ */
def AccessVehicleContents(vehicle: Vehicle): Unit = { def AccessVehicleContents(vehicle: Vehicle): Unit = {
accessedContainer = Some(vehicle) accessedContainer = Some(vehicle)
AccessVehicleContainerChannel(vehicle) AccessContainerChannel(continent.VehicleEvents, vehicle.Actor.toString)
DisplayContainerContents(vehicle.GUID, vehicle) 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 obj = entry.obj
val objDef = obj.Definition val objDef = obj.Definition
sendResponse( 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 = { def UnaccessContainer(): Unit = {
accessedContainer match { accessedContainer match {
case Some(container) => UnaccessContainer(container) 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 = { def UnaccessContainer(container: Container): Unit = {
container match { container match {
case v: Vehicle => case v: Vehicle =>
UnaccessVehicleContainer(v) UnaccessVehicleContainer(v)
case o: LockerContainer => case o: LockerContainer =>
UnaccessGenericContainer(o) UnaccessGenericContainer(o)
case p: Player if p.isBackpack =>
UnaccessCorpseContainer(p)
case _: PlanetSideServerObject with Container => case _: PlanetSideServerObject with Container =>
accessedContainer = None accessedContainer = None
case _ => ; case _ => ;
@ -6048,7 +6091,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
def UnaccessGenericContainer(container: Container): Unit = { def UnaccessGenericContainer(container: Container): Unit = {
accessedContainer = None 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)) { if (vehicle.AccessingTrunk.contains(player.GUID)) {
vehicle.AccessingTrunk = None vehicle.AccessingTrunk = None
} }
UnaccessVehicleContainerChannel(vehicle) UnaccessContainerChannel(continent.VehicleEvents, vehicle.Actor.toString)
HideContainerContents(vehicle) 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)) sendResponse(ObjectDeleteMessage(entry.obj.GUID, 0))
}) }
} }
/** /**
@ -7189,6 +7256,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
*/ */
def DepictPlayerAsCorpse(tplayer: Player): Unit = { def DepictPlayerAsCorpse(tplayer: Player): Unit = {
val guid = tplayer.GUID val guid = tplayer.GUID
//the corpse as a receptacle
sendResponse( sendResponse(
ObjectCreateDetailedMessage( ObjectCreateDetailedMessage(
ObjectClass.avatar, ObjectClass.avatar,
@ -8241,23 +8309,20 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
* @return all discovered `BoomTrigger` objects * @return all discovered `BoomTrigger` objects
*/ */
def RemoveBoomerTriggersFromInventory(): List[BoomerTrigger] = { def RemoveBoomerTriggersFromInventory(): List[BoomerTrigger] = {
val holstersWithIndex = player.Holsters().zipWithIndex val events = continent.AvatarEvents
((player.Inventory.Items.collect({ case InventoryItem(obj: BoomerTrigger, index) => (obj, index) })) ++ val zoneId = continent.id
(holstersWithIndex (player.Inventory.Items ++ player.HolsterItems())
.map({ case ((slot, index)) => (slot.Equipment, index) }) .collect { case InventoryItem(obj: BoomerTrigger, index) =>
.collect { case ((Some(obj: BoomerTrigger), index)) => (obj, index) })) player.Slot(index).Equipment = None
.map({ sendResponse(ObjectDeleteMessage(obj.GUID, 0))
case ((obj, index)) => if (player.HasGUID && player.VisibleSlots.contains(index)) {
player.Slot(index).Equipment = None events ! AvatarServiceMessage(
sendResponse(ObjectDeleteMessage(obj.GUID, 0)) zoneId,
if (player.VisibleSlots.contains(index) && player.HasGUID) { AvatarAction.ObjectDelete(player.GUID, obj.GUID)
continent.AvatarEvents ! AvatarServiceMessage( )
continent.id, }
AvatarAction.ObjectDelete(player.GUID, obj.GUID) obj
) }
}
obj
})
} }
/** /**

View file

@ -190,6 +190,19 @@ class Player(var avatar: Avatar)
def Holsters(): Array[EquipmentSlot] = holsters 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 def Inventory: GridInventory = inventory
override def Fit(obj: Equipment): Option[Int] = { override def Fit(obj: Equipment): Option[Int] = {

View file

@ -260,12 +260,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
afterHolsters.foreach({ elem => player.Slot(elem.start).Equipment = elem.obj }) afterHolsters.foreach({ elem => player.Slot(elem.start).Equipment = elem.obj })
val remainder = Players.fillEmptyHolsters(player.Holsters().iterator, toInventory ++ beforeInventory) val remainder = Players.fillEmptyHolsters(player.Holsters().iterator, toInventory ++ beforeInventory)
( (
player player.HolsterItems(),
.Holsters()
.zipWithIndex
.map { case (slot, i) => (slot.Equipment, i) }
.collect { case (Some(obj), index) => InventoryItem(obj, index) }
.toList,
remainder remainder
) )
} }
@ -408,11 +403,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
player.Holsters().iterator, player.Holsters().iterator,
(curatedHolsters ++ curatedInventory).filterNot(dropPred) (curatedHolsters ++ curatedInventory).filterNot(dropPred)
) )
val finalHolsters = player val finalHolsters = player.HolsterItems()
.Holsters()
.zipWithIndex
.collect { case (slot, index) if slot.Equipment.nonEmpty => InventoryItem(slot.Equipment.get, index) }
.toList
//inventory //inventory
val (finalInventory, _) = GridInventory.recoverInventory(leftoversForInventory, player.Inventory) val (finalInventory, _) = GridInventory.recoverInventory(leftoversForInventory, player.Inventory)
(finalHolsters, finalInventory) (finalHolsters, finalInventory)
@ -822,14 +813,11 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
def MessageDeferredCallback(msg: Any): Unit = { def MessageDeferredCallback(msg: Any): Unit = {
msg match { msg match {
case Containable.MoveItem(_, item, _) => case Containable.MoveItem(_, item, _) =>
//momentarily put item back where it was originally //momentarily depict item back where it was originally
val obj = ContainerObject val obj = ContainerObject
obj.Find(item) match { obj.Find(item) match {
case Some(slot) => case Some(slot) =>
obj.Zone.AvatarEvents ! AvatarServiceMessage( PutItemInSlotCallback(item, slot)
player.Name,
AvatarAction.SendResponse(Service.defaultPlayerGUID, ObjectAttachMessage(obj.GUID, item.GUID, slot))
)
case None => ; case None => ;
} }
case _ => ; case _ => ;
@ -840,7 +828,13 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
val obj = ContainerObject val obj = ContainerObject
val zone = obj.Zone val zone = obj.Zone
val events = zone.AvatarEvents 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 item.Faction = PlanetSideEmpire.NEUTRAL
if (slot == obj.DrawnSlot) { if (slot == obj.DrawnSlot) {
obj.DrawnSlot = Player.HandsDownSlot obj.DrawnSlot = Player.HandsDownSlot
@ -856,9 +850,10 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
val name = player.Name val name = player.Name
val definition = item.Definition val definition = item.Definition
val faction = obj.Faction val faction = obj.Faction
val toChannel = if (player.isBackpack) { self.toString } else { name }
item.Faction = faction item.Faction = faction
events ! AvatarServiceMessage( events ! AvatarServiceMessage(
name, toChannel,
AvatarAction.SendResponse( AvatarAction.SendResponse(
Service.defaultPlayerGUID, Service.defaultPlayerGUID,
ObjectCreateDetailedMessage( 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)) events ! AvatarServiceMessage(zone.id, AvatarAction.EquipmentInHand(guid, guid, slot, item))
} }
//handle specific types of items //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 = { def SwapItemCallback(item: Equipment, fromSlot: Int): Unit = {
val obj = ContainerObject val obj = ContainerObject
val zone = obj.Zone 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( zone.AvatarEvents ! AvatarServiceMessage(
toChannel, toChannel,
AvatarAction.ObjectDelete(Service.defaultPlayerGUID, item.GUID) AvatarAction.ObjectDelete(Service.defaultPlayerGUID, item.GUID)

View file

@ -70,7 +70,7 @@ object AvatarConverter {
obj.Faction, obj.Faction,
bops = false, bops = false,
alt_model_flag, alt_model_flag,
false, v1 = false,
None, None,
obj.Jammed, obj.Jammed,
None, None,
@ -89,11 +89,11 @@ object AvatarConverter {
0L, 0L,
outfit_name = "", outfit_name = "",
outfit_logo = 0, outfit_logo = 0,
false, unk1 = false,
obj.isBackpack, obj.isBackpack,
false, unk2 = false,
false, unk3 = false,
false, unk4 = false,
facingPitch = obj.Orientation.y, facingPitch = obj.Orientation.y,
facingYawUpper = obj.FacingYawUpper, facingYawUpper = obj.FacingYawUpper,
obj.avatar.lookingForSquad, obj.avatar.lookingForSquad,
@ -163,21 +163,21 @@ object AvatarConverter {
Some(DCDExtra2(0, 0)), Some(DCDExtra2(0, 0)),
Nil, Nil,
Nil, Nil,
false, unkC = false,
obj.avatar.cosmetics obj.avatar.cosmetics
) )
pad_length: Option[Int] => DetailedCharacterData(ba, bb(obj.avatar.bep, pad_length))(pad_length) pad_length: Option[Int] => DetailedCharacterData(ba, bb(obj.avatar.bep, pad_length))(pad_length)
} }
def MakeInventoryData(obj: Player): InventoryData = { def MakeInventoryData(obj: Player): InventoryData = {
InventoryData(MakeHolsters(obj, BuildEquipment).sortBy(_.parentSlot)) InventoryData(MakeHolsters(obj, BuildEquipment))
} }
def MakeDetailedInventoryData(obj: Player): InventoryData = { def MakeDetailedInventoryData(obj: Player): InventoryData = {
InventoryData( InventoryData(
(MakeHolsters(obj, BuildDetailedEquipment) ++ MakeHolsters(obj, BuildDetailedEquipment) ++
MakeFifthSlot(obj) ++ 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 * @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 * @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) 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 * @return a list of any item that was in the fifth holster in decoded packet form
*/ */
private def MakeFifthSlot(obj: Player): List[InternalSlot] = { private def MakeFifthSlot(obj: Player): List[InternalSlot] = {
obj.Slot(5).Equipment match { obj.Slot(slot = 5).Equipment match {
case Some(equip) => case Some(equip) =>
//List(BuildDetailedEquipment(5, equip))
List(InternalSlot( List(InternalSlot(
equip.Definition.ObjectId, equip.Definition.ObjectId,
equip.GUID, equip.GUID,

View file

@ -3,11 +3,9 @@ package net.psforever.objects.definition.converter
import net.psforever.objects.Player import net.psforever.objects.Player
import net.psforever.objects.avatar.Certification import net.psforever.objects.avatar.Certification
import net.psforever.objects.equipment.{Equipment, EquipmentSlot}
import net.psforever.packet.game.objectcreate._ import net.psforever.packet.game.objectcreate._
import net.psforever.types.{PlanetSideGUID, _} import net.psforever.types.{PlanetSideGUID, _}
import scala.annotation.tailrec
import scala.util.{Failure, Success, Try} import scala.util.{Failure, Success, Try}
class CorpseConverter extends AvatarConverter { class CorpseConverter extends AvatarConverter {
@ -20,7 +18,6 @@ class CorpseConverter extends AvatarConverter {
PlacementData(obj.Position, Vector3(0, 0, obj.Orientation.z)), PlacementData(obj.Position, Vector3(0, 0, obj.Orientation.z)),
MakeAppearanceData(obj), MakeAppearanceData(obj),
MakeDetailedCharacterData(obj), MakeDetailedCharacterData(obj),
InventoryData((MakeHolsters(obj) ++ MakeInventory(obj)).sortBy(_.parentSlot)),
DrawnSlot.None DrawnSlot.None
) )
) )
@ -117,78 +114,7 @@ class CorpseConverter extends AvatarConverter {
unkC = false, unkC = false,
cosmetics = None cosmetics = None
) )
(pad_length: Option[Int]) => DetailedCharacterData(ba, bb(0, pad_length))(pad_length) 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
)
} }
} }