mirror of
https://github.com/2revoemag/PSF-BotServer.git
synced 2026-01-19 18:14:44 +00:00
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:
parent
e357663364
commit
babd455753
2
.github/workflows/publish.yaml
vendored
2
.github/workflows/publish.yaml
vendored
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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] = {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue