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
- 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:

View file

@ -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,

View file

@ -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
}

View file

@ -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

View file

@ -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 {

View file

@ -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)
}
}

View file

@ -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
}

View file

@ -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
}
}
/**

View file

@ -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] = {

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 })
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)

View file

@ -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,

View file

@ -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)
}
}