Building:

Replaces class object/serverobject/door/Base.scala.  It performs nearly the exact same purpose but now has a list of owned objects called Amenities.  Buildings are now a PlanetSideServerObject (PSSO), which allows them to have accept a *Control Actor and possess FactionAffinity.

FoundationBuilder:
FoundationBuilder : Building :: ServerObjectBuilder : [T <: PlanetSideServerObject]

Amenity:
Most PSSO's now accept Amenity as their parent in class hierarchy.  Flagged PSSO's like Building and Vehicle are, on the other hand, capable of becoming the owner for these Amenity PSSOs, which allows them to inherit the same FactionAffinity.

FactionAffinity:
A trait that connects objects that are intended to communicate PlanetSideEmpire values.

MountableBhevaior:
Split between Mount and Dismount behavior.  Passes appropriate messages to ensure coherent workflows.

Control Actors:
FactionAffinityBehavior and MountableBehavior are PartialFunctions that get processed in series.

VehicleControl:
Distinguished behavior allowed between an operational vehicle and a deactivated one.

WSA:
Tightened up DismountVehicleMsg handling code, since MountableBehavior has been enhanced.

Minor:
Shotgun shell stacking goes from 32 to 16.  Various PSSO classes now have reliable Definition objects.

Tests:
We now have 1012 tests, some of them useful.
This commit is contained in:
FateJH 2018-01-26 15:32:08 -05:00
parent 8c02f8d519
commit eefe4d2e20
56 changed files with 1362 additions and 425 deletions

View file

@ -7,6 +7,7 @@ import net.psforever.objects.serverobject.implantmech.ImplantTerminalMech
import net.psforever.objects.serverobject.locks.IFFLock
import net.psforever.objects.serverobject.mblocker.Locker
import net.psforever.objects.serverobject.pad.VehicleSpawnPad
import net.psforever.objects.serverobject.structures.{Building, FoundationBuilder}
import net.psforever.objects.serverobject.terminals.Terminal
import net.psforever.types.Vector3
@ -103,30 +104,46 @@ object Maps {
VehicleSpawnPad.Constructor(Vector3(3508.9844f, 2895.961f, 92.296875f), Vector3(0f, 0f, 270.0f))
)) //TODO guid not correct
LocalBases = 30
LocalBuilding(2, FoundationBuilder(Building.Structure))
ObjectToBuilding(186, 2)
ObjectToBuilding(187, 2)
ObjectToBuilding(188, 2)
ObjectToBuilding(522, 2)
ObjectToBuilding(523, 2)
ObjectToBuilding(524, 2)
ObjectToBuilding(525, 2)
ObjectToBuilding(526, 2)
ObjectToBuilding(527, 2)
ObjectToBuilding(528, 2)
ObjectToBuilding(529, 2)
ObjectToBuilding(686, 2)
ObjectToBuilding(687, 2)
ObjectToBuilding(688, 2)
ObjectToBuilding(689, 2)
ObjectToBuilding(690, 2)
ObjectToBuilding(691, 2)
ObjectToBuilding(692, 2)
ObjectToBuilding(693, 2)
ObjectToBuilding(842, 2)
ObjectToBuilding(843, 2)
ObjectToBuilding(844, 2)
ObjectToBuilding(845, 2)
ObjectToBuilding(853, 2) //TODO check building_id
ObjectToBuilding(855, 2) //TODO check building_id
ObjectToBuilding(860, 2) //TODO check building_id
ObjectToBuilding(1063, 2) //TODO unowned courtyard terminal?
ObjectToBuilding(500, 2) //TODO unowned courtyard spawnpad?
ObjectToBuilding(304, 2) //TODO unowned courtyard terminal?
ObjectToBuilding(501, 2) //TODO unowned courtyard spawnpad?
ObjectToBase(330, 29)
ObjectToBase(331, 29)
ObjectToBase(332, 29)
ObjectToBase(333, 29)
//ObjectToBase(520, 29)
ObjectToBase(522, 2)
ObjectToBase(523, 2)
ObjectToBase(524, 2)
ObjectToBase(525, 2)
ObjectToBase(526, 2)
ObjectToBase(527, 2)
ObjectToBase(528, 2)
ObjectToBase(529, 2)
ObjectToBase(556, 29)
ObjectToBase(557, 29)
ObjectToBase(558, 29)
ObjectToBase(559, 29)
ObjectToBase(1081, 2)
ObjectToBase(1063, 2) //TODO unowned courtyard terminal?
ObjectToBase(500, 2) //TODO unowned courtyard spawnpad?
ObjectToBase(304, 2) //TODO unowned courtyard terminal?
ObjectToBase(501, 2) //TODO unowned courtyard spawnpad?
LocalBuilding(29, FoundationBuilder(Building.Structure))
ObjectToBuilding(330, 29)
ObjectToBuilding(332, 29)
ObjectToBuilding(556, 29)
ObjectToBuilding(558, 29)
//ObjectToBuilding(1081, ?)
//ObjectToBuilding(520, ?)
DoorToLock(330, 558)
DoorToLock(331, 559)

