Implant Terminals:

Implant terminals (mech) are now properly mountable and implant terminals (interface) are also properly interactive.  Player can select to equip or to remove implants properly.

Mountable:

Vehicles and implant terminal mechs now use common Mountable logic.

home3 Hart C:

All doors, save for those to the shuttle, and all implant terminals in this building are now rigged to operate.
This commit is contained in:
FateJH 2017-11-29 22:30:25 -05:00
parent 47a0aa3e0c
commit f9beb47073
27 changed files with 1134 additions and 176 deletions

View file

@ -7,6 +7,7 @@ import net.psforever.objects.serverobject.doors.DoorDefinition
import net.psforever.objects.equipment.CItem.DeployedItem import net.psforever.objects.equipment.CItem.DeployedItem
import net.psforever.objects.equipment._ import net.psforever.objects.equipment._
import net.psforever.objects.inventory.InventoryTile import net.psforever.objects.inventory.InventoryTile
import net.psforever.objects.serverobject.implantmech.ImplantTerminalMechDefinition
import net.psforever.objects.serverobject.locks.IFFLockDefinition import net.psforever.objects.serverobject.locks.IFFLockDefinition
import net.psforever.objects.serverobject.terminals._ import net.psforever.objects.serverobject.terminals._
import net.psforever.objects.vehicles.SeatArmorRestriction import net.psforever.objects.vehicles.SeatArmorRestriction
@ -467,6 +468,10 @@ object GlobalDefinitions {
val cert_terminal = new CertTerminalDefinition val cert_terminal = new CertTerminalDefinition
val implant_terminal_mech = new ImplantTerminalMechDefinition
val implant_terminal_interface = new ImplantTerminalInterfaceDefinition
val ground_vehicle_terminal = new GroundVehicleTerminalDefinition val ground_vehicle_terminal = new GroundVehicleTerminalDefinition
val air_vehicle_terminal = new AirVehicleTerminalDefinition val air_vehicle_terminal = new AirVehicleTerminalDefinition

View file

@ -26,7 +26,7 @@ class ImplantSlot {
def Unlocked : Boolean = unlocked def Unlocked : Boolean = unlocked
def Unlocked_=(lock : Boolean) : Boolean = { def Unlocked_=(lock : Boolean) : Boolean = {
unlocked = lock || unlocked unlocked = lock || unlocked //do not let re-lock
Unlocked Unlocked
} }
@ -45,13 +45,13 @@ class ImplantSlot {
Active Active
} }
def Implant : ImplantType.Value = if(Installed.isDefined) { def Implant : ImplantType.Value = Installed match {
implant.get.Type case Some(idef) =>
} idef.Type
else { case None =>
Active = false Active = false
Initialized = false Initialized = false
ImplantType.None ImplantType.None
} }
def Implant_=(anImplant : ImplantDefinition) : ImplantType.Value = { def Implant_=(anImplant : ImplantDefinition) : ImplantType.Value = {
@ -64,6 +64,8 @@ class ImplantSlot {
case Some(_) => case Some(_) =>
implant = anImplant implant = anImplant
case None => case None =>
Active = false
Initialized = false
implant = None implant = None
} }
} }

View file

