Operational vehicle terminals:

Vehicles can now be pulled from assigned and initialized terminals.  The vehicle's chosen spawn pad controls (or paces) all aspects of the spawning process.  Support Actors ensure that a fully-realized Vehicle will be unloaded and unregistered if left alone, either right after spawning on the pad or after an extended period of time.  The latter half of the procedure used for spawning vehicles is a temporary workaround until future analysis and functionality of the server vehicle override packet is incorporated.

Weapons:

Weapons will now construct their own default magazines thanks to a switch from Ammo.Value to AmmoBoxDefinition in the ToolDefinition.

GenericObjectActionMessage :

The only thing this packet does, at the moment, is obscure the player when he is being promoted into the owner of a vehicle.
This commit is contained in:
FateJH 2017-11-08 21:00:46 -05:00
parent 73d0553b2c
commit 5428bbbfbf
57 changed files with 2957 additions and 538 deletions

View file

@ -14,7 +14,8 @@ import com.typesafe.config.ConfigFactory
import net.psforever.crypto.CryptoInterface
import net.psforever.objects.zones._
import net.psforever.objects.guid.TaskResolver
import net.psforever.objects.serverobject.builders.{DoorObjectBuilder, IFFLockObjectBuilder, TerminalObjectBuilder}
import net.psforever.objects.serverobject.builders.{DoorObjectBuilder, IFFLockObjectBuilder, TerminalObjectBuilder, VehicleSpawnPadObjectBuilder}
import net.psforever.types.Vector3
import org.slf4j
import org.fusesource.jansi.Ansi._
import org.fusesource.jansi.Ansi.Color._
@ -244,6 +245,10 @@ object PsLogin {
LocalObject(TerminalObjectBuilder(order_terminal, 853))
LocalObject(TerminalObjectBuilder(order_terminal, 855))
LocalObject(TerminalObjectBuilder(order_terminal, 860))
LocalObject(TerminalObjectBuilder(ground_vehicle_terminal, 1063))
LocalObject(VehicleSpawnPadObjectBuilder(spawn_pad, 500)) //TODO guid not correct
LocalObject(TerminalObjectBuilder(dropship_vehicle_terminal, 304))
LocalObject(VehicleSpawnPadObjectBuilder(spawn_pad, 501)) //TODO guid not correct
LocalBases = 30
@ -251,8 +256,14 @@ object PsLogin {
ObjectToBase(332, 29)
ObjectToBase(556, 29)
ObjectToBase(558, 29)
ObjectToBase(1063, 29) //TODO unowned courtyard terminal?
ObjectToBase(500, 29) //TODO unowned courtyard spawnpad?
ObjectToBase(304, 29) //TODO unowned courtyard terminal?
ObjectToBase(501, 29) //TODO unowned courtyard spawnpad?
DoorToLock(330, 558)
DoorToLock(332, 556)
TerminalToSpawnPad(1063, 500)
TerminalToSpawnPad(304, 501)
}
val home3 = new Zone("home3", map13, 13) {
override def Init(implicit context : ActorContext) : Unit = {
@ -261,6 +272,19 @@ object PsLogin {
import net.psforever.types.PlanetSideEmpire
Base(2).get.Faction = PlanetSideEmpire.VS //HART building C
Base(29).get.Faction = PlanetSideEmpire.NC //South Villa Gun Tower
GUID(500) match {
case Some(pad) =>
pad.Position = Vector3(3506.0f, 2820.0f, 92.0f)
pad.Orientation = Vector3(0f, 0f, 270.0f)
case None => ;
}
GUID(501) match {
case Some(pad) =>
pad.Position = Vector3(3508.9844f, 2895.961f, 92.296875f)
pad.Orientation = Vector3(0f, 0f, 270.0f)
case None => ;
}
}
}

View file

@ -17,8 +17,9 @@ import net.psforever.objects.inventory.{GridInventory, InventoryItem}
import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject}
import net.psforever.objects.serverobject.doors.Door
import net.psforever.objects.serverobject.locks.IFFLock
import net.psforever.objects.serverobject.pad.VehicleSpawnPad
import net.psforever.objects.serverobject.terminals.Terminal
import net.psforever.objects.vehicles.{AccessPermissionGroup, Seat, VehicleLockState}
import net.psforever.objects.vehicles.{AccessPermissionGroup, VehicleLockState}
import net.psforever.objects.zones.{InterstellarCluster, Zone}
import net.psforever.packet.game.objectcreate._
import net.psforever.types._
@ -45,25 +46,43 @@ class WorldSessionActor extends Actor with MDCContextAware {
var continent : Zone = null
var progressBarValue : Option[Float] = None
var clientKeepAlive : Cancellable = WorldSessionActor.DefaultCancellable
var progressBarUpdate : Cancellable = WorldSessionActor.DefaultCancellable
var clientKeepAlive : Cancellable = DefaultCancellable.obj
var progressBarUpdate : Cancellable = DefaultCancellable.obj
override def postStop() = {
if(clientKeepAlive != null)
clientKeepAlive.cancel()
localService ! Service.Leave()
vehicleService ! Service.Leave()
avatarService ! Service.Leave()
LivePlayerList.Remove(sessionId) match {
case Some(tplayer) =>
tplayer.VehicleSeated match {
case Some(vehicle_guid) =>
vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.KickPassenger(tplayer.GUID, 0, true))
case None => ;
}
tplayer.VehicleOwned match {
case Some(vehicle_guid) =>
continent.GUID(vehicle_guid) match {
case Some(vehicle : Vehicle) =>
vehicle.Owner = None
//TODO temporary solution; to un-own, permit driver seat to Empire access level
vehicle.PermissionGroup(10, VehicleLockState.Empire.id)
vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.SeatPermissions(tplayer.GUID, vehicle_guid, 10, VehicleLockState.Empire.id))
case _ => ;
}
case None => ;
}
avatarService ! Service.Leave()
localService ! Service.Leave()
vehicleService ! Service.Leave()
LivePlayerList.Remove(sessionId) match {
case Some(tplayer) =>
if(tplayer.HasGUID) {
val guid = tplayer.GUID
avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.ObjectDelete(guid, guid))
taskResolver ! GUIDTask.UnregisterAvatar(tplayer)(continent.GUID)
//TODO normally, the actual player avatar persists a minute or so after the user disconnects
}
case None => ;
if(tplayer.HasGUID) {
val guid = tplayer.GUID
avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.ObjectDelete(guid, guid))
taskResolver ! GUIDTask.UnregisterAvatar(tplayer)(continent.GUID)
//TODO normally, the actual player avatar persists a minute or so after the user disconnects
}
case None => ;
}
}
@ -124,6 +143,11 @@ class WorldSessionActor extends Actor with MDCContextAware {
sendResponse(PacketCoding.CreateGamePacket(0, ArmorChangedMessage(guid, suit, subtype)))
}
case AvatarResponse.ConcealPlayer() =>
if(player.GUID != guid) {
sendResponse(PacketCoding.CreateGamePacket(0, GenericObjectActionMessage(guid, 36)))
}
case AvatarResponse.EquipmentInHand(slot, item) =>
if(player.GUID != guid) {
val definition = item.Definition
@ -186,7 +210,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
}
else {
val before = player.lastSeenStreamMessage(guid.guid)
val dist = WorldSessionActor.DistanceSquared(player.Position, msg.pos)
val dist = Vector3.DistanceSquared(player.Position, msg.pos)
(msg.pos, now - before, dist)
}
@ -280,9 +304,6 @@ class WorldSessionActor extends Actor with MDCContextAware {
case VehicleResponse.MountVehicle(vehicle_guid, seat) =>
if(player.GUID != guid) {
sendResponse(PacketCoding.CreateGamePacket(0, ObjectAttachMessage(vehicle_guid, guid, seat)))
if(player.VehicleOwned.contains(vehicle_guid)) { //simplistic vehicle ownership management
player.VehicleOwned = None
}
}
case VehicleResponse.SeatPermissions(vehicle_guid, seat_group, permission) =>
@ -524,6 +545,19 @@ class WorldSessionActor extends Actor with MDCContextAware {
sendResponse(PacketCoding.CreateGamePacket(0, ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Learn, false)))
}
case Terminal.BuyVehicle(vehicle, loadout) =>
continent.Map.TerminalToSpawnPad.get(msg.terminal_guid.guid) match {
case Some(pad_guid) =>
val pad = continent.GUID(pad_guid).get.asInstanceOf[VehicleSpawnPad]
vehicle.Faction = tplayer.Faction
vehicle.Position = pad.Position
vehicle.Orientation = pad.Orientation
taskResolver ! RegisterNewVehicle(vehicle, pad)
sendResponse(PacketCoding.CreateGamePacket(0, ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Learn, true)))
case None =>
log.error(s"$tplayer wanted to spawn a vehicle, but there was no spawn pad associated with terminal ${msg.terminal_guid} to accept it")
}
case Terminal.NoDeal() =>
log.warn(s"$tplayer made a request but the terminal rejected the order $msg")
sendResponse(PacketCoding.CreateGamePacket(0, ItemTransactionResultMessage(msg.terminal_guid, msg.transaction_type, false)))
@ -533,9 +567,21 @@ class WorldSessionActor extends Actor with MDCContextAware {
reply match {
case Vehicle.CanSeatPlayer(vehicle, seat_num) =>
log.info(s"MountVehicleMsg: ${player.GUID} mounts ${vehicle.GUID} @ $seat_num")
vehicleService ! VehicleServiceMessage.UnscheduleDeconstruction(vehicle.GUID) //clear all deconstruction timers
val vehicle_guid : PlanetSideGUID = vehicle.GUID
tplayer.VehicleSeated = Some(vehicle_guid)
if(seat_num == 0) { //simplistic vehicle ownership management
vehicle.Owner match {
case Some(owner_guid) =>
continent.GUID(owner_guid) match {
case Some(previous_owner : Player) =>
if(previous_owner.VehicleOwned.contains(vehicle_guid)) {
previous_owner.VehicleOwned = None //simplistic ownership management, player loses vehicle ownership
}
case _ => ;
}
case None => ;
}
player.VehicleOwned = Some(vehicle_guid)
vehicle.Owner = Some(player.GUID)
}
@ -558,12 +604,52 @@ class WorldSessionActor extends Actor with MDCContextAware {
vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.MountVehicle(player_guid, vehicle_guid, seat_num))
case Vehicle.CannotSeatPlayer(vehicle, seat_num) =>
val seat : Seat = vehicle.Seat(seat_num).get
log.warn(s"MountVehicleMsg: player $tplayer attempted to board vehicle ${vehicle.GUID}'s seat $seat_num, but was not allowed")
case _ => ;
}
case VehicleSpawnPad.ConcealPlayer =>
sendResponse(PacketCoding.CreateGamePacket(0, GenericObjectActionMessage(player.GUID, 36)))
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ConcealPlayer(player.GUID))
case VehicleSpawnPad.LoadVehicle(vehicle, _/*pad*/) =>
val player_guid = player.GUID
val definition = vehicle.Definition
val objedtId = definition.ObjectId
val vehicle_guid = vehicle.GUID
val vdata = definition.Packet.ConstructorData(vehicle).get
sendResponse(PacketCoding.CreateGamePacket(0, ObjectCreateMessage(objedtId, vehicle_guid, vdata)))
continent.Transport ! Zone.SpawnVehicle(vehicle)
vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.LoadVehicle(player_guid, vehicle, objedtId, vehicle_guid, vdata))
sendResponse(PacketCoding.CreateGamePacket(0, PlanetsideAttributeMessage(vehicle_guid, 22, 1L))) //mount points off?
//sendResponse(PacketCoding.CreateGamePacket(0, PlanetsideAttributeMessage(vehicle_guid, 21, player_guid.guid))) //fte and ownership?
//sendResponse(PacketCoding.CreateGamePacket(0, ObjectAttachMessage(vehicle_guid, player_guid, 0)))
vehicleService ! VehicleServiceMessage.UnscheduleDeconstruction(vehicle_guid) //cancel queue timeout delay
vehicleService ! VehicleServiceMessage.DelayedVehicleDeconstruction(vehicle, continent, 21L) //temporary drive away from pad delay
vehicle.Actor ! Vehicle.TrySeatPlayer(0, player)
case VehicleSpawnPad.PlayerSeatedInVehicle(vehicle) =>
vehicleService ! VehicleServiceMessage.DelayedVehicleDeconstruction(vehicle, continent, 21L) //sitting in the vehicle clears the drive away delay
val vehicle_guid = vehicle.GUID
sendResponse(PacketCoding.CreateGamePacket(0, PlanetsideAttributeMessage(vehicle_guid, 22, 0L))) //mount points on?
//sendResponse(PacketCoding.CreateGamePacket(0, PlanetsideAttributeMessage(vehicle_guid, 0, vehicle.Definition.MaxHealth)))
sendResponse(PacketCoding.CreateGamePacket(0, PlanetsideAttributeMessage(vehicle_guid, 68, 0L))) //???
sendResponse(PacketCoding.CreateGamePacket(0, PlanetsideAttributeMessage(vehicle_guid, 113, 0L))) //???
ReloadVehicleAccessPermissions(vehicle)
case VehicleSpawnPad.SpawnPadBlockedWarning(vehicle, warning_count) =>
if(warning_count > 2) {
sendResponse(PacketCoding.CreateGamePacket(0, TriggerSoundMessage(TriggeredSound.Unknown14, vehicle.Position, 20, 1f)))
sendResponse(PacketCoding.CreateGamePacket(0,
ChatMsg(ChatMessageType.CMT_TELL, true, "", "\\#FYour vehicle is blocking the spawn pad, and will be deconstructed if not moved.", None))
)
}
case VehicleSpawnPad.SpawnPadUnblocked(vehicle_guid) =>
//vehicle has moved away from spawn pad after initial spawn
vehicleService ! VehicleServiceMessage.UnscheduleDeconstruction(vehicle_guid) //cancel temporary drive away from pad delay
case ListAccountCharacters =>
import net.psforever.objects.definition.converter.CharacterSelectConverter
val gen : AtomicInteger = new AtomicInteger(1)
@ -605,15 +691,8 @@ class WorldSessionActor extends Actor with MDCContextAware {
failWithError(s"$tplayer failed to load anywhere")
}
case VehicleLoaded(vehicle) =>
val definition = vehicle.Definition
val objedtId = definition.ObjectId
val vehicle_guid = vehicle.GUID
val vdata = definition.Packet.ConstructorData(vehicle).get
sendResponse(PacketCoding.CreateGamePacket(0, ObjectCreateMessage(objedtId, vehicle_guid, vdata)))
ReloadVehicleAccessPermissions(vehicle)
continent.Transport ! Zone.SpawnVehicle(vehicle)
vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.LoadVehicle(player.GUID, vehicle, objedtId, vehicle_guid, vdata))
case VehicleLoaded(_/*vehicle*/) => ;
//currently being handled by VehicleSpawnPad.LoadVehicle during testing phase
case Zone.ClientInitialization(/*initList*/_) =>
//TODO iterate over initList; for now, just do this
@ -795,7 +874,6 @@ class WorldSessionActor extends Actor with MDCContextAware {
}
var player : Player = null
var harasser : Vehicle = null //TODO used in testing
def handleGamePkt(pkt : PlanetSideGamePacket) = pkt match {
case ConnectToWorldRequestMessage(server, token, majorVersion, minorVersion, revision, buildDate, unk) =>
@ -803,18 +881,9 @@ class WorldSessionActor extends Actor with MDCContextAware {
log.info(s"New world login to $server with Token:$token. $clientVersion")
//TODO begin temp player character auto-loading; remove later
import net.psforever.objects.GlobalDefinitions._
val
beamer1 = Tool(beamer)
beamer1.AmmoSlots.head.Box = AmmoBox(energy_cell, 16)
val
suppressor1 = Tool(suppressor)
suppressor1.AmmoSlots.head.Box = AmmoBox(bullet_9mm, 25)
val
forceblade1 = Tool(forceblade)
forceblade1.AmmoSlots.head.Box = AmmoBox(melee_ammo)
player = Player("TestCharacter"+sessionId.toString, PlanetSideEmpire.VS, CharacterGender.Female, 41, 1)
player.Position = Vector3(3674.8438f, 2726.789f, 91.15625f)
//player.Position = Vector3(3674.8438f, 2726.789f, 91.15625f)
player.Position = Vector3(3523.039f, 2855.5078f, 90.859375f)
player.Orientation = Vector3(0f, 0f, 90f)
player.Certifications += CertificationType.StandardAssault
player.Certifications += CertificationType.MediumAssault
@ -823,9 +892,24 @@ class WorldSessionActor extends Actor with MDCContextAware {
player.Certifications += CertificationType.ReinforcedExoSuit
player.Certifications += CertificationType.ATV
player.Certifications += CertificationType.Harasser
player.Slot(0).Equipment = beamer1
player.Slot(2).Equipment = suppressor1
player.Slot(4).Equipment = forceblade1
//
player.Certifications += CertificationType.GroundSupport
player.Certifications += CertificationType.GroundTransport
player.Certifications += CertificationType.Flail
player.Certifications += CertificationType.Switchblade
player.Certifications += CertificationType.AssaultBuggy
player.Certifications += CertificationType.ArmoredAssault1
player.Certifications += CertificationType.ArmoredAssault2
player.Certifications += CertificationType.AirCavalryScout
player.Certifications += CertificationType.AirCavalryAssault
player.Certifications += CertificationType.AirCavalryInterceptor
player.Certifications += CertificationType.AirSupport
player.Certifications += CertificationType.GalaxyGunship
player.Certifications += CertificationType.Phantasm
//player.ExoSuit = ExoSuitType.Infiltrator
player.Slot(0).Equipment = Tool(beamer)
player.Slot(2).Equipment = Tool(suppressor)
player.Slot(4).Equipment = Tool(forceblade)
player.Slot(6).Equipment = AmmoBox(bullet_9mm)
player.Slot(9).Equipment = AmmoBox(bullet_9mm)
player.Slot(12).Equipment = AmmoBox(bullet_9mm)
@ -917,21 +1001,6 @@ class WorldSessionActor extends Actor with MDCContextAware {
})
ReloadVehicleAccessPermissions(vehicle)
})
//TODO begin temp vehicle auto-loading
import net.psforever.objects.GlobalDefinitions._
if(continent.Vehicles.isEmpty) {
harasser = Vehicle(two_man_assault_buggy)
harasser.Position = Vector3(3674.8438f, 2730.789f, 91.15625f)
harasser.Faction = PlanetSideEmpire.VS
harasser.Orientation = Vector3(0f, 0f, 90f)
harasser.Weapons(2).Equipment.get.asInstanceOf[Tool].AmmoSlots.head.Box = AmmoBox(bullet_12mm, 150)
harasser.Trunk += 30 -> AmmoBox(bullet_12mm, 100)
taskResolver ! RegisterNewVehicle(harasser)
}
else {
harasser = continent.Vehicles.head //subsequent players after first
}
//TODO end temp vehicle auto-loading
avatarService ! Service.Join(player.Continent)
localService ! Service.Join(player.Continent)
vehicleService ! Service.Join(player.Continent)
@ -1129,6 +1198,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
continent.GUID(object_guid) match {
case Some(vehicle : Vehicle) =>
if(player.VehicleOwned.contains(object_guid) && vehicle.Owner.contains(player.GUID)) {
vehicleService ! VehicleServiceMessage.UnscheduleDeconstruction(object_guid)
vehicleService ! VehicleServiceMessage.RequestDeleteVehicle(vehicle, continent)
log.info(s"RequestDestroy: vehicle $object_guid")
}
@ -1157,14 +1227,14 @@ class WorldSessionActor extends Actor with MDCContextAware {
sendResponse(PacketCoding.CreateGamePacket(0, ObjectDeleteMessage(object_guid, 0)))
log.info("ObjectDelete: " + msg)
case msg @ MoveItemMessage(item_guid, avatar_guid_1, avatar_guid_2, dest, unk1) =>
case msg @ MoveItemMessage(item_guid, source_guid, destination_guid, dest, unk1) =>
player.Find(item_guid) match {
case Some(index) =>
val indexSlot = player.Slot(index)
var itemOpt = indexSlot.Equipment //use this to short circuit
var itemOpt : Option[Equipment] = indexSlot.Equipment
//use this to short circuit
val item = itemOpt.get
val destSlot = player.Slot(dest)
val destItem = if((-1 < dest && dest < 5) || dest == Player.FreeHandSlot) {
destSlot.Equipment match {
case Some(found) =>
@ -1181,20 +1251,21 @@ class WorldSessionActor extends Actor with MDCContextAware {
case Success(_) | scala.util.Failure(_) => itemOpt = None; None //abort item move altogether
}
}
if(itemOpt.isDefined) {
log.info(s"MoveItem: $item_guid moved from $avatar_guid_1 @ $index to $avatar_guid_1 @ $dest")
log.info(s"MoveItem: $item_guid moved from $source_guid @ $index to $source_guid @ $dest")
indexSlot.Equipment = None
destItem match { //do we have a swap item?
destItem match {
//do we have a swap item?
case Some(entry) => //yes, swap
val item2 = entry.obj
player.Slot(entry.start).Equipment = None //remove item2 to make room for item
destSlot.Equipment = item //in case dest and index could block each other
(indexSlot.Equipment = entry.obj) match {
case Some(_) => //item and item2 swapped places successfully
log.info(s"MoveItem: ${item2.GUID} swapped to $avatar_guid_1 @ $index")
log.info(s"MoveItem: ${item2.GUID} swapped to $source_guid @ $index")
//we must shuffle items around cleanly to avoid causing icons to "disappear"
if(index == Player.FreeHandSlot) { //temporarily put in safe location, A -> C
if(index == Player.FreeHandSlot) {
//temporarily put in safe location, A -> C
sendResponse(PacketCoding.CreateGamePacket(0, ObjectDetachMessage(player.GUID, item.GUID, Vector3(0f, 0f, 0f), 0f, 0f, 0f))) //ground
}
else {
@ -1217,13 +1288,13 @@ class WorldSessionActor extends Actor with MDCContextAware {
case None => //just move item over
destSlot.Equipment = item
}
sendResponse(PacketCoding.CreateGamePacket(0, ObjectAttachMessage(avatar_guid_1, item_guid, dest)))
sendResponse(PacketCoding.CreateGamePacket(0, ObjectAttachMessage(source_guid, item_guid, dest)))
if(0 <= dest && dest < 5) {
avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.EquipmentInHand(player.GUID, dest, item))
}
}
case None =>
log.info(s"MoveItem: $avatar_guid_1 wanted to move the item $item_guid but could not find it")
log.info(s"MoveItem: $source_guid wanted to move the item $item_guid but could not find it")
}
case msg @ ChangeAmmoMessage(item_guid, unk1) =>
@ -1384,6 +1455,9 @@ class WorldSessionActor extends Actor with MDCContextAware {
player.VehicleSeated = None
sendResponse(PacketCoding.CreateGamePacket(0, DismountVehicleMsg(player_guid, unk1, unk2))) //should be safe; replace with ObjectDetachMessage later
vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.DismountVehicle(player_guid, unk1, unk2))
if(obj.Seats.count(seat => seat.isOccupied) == 0) {
vehicleService ! VehicleServiceMessage.DelayedVehicleDeconstruction(obj, continent, 600L) //start vehicle decay (10m)
}
}
case None =>
log.warn(s"DismountVehicleMsg: can not find where player $player_guid is seated in vehicle $vehicle_guid")
@ -1409,6 +1483,9 @@ class WorldSessionActor extends Actor with MDCContextAware {
seat.Occupant = None
tplayer.VehicleSeated = None
vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.KickPassenger(player_guid, unk1, unk2))
if(obj.Seats.count(seat => seat.isOccupied) == 0) {
vehicleService ! VehicleServiceMessage.DelayedVehicleDeconstruction(obj, continent, 600L) //start vehicle decay (10m)
}
case None =>
log.warn(s"DismountVehicleMsg: can not find where player $player_guid is seated in vehicle $vehicle_guid")
}
@ -1733,12 +1810,16 @@ class WorldSessionActor extends Actor with MDCContextAware {
* @see `RegisterVehicle`
* @return a `TaskResolver.GiveTask` message
*/
def RegisterNewVehicle(obj : Vehicle) : TaskResolver.GiveTask = {
def RegisterNewVehicle(obj : Vehicle, pad : VehicleSpawnPad) : TaskResolver.GiveTask = {
TaskResolver.GiveTask(
new Task() {
private val localVehicle = obj
private val localAnnounce = vehicleService
private val localSession : String = sessionId.toString
private val localPad = pad.Actor
private val localPlayer = player
private val localVehicleService = vehicleService
private val localZone = continent
override def isComplete : Task.Resolution.Value = {
if(localVehicle.Actor != ActorRef.noSender) {
@ -1751,6 +1832,8 @@ class WorldSessionActor extends Actor with MDCContextAware {
def Execute(resolver : ActorRef) : Unit = {
localAnnounce ! VehicleServiceMessage.GiveActorControl(obj, localSession)
localPad ! VehicleSpawnPad.VehicleOrder(localPlayer, localVehicle)
localVehicleService ! VehicleServiceMessage.DelayedVehicleDeconstruction(localVehicle, localZone, 60L)
resolver ! scala.util.Success(this)
}
}, List(RegisterVehicle(obj)))
@ -1945,35 +2028,4 @@ object WorldSessionActor {
delta : Float,
completeAction : () => Unit,
tickAction : Option[() => Unit] = None)
/**
* A placeholder `Cancellable` object.
*/
private final val DefaultCancellable = new Cancellable() {
def cancel : Boolean = true
def isCancelled() : Boolean = true
}
/**
* Calculate the actual distance between two points.
* @param pos1 the first point
* @param pos2 the second point
* @return the distance
*/
def Distance(pos1 : Vector3, pos2 : Vector3) : Float = {
math.sqrt(DistanceSquared(pos1, pos2)).toFloat
}
/**
* Calculate the squared distance between two points.
* Though some time is saved care must be taken that any comparative distance is also squared.
* @param pos1 the first point
* @param pos2 the second point
* @return the distance
*/
def DistanceSquared(pos1 : Vector3, pos2 : Vector3) : Float = {
val dx : Float = pos1.x - pos2.x
val dy : Float = pos1.y - pos2.y
val dz : Float = pos1.z - pos2.z
(dx * dx) + (dy * dy) + (dz * dz)
}
}

View file

@ -10,6 +10,7 @@ object AvatarAction {
trait Action
final case class ArmorChanged(player_guid : PlanetSideGUID, suit : ExoSuitType.Value, subtype : Int) extends Action
final case class ConcealPlayer(player_guid : PlanetSideGUID) extends Action
//final case class DropItem(pos : Vector3, orient : Vector3, item : PlanetSideGUID) extends Action
final case class EquipmentInHand(player_guid : PlanetSideGUID, slot : Int, item : Equipment) extends Action
final case class EquipmentOnGround(player_guid : PlanetSideGUID, pos : Vector3, orient : Vector3, item : Equipment) extends Action

View file

@ -10,6 +10,7 @@ object AvatarResponse {
trait Response
final case class ArmorChanged(suit : ExoSuitType.Value, subtype : Int) extends Response
final case class ConcealPlayer() extends Response
//final case class DropItem(pos : Vector3, orient : Vector3, item : PlanetSideGUID) extends Response
final case class EquipmentInHand(slot : Int, item : Equipment) extends Response
final case class EquipmentOnGround(pos : Vector3, orient : Vector3, item : Equipment) extends Response

View file

@ -33,6 +33,10 @@ class AvatarService extends Actor {
AvatarEvents.publish(
AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.ArmorChanged(suit, subtype))
)
case AvatarAction.ConcealPlayer(player_guid) =>
AvatarEvents.publish(
AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.ConcealPlayer())
)
case AvatarAction.EquipmentInHand(player_guid, slot, obj) =>
AvatarEvents.publish(
AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.EquipmentInHand(slot, obj))

View file

@ -2,6 +2,7 @@
package services.local.support
import akka.actor.{Actor, Cancellable}
import net.psforever.objects.DefaultCancellable
import net.psforever.objects.serverobject.doors.Door
import net.psforever.objects.zones.Zone
import net.psforever.packet.game.PlanetSideGUID
@ -16,7 +17,7 @@ import scala.concurrent.duration._
*/
class DoorCloseActor() extends Actor {
/** The periodic `Executor` that checks for doors to be closed */
private var doorCloserTrigger : Cancellable = DoorCloseActor.DefaultCloser
private var doorCloserTrigger : Cancellable = DefaultCancellable.obj
/** A `List` of currently open doors */
private var openDoors : List[DoorCloseActor.DoorEntry] = Nil
//private[this] val log = org.log4s.getLogger
@ -98,11 +99,6 @@ object DoorCloseActor {
/** The wait before an open door closes; as a `FiniteDuration` for `Executor` simplicity */
private final val timeout : FiniteDuration = timeout_time nanoseconds
private final val DefaultCloser : Cancellable = new Cancellable() {
override def cancel : Boolean = true
override def isCancelled : Boolean = true
}
/**
* Message that carries information about a door that has been opened.
* @param door the door object

View file

@ -2,6 +2,7 @@
package services.local.support
import akka.actor.{Actor, Cancellable}
import net.psforever.objects.DefaultCancellable
import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject}
import net.psforever.objects.zones.Zone
import net.psforever.packet.game.PlanetSideGUID
@ -16,7 +17,7 @@ import scala.concurrent.duration._
*/
class HackClearActor() extends Actor {
/** The periodic `Executor` that checks for server objects to be unhacked */
private var clearTrigger : Cancellable = HackClearActor.DefaultClearer
private var clearTrigger : Cancellable = DefaultCancellable.obj
/** A `List` of currently hacked server objects */
private var hackedObjects : List[HackClearActor.HackEntry] = Nil
//private[this] val log = org.log4s.getLogger
@ -99,11 +100,6 @@ object HackClearActor {
/** The wait before a server object is to unhack; as a `FiniteDuration` for `Executor` simplicity */
private final val timeout : FiniteDuration = timeout_time nanoseconds
private final val DefaultClearer : Cancellable = new Cancellable() {
override def cancel : Boolean = true
override def isCancelled : Boolean = true
}
/**
* Message that carries information about a server object that has been hacked.
* @param target the server object

View file

@ -2,12 +2,13 @@
package services.vehicle
import akka.actor.{Actor, ActorRef, Props}
import services.vehicle.support.{DeconstructionActor, VehicleContextActor}
import services.vehicle.support.{DeconstructionActor, DelayedDeconstructionActor, VehicleContextActor}
import services.{GenericEventBus, Service}
class VehicleService extends Actor {
private val vehicleContext : ActorRef = context.actorOf(Props[VehicleContextActor], "vehicle-context-root")
private val vehicleDecon : ActorRef = context.actorOf(Props[DeconstructionActor], "vehicle-decon-agent")
private val vehicleDelayedDecon : ActorRef = context.actorOf(Props[DelayedDeconstructionActor], "vehicle-delayed-decon-agent")
vehicleDecon ! DeconstructionActor.RequestTaskResolver
private [this] val log = org.log4s.getLogger
@ -79,6 +80,14 @@ class VehicleService extends Actor {
case VehicleServiceMessage.RequestDeleteVehicle(vehicle, continent) =>
vehicleDecon ! DeconstructionActor.RequestDeleteVehicle(vehicle, continent)
//message to DelayedDeconstructionActor
case VehicleServiceMessage.DelayedVehicleDeconstruction(vehicle, zone, timeAlive) =>
vehicleDelayedDecon ! DelayedDeconstructionActor.ScheduleDeconstruction(vehicle, zone, timeAlive)
//message to DelayedDeconstructionActor
case VehicleServiceMessage.UnscheduleDeconstruction(vehicle_guid) =>
vehicleDelayedDecon ! DelayedDeconstructionActor.UnscheduleDeconstruction(vehicle_guid)
//response from DeconstructionActor
case DeconstructionActor.DeleteVehicle(vehicle_guid, zone_id) =>
VehicleEvents.publish(

View file

@ -3,11 +3,14 @@ package services.vehicle
import net.psforever.objects.Vehicle
import net.psforever.objects.zones.Zone
import net.psforever.packet.game.PlanetSideGUID
final case class VehicleServiceMessage(forChannel : String, actionMessage : VehicleAction.Action)
object VehicleServiceMessage {
final case class DelayedVehicleDeconstruction(vehicle : Vehicle, continent : Zone, timeAlive : Long)
final case class GiveActorControl(vehicle : Vehicle, actorName : String)
final case class RevokeActorControl(vehicle : Vehicle)
final case class RequestDeleteVehicle(vehicle : Vehicle, continent : Zone)
final case class UnscheduleDeconstruction(vehicle_guid : PlanetSideGUID)
}

View file

@ -2,7 +2,7 @@
package services.vehicle.support
import akka.actor.{Actor, ActorRef, Cancellable}
import net.psforever.objects.Vehicle
import net.psforever.objects.{DefaultCancellable, Vehicle}
import net.psforever.objects.guid.TaskResolver
import net.psforever.objects.vehicles.Seat
import net.psforever.objects.zones.Zone
@ -20,11 +20,13 @@ import scala.concurrent.duration._
* A reference to a vehicle should be passed to this object as soon as it is going to be cleaned-up from the game world.
* Once accepted, only a few seconds will remain before the vehicle is deleted.
* To ensure that no players are lost in the deletion, all occupants of the vehicle are kicked out.
* Furthermore, the vehicle is rendered "dead" and inaccessible right up to the point where it is removed.
* Furthermore, the vehicle is rendered "dead" and inaccessible right up to the point where it is removed.<br>
* <br>
* This `Actor` is intended to sit on top of the event system that handles broadcast messaging.
*/
class DeconstructionActor extends Actor {
/** The periodic `Executor` that scraps the next vehicle on the list */
private var scrappingProcess : Cancellable = DeconstructionActor.DefaultProcess
private var scrappingProcess : Cancellable = DefaultCancellable.obj
/** A `List` of currently doomed vehicles */
private var vehicles : List[DeconstructionActor.VehicleEntry] = Nil
/** The manager that helps unregister the vehicle from its current GUID scope */
@ -171,11 +173,6 @@ object DeconstructionActor {
/** The wait before completely deleting a vehicle; as a `FiniteDuration` for `Executor` simplicity */
private final val timeout : FiniteDuration = timeout_time nanoseconds
private final val DefaultProcess : Cancellable = new Cancellable() {
override def cancel : Boolean = true
override def isCancelled : Boolean = true
}
final case class RequestTaskResolver()
/**

View file

@ -0,0 +1,104 @@
// Copyright (c) 2017 PSForever
package services.vehicle.support
import akka.actor.{Actor, Cancellable}
import net.psforever.objects.{DefaultCancellable, Vehicle}
import net.psforever.objects.zones.Zone
import net.psforever.packet.game.PlanetSideGUID
import services.vehicle.VehicleServiceMessage
import scala.concurrent.duration._
/**
* Maintain and curate a list of timed `vehicle` object deconstruction tasks.<br>
* <br>
* These tasks are queued or dismissed by player activity but they are executed independent of player activity.
* A common disconnected cause of deconstruction is neglect for an extended period of time.
* At that point, the original owner of the vehicle no longer matters.
* Deconstruction neglect, however, is averted by having someone become seated.
* A realized deconstruction is entirely based on a fixed interval after an unresolved request has been received.
* The actual process of deconstructing the vehicle and cleaning up its resources is performed by an external agent.<br>
* <br>
* This `Actor` is intended to sit on top of the event system that handles broadcast messaging.
*/
class DelayedDeconstructionActor extends Actor {
/** The periodic `Executor` that scraps the next vehicle on the list */
private var monitor : Cancellable = DefaultCancellable.obj
/** A `List` of currently doomed vehicles */
private var vehicles : List[DelayedDeconstructionActor.VehicleEntry] = Nil
private[this] val log = org.log4s.getLogger
private[this] def trace(msg : String) : Unit = log.trace(msg)
def receive : Receive = {
case DelayedDeconstructionActor.ScheduleDeconstruction(vehicle, zone, timeAlive) =>
trace(s"delayed deconstruction order for $vehicle in $timeAlive")
vehicles = vehicles :+ DelayedDeconstructionActor.VehicleEntry(vehicle, zone, timeAlive * 1000000000L)
if(vehicles.size == 1) { //we were the only entry so the event must be started from scratch
import scala.concurrent.ExecutionContext.Implicits.global
monitor = context.system.scheduler.scheduleOnce(DelayedDeconstructionActor.periodicTest, self, DelayedDeconstructionActor.PeriodicTaskCulling)
}
case DelayedDeconstructionActor.UnscheduleDeconstruction(vehicle_guid) =>
//all tasks for this vehicle are cleared from the queue
//clear any task that is no longer valid by determination of unregistered GUID
val before = vehicles.length
vehicles = vehicles.filter(entry => { !entry.vehicle.HasGUID || entry.vehicle.GUID != vehicle_guid })
trace(s"attempting to clear deconstruction order for vehicle $vehicle_guid, found ${before - vehicles.length}")
if(vehicles.isEmpty) {
monitor.cancel
}
case DelayedDeconstructionActor.PeriodicTaskCulling =>
//filter the list of deconstruction tasks for any that are need to be triggered
monitor.cancel
val now : Long = System.nanoTime
val (vehiclesToDecon, vehiclesRemain) = vehicles.partition(entry => { now - entry.logTime >= entry.survivalTime })
vehicles = vehiclesRemain
trace(s"vehicle culling - ${vehiclesToDecon.length} deconstruction tasks found")
vehiclesToDecon.foreach(entry => { context.parent ! VehicleServiceMessage.RequestDeleteVehicle(entry.vehicle, entry.zone) })
if(vehiclesRemain.nonEmpty) {
import scala.concurrent.ExecutionContext.Implicits.global
monitor = context.system.scheduler.scheduleOnce(DelayedDeconstructionActor.periodicTest, self, DelayedDeconstructionActor.PeriodicTaskCulling)
}
case _ => ;
}
}
object DelayedDeconstructionActor {
/**
* Timer for the repeating executor.
*/
private final val periodicTest : FiniteDuration = 5000000000L nanoseconds //5s
/**
* Queue a future vehicle deconstruction action.
* @param vehicle the `Vehicle` object
* @param zone the `Zone` that the vehicle currently occupies
* @param survivalTime how long until the vehicle will be deconstructed in seconds
*/
final case class ScheduleDeconstruction(vehicle : Vehicle, zone : Zone, survivalTime : Long)
/**
* Dequeue a vehicle from being deconstructed.
* @param vehicle_guid the vehicle
*/
final case class UnscheduleDeconstruction(vehicle_guid : PlanetSideGUID)
/**
* A message the `Actor` sends to itself.
* The trigger for the periodic deconstruction task.
*/
private final case class PeriodicTaskCulling()
/**
* An entry that stores vehicle deconstruction tasks.
* @param vehicle the `Vehicle` object
* @param zone the `Zone` that the vehicle currently occupies
* @param survivalTime how long until the vehicle will be deconstructed in nanoseconds
* @param logTime when this deconstruction request was initially created in nanoseconds;
* initialized by default to a "now"
*/
private final case class VehicleEntry(vehicle : Vehicle, zone : Zone, survivalTime : Long, logTime : Long = System.nanoTime())
}

View file

@ -1,7 +1,7 @@
// Copyright (c) 2017 PSForever
package services.vehicle.support
import akka.actor.{Actor, Props}
import akka.actor.{Actor, ActorRef, Props}
import net.psforever.objects.vehicles.VehicleControl
import services.vehicle.VehicleServiceMessage
@ -15,15 +15,18 @@ import services.vehicle.VehicleServiceMessage
* <br>
* The only purpose of this `Actor` is to allow vehicles to borrow a context for the purpose of `Actor` creation.
* It is also be allowed to be responsible for cleaning up that context.
* (In reality, it can be cleaned up anywhere a `PoisonPill` can be sent.)
* (In reality, it can be cleaned up anywhere a `PoisonPill` can be sent.)<br>
* <br>
* This `Actor` is intended to sit on top of the event system that handles broadcast messaging.
*/
class VehicleContextActor() extends Actor {
def receive : Receive = {
case VehicleServiceMessage.GiveActorControl(vehicle, actorName) =>
vehicle.Actor = context.actorOf(Props(classOf[VehicleControl], vehicle), s"${vehicle.Definition.Name}_$actorName")
vehicle.Actor = context.actorOf(Props(classOf[VehicleControl], vehicle), s"${vehicle.Definition.Name}_$actorName.${System.nanoTime()}")
case VehicleServiceMessage.RevokeActorControl(vehicle) =>
vehicle.Actor ! akka.actor.PoisonPill
vehicle.Actor = ActorRef.noSender
case _ => ;
}