View file

@ -14,7 +14,8 @@ import net.psforever.objects._
import net.psforever.objects.equipment._
import net.psforever.objects.guid.{GUIDTask, Task, TaskResolver}
import net.psforever.objects.inventory.{Container, GridInventory, InventoryItem}
import net.psforever.objects.mount.Mountable
import net.psforever.objects.serverobject.mount.Mountable
import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject}
import net.psforever.objects.serverobject.doors.Door
import net.psforever.objects.serverobject.implantmech.ImplantTerminalMech
@ -424,7 +425,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
val player_guid : PlanetSideGUID = tplayer.GUID
val obj_guid : PlanetSideGUID = obj.GUID
log.info(s"MountVehicleMsg: $player_guid mounts $obj @ $seat_num")
tplayer.VehicleSeated = Some(obj_guid)
//tplayer.VehicleSeated = Some(obj_guid)
sendResponse(PacketCoding.CreateGamePacket(0, PlanetsideAttributeMessage(obj_guid, 0, 1000L))) //health of mech
sendResponse(PacketCoding.CreateGamePacket(0, ObjectAttachMessage(obj_guid, player_guid, seat_num)))
vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.MountVehicle(player_guid, obj_guid, seat_num))
@ -434,7 +435,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
val player_guid : PlanetSideGUID = tplayer.GUID
log.info(s"MountVehicleMsg: $player_guid mounts $obj_guid @ $seat_num")
vehicleService ! VehicleServiceMessage.UnscheduleDeconstruction(obj_guid) //clear all deconstruction timers
tplayer.VehicleSeated = Some(obj_guid)
//tplayer.VehicleSeated = Some(obj_guid)
if(seat_num == 0) { //simplistic vehicle ownership management
obj.Owner match {
case Some(owner_guid) =>
@ -466,8 +467,37 @@ class WorldSessionActor extends Actor with MDCContextAware {
case Mountable.CanMount(obj : Mountable, _) =>
log.warn(s"MountVehicleMsg: $obj is some generic mountable object and nothing will happen")
case Mountable.CanDismount(obj : ImplantTerminalMech, seat_num) =>
val obj_guid : PlanetSideGUID = obj.GUID
val player_guid : PlanetSideGUID = tplayer.GUID
log.info(s"DismountVehicleMsg: $player_guid dismounts $obj @ $seat_num")
sendResponse(PacketCoding.CreateGamePacket(0, DismountVehicleMsg(player_guid, seat_num, false)))
vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.DismountVehicle(player_guid, seat_num, false))
case Mountable.CanDismount(obj : Vehicle, seat_num) =>
val player_guid : PlanetSideGUID = tplayer.GUID
if(player_guid == player.GUID) {
//disembarking self
log.info(s"DismountVehicleMsg: $player_guid dismounts $obj @ $seat_num")
sendResponse(PacketCoding.CreateGamePacket(0, DismountVehicleMsg(player_guid, seat_num, false)))
vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.DismountVehicle(player_guid, seat_num, false))
UnAccessContents(obj)
}
else {
vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.KickPassenger(player_guid, seat_num, true, obj.GUID))
}
if(obj.Seats.values.count(seat => seat.isOccupied) == 0) {
vehicleService ! VehicleServiceMessage.DelayedVehicleDeconstruction(obj, continent, 600L) //start vehicle decay (10m)
}
case Mountable.CanDismount(obj : Mountable, _) =>
log.warn(s"DismountVehicleMsg: $obj is some generic mountable object and nothing will happen")
case Mountable.CanNotMount(obj, seat_num) =>
log.warn(s"MountVehicleMsg: $tplayer attempted to mount $obj's seat $seat_num, but was not allowed")
case Mountable.CanNotDismount(obj, seat_num) =>
log.warn(s"DismountVehicleMsg: $tplayer attempted to dismount $obj's seat $seat_num, but was not allowed")
}
case Terminal.TerminalMessage(tplayer, msg, order) =>
@ -961,11 +991,10 @@ class WorldSessionActor extends Actor with MDCContextAware {
log.info("Load the now-registered player")
//load the now-registered player
tplayer.Spawn
sendResponse(PacketCoding.CreateGamePacket(0,
ObjectCreateDetailedMessage(ObjectClass.avatar, tplayer.GUID, tplayer.Definition.Packet.DetailedConstructorData(tplayer).get)
))
val dcdata = tplayer.Definition.Packet.DetailedConstructorData(tplayer).get
sendResponse(PacketCoding.CreateGamePacket(0, ObjectCreateDetailedMessage(ObjectClass.avatar, tplayer.GUID, dcdata)))
avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.LoadPlayer(tplayer.GUID, tplayer.Definition.Packet.ConstructorData(tplayer).get))
log.debug(s"ObjectCreateDetailedMessage: ${tplayer.Definition.Packet.DetailedConstructorData(tplayer).get}")
log.debug(s"ObjectCreateDetailedMessage: $dcdata")
case SetCurrentAvatar(tplayer) =>
val guid = tplayer.GUID
@ -1823,52 +1852,49 @@ class WorldSessionActor extends Actor with MDCContextAware {
// TODO: Not all incoming UseItemMessage's respond with another UseItemMessage (i.e. doors only send out GenericObjectStateMsg)
continent.GUID(object_guid) match {
case Some(door : Door) =>
continent.Map.DoorToLock.get(object_guid.guid) match { //check for IFF Lock
case Some(lock_guid) =>
val lock_hacked = continent.GUID(lock_guid).get.asInstanceOf[IFFLock].HackedBy match {
case Some(_) =>
true
case None =>
Vector3.ScalarProjection(door.Outwards, player.Position - door.Position) < 0f
}
continent.Map.ObjectToBase.get(lock_guid) match { //check for associated base
case Some(base_id) =>
if(continent.Base(base_id).get.Faction == player.Faction || lock_hacked) { //either base allegiance aligns or locks is hacked
door.Actor ! Door.Use(player, msg)
}
case None =>
if(lock_hacked) { //is lock hacked? this may be a weird case
door.Actor ! Door.Use(player, msg)
}
}
case None =>
door.Actor ! Door.Use(player, msg) //let door open freely
if(player.Faction == door.Faction || ((continent.Map.DoorToLock.get(object_guid.guid) match {
case Some(lock_guid) => continent.GUID(lock_guid).get.asInstanceOf[IFFLock].HackedBy.isDefined
case None => !door.isOpen
}) || Vector3.ScalarProjection(door.Outwards, player.Position - door.Position) < 0f)) {
door.Actor ! Door.Use(player, msg)
}
else if(door.isOpen) {
//the door is open globally ... except on our screen
sendResponse(PacketCoding.CreateGamePacket(0, GenericObjectStateMsg(object_guid, 16)))
}
case Some(panel : IFFLock) =>
player.Slot(player.DrawnSlot).Equipment match {
case Some(tool : SimpleItem) =>
if(tool.Definition == GlobalDefinitions.remote_electronics_kit) {
//TODO get player hack level (for now, presume 15s in intervals of 4/s)
progressBarValue = Some(-2.66f)
self ! WorldSessionActor.ItemHacking(player, panel, tool.GUID, 2.66f, FinishHackingDoor(panel, 1114636288L))
log.info("Hacking a door~")
}
case _ => ;
if(panel.Faction != player.Faction && panel.HackedBy.isEmpty) {
player.Slot(player.DrawnSlot).Equipment match {
case Some(tool : SimpleItem) =>
if(tool.Definition == GlobalDefinitions.remote_electronics_kit) {
//TODO get player hack level (for now, presume 15s in intervals of 4/s)
progressBarValue = Some(-2.66f)
self ! WorldSessionActor.ItemHacking(player, panel, tool.GUID, 2.66f, FinishHackingDoor(panel, 1114636288L))
log.info("Hacking a door~")
}
case _ => ;
}
}
case Some(obj : Locker) =>
val container = player.Locker
accessedContainer = Some(container)
sendResponse(PacketCoding.CreateGamePacket(0, UseItemMessage(avatar_guid, unk1, container.GUID, unk2, unk3, unk4, unk5, unk6, unk7, unk8, 456)))
if(player.Faction == obj.Faction) {
log.info(s"UseItem: $player accessing a locker")
val container = player.Locker
accessedContainer = Some(container)
sendResponse(PacketCoding.CreateGamePacket(0, UseItemMessage(avatar_guid, unk1, container.GUID, unk2, unk3, unk4, unk5, unk6, unk7, unk8, 456)))
}
else {
log.info(s"UseItem: not $player's locker")
}
case Some(obj : Vehicle) =>
if(obj.Faction == player.Faction) {
val equipment = player.Slot(player.DrawnSlot).Equipment
val equipment = player.Slot(player.DrawnSlot).Equipment
if(player.Faction == obj.Faction) {
if(equipment match {
case Some(tool : Tool) =>
tool.Definition match {
case GlobalDefinitions.nano_dispenser | GlobalDefinitions.remote_electronics_kit => false
case GlobalDefinitions.nano_dispenser => false
case _ => true
}
case _ => true
@ -1889,13 +1915,19 @@ class WorldSessionActor extends Actor with MDCContextAware {
case GlobalDefinitions.nano_dispenser =>
//TODO repairing behavior
case GlobalDefinitions.remote_electronics_kit =>
//TODO hacking behavior
case _ => ;
}
}
}
//enemy player interactions
else if(equipment.isDefined) {
equipment.get.Definition match {
case GlobalDefinitions.remote_electronics_kit =>
//TODO hacking behavior
case _ => ;
}
}
case Some(obj : PlanetSideGameObject) =>
if(itemType != 121) {
@ -1933,7 +1965,9 @@ class WorldSessionActor extends Actor with MDCContextAware {
log.info("ItemTransaction: " + msg)
continent.GUID(terminal_guid) match {
case Some(term : Terminal) =>
term.Actor ! Terminal.Request(player, msg)
if(player.Faction == term.Faction) {
term.Actor ! Terminal.Request(player, msg)
}
case Some(obj : PlanetSideGameObject) => ;
case None => ;
}
@ -2015,36 +2049,19 @@ class WorldSessionActor extends Actor with MDCContextAware {
case msg @ DismountVehicleMsg(player_guid, unk1, unk2) =>
//TODO optimize this later
log.info(s"DismountVehicleMsg: $msg")
//common warning for this section
def dismountWarning(msg : String) : Unit = {
log.warn(s"$msg; some vehicle might not know that a player is no longer sitting in it")
}
if(player.GUID == player_guid) {
//normally disembarking from a seat
val previouslySeated = player.VehicleSeated
player.VehicleSeated = None
sendResponse(PacketCoding.CreateGamePacket(0, DismountVehicleMsg(player_guid, unk1, unk2)))
vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.DismountVehicle(player_guid, unk1, unk2))
//common warning for this section
def dismountWarning(msg : String) : Unit = {
log.warn(s"$msg; some vehicle might not know that a player is no longer sitting in it")
}
//find vehicle seat and disembark it
previouslySeated match {
player.VehicleSeated match {
case Some(obj_guid) =>
continent.GUID(obj_guid) match {
case Some(obj : Mountable) =>
val seats = obj.Seats.values
seats.find(seat => seat.Occupant.contains(player)) match {
case Some(seat) =>
if(seat.Bailable || obj.Velocity.isEmpty || Vector3.MagnitudeSquared(obj.Velocity.get).toInt == 0) { //ugh, float comparison
seat.Occupant = None
//special actions
obj match {
case (veh : Vehicle) =>
if(seats.count(seat => seat.isOccupied) == 0) {
vehicleService ! VehicleServiceMessage.DelayedVehicleDeconstruction(veh, continent, 600L) //start vehicle decay (10m)
UnAccessContents(veh)
}
case _ => ;
}
}
obj.PassengerInSeat(player) match {
case Some(seat_num : Int) =>
obj.Actor ! Mountable.TryDismount(player, seat_num)
case None =>
dismountWarning(s"DismountVehicleMsg: can not find where player $player_guid is seated in mountable $obj_guid")
}
@ -2056,35 +2073,23 @@ class WorldSessionActor extends Actor with MDCContextAware {
}
}
else {
//kicking someone else out of a seat; need to own that seat
//kicking someone else out of a seat; need to own that seat/mountable
player.VehicleOwned match {
case Some(vehicle_guid) =>
continent.GUID(player_guid) match {
case Some(tplayer : Player) =>
if(tplayer.VehicleSeated.contains(vehicle_guid)) {
continent.GUID(vehicle_guid) match {
case Some(obj : Vehicle) =>
val seats = obj.Seats.values
seats.find(seat => seat.Occupant.contains(tplayer)) match {
case Some(seat) =>
seat.Occupant = None
tplayer.VehicleSeated = None
vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.KickPassenger(player_guid, unk1, unk2, vehicle_guid))
if(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")
}
case _ =>
log.warn(s"DismountVehicleMsg: can not find vehicle $vehicle_guid")
}
case Some(obj_guid) =>
(continent.GUID(obj_guid), continent.GUID(player_guid)) match {
case (Some(obj : Mountable), Some(tplayer : Player)) =>
obj.PassengerInSeat(tplayer) match {
case Some(seat_num : Int) =>
obj.Actor ! Mountable.TryDismount(tplayer, seat_num)
case None =>
dismountWarning(s"DismountVehicleMsg: can not find where other player $player_guid is seated in mountable $obj_guid")
}
else {
log.warn(s"DismountVehicleMsg: non-owner player $player trying to kick player $tplayer out of his seat")
}
case _ =>
case (None, _) => ;
log.warn(s"DismountVehicleMsg: $player can not find his vehicle")
case (_, None) => ;
log.warn(s"DismountVehicleMsg: player $player_guid could not be found to kick")
case _ =>
log.warn(s"DismountVehicleMsg: object is either not a Mountable or not a Player")
}
case None =>
log.warn(s"DismountVehicleMsg: $player does not own a vehicle")

View file

@ -32,8 +32,8 @@ object Zones {
super.Init(context)
import net.psforever.types.PlanetSideEmpire
Base(2).get.Faction = PlanetSideEmpire.VS //HART building C
Base(29).get.Faction = PlanetSideEmpire.NC //South Villa Gun Tower
Building(2).get.Faction = PlanetSideEmpire.VS //HART building C
Building(29).get.Faction = PlanetSideEmpire.NC //South Villa Gun Tower
}
}

View file

@ -53,7 +53,7 @@ class DeconstructionActor extends Actor {
case DeconstructionActor.RequestDeleteVehicle(vehicle, zone, time) =>
vehicles = vehicles :+ DeconstructionActor.VehicleEntry(vehicle, zone, time)
vehicle.Actor ! Vehicle.PrepareForDeletion
//kick everyone out
//kick everyone out; this is a no-blocking manual form of MountableBehavior ! Mountable.TryDismount
vehicle.Definition.MountPoints.values.foreach(seat_num => {
val zone_id : String = zone.Id
val seat : Seat = vehicle.Seat(seat_num).get