@ -37,6 +37,15 @@ class Player(private val name : String,
private var bep : Long = 0 private var bep : Long = 0
private var cep : Long = 0 private var cep : Long = 0
private val certifications : mutable.Set[CertificationType.Value] = mutable.Set[CertificationType.Value]() private val certifications : mutable.Set[CertificationType.Value] = mutable.Set[CertificationType.Value]()
/**
* Unlike other objects, the maximum number of `ImplantSlots` are built into the `Player`.
* Additionally, "implants" do not have tightly-coupled "`Definition` objects" that explain a formal implant object.
* The `ImplantDefinition` objects themselves are moved around as if they were the implants.
* The term internally used for this process is "installed" and "uninstalled."
* @see `ImplantSlot`
* @see `DetailedCharacterData.implants`
* @see `AvatarConverter.MakeImplantEntries`
*/
private val implants : Array[ImplantSlot] = Array.fill[ImplantSlot](3)(new ImplantSlot) private val implants : Array[ImplantSlot] = Array.fill[ImplantSlot](3)(new ImplantSlot)
// private var tosRibbon : MeritCommendation.Value = MeritCommendation.None // private var tosRibbon : MeritCommendation.Value = MeritCommendation.None
@ -330,56 +339,94 @@ class Player(private val name : String,
def Certifications : mutable.Set[CertificationType.Value] = certifications def Certifications : mutable.Set[CertificationType.Value] = certifications
/**
* Retrieve the three implant slots for this player.
* @return an `Array` of `ImplantSlot` objects
*/
def Implants : Array[ImplantSlot] = implants def Implants : Array[ImplantSlot] = implants
/**
* What kind of implant is installed into the given slot number?
* @see `ImplantType`
* @param slot the slot number
* @return the tye of implant
*/
def Implant(slot : Int) : ImplantType.Value = { def Implant(slot : Int) : ImplantType.Value = {
if(-1 < slot && slot < implants.length) { implants(slot).Implant } else { ImplantType.None } if(-1 < slot && slot < implants.length) { implants(slot).Implant } else { ImplantType.None }
} }
def InstallImplant(implant : ImplantDefinition) : Boolean = { /**
* Given a new implant, assign it into a vacant implant slot on this player.<br>
* <br>
* The implant must be unique in terms of which implants have already been assigned to this player.
* Multiple of a type of implant being assigned at once is not supported.
* Additionally, the implant is inserted into the earliest yet-unknown but vacant slot.
* Implant slots are vacant by just being unlocked or by having their previous implant uninstalled.
* @param implant the implant being installed
* @return the index of the `ImplantSlot` where the implant was installed
*/
def InstallImplant(implant : ImplantDefinition) : Option[Int] = {
implants.find({p => p.Installed.contains(implant)}) match { //try to find the installed implant implants.find({p => p.Installed.contains(implant)}) match { //try to find the installed implant
case None => case None =>
//install in a free slot recursiveFindImplantInSlot(implants.iterator, ImplantType.None) match { //install in a free slot
getAvailableImplantSlot(implants.iterator, implant.Type) match { case out @ Some(slot) =>
case Some(slot) => implants(slot).Implant = implant
slot.Implant = implant out
true
case None => case None =>
false None
} }
case Some(_) => case Some(_) =>
false None
} }
} }
@tailrec private def getAvailableImplantSlot(iter : Iterator[ImplantSlot], implantType : ImplantType.Value) : Option[ImplantSlot] = { /**
* Remove a specific implant from a player's allocated installed implants.<br>
* <br>
* Due to the exclusiveness of installed implants,
* any implant slot with a matching `Definition` can be uninstalled safely.
* (There will never be any doubles.)
* This operation can lead to an irregular pattern of installed and uninstalled `ImplantSlot` objects.
* Despite that breach of pattern, the logic here is consistent as demonstrated by the client and by packets.
* The client also assigns and removes implants based on slot numbers that only express availability of a "slot."
* @see `AvatarImplantMessage.implantSlot`
* @param implantType the type of implant being uninstalled
* @return the index of the `ImplantSlot` where the implant was found and uninstalled
*/
def UninstallImplant(implantType : ImplantType.Value) : Option[Int] = {
recursiveFindImplantInSlot(implants.iterator, implantType) match {
case out @ Some(slot) =>
implants(slot).Implant = None
out
case None =>
None
}
}
/**
* Locate the index of the encountered implant type.
* Functional implants may be exclusive in as far as the input `Iterator`'s source is concerned,
* but any number of `ImplantType.None` values are alway allowed in the source in any order.
* @param iter an `Iterator` of `ImplantSlot` objects
* @param implantType the target implant being sought
* @param index a defaulted index value representing the structure underlying the `Iterator` param
* @return the index where the target implant is installed
*/
@tailrec private def recursiveFindImplantInSlot(iter : Iterator[ImplantSlot], implantType : ImplantType.Value, index : Int = 0) : Option[Int] = {
if(!iter.hasNext) { if(!iter.hasNext) {
None None
} }
else { else {
val slot = iter.next val slot = iter.next
if(!slot.Unlocked || slot.Implant == implantType) { if(slot.Unlocked && slot.Implant == implantType) {
None Some(index)
}
else if(slot.Installed.isEmpty) {
Some(slot)
} }
else { else {
getAvailableImplantSlot(iter, implantType) recursiveFindImplantInSlot(iter, implantType, index + 1)
} }
} }
} }
def UninstallImplant(implantType : ImplantType.Value) : Boolean = {
implants.find({slot => slot.Implant == implantType}) match {
case Some(slot) =>
slot.Implant = None
true
case None =>
false
}
}
def ResetAllImplants() : Unit = { def ResetAllImplants() : Unit = {
implants.foreach(slot => { implants.foreach(slot => {
slot.Installed match { slot.Installed match {

View file

@ -4,6 +4,7 @@ package net.psforever.objects
import net.psforever.objects.definition.VehicleDefinition import net.psforever.objects.definition.VehicleDefinition
import net.psforever.objects.equipment.{Equipment, EquipmentSize} import net.psforever.objects.equipment.{Equipment, EquipmentSize}
import net.psforever.objects.inventory.{GridInventory, InventoryTile} import net.psforever.objects.inventory.{GridInventory, InventoryTile}
import net.psforever.objects.mount.Mountable
import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.vehicles.{AccessPermissionGroup, Seat, Utility, VehicleLockState} import net.psforever.objects.vehicles.{AccessPermissionGroup, Seat, Utility, VehicleLockState}
import net.psforever.packet.game.PlanetSideGUID import net.psforever.packet.game.PlanetSideGUID
@ -26,7 +27,7 @@ import scala.collection.mutable
* stores and unloads pertinent information about the `Vehicle`'s configuration; * stores and unloads pertinent information about the `Vehicle`'s configuration;
* used in the initialization process (`loadVehicleDefinition`) * used in the initialization process (`loadVehicleDefinition`)
*/ */
class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideServerObject { class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideServerObject with Mountable {
private var faction : PlanetSideEmpire.Value = PlanetSideEmpire.TR private var faction : PlanetSideEmpire.Value = PlanetSideEmpire.TR
private var owner : Option[PlanetSideGUID] = None private var owner : Option[PlanetSideGUID] = None
private var health : Int = 1 private var health : Int = 1
@ -97,7 +98,7 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideServ
} }
def MaxHealth : Int = { def MaxHealth : Int = {
this.vehicleDef.MaxHealth Definition.MaxHealth
} }
def Shields : Int = { def Shields : Int = {
@ -110,7 +111,7 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideServ
} }
def MaxShields : Int = { def MaxShields : Int = {
vehicleDef.MaxShields Definition.MaxShields
} }
def Drive : DriveState.Value = { def Drive : DriveState.Value = {
@ -118,7 +119,7 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideServ
} }
def Drive_=(deploy : DriveState.Value) : DriveState.Value = { def Drive_=(deploy : DriveState.Value) : DriveState.Value = {
if(vehicleDef.Deployment) { if(Definition.Deployment) {
this.deployed = deploy this.deployed = deploy
} }
Drive Drive
@ -153,9 +154,11 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideServ
* @return a seat number, or `None` * @return a seat number, or `None`
*/ */
def GetSeatFromMountPoint(mountPoint : Int) : Option[Int] = { def GetSeatFromMountPoint(mountPoint : Int) : Option[Int] = {
vehicleDef.MountPoints.get(mountPoint) Definition.MountPoints.get(mountPoint)
} }
def MountPoints : Map[Int, Int] = Definition.MountPoints.toMap
/** /**
* What are the access permissions for a position on this vehicle, seats or trunk? * What are the access permissions for a position on this vehicle, seats or trunk?
* @param group the group index * @param group the group index
@ -225,8 +228,8 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideServ
} }
} }
def Seats : List[Seat] = { def Seats : Map[Int, Seat] = {
seats.values.toList seats
} }
def SeatPermissionGroup(seatNumber : Int) : Option[AccessPermissionGroup.Value] = { def SeatPermissionGroup(seatNumber : Int) : Option[AccessPermissionGroup.Value] = {
@ -428,27 +431,6 @@ object Vehicle {
*/ */
final case class PrepareForDeletion() final case class PrepareForDeletion()
/**
* This player wants to sit down in an available(?) seat.
* @param seat_num the seat where the player is trying to occupy;
* this is NOT the entry mount point index;
* make certain to convert!
* @param player the `Player` object
*/
final case class TrySeatPlayer(seat_num : Int, player : Player)
/**
* The recipient player of this packet is being allowed to sit in the assigned seat.
* @param vehicle the `Vehicle` object that generated this message
* @param seat_num the seat that the player will occupy
*/
final case class CanSeatPlayer(vehicle : Vehicle, seat_num : Int) extends Exchange
/**
* The recipient player of this packet is not allowed to sit in the requested seat.
* @param vehicle the `Vehicle` object that generated this message
* @param seat_num the seat that the player can not occupy
*/
final case class CannotSeatPlayer(vehicle : Vehicle, seat_num : Int) extends Exchange
/** /**
* Overloaded constructor. * Overloaded constructor.
* @param vehicleDef the vehicle's definition entry * @param vehicleDef the vehicle's definition entry
@ -494,7 +476,7 @@ object Vehicle {
* @return the string output * @return the string output
*/ */
def toString(obj : Vehicle) : String = { def toString(obj : Vehicle) : String = {
val occupancy = obj.Seats.count(seat => seat.isOccupied) val occupancy = obj.Seats.values.count(seat => seat.isOccupied)
s"${obj.Definition.Name}, owned by ${obj.Owner}: (${obj.Health}/${obj.MaxHealth})(${obj.Shields}/${obj.MaxShields}) ($occupancy)" s"${obj.Definition.Name}, owned by ${obj.Owner}: (${obj.Health}/${obj.MaxHealth})(${obj.Shields}/${obj.MaxShields}) ($occupancy)"
} }
} }

View file

@ -0,0 +1,15 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.definition.converter
import net.psforever.objects.serverobject.terminals.Terminal
import net.psforever.packet.game.objectcreate.CommonTerminalData
import scala.util.{Failure, Success, Try}
class ImplantTerminalInterfaceConverter extends ObjectCreateConverter[Terminal]() {
override def DetailedConstructorData(obj : Terminal) : Try[CommonTerminalData] =
Failure(new Exception("ImplantTerminalInterfaceConverter should not be used to generate detailed CommonTerminalData"))
override def ConstructorData(obj : Terminal) : Try[CommonTerminalData] =
Success(CommonTerminalData(net.psforever.types.PlanetSideEmpire.VS)) //TODO shortcut
}

View file

@ -9,7 +9,8 @@ import net.psforever.packet.game.objectcreate.{InventoryItemData, _}
import scala.util.{Failure, Success, Try} import scala.util.{Failure, Success, Try}
class VehicleConverter extends ObjectCreateConverter[Vehicle]() { class VehicleConverter extends ObjectCreateConverter[Vehicle]() {
override def DetailedConstructorData(obj : Vehicle) : Try[VehicleData] = Failure(new Exception("VehicleConverter should not be used to generate detailed VehicleData")) override def DetailedConstructorData(obj : Vehicle) : Try[VehicleData] =
Failure(new Exception("VehicleConverter should not be used to generate detailed VehicleData"))
override def ConstructorData(obj : Vehicle) : Try[VehicleData] = { override def ConstructorData(obj : Vehicle) : Try[VehicleData] = {
Success( Success(

View file

@ -0,0 +1,92 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.mount
import akka.actor.ActorRef
import net.psforever.objects.Player
import net.psforever.objects.vehicles.Seat
/**
* A `Trait` common to all game objects that permit players to
* interact with established spatial locations external to the object ("mount points") and
* attach to the object in internal indices ("seats") for an undefined length of time.
* @see `Seat`
*/
trait Mountable {
/**
* Retrieve a mapping of each seat from its internal index.
* @return the mapping of index to seat
*/
def Seats : Map[Int, Seat]
/**
* Given a seat's index position, retrieve the internal `Seat` object.
* @return the specific seat
*/
def Seat(seatNum : Int) : Option[Seat]
/**
* Retrieve a mapping of each seat from its mount point index.
* @return the mapping of mount point to seat
*/
def MountPoints : Map[Int, Int]
/**
* Given a mount point index, return the associated seat index.
* @param mount the mount point
* @return the seat index
*/
def GetSeatFromMountPoint(mount : Int) : Option[Int]
/**
* Given a player, determine if that player is seated.
* @param user the player
* @return the seat index
*/
def PassengerInSeat(user : Player) : Option[Int]
/**
* A reference to an `Actor` that governs the logic of the object to accept `Mountable` messages.
* Specifically, the `Actor` should intercept the logic of `MountableControl.`
* @see `MountableControl`
* @see `PlanetSideServerObject.Actor`
* @return the internal `ActorRef`
*/
def Actor : ActorRef //TODO can we enforce this desired association to MountableControl?
}
object Mountable {
/**
* Message used by the player to indicate the desire to board a `Mountable` object.
* @param player the player who sent this request message
* @param seat_num the seat index
*/
final case class TryMount(player : Player, seat_num : Int)
/**
* A basic `Trait` connecting all of the actionable `Mountable` response messages.
*/
sealed trait Exchange
/**
* Message that carries the result of the processed request message back to the original user (`player`).
* @param player the player who sent this request message
* @param response the result of the processed request
*/
final case class MountMessages(player : Player, response : Exchange)
/**
* Message sent in response to the player succeeding to access a `Mountable` object.
* The player should be seated at the given index.
* @param obj the `Mountable` object
* @param seat_num the seat index
*/
final case class CanMount(obj : Mountable, seat_num : Int) extends Exchange
/**
* Message sent in response to the player failing to access a `Mountable` object.
* The player would have been be seated at the given index.
* @param obj the `Mountable` object
* @param seat_num the seat index
*/
final case class CanNotMount(obj : Mountable, seat_num : Int) extends Exchange
}

View file

@ -0,0 +1,27 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.mount
import akka.actor.Actor
/**
* The logic governing `Mountable` objects that use the `TryMount` message.
* @see `Seat`
* @see `Mountable`
* @param obj the `Mountable` object governed beholden to this logic
*/
abstract class MountableControl(obj : Mountable) extends Actor {
def receive : Receive = {
case Mountable.TryMount(user, seat_num) =>
obj.Seat(seat_num) match {
case Some(seat) =>
if((seat.Occupant = user).contains(user)) {
sender ! Mountable.MountMessages(user, Mountable.CanMount(obj, seat_num))
}
else {
sender ! Mountable.MountMessages(user, Mountable.CanNotMount(obj, seat_num))
}
case None =>
sender ! Mountable.MountMessages(user, Mountable.CanNotMount(obj, seat_num))
}
}
}

View file

@ -0,0 +1,34 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.builders
import akka.actor.Props
import net.psforever.objects.serverobject.implantmech.{ImplantTerminalMech, ImplantTerminalMechControl, ImplantTerminalMechDefinition}
/**
* Wrapper `Class` designed to instantiate a `ImplantTerminalMech` server object.
* @param idef a `ImplantTerminalMechDefinition` object, indicating the specific functionality of the resulting `Door`
* @param id the globally unique identifier to which this "tube" will be registered
*/
class ImplantTerminalMechObjectBuilder(private val idef : ImplantTerminalMechDefinition, private val id : Int) extends ServerObjectBuilder[ImplantTerminalMech] {
import akka.actor.ActorContext
import net.psforever.objects.guid.NumberPoolHub
def Build(implicit context : ActorContext, guid : NumberPoolHub) : ImplantTerminalMech = {
val obj = ImplantTerminalMech(idef)
guid.register(obj, id) //non-Actor GUID registration
obj.Actor = context.actorOf(Props(classOf[ImplantTerminalMechControl], obj), s"${idef.Name}_${obj.GUID.guid}")
obj
}
}
object ImplantTerminalMechObjectBuilder {
/**
* Overloaded constructor for a `DoorObjectBuilder`.
* @param idef a `DoorDefinition` object
* @param id a globally unique identifier
* @return a `DoorObjectBuilder` object
*/
def apply(idef : ImplantTerminalMechDefinition, id : Int) : ImplantTerminalMechObjectBuilder = {
new ImplantTerminalMechObjectBuilder(idef, id)
}
}

View file

@ -0,0 +1,42 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.implantmech
import net.psforever.objects.Player
import net.psforever.objects.definition.ObjectDefinition
import net.psforever.objects.mount.Mountable
import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.vehicles.Seat
/**
* A structure-owned server object that is the visible and `Mountable` component of an implant terminal.
* For the most part, it merely implements the support data structures indicated by `Mountable`.
* @param idef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields
*/
class ImplantTerminalMech(private val idef : ImplantTerminalMechDefinition) extends PlanetSideServerObject with Mountable {
private val seats : Map[Int, Seat] = Map( 0 -> new Seat(idef.Seats(0)) )
def Seats : Map[Int, Seat] = seats
def Seat(seatNum : Int) : Option[Seat] = seats.get(seatNum)
def MountPoints : Map[Int, Int] = idef.MountPoints
def GetSeatFromMountPoint(mount : Int) : Option[Int] = idef.MountPoints.get(mount)
def PassengerInSeat(user : Player) : Option[Int] = {
if(seats(0).Occupant.contains(user)) {
Some(0)
}
else {
None
}
}
def Definition : ObjectDefinition = idef
}
object ImplantTerminalMech {
def apply(idef : ImplantTerminalMechDefinition) : ImplantTerminalMech = {
new ImplantTerminalMech(idef)
}
}

View file

@ -0,0 +1,14 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.implantmech
import net.psforever.objects.mount.MountableControl
/**
* An `Actor` that handles messages being dispatched to a specific `ImplantTerminalMech`.
* @param mech the "mech" object being governed
*/
class ImplantTerminalMechControl(mech : ImplantTerminalMech) extends MountableControl(mech) {
override def receive : Receive = super[MountableControl].receive.orElse {
case _ => ;
}
}

View file

@ -0,0 +1,22 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.implantmech
import net.psforever.objects.definition.{ObjectDefinition, SeatDefinition}
import net.psforever.objects.vehicles.SeatArmorRestriction
/**
* The `Definition` for any `Terminal` that is of a type "implant_terminal_interface."
* Implant terminals are composed of two components.
* This `Definition` constructs the visible mechanical tube component that can be mounted.
*/
class ImplantTerminalMechDefinition extends ObjectDefinition(410) {
/* key - seat index, value - seat object */
private val seats : Map[Int, SeatDefinition] = Map(0 -> new SeatDefinition)
/* key - entry point index, value - seat index */
private val mountPoints : Map[Int, Int] = Map(1 -> 0)
Name = "implant_terminal_mech"
def Seats : Map[Int, SeatDefinition] = seats
def MountPoints : Map[Int, Int] = mountPoints
}

View file

@ -0,0 +1,50 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.terminals
import net.psforever.objects.{GlobalDefinitions, Player}
import net.psforever.objects.definition.ImplantDefinition
import net.psforever.objects.definition.converter.ImplantTerminalInterfaceConverter
import net.psforever.packet.game.ItemTransactionMessage
import net.psforever.packet.game.objectcreate.ObjectClass
/**
* The `Definition` for any `Terminal` that is of a type "implant_terminal_interface."
* Implant terminals are composed of two components.
* This `Definition` constructs the invisible interface component (interacted with as a game window).
* Unlike other `Terminal` objects in the game, this one must be constructed on the client and
* attached as a child of the visible implant terminal component - the "implant_terminal_mech."
*/
class ImplantTerminalInterfaceDefinition extends TerminalDefinition(ObjectClass.implant_terminal_interface) {
private val implants : Map[String, ImplantDefinition] = Map (
"advanced_regen" -> GlobalDefinitions.advanced_regen,
"targeting" -> GlobalDefinitions.targeting,
"audio_amplifier" -> GlobalDefinitions.audio_amplifier,
"darklight_vision" -> GlobalDefinitions.darklight_vision,
"melee_booster" -> GlobalDefinitions.melee_booster,
"personal_shield" -> GlobalDefinitions.personal_shield,
"range_magnifier" -> GlobalDefinitions.range_magnifier,
"second_wind" -> GlobalDefinitions.second_wind,
"silent_run" -> GlobalDefinitions.silent_run,
"surge" -> GlobalDefinitions.surge
)
Packet = new ImplantTerminalInterfaceConverter
Name = "implante_terminal_interface"
def Buy(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = {
implants.get(msg.item_name) match {
case Some(implant) =>
Terminal.LearnImplant(implant)
case None =>
Terminal.NoDeal()
}
}
override def Sell(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = {
implants.get(msg.item_name) match {
case Some(implant) =>
Terminal.SellImplant(implant)
case None =>
Terminal.NoDeal()
}
}
}

View file

@ -2,6 +2,7 @@
package net.psforever.objects.serverobject.terminals package net.psforever.objects.serverobject.terminals
import net.psforever.objects.Player import net.psforever.objects.Player
import net.psforever.objects.definition.ImplantDefinition
import net.psforever.objects.equipment.Equipment import net.psforever.objects.equipment.Equipment
import net.psforever.objects.inventory.InventoryItem import net.psforever.objects.inventory.InventoryItem
import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.serverobject.PlanetSideServerObject
@ -151,7 +152,24 @@ object Terminal {
*/ */
final case class SellCertification(cert : CertificationType.Value, cost : Int) extends Exchange final case class SellCertification(cert : CertificationType.Value, cost : Int) extends Exchange
/**
* Provide the implant type unlocked by the player.
* @param implant the implant (definition) requested
*/
final case class LearnImplant(implant : ImplantDefinition) extends Exchange
/**
* Provide the implant type freed-up by the player.
* @param implant the implant (definition) returned
*/
final case class SellImplant(implant : ImplantDefinition) extends Exchange
import net.psforever.objects.Vehicle import net.psforever.objects.Vehicle
/**
* Provide a vehicle that was constructed for the player.
* @param vehicle the vehicle
* @param loadout the vehicle's trunk contents
*/
final case class BuyVehicle(vehicle : Vehicle, loadout: List[Any]) extends Exchange final case class BuyVehicle(vehicle : Vehicle, loadout: List[Any]) extends Exchange
/** /**

View file

@ -1,8 +1,8 @@
// Copyright (c) 2017 PSForever // Copyright (c) 2017 PSForever
package net.psforever.objects.vehicles package net.psforever.objects.vehicles
import akka.actor.Actor
import net.psforever.objects.Vehicle import net.psforever.objects.Vehicle
import net.psforever.objects.mount.MountableControl
/** /**
* An `Actor` that handles messages being dispatched to a specific `Vehicle`.<br> * An `Actor` that handles messages being dispatched to a specific `Vehicle`.<br>
@ -11,23 +11,11 @@ import net.psforever.objects.Vehicle
* The latter is applicable only when the specific vehicle is being deconstructed. * The latter is applicable only when the specific vehicle is being deconstructed.
* @param vehicle the `Vehicle` object being governed * @param vehicle the `Vehicle` object being governed
*/ */
class VehicleControl(private val vehicle : Vehicle) extends Actor { class VehicleControl(private val vehicle : Vehicle) extends MountableControl(vehicle) {
def receive : Receive = { override def receive : Receive = super[MountableControl].receive.orElse {
case Vehicle.PrepareForDeletion => case Vehicle.PrepareForDeletion =>
context.become(Disabled) context.become(Disabled)
case Vehicle.TrySeatPlayer(seat_num, player) =>
vehicle.Seat(seat_num) match {
case Some(seat) =>
if((seat.Occupant = player).contains(player)) {
sender ! Vehicle.VehicleMessages(player, Vehicle.CanSeatPlayer(vehicle, seat_num))
}
else {
sender ! Vehicle.VehicleMessages(player, Vehicle.CannotSeatPlayer(vehicle, seat_num))
}
case None =>
sender ! Vehicle.VehicleMessages(player, Vehicle.CannotSeatPlayer(vehicle, seat_num))
}
case _ => ; case _ => ;
} }

View file

@ -45,16 +45,16 @@ class ZoneActor(zone : Zone) extends Actor {
} }
catch { catch {
case _ : Exception => case _ : Exception =>
slog.error(s"expected a door at id $door_guid, but looking for uninitialized object") slog.error(s"expected a door at id $door_guid but no object is initialized")
} }
try { try {
if(!guid(lock_guid).get.isInstanceOf[IFFLock]) { if(!guid(lock_guid).get.isInstanceOf[IFFLock]) {
slog.error(s"expected id $lock_guid to be an IFF locks, but it was not") slog.error(s"expected id $lock_guid to be an IFF locks but it was not")
} }
} }
catch { catch {
case _ : Exception => case _ : Exception =>
slog.error(s"expected an IFF locks at id $lock_guid, but looking for uninitialized object") slog.error(s"expected an IFF locks at id $lock_guid but no object is initialized")
} }
}) })
@ -69,7 +69,7 @@ class ZoneActor(zone : Zone) extends Actor {
} }
catch { catch {
case _ : Exception => case _ : Exception =>
slog.error(s"expected a terminal at id $term_guid, but looking for uninitialized object") slog.error(s"expected a terminal at id $term_guid but no object is initialized")
} }
try { try {
if(!guid(pad_guid).get.isInstanceOf[VehicleSpawnPad]) { if(!guid(pad_guid).get.isInstanceOf[VehicleSpawnPad]) {
@ -78,7 +78,30 @@ class ZoneActor(zone : Zone) extends Actor {
} }
catch { catch {
case _ : Exception => case _ : Exception =>
slog.error(s"expected a spawn pad at id $pad_guid, but looking for uninitialized object") slog.error(s"expected a spawn pad at id $pad_guid but no object is initialized")
}
})
//check implant terminal mech to implant terminal interface association
import net.psforever.objects.serverobject.implantmech.ImplantTerminalMech
map.TerminalToInterface.foreach({case ((mech_guid, interface_guid)) =>
try {
if(!guid(mech_guid).get.isInstanceOf[ImplantTerminalMech]) {
slog.error(s"expected id $mech_guid to be an implant terminal mech, but it was not")
}
}
catch {
case _ : Exception =>
slog.error(s"expected a implant terminal mech at id $mech_guid but no object is initialized")
}
try {
if(!guid(interface_guid).get.isInstanceOf[Terminal]) { //TODO check is implant terminal
slog.error(s"expected id $interface_guid to be an implant terminal interface, but it was not")
}
}
catch {
case _ : Exception =>
slog.error(s"expected a implant terminal interface at id $interface_guid but no object is initialized")
} }
}) })
} }

View file

@ -26,6 +26,7 @@ import net.psforever.objects.serverobject.builders.ServerObjectBuilder
class ZoneMap(private val name : String) { class ZoneMap(private val name : String) {
private var localObjects : List[ServerObjectBuilder[_]] = List() private var localObjects : List[ServerObjectBuilder[_]] = List()
private var linkTerminalPad : Map[Int, Int] = Map() private var linkTerminalPad : Map[Int, Int] = Map()
private var linkTerminalInterface : Map[Int, Int] = Map()
private var linkDoorLock : Map[Int, Int] = Map() private var linkDoorLock : Map[Int, Int] = Map()
private var linkObjectBase : Map[Int, Int] = Map() private var linkObjectBase : Map[Int, Int] = Map()
private var numBases : Int = 0 private var numBases : Int = 0
@ -74,4 +75,10 @@ class ZoneMap(private val name : String) {
def TerminalToSpawnPad(terminal_guid : Int, pad_guid : Int) : Unit = { def TerminalToSpawnPad(terminal_guid : Int, pad_guid : Int) : Unit = {
linkTerminalPad = linkTerminalPad ++ Map(terminal_guid -> pad_guid) linkTerminalPad = linkTerminalPad ++ Map(terminal_guid -> pad_guid)
} }
def TerminalToInterface : Map[Int, Int] = linkTerminalInterface
def TerminalToInterface(interface_guid : Int, terminal_guid : Int) : Unit = {
linkTerminalInterface = linkTerminalInterface ++ Map(interface_guid -> terminal_guid)
}
} }

View file

@ -1268,7 +1268,7 @@ object ObjectClass {
case ObjectClass.ams_respawn_tube => DroppedItemData.genericCodec(CommonTerminalData.codec, "terminal") case ObjectClass.ams_respawn_tube => DroppedItemData.genericCodec(CommonTerminalData.codec, "terminal")
case ObjectClass.avatar => ConstructorData.genericCodec(CharacterData.codec, "avatar") case ObjectClass.avatar => ConstructorData.genericCodec(CharacterData.codec, "avatar")
case ObjectClass.capture_flag => ConstructorData.genericCodec(CaptureFlagData.codec, "capture flag") case ObjectClass.capture_flag => ConstructorData.genericCodec(CaptureFlagData.codec, "capture flag")
case ObjectClass.implant_terminal_interface => DroppedItemData.genericCodec(CommonTerminalData.codec, "implant terminal") case ObjectClass.implant_terminal_interface => ConstructorData.genericCodec(CommonTerminalData.codec, "implant terminal")
case ObjectClass.locker_container => ConstructorData.genericCodec(LockerContainerData.codec, "locker container") case ObjectClass.locker_container => ConstructorData.genericCodec(LockerContainerData.codec, "locker container")
case ObjectClass.matrix_terminala => DroppedItemData.genericCodec(CommonTerminalData.codec, "terminal") case ObjectClass.matrix_terminala => DroppedItemData.genericCodec(CommonTerminalData.codec, "terminal")
case ObjectClass.matrix_terminalb => DroppedItemData.genericCodec(CommonTerminalData.codec, "terminal") case ObjectClass.matrix_terminalb => DroppedItemData.genericCodec(CommonTerminalData.codec, "terminal")

View file

@ -0,0 +1,87 @@
// Copyright (c) 2017 PSForever
package objects
import akka.actor.{ActorRef, Props}
import net.psforever.objects.Player
import net.psforever.objects.definition.{ObjectDefinition, SeatDefinition}
import net.psforever.objects.mount.{Mountable, MountableControl}
import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.vehicles.Seat
import net.psforever.types.{CharacterGender, PlanetSideEmpire}
import scala.concurrent.duration.Duration
class MountableControl1Test extends ActorTest() {
"MountableControl" should {
"construct" in {
val obj = new MountableTest.MountableTestObject
obj.Actor = system.actorOf(Props(classOf[MountableTest.MountableTestControl], obj), "mech")
assert(obj.Actor != ActorRef.noSender)
}
}
}
class MountableControl2Test extends ActorTest() {
"MountableControl" should {
"let a player mount" in {
val player = Player("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)
val obj = new MountableTest.MountableTestObject
obj.Actor = system.actorOf(Props(classOf[MountableTest.MountableTestControl], obj), "mountable")
val msg = Mountable.TryMount(player, 0)
obj.Actor ! msg
val reply = receiveOne(Duration.create(100, "ms"))
assert(reply.isInstanceOf[Mountable.MountMessages])
val reply2 = reply.asInstanceOf[Mountable.MountMessages]
assert(reply2.player == player)
assert(reply2.response.isInstanceOf[Mountable.CanMount])
val reply3 = reply2.response.asInstanceOf[Mountable.CanMount]
assert(reply3.obj == obj)
assert(reply3.seat_num == 0)
}
}
}
class MountableControl3Test extends ActorTest() {
"MountableControl" should {
"block a player from mounting" in {
val player1 = Player("test1", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)
val player2 = Player("test2", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)
val obj = new MountableTest.MountableTestObject
obj.Actor = system.actorOf(Props(classOf[MountableTest.MountableTestControl], obj), "mountable")
obj.Actor ! Mountable.TryMount(player1, 0)
receiveOne(Duration.create(100, "ms")) //consume reply
obj.Actor ! Mountable.TryMount(player2, 0)
val reply = receiveOne(Duration.create(100, "ms"))
assert(reply.isInstanceOf[Mountable.MountMessages])
val reply2 = reply.asInstanceOf[Mountable.MountMessages]
assert(reply2.player == player2)
assert(reply2.response.isInstanceOf[Mountable.CanNotMount])
val reply3 = reply2.response.asInstanceOf[Mountable.CanNotMount]
assert(reply3.obj == obj)
assert(reply3.seat_num == 0)
}
}
}
object MountableTest {
class MountableTestObject extends PlanetSideServerObject with Mountable {
private val seats : Map[Int, Seat] = Map( 0 -> new Seat(new SeatDefinition()) )
def Seats : Map[Int, Seat] = seats
def Seat(seatNum : Int) : Option[Seat] = seats.get(seatNum)
def MountPoints : Map[Int, Int] = Map(1 -> 0)
def GetSeatFromMountPoint(mount : Int) : Option[Int] = MountPoints.get(mount)
def PassengerInSeat(user : Player) : Option[Int] = {
if(seats(0).Occupant.contains(user)) {
Some(0)
}
else {
None
}
}
def Definition : ObjectDefinition = null //eh whatever
}
class MountableTestControl(obj : Mountable) extends MountableControl(obj)
}

View file

@ -110,7 +110,7 @@ class PlayerTest extends Specification {
val testplant : ImplantDefinition = ImplantDefinition(1) val testplant : ImplantDefinition = ImplantDefinition(1)
val obj = new Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) val obj = new Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)
obj.Implants(0).Unlocked = true obj.Implants(0).Unlocked = true
obj.InstallImplant(testplant) mustEqual true obj.InstallImplant(testplant) mustEqual Some(0)
obj.Implants.find({p => p.Implant == ImplantType(1)}) match { //find the installed implant obj.Implants.find({p => p.Implant == ImplantType(1)}) match { //find the installed implant
case Some(slot) => case Some(slot) =>
slot.Installed mustEqual Some(testplant) slot.Installed mustEqual Some(testplant)
@ -126,15 +126,15 @@ class PlayerTest extends Specification {
val obj = new Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) val obj = new Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)
obj.Implants(0).Unlocked = true obj.Implants(0).Unlocked = true
obj.Implants(1).Unlocked = true obj.Implants(1).Unlocked = true
obj.InstallImplant(testplant1) mustEqual true obj.InstallImplant(testplant1) mustEqual Some(0)
obj.InstallImplant(testplant2) mustEqual false obj.InstallImplant(testplant2) mustEqual Some(1)
} }
"uninstall implants" in { "uninstall implants" in {
val testplant : ImplantDefinition = ImplantDefinition(1) val testplant : ImplantDefinition = ImplantDefinition(1)
val obj = new Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) val obj = new Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)
obj.Implants(0).Unlocked = true obj.Implants(0).Unlocked = true
obj.InstallImplant(testplant) mustEqual true obj.InstallImplant(testplant) mustEqual Some(0)
obj.Implants(0).Installed mustEqual Some(testplant) obj.Implants(0).Installed mustEqual Some(testplant)
obj.UninstallImplant(testplant.Type) obj.UninstallImplant(testplant.Type)

View file

@ -0,0 +1,116 @@
// Copyright (c) 2017 PSForever
package objects
import akka.actor.{Actor, Props}
import net.psforever.objects.GlobalDefinitions
import net.psforever.objects.guid.NumberPoolHub
import net.psforever.packet.game.PlanetSideGUID
import net.psforever.objects.serverobject.builders.ServerObjectBuilder
import scala.concurrent.duration.Duration
class DoorObjectBuilderTest extends ActorTest {
import net.psforever.objects.serverobject.doors.Door
import net.psforever.objects.serverobject.builders.DoorObjectBuilder
"DoorObjectBuilder" should {
"build" in {
val hub = ServerObjectBuilderTest.NumberPoolHub
val actor = system.actorOf(Props(classOf[ServerObjectBuilderTest.BuilderTestActor], DoorObjectBuilder(GlobalDefinitions.door, 1), hub), "door")
actor ! "!"
val reply = receiveOne(Duration.create(100, "ms"))
assert(reply.isInstanceOf[Door])
assert(reply.asInstanceOf[Door].HasGUID)
assert(reply.asInstanceOf[Door].GUID == PlanetSideGUID(1))
assert(reply == hub(1).get)
}
}
}
class IFFLockObjectBuilderTest extends ActorTest {
import net.psforever.objects.serverobject.locks.IFFLock
import net.psforever.objects.serverobject.builders.IFFLockObjectBuilder
"IFFLockObjectBuilder" should {
"build" in {
val hub = ServerObjectBuilderTest.NumberPoolHub
val actor = system.actorOf(Props(classOf[ServerObjectBuilderTest.BuilderTestActor], IFFLockObjectBuilder(GlobalDefinitions.lock_external, 1), hub), "lock")
actor ! "!"
val reply = receiveOne(Duration.create(100, "ms"))
assert(reply.isInstanceOf[IFFLock])
assert(reply.asInstanceOf[IFFLock].HasGUID)
assert(reply.asInstanceOf[IFFLock].GUID == PlanetSideGUID(1))
assert(reply == hub(1).get)
}
}
}
class ImplantTerminalMechObjectBuilderTest extends ActorTest {
import net.psforever.objects.serverobject.implantmech.ImplantTerminalMech
import net.psforever.objects.serverobject.builders.ImplantTerminalMechObjectBuilder
"IFFLockObjectBuilder" should {
"build" in {
val hub = ServerObjectBuilderTest.NumberPoolHub
val actor = system.actorOf(Props(classOf[ServerObjectBuilderTest.BuilderTestActor], ImplantTerminalMechObjectBuilder(GlobalDefinitions.implant_terminal_mech, 1), hub), "mech")
actor ! "!"
val reply = receiveOne(Duration.create(100, "ms"))
assert(reply.isInstanceOf[ImplantTerminalMech])
assert(reply.asInstanceOf[ImplantTerminalMech].HasGUID)
assert(reply.asInstanceOf[ImplantTerminalMech].GUID == PlanetSideGUID(1))
assert(reply == hub(1).get)
}
}
}
class TerminalObjectBuilderTest extends ActorTest {
import net.psforever.objects.serverobject.terminals.Terminal
import net.psforever.objects.serverobject.builders.TerminalObjectBuilder
"TerminalObjectBuilder" should {
"build" in {
val hub = ServerObjectBuilderTest.NumberPoolHub
val actor = system.actorOf(Props(classOf[ServerObjectBuilderTest.BuilderTestActor], TerminalObjectBuilder(GlobalDefinitions.order_terminal, 1), hub), "term")
actor ! "!"
val reply = receiveOne(Duration.create(100, "ms"))
assert(reply.isInstanceOf[Terminal])
assert(reply.asInstanceOf[Terminal].HasGUID)
assert(reply.asInstanceOf[Terminal].GUID == PlanetSideGUID(1))
assert(reply == hub(1).get)
}
}
}
class VehicleSpawnPadObjectBuilderTest extends ActorTest {
import net.psforever.objects.serverobject.pad.VehicleSpawnPad
import net.psforever.objects.serverobject.builders.VehicleSpawnPadObjectBuilder
"TerminalObjectBuilder" should {
"build" in {
val hub = ServerObjectBuilderTest.NumberPoolHub
val actor = system.actorOf(Props(classOf[ServerObjectBuilderTest.BuilderTestActor], VehicleSpawnPadObjectBuilder(GlobalDefinitions.spawn_pad, 1), hub), "pad")
actor ! "!"
val reply = receiveOne(Duration.create(100, "ms"))
assert(reply.isInstanceOf[VehicleSpawnPad])
assert(reply.asInstanceOf[VehicleSpawnPad].HasGUID)
assert(reply.asInstanceOf[VehicleSpawnPad].GUID == PlanetSideGUID(1))
assert(reply == hub(1).get)
}
}
}
object ServerObjectBuilderTest {
import net.psforever.objects.guid.source.LimitedNumberSource
def NumberPoolHub : NumberPoolHub = {
val obj = new NumberPoolHub(new LimitedNumberSource(2))
obj
}
class BuilderTestActor(builder : ServerObjectBuilder[_], hub : NumberPoolHub) extends Actor {
def receive : Receive = {
case _ =>
sender ! builder.Build(context, hub)
}
}
}

View file

@ -128,11 +128,11 @@ class VehicleTest extends Specification {
val fury_vehicle = Vehicle(GlobalDefinitions.fury) val fury_vehicle = Vehicle(GlobalDefinitions.fury)
fury_vehicle.Owner mustEqual None fury_vehicle.Owner mustEqual None
fury_vehicle.Seats.size mustEqual 1 fury_vehicle.Seats.size mustEqual 1
fury_vehicle.Seats.head.ArmorRestriction mustEqual SeatArmorRestriction.NoMax fury_vehicle.Seats(0).ArmorRestriction mustEqual SeatArmorRestriction.NoMax
fury_vehicle.Seats.head.isOccupied mustEqual false fury_vehicle.Seats(0).isOccupied mustEqual false
fury_vehicle.Seats.head.Occupant mustEqual None fury_vehicle.Seats(0).Occupant mustEqual None
fury_vehicle.Seats.head.Bailable mustEqual true fury_vehicle.Seats(0).Bailable mustEqual true
fury_vehicle.Seats.head.ControlledWeapon mustEqual Some(1) fury_vehicle.Seats(0).ControlledWeapon mustEqual Some(1)
fury_vehicle.PermissionGroup(0) mustEqual Some(VehicleLockState.Locked) //driver fury_vehicle.PermissionGroup(0) mustEqual Some(VehicleLockState.Locked) //driver
fury_vehicle.PermissionGroup(1) mustEqual Some(VehicleLockState.Empire) //gunner fury_vehicle.PermissionGroup(1) mustEqual Some(VehicleLockState.Empire) //gunner
fury_vehicle.PermissionGroup(2) mustEqual Some(VehicleLockState.Empire) //passenger fury_vehicle.PermissionGroup(2) mustEqual Some(VehicleLockState.Empire) //passenger

View file

@ -44,6 +44,24 @@ class ZoneTest extends Specification {
map.DoorToLock(3, 4) map.DoorToLock(3, 4)
map.DoorToLock mustEqual Map(1 -> 2, 3 -> 4) map.DoorToLock mustEqual Map(1 -> 2, 3 -> 4)
} }
"associates terminals to spawn pads (doesn't check numbers)" in {
val map = new ZoneMap("map13")
map.TerminalToSpawnPad mustEqual Map.empty
map.TerminalToSpawnPad(1, 2)
map.TerminalToSpawnPad mustEqual Map(1 -> 2)
map.TerminalToSpawnPad(3, 4)
map.TerminalToSpawnPad mustEqual Map(1 -> 2, 3 -> 4)
}
"associates mechanical components to implant terminals (doesn't check numbers)" in {
val map = new ZoneMap("map13")
map.TerminalToInterface mustEqual Map.empty
map.TerminalToInterface(1, 2)
map.TerminalToInterface mustEqual Map(1 -> 2)
map.TerminalToInterface(3, 4)
map.TerminalToInterface mustEqual Map(1 -> 2, 3 -> 4)
}
} }
val map13 = new ZoneMap("map13") val map13 = new ZoneMap("map13")

View file

@ -0,0 +1,52 @@
// Copyright (c) 2017 PSForever
package objects.terminal
import akka.actor.ActorRef
import net.psforever.objects.{GlobalDefinitions, Player}
import net.psforever.objects.serverobject.terminals.Terminal
import net.psforever.packet.game.{ItemTransactionMessage, PlanetSideGUID}
import net.psforever.types.{CharacterGender, PlanetSideEmpire, TransactionType}
import org.specs2.mutable.Specification
class ImplantTerminalInterfaceTest extends Specification {
"Implant_Terminal_Interface" should {
val player = Player("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)
"construct" in {
val terminal = Terminal(GlobalDefinitions.implant_terminal_interface)
terminal.Actor mustEqual ActorRef.noSender
}
"player can learn an implant ('darklight_vision')" in {
val terminal = Terminal(GlobalDefinitions.implant_terminal_interface)
val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Buy, 0, "darklight_vision", 0, PlanetSideGUID(0))
val reply = terminal.Request(player, msg)
reply.isInstanceOf[Terminal.LearnImplant] mustEqual true
val reply2 = reply.asInstanceOf[Terminal.LearnImplant]
reply2.implant mustEqual GlobalDefinitions.darklight_vision
}
"player can not learn a fake implant ('aimbot')" in {
val terminal = Terminal(GlobalDefinitions.implant_terminal_interface)
val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Buy, 0, "aimbot", 0, PlanetSideGUID(0))
terminal.Request(player, msg) mustEqual Terminal.NoDeal()
}
"player can surrender an implant ('darklight_vision')" in {
val terminal = Terminal(GlobalDefinitions.implant_terminal_interface)
val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Sell, 0, "darklight_vision", 0, PlanetSideGUID(0))
val reply = terminal.Request(player, msg)
reply.isInstanceOf[Terminal.SellImplant] mustEqual true
val reply2 = reply.asInstanceOf[Terminal.SellImplant]
reply2.implant mustEqual GlobalDefinitions.darklight_vision
}
"player can not surrender a fake implant ('aimbot')" in {
val terminal = Terminal(GlobalDefinitions.implant_terminal_interface)
val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Sell, 0, "aimbot", 0, PlanetSideGUID(0))
terminal.Request(player, msg) mustEqual Terminal.NoDeal()
}
}
}

View file

@ -0,0 +1,110 @@
// Copyright (c) 2017 PSForever
package objects.terminal
import akka.actor.{ActorRef, Props}
import net.psforever.objects.definition.SeatDefinition
import net.psforever.objects.mount.Mountable
import net.psforever.objects.serverobject.implantmech.{ImplantTerminalMech, ImplantTerminalMechControl}
import net.psforever.objects.vehicles.Seat
import net.psforever.objects.{GlobalDefinitions, Player}
import net.psforever.types.{CharacterGender, PlanetSideEmpire}
import objects.ActorTest
import org.specs2.mutable.Specification
import scala.concurrent.duration.Duration
class ImplantTerminalMechTest extends Specification {
"Implant_Terminal_Mech" should {
"define" in {
val implant_terminal_mech = GlobalDefinitions.implant_terminal_mech
implant_terminal_mech.ObjectId mustEqual 410
implant_terminal_mech.MountPoints mustEqual Map(1 -> 0)
implant_terminal_mech.Seats.keySet mustEqual Set(0)
implant_terminal_mech.Seats(0).isInstanceOf[SeatDefinition] mustEqual true
implant_terminal_mech.Seats(0).ArmorRestriction mustEqual net.psforever.objects.vehicles.SeatArmorRestriction.NoMax
implant_terminal_mech.Seats(0).Bailable mustEqual false
implant_terminal_mech.Seats(0).ControlledWeapon mustEqual None
}
}
"VehicleSpawnPad" should {
"construct" in {
val obj = ImplantTerminalMech(GlobalDefinitions.implant_terminal_mech)
obj.Actor mustEqual ActorRef.noSender
obj.Definition mustEqual GlobalDefinitions.implant_terminal_mech
obj.Seats.keySet mustEqual Set(0)
obj.Seats(0).isInstanceOf[Seat] mustEqual true
}
"get seat from mount points" in {
val obj = ImplantTerminalMech(GlobalDefinitions.implant_terminal_mech)
obj.GetSeatFromMountPoint(0) mustEqual None
obj.GetSeatFromMountPoint(1) mustEqual Some(0)
obj.GetSeatFromMountPoint(2) mustEqual None
}
"get passenger in a seat" in {
val player = Player("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)
val obj = ImplantTerminalMech(GlobalDefinitions.implant_terminal_mech)
obj.PassengerInSeat(player) mustEqual None
obj.Seats(0).Occupant = player
obj.PassengerInSeat(player) mustEqual Some(0)
obj.Seats(0).Occupant = None
obj.PassengerInSeat(player) mustEqual None
}
}
}
class ImplantTerminalMechControl1Test extends ActorTest() {
"ImplantTerminalMechControl" should {
"construct" in {
val obj = ImplantTerminalMech(GlobalDefinitions.implant_terminal_mech)
obj.Actor = system.actorOf(Props(classOf[ImplantTerminalMechControl], obj), "mech")
assert(obj.Actor != ActorRef.noSender)
}
}
}
class ImplantTerminalMechControl2Test extends ActorTest() {
"ImplantTerminalMechControl" should {
"let a player mount" in {
val player = Player("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)
val obj = ImplantTerminalMech(GlobalDefinitions.implant_terminal_mech)
obj.Actor = system.actorOf(Props(classOf[ImplantTerminalMechControl], obj), "mech")
val msg = Mountable.TryMount(player, 0)
obj.Actor ! msg
val reply = receiveOne(Duration.create(100, "ms"))
assert(reply.isInstanceOf[Mountable.MountMessages])
val reply2 = reply.asInstanceOf[Mountable.MountMessages]
assert(reply2.player == player)
assert(reply2.response.isInstanceOf[Mountable.CanMount])
val reply3 = reply2.response.asInstanceOf[Mountable.CanMount]
assert(reply3.obj == obj)
assert(reply3.seat_num == 0)
}
}
}
class ImplantTerminalMechControl3Test extends ActorTest() {
"ImplantTerminalMechControl" should {
"block a player from mounting" in {
val player1 = Player("test1", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)
val player2 = Player("test2", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)
val obj = ImplantTerminalMech(GlobalDefinitions.implant_terminal_mech)
obj.Actor = system.actorOf(Props(classOf[ImplantTerminalMechControl], obj), "mech")
obj.Actor ! Mountable.TryMount(player1, 0)
receiveOne(Duration.create(100, "ms")) //consume reply
obj.Actor ! Mountable.TryMount(player2, 0)
val reply = receiveOne(Duration.create(100, "ms"))
assert(reply.isInstanceOf[Mountable.MountMessages])
val reply2 = reply.asInstanceOf[Mountable.MountMessages]
assert(reply2.player == player2)
assert(reply2.response.isInstanceOf[Mountable.CanNotMount])
val reply3 = reply2.response.asInstanceOf[Mountable.CanNotMount]
assert(reply3.obj == obj)
assert(reply3.seat_num == 0)
}
}
}

View file

@ -14,7 +14,7 @@ import com.typesafe.config.ConfigFactory
import net.psforever.crypto.CryptoInterface import net.psforever.crypto.CryptoInterface
import net.psforever.objects.zones._ import net.psforever.objects.zones._
import net.psforever.objects.guid.TaskResolver import net.psforever.objects.guid.TaskResolver
import net.psforever.objects.serverobject.builders.{DoorObjectBuilder, IFFLockObjectBuilder, TerminalObjectBuilder, VehicleSpawnPadObjectBuilder} import net.psforever.objects.serverobject.builders._
import net.psforever.types.Vector3 import net.psforever.types.Vector3
import org.slf4j import org.slf4j
import org.fusesource.jansi.Ansi._ import org.fusesource.jansi.Ansi._
@ -233,10 +233,29 @@ object PsLogin {
LocalObject(DoorObjectBuilder(door, 330)) LocalObject(DoorObjectBuilder(door, 330))
LocalObject(DoorObjectBuilder(door, 332)) LocalObject(DoorObjectBuilder(door, 332))
LocalObject(DoorObjectBuilder(door, 362))
LocalObject(DoorObjectBuilder(door, 370)) LocalObject(DoorObjectBuilder(door, 370))
LocalObject(DoorObjectBuilder(door, 371)) LocalObject(DoorObjectBuilder(door, 371))
LocalObject(DoorObjectBuilder(door, 372)) LocalObject(DoorObjectBuilder(door, 372))
LocalObject(DoorObjectBuilder(door, 373)) LocalObject(DoorObjectBuilder(door, 373))
LocalObject(DoorObjectBuilder(door, 374))
LocalObject(DoorObjectBuilder(door, 375))
LocalObject(DoorObjectBuilder(door, 394))
LocalObject(DoorObjectBuilder(door, 395))
LocalObject(DoorObjectBuilder(door, 396))
LocalObject(DoorObjectBuilder(door, 397))
LocalObject(DoorObjectBuilder(door, 398))
LocalObject(DoorObjectBuilder(door, 462))
LocalObject(DoorObjectBuilder(door, 463))
LocalObject(ImplantTerminalMechObjectBuilder(implant_terminal_mech, 520)) //Hart B
LocalObject(ImplantTerminalMechObjectBuilder(implant_terminal_mech, 522)) //Hart C
LocalObject(ImplantTerminalMechObjectBuilder(implant_terminal_mech, 523)) //Hart C
LocalObject(ImplantTerminalMechObjectBuilder(implant_terminal_mech, 524)) //Hart C
LocalObject(ImplantTerminalMechObjectBuilder(implant_terminal_mech, 525)) //Hart C
LocalObject(ImplantTerminalMechObjectBuilder(implant_terminal_mech, 526)) //Hart C
LocalObject(ImplantTerminalMechObjectBuilder(implant_terminal_mech, 527)) //Hart C
LocalObject(ImplantTerminalMechObjectBuilder(implant_terminal_mech, 528)) //Hart C
LocalObject(ImplantTerminalMechObjectBuilder(implant_terminal_mech, 529)) //Hart C
LocalObject(IFFLockObjectBuilder(lock_external, 556)) LocalObject(IFFLockObjectBuilder(lock_external, 556))
LocalObject(IFFLockObjectBuilder(lock_external, 558)) LocalObject(IFFLockObjectBuilder(lock_external, 558))
LocalObject(TerminalObjectBuilder(cert_terminal, 186)) LocalObject(TerminalObjectBuilder(cert_terminal, 186))
@ -245,6 +264,15 @@ object PsLogin {
LocalObject(TerminalObjectBuilder(order_terminal, 853)) LocalObject(TerminalObjectBuilder(order_terminal, 853))
LocalObject(TerminalObjectBuilder(order_terminal, 855)) LocalObject(TerminalObjectBuilder(order_terminal, 855))
LocalObject(TerminalObjectBuilder(order_terminal, 860)) LocalObject(TerminalObjectBuilder(order_terminal, 860))
LocalObject(TerminalObjectBuilder(implant_terminal_interface, 1081)) //tube 520
LocalObject(TerminalObjectBuilder(implant_terminal_interface, 1082)) //TODO guid not correct
LocalObject(TerminalObjectBuilder(implant_terminal_interface, 1083)) //TODO guid not correct
LocalObject(TerminalObjectBuilder(implant_terminal_interface, 1084)) //TODO guid not correct
LocalObject(TerminalObjectBuilder(implant_terminal_interface, 1085)) //TODO guid not correct
LocalObject(TerminalObjectBuilder(implant_terminal_interface, 1086)) //TODO guid not correct
LocalObject(TerminalObjectBuilder(implant_terminal_interface, 1087)) //TODO guid not correct
LocalObject(TerminalObjectBuilder(implant_terminal_interface, 1088)) //TODO guid not correct
LocalObject(TerminalObjectBuilder(implant_terminal_interface, 1089)) //TODO guid not correct
LocalObject(TerminalObjectBuilder(ground_vehicle_terminal, 1063)) LocalObject(TerminalObjectBuilder(ground_vehicle_terminal, 1063))
LocalObject(VehicleSpawnPadObjectBuilder(spawn_pad, 500)) //TODO guid not correct LocalObject(VehicleSpawnPadObjectBuilder(spawn_pad, 500)) //TODO guid not correct
LocalObject(TerminalObjectBuilder(dropship_vehicle_terminal, 304)) LocalObject(TerminalObjectBuilder(dropship_vehicle_terminal, 304))
@ -254,16 +282,36 @@ object PsLogin {
ObjectToBase(330, 29) ObjectToBase(330, 29)
ObjectToBase(332, 29) ObjectToBase(332, 29)
//ObjectToBase(520, 29)
ObjectToBase(522, 29)
ObjectToBase(523, 29)
ObjectToBase(524, 29)
ObjectToBase(525, 29)
ObjectToBase(526, 29)
ObjectToBase(527, 29)
ObjectToBase(528, 29)
ObjectToBase(529, 29)
ObjectToBase(556, 29) ObjectToBase(556, 29)
ObjectToBase(558, 29) ObjectToBase(558, 29)
ObjectToBase(1063, 29) //TODO unowned courtyard terminal? ObjectToBase(1081, 29)
ObjectToBase(500, 29) //TODO unowned courtyard spawnpad? ObjectToBase(1063, 2) //TODO unowned courtyard terminal?
ObjectToBase(304, 29) //TODO unowned courtyard terminal? ObjectToBase(500, 2) //TODO unowned courtyard spawnpad?
ObjectToBase(501, 29) //TODO unowned courtyard spawnpad? ObjectToBase(304, 2) //TODO unowned courtyard terminal?
ObjectToBase(501, 2) //TODO unowned courtyard spawnpad?
DoorToLock(330, 558) DoorToLock(330, 558)
DoorToLock(332, 556) DoorToLock(332, 556)
TerminalToSpawnPad(1063, 500) TerminalToSpawnPad(1063, 500)
TerminalToSpawnPad(304, 501) TerminalToSpawnPad(304, 501)
TerminalToInterface(520, 1081)
TerminalToInterface(522, 1082)
TerminalToInterface(523, 1083)
TerminalToInterface(524, 1084)
TerminalToInterface(525, 1085)
TerminalToInterface(526, 1086)
TerminalToInterface(527, 1087)
TerminalToInterface(528, 1088)
TerminalToInterface(529, 1089)
} }
val home3 = new Zone("home3", map13, 13) { val home3 = new Zone("home3", map13, 13) {
override def Init(implicit context : ActorContext) : Unit = { override def Init(implicit context : ActorContext) : Unit = {

View file

@ -14,14 +14,16 @@ import net.psforever.objects._
import net.psforever.objects.equipment._ import net.psforever.objects.equipment._
import net.psforever.objects.guid.{GUIDTask, Task, TaskResolver} import net.psforever.objects.guid.{GUIDTask, Task, TaskResolver}
import net.psforever.objects.inventory.{GridInventory, InventoryItem} import net.psforever.objects.inventory.{GridInventory, InventoryItem}
import net.psforever.objects.mount.Mountable
import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject} import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject}
import net.psforever.objects.serverobject.doors.Door import net.psforever.objects.serverobject.doors.Door
import net.psforever.objects.serverobject.implantmech.ImplantTerminalMech
import net.psforever.objects.serverobject.locks.IFFLock import net.psforever.objects.serverobject.locks.IFFLock
import net.psforever.objects.serverobject.pad.VehicleSpawnPad import net.psforever.objects.serverobject.pad.VehicleSpawnPad
import net.psforever.objects.serverobject.terminals.Terminal import net.psforever.objects.serverobject.terminals.Terminal
import net.psforever.objects.vehicles.{AccessPermissionGroup, VehicleLockState} import net.psforever.objects.vehicles.{AccessPermissionGroup, VehicleLockState}
import net.psforever.objects.zones.{InterstellarCluster, Zone} import net.psforever.objects.zones.{InterstellarCluster, Zone}
import net.psforever.packet.game.objectcreate._ import net.psforever.packet.game.objectcreate.{DetailedCharacterData, _}
import net.psforever.types._ import net.psforever.types._
import services._ import services._
import services.avatar._ import services.avatar._
@ -342,6 +344,62 @@ class WorldSessionActor extends Actor with MDCContextAware {
case Door.NoEvent() => ; case Door.NoEvent() => ;
} }
case Mountable.MountMessages(tplayer, reply) =>
reply match {
case Mountable.CanMount(obj : ImplantTerminalMech, seat_num) =>
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)
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))
case Mountable.CanMount(obj : Vehicle, seat_num) =>
val obj_guid : PlanetSideGUID = obj.GUID
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)
if(seat_num == 0) { //simplistic vehicle ownership management
obj.Owner match {
case Some(owner_guid) =>
continent.GUID(owner_guid) match {
case Some(previous_owner : Player) =>
if(previous_owner.VehicleOwned.contains(obj_guid)) {
previous_owner.VehicleOwned = None //simplistic ownership management, player loses vehicle ownership
}
case _ => ;
}
case None => ;
}
tplayer.VehicleOwned = Some(obj_guid)
obj.Owner = Some(player_guid)
}
obj.WeaponControlledFromSeat(seat_num) match {
case Some(weapon : Tool) =>
//update mounted weapon belonging to seat
val magazine = weapon.AmmoSlots(weapon.FireModeIndex).Box //update the magazine in the weapon, specifically
sendResponse(PacketCoding.CreateGamePacket(0, InventoryStateMessage(magazine.GUID, 0, weapon.GUID, weapon.Magazine.toLong)))
//update all related ammunition objects in trunk
obj.Trunk.Items
.filter({ case ((_, item)) => item.obj.isInstanceOf[AmmoBox] && item.obj.asInstanceOf[AmmoBox].AmmoType == weapon.AmmoType })
.foreach({ case ((_, item)) =>
val box = item.obj.asInstanceOf[AmmoBox]
sendResponse(PacketCoding.CreateGamePacket(0, InventoryStateMessage(box.GUID, 0, obj_guid, box.Capacity.toLong)))
})
case _ => ; //no weapons to update
}
sendResponse(PacketCoding.CreateGamePacket(0, ObjectAttachMessage(obj_guid, player_guid, seat_num)))
vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.MountVehicle(player_guid, obj_guid, seat_num))
case Mountable.CanMount(obj : Mountable, seat_num) =>
log.warn(s"MountVehicleMsg: $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 Terminal.TerminalMessage(tplayer, msg, order) => case Terminal.TerminalMessage(tplayer, msg, order) =>
order match { order match {
case Terminal.BuyExosuit(exosuit, subtype) => case Terminal.BuyExosuit(exosuit, subtype) =>
@ -545,6 +603,84 @@ class WorldSessionActor extends Actor with MDCContextAware {
sendResponse(PacketCoding.CreateGamePacket(0, ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Learn, false))) sendResponse(PacketCoding.CreateGamePacket(0, ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Learn, false)))
} }
case Terminal.LearnImplant(implant) =>
val terminal_guid = msg.terminal_guid
val implant_type = implant.Type
val message = s"Implants: $tplayer wants to learn $implant_type"
val (interface, slotNumber) = tplayer.VehicleSeated match {
case Some(mech_guid) =>
(
continent.Map.TerminalToInterface.get(mech_guid.guid),
if(!tplayer.Implants.exists({slot => slot.Implant == implant_type})) { //no duplicates
tplayer.InstallImplant(implant)
}
else {
None
}
)
case _ =>
(None, None)
}
if(interface.contains(terminal_guid.guid) && slotNumber.isDefined) {
val slot = slotNumber.get
log.info(s"$message - put in slot $slot")
sendResponse(PacketCoding.CreateGamePacket(0, AvatarImplantMessage(tplayer.GUID, 0, slot, implant_type.id)))
sendResponse(PacketCoding.CreateGamePacket(0, ItemTransactionResultMessage(terminal_guid, TransactionType.Learn, true)))
}
else {
if(interface.isEmpty) {
log.warn(s"$message - not interacting with a terminal")
}
else if(!interface.contains(terminal_guid.guid)) {
log.warn(s"$message - interacting with the wrong terminal, ${interface.get}")
}
else if(slotNumber.isEmpty) {
log.warn(s"$message - already knows that implant")
}
else {
log.warn(s"$message - forgot to sit at a terminal")
}
sendResponse(PacketCoding.CreateGamePacket(0, ItemTransactionResultMessage(terminal_guid, TransactionType.Learn, false)))
}
case Terminal.SellImplant(implant) =>
val terminal_guid = msg.terminal_guid
val implant_type = implant.Type
val (interface, slotNumber) = tplayer.VehicleSeated match {
case Some(mech_guid) =>
(
continent.Map.TerminalToInterface.get(mech_guid.guid),
tplayer.UninstallImplant(implant_type)
)
case None =>
(None, None)
}
if(interface.contains(terminal_guid.guid) && slotNumber.isDefined) {
val slot = slotNumber.get
log.info(s"$tplayer is selling $implant_type - take from slot $slot")
sendResponse(PacketCoding.CreateGamePacket(0, AvatarImplantMessage(tplayer.GUID, 1, slot, 0)))
sendResponse(PacketCoding.CreateGamePacket(0, ItemTransactionResultMessage(terminal_guid, TransactionType.Sell, true)))
}
else {
val message = s"$tplayer can not sell $implant_type"
if(interface.isEmpty) {
log.warn(s"$message - not interacting with a terminal")
}
else if(!interface.contains(terminal_guid.guid)) {
log.warn(s"$message - interacting with the wrong terminal, ${interface.get}")
}
else if(slotNumber.isEmpty) {
log.warn(s"$message - does not know that implant")
}
else {
log.warn(s"$message - forgot to sit at a terminal")
}
sendResponse(PacketCoding.CreateGamePacket(0, ItemTransactionResultMessage(terminal_guid, TransactionType.Sell, false)))
}
case Terminal.BuyVehicle(vehicle, loadout) => case Terminal.BuyVehicle(vehicle, loadout) =>
continent.Map.TerminalToSpawnPad.get(msg.terminal_guid.guid) match { continent.Map.TerminalToSpawnPad.get(msg.terminal_guid.guid) match {
case Some(pad_guid) => case Some(pad_guid) =>
@ -563,52 +699,6 @@ class WorldSessionActor extends Actor with MDCContextAware {
sendResponse(PacketCoding.CreateGamePacket(0, ItemTransactionResultMessage(msg.terminal_guid, msg.transaction_type, false))) sendResponse(PacketCoding.CreateGamePacket(0, ItemTransactionResultMessage(msg.terminal_guid, msg.transaction_type, false)))
} }
case Vehicle.VehicleMessages(tplayer, reply) =>
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)
}
vehicle.WeaponControlledFromSeat(seat_num) match {
case Some(weapon : Tool) =>
//update mounted weapon belonging to seat
val magazine = weapon.AmmoSlots(weapon.FireModeIndex).Box //update the magazine in the weapon, specifically
sendResponse(PacketCoding.CreateGamePacket(0, InventoryStateMessage(magazine.GUID, 0, weapon.GUID, weapon.Magazine.toLong)))
//update all related ammunition objects in trunk
vehicle.Trunk.Items
.filter({ case ((_, item)) => item.obj.isInstanceOf[AmmoBox] && item.obj.asInstanceOf[AmmoBox].AmmoType == weapon.AmmoType })
.foreach({ case ((_, item)) =>
val box = item.obj.asInstanceOf[AmmoBox]
sendResponse(PacketCoding.CreateGamePacket(0, InventoryStateMessage(box.GUID, 0, vehicle_guid, box.Capacity.toLong)))
})
case _ => ; //no weapons to update
}
val player_guid : PlanetSideGUID = tplayer.GUID
sendResponse(PacketCoding.CreateGamePacket(0, ObjectAttachMessage(vehicle_guid, player_guid, seat_num)))
vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.MountVehicle(player_guid, vehicle_guid, seat_num))
case Vehicle.CannotSeatPlayer(vehicle, seat_num) =>
log.warn(s"MountVehicleMsg: player $tplayer attempted to board vehicle ${vehicle.GUID}'s seat $seat_num, but was not allowed")
case _ => ;
}
case VehicleSpawnPad.ConcealPlayer => case VehicleSpawnPad.ConcealPlayer =>
sendResponse(PacketCoding.CreateGamePacket(0, GenericObjectActionMessage(player.GUID, 36))) sendResponse(PacketCoding.CreateGamePacket(0, GenericObjectActionMessage(player.GUID, 36)))
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ConcealPlayer(player.GUID)) avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ConcealPlayer(player.GUID))
@ -627,7 +717,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
//sendResponse(PacketCoding.CreateGamePacket(0, ObjectAttachMessage(vehicle_guid, player_guid, 0))) //sendResponse(PacketCoding.CreateGamePacket(0, ObjectAttachMessage(vehicle_guid, player_guid, 0)))
vehicleService ! VehicleServiceMessage.UnscheduleDeconstruction(vehicle_guid) //cancel queue timeout delay vehicleService ! VehicleServiceMessage.UnscheduleDeconstruction(vehicle_guid) //cancel queue timeout delay
vehicleService ! VehicleServiceMessage.DelayedVehicleDeconstruction(vehicle, continent, 21L) //temporary drive away from pad delay vehicleService ! VehicleServiceMessage.DelayedVehicleDeconstruction(vehicle, continent, 21L) //temporary drive away from pad delay
vehicle.Actor ! Vehicle.TrySeatPlayer(0, player) vehicle.Actor ! Mountable.TryMount(player, 0)
case VehicleSpawnPad.PlayerSeatedInVehicle(vehicle) => case VehicleSpawnPad.PlayerSeatedInVehicle(vehicle) =>
vehicleService ! VehicleServiceMessage.DelayedVehicleDeconstruction(vehicle, continent, 21L) //sitting in the vehicle clears the drive away delay vehicleService ! VehicleServiceMessage.DelayedVehicleDeconstruction(vehicle, continent, 21L) //sitting in the vehicle clears the drive away delay
@ -906,7 +996,8 @@ class WorldSessionActor extends Actor with MDCContextAware {
player.Certifications += CertificationType.AirSupport player.Certifications += CertificationType.AirSupport
player.Certifications += CertificationType.GalaxyGunship player.Certifications += CertificationType.GalaxyGunship
player.Certifications += CertificationType.Phantasm player.Certifications += CertificationType.Phantasm
//player.ExoSuit = ExoSuitType.Infiltrator AwardBattleExperiencePoints(player, 1000000L)
// player.ExoSuit = ExoSuitType.MAX //TODO strange issue; divide number above by 10 when uncommenting
player.Slot(0).Equipment = Tool(beamer) player.Slot(0).Equipment = Tool(beamer)
player.Slot(2).Equipment = Tool(suppressor) player.Slot(2).Equipment = Tool(suppressor)
player.Slot(4).Equipment = Tool(forceblade) player.Slot(4).Equipment = Tool(forceblade)
@ -919,7 +1010,6 @@ class WorldSessionActor extends Actor with MDCContextAware {
player.Slot(5).Equipment.get.asInstanceOf[LockerContainer].Inventory += 0 -> SimpleItem(remote_electronics_kit) player.Slot(5).Equipment.get.asInstanceOf[LockerContainer].Inventory += 0 -> SimpleItem(remote_electronics_kit)
//TODO end temp player character auto-loading //TODO end temp player character auto-loading
self ! ListAccountCharacters self ! ListAccountCharacters
import scala.concurrent.duration._ import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.ExecutionContext.Implicits.global
clientKeepAlive.cancel clientKeepAlive.cancel
@ -982,25 +1072,58 @@ class WorldSessionActor extends Actor with MDCContextAware {
//load active vehicles in zone //load active vehicles in zone
continent.Vehicles.foreach(vehicle => { continent.Vehicles.foreach(vehicle => {
val definition = vehicle.Definition val definition = vehicle.Definition
sendResponse( sendResponse(PacketCoding.CreateGamePacket(0,
PacketCoding.CreateGamePacket(0, ObjectCreateMessage(
ObjectCreateMessage( definition.ObjectId,
definition.ObjectId, vehicle.GUID,
vehicle.GUID, definition.Packet.ConstructorData(vehicle).get
definition.Packet.ConstructorData(vehicle).get
)
) )
) ))
//seat vehicle occupants //seat vehicle occupants
vehicle.Definition.MountPoints.values.foreach(seat_num => { vehicle.Definition.MountPoints.values.foreach(seat_num => {
vehicle.Seat(seat_num).get.Occupant match { vehicle.Seat(seat_num).get.Occupant match {
case Some(tplayer) => case Some(tplayer) =>
sendResponse(PacketCoding.CreateGamePacket(0, ObjectAttachMessage(vehicle.GUID, tplayer.GUID, seat_num))) if(tplayer.HasGUID) {
sendResponse(PacketCoding.CreateGamePacket(0, ObjectAttachMessage(vehicle.GUID, tplayer.GUID, seat_num)))
}
case None => ; case None => ;
} }
}) })
ReloadVehicleAccessPermissions(vehicle) ReloadVehicleAccessPermissions(vehicle)
}) })
//implant terminals
continent.Map.TerminalToInterface.foreach({ case((terminal_guid, interface_guid)) =>
val parent_guid = PlanetSideGUID(terminal_guid)
continent.GUID(interface_guid) match {
case Some(obj : Terminal) =>
val obj_def = obj.Definition
val obj_uid = obj_def.ObjectId
val obj_data = obj_def.Packet.ConstructorData(obj).get
sendResponse(PacketCoding.CreateGamePacket(0,
ObjectCreateMessage(
obj_uid,
PlanetSideGUID(interface_guid),
ObjectCreateMessageParent(parent_guid, 1),
obj_data
)
))
case _ => ;
}
//seat terminal occupants
continent.GUID(terminal_guid) match {
case Some(obj : Mountable) =>
obj.MountPoints.foreach({ case((_, seat_num)) =>
obj.Seat(seat_num).get.Occupant match {
case Some(tplayer) =>
if(tplayer.HasGUID) {
sendResponse(PacketCoding.CreateGamePacket(0, ObjectAttachMessage(parent_guid, tplayer.GUID, seat_num)))
}
case None => ;
}
})
case _ => ;
}
})
avatarService ! Service.Join(player.Continent) avatarService ! Service.Join(player.Continent)
localService ! Service.Join(player.Continent) localService ! Service.Join(player.Continent)
vehicleService ! Service.Join(player.Continent) vehicleService ! Service.Join(player.Continent)
@ -1423,18 +1546,18 @@ class WorldSessionActor extends Actor with MDCContextAware {
case msg @ WarpgateRequest(continent_guid, building_guid, dest_building_guid, dest_continent_guid, unk1, unk2) => case msg @ WarpgateRequest(continent_guid, building_guid, dest_building_guid, dest_continent_guid, unk1, unk2) =>
log.info("WarpgateRequest: " + msg) log.info("WarpgateRequest: " + msg)
case msg @ MountVehicleMsg(player_guid, vehicle_guid, unk) => case msg @ MountVehicleMsg(player_guid, mountable_guid, unk) =>
//log.info("MountVehicleMsg: "+msg) log.info("MountVehicleMsg: "+msg)
continent.GUID(vehicle_guid) match { continent.GUID(mountable_guid) match {
case Some(obj : Vehicle) => case Some(obj : Mountable) =>
obj.GetSeatFromMountPoint(unk) match { obj.GetSeatFromMountPoint(unk) match {
case Some(seat_num) => case Some(seat_num) =>
obj.Actor ! Vehicle.TrySeatPlayer(seat_num, player) obj.Actor ! Mountable.TryMount(player, seat_num)
case None => case None =>
log.warn(s"MountVehicleMsg: attempted to board vehicle $vehicle_guid's seat $unk, but no seat exists there") log.warn(s"MountVehicleMsg: attempted to board mountable $mountable_guid's seat $unk, but no seat exists there")
} }
case None | Some(_) => case None | Some(_) =>
log.warn(s"MountVehicleMsg: not a vehicle") log.warn(s"MountVehicleMsg: not a mountable thing")
} }
case msg @ DismountVehicleMsg(player_guid, unk1, unk2) => case msg @ DismountVehicleMsg(player_guid, unk1, unk2) =>
@ -1443,31 +1566,37 @@ class WorldSessionActor extends Actor with MDCContextAware {
if(player.GUID == player_guid) { if(player.GUID == player_guid) {
//common warning for this section //common warning for this section
def dismountWarning(msg : String) : Unit = { def dismountWarning(msg : String) : Unit = {
log.warn(s"$msg; a vehicle may not know that a player is no longer sitting it in") log.warn(s"$msg; some vehicle might not know that a player is no longer sitting in it")
} }
//normally disembarking from a seat //normally disembarking from a seat
player.VehicleSeated match { player.VehicleSeated match {
case Some(vehicle_guid) => case Some(obj_guid) =>
continent.GUID(vehicle_guid) match { continent.GUID(obj_guid) match {
case Some(obj : Vehicle) => case Some(obj : Mountable) =>
obj.Seats.find(seat => seat.Occupant.contains(player)) match { val seats = obj.Seats.values
seats.find(seat => seat.Occupant.contains(player)) match {
case Some(seat) => case Some(seat) =>
val vel = obj.Velocity.getOrElse(Vector3(0f, 0f, 0f)) val vel = obj.Velocity.getOrElse(Vector3(0f, 0f, 0f))
val has_vel : Int = math.abs(vel.x * vel.y * vel.z).toInt val has_vel : Int = math.abs(vel.x * vel.y * vel.z).toInt
if(seat.Bailable || obj.Velocity.isEmpty || has_vel == 0) { //ugh, float comparison if(seat.Bailable || obj.Velocity.isEmpty || has_vel == 0) { //ugh, float comparison
seat.Occupant = None seat.Occupant = None
if(obj.Seats.count(seat => seat.isOccupied) == 0) { //special actions
vehicleService ! VehicleServiceMessage.DelayedVehicleDeconstruction(obj, continent, 600L) //start vehicle decay (10m) obj match {
case (veh : Vehicle) =>
if(seats.count(seat => seat.isOccupied) == 0) {
vehicleService ! VehicleServiceMessage.DelayedVehicleDeconstruction(veh, continent, 600L) //start vehicle decay (10m)
}
case _ => ;
} }
} }
case None => case None =>
dismountWarning(s"DismountVehicleMsg: can not find where player $player_guid is seated in vehicle $vehicle_guid") dismountWarning(s"DismountVehicleMsg: can not find where player $player_guid is seated in mountable $obj_guid")
} }
case _ => case _ =>
dismountWarning(s"DismountVehicleMsg: can not find vehicle $vehicle_guid") dismountWarning(s"DismountVehicleMsg: can not find mountable entity $obj_guid")
} }
case None => case None =>
dismountWarning(s"DismountVehicleMsg: player $player_guid not considered seated in a vehicle") dismountWarning(s"DismountVehicleMsg: player $player_guid not considered seated in a mountable entity")
} }
//should be safe //should be safe
player.VehicleSeated = None player.VehicleSeated = None
@ -1483,12 +1612,13 @@ class WorldSessionActor extends Actor with MDCContextAware {
if(tplayer.VehicleSeated.contains(vehicle_guid)) { if(tplayer.VehicleSeated.contains(vehicle_guid)) {
continent.GUID(vehicle_guid) match { continent.GUID(vehicle_guid) match {
case Some(obj : Vehicle) => case Some(obj : Vehicle) =>
obj.Seats.find(seat => seat.Occupant.contains(tplayer)) match { val seats = obj.Seats.values
seats.find(seat => seat.Occupant.contains(tplayer)) match {
case Some(seat) => case Some(seat) =>
seat.Occupant = None seat.Occupant = None
tplayer.VehicleSeated = None tplayer.VehicleSeated = None
vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.KickPassenger(player_guid, unk1, unk2)) vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.KickPassenger(player_guid, unk1, unk2))
if(obj.Seats.count(seat => seat.isOccupied) == 0) { if(seats.count(seat => seat.isOccupied) == 0) {
vehicleService ! VehicleServiceMessage.DelayedVehicleDeconstruction(obj, continent, 600L) //start vehicle decay (10m) vehicleService ! VehicleServiceMessage.DelayedVehicleDeconstruction(obj, continent, 600L) //start vehicle decay (10m)
} }
case None => case None =>
@ -1984,6 +2114,34 @@ class WorldSessionActor extends Actor with MDCContextAware {
}) })
} }
/**
* Gives a target player positive battle experience points only.
* If the player has access to more implant slots as a result of changing battle experience points, unlock those slots.
* @param tplayer the player
* @param bep the change in experience points, positive by assertion
* @return the player's current battle experience points
*/
def AwardBattleExperiencePoints(tplayer : Player, bep : Long) : Long = {
val oldBep = tplayer.BEP
if(bep <= 0) {
log.error(s"trying to set $bep battle experience points on $tplayer; value can not be negative")
oldBep
}
else {
val oldSlots = DetailedCharacterData.numberOfImplantSlots(oldBep)
val newBep = oldBep + bep
val newSlots = DetailedCharacterData.numberOfImplantSlots(newBep)
tplayer.BEP = newBep
if(newSlots > oldSlots) {
(oldSlots until newSlots).foreach(slotNumber => {
tplayer.Implants(slotNumber).Unlocked = true
log.info(s"unlocking implant slot $slotNumber for $tplayer")
})
}
newBep
}
}
def failWithError(error : String) = { def failWithError(error : String) = {
log.error(error) log.error(error)
sendResponse(PacketCoding.CreateControlPacket(ConnectionClose())) sendResponse(PacketCoding.CreateControlPacket(ConnectionClose()))