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._
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.terminals._
import net.psforever.objects.vehicles.SeatArmorRestriction
@ -467,6 +468,10 @@ object GlobalDefinitions {
val cert_terminal = new CertTerminalDefinition
val implant_terminal_mech = new ImplantTerminalMechDefinition
val implant_terminal_interface = new ImplantTerminalInterfaceDefinition
val ground_vehicle_terminal = new GroundVehicleTerminalDefinition
val air_vehicle_terminal = new AirVehicleTerminalDefinition

View file

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

View file

@ -37,6 +37,15 @@ class Player(private val name : String,
private var bep : Long = 0
private var cep : Long = 0
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 var tosRibbon : MeritCommendation.Value = MeritCommendation.None
@ -330,56 +339,94 @@ class Player(private val name : String,
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
/**
* 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 = {
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
case None =>
//install in a free slot
getAvailableImplantSlot(implants.iterator, implant.Type) match {
case Some(slot) =>
slot.Implant = implant
true
recursiveFindImplantInSlot(implants.iterator, ImplantType.None) match { //install in a free slot
case out @ Some(slot) =>
implants(slot).Implant = implant
out
case None =>
false
None
}
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) {
None
}
else {
val slot = iter.next
if(!slot.Unlocked || slot.Implant == implantType) {
None
}
else if(slot.Installed.isEmpty) {
Some(slot)
if(slot.Unlocked && slot.Implant == implantType) {
Some(index)
}
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 = {
implants.foreach(slot => {
slot.Installed match {

View file

@ -4,6 +4,7 @@ package net.psforever.objects
import net.psforever.objects.definition.VehicleDefinition
import net.psforever.objects.equipment.{Equipment, EquipmentSize}
import net.psforever.objects.inventory.{GridInventory, InventoryTile}
import net.psforever.objects.mount.Mountable
import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.vehicles.{AccessPermissionGroup, Seat, Utility, VehicleLockState}
import net.psforever.packet.game.PlanetSideGUID
@ -26,7 +27,7 @@ import scala.collection.mutable
* stores and unloads pertinent information about the `Vehicle`'s configuration;
* 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 owner : Option[PlanetSideGUID] = None
private var health : Int = 1
@ -97,7 +98,7 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideServ
}
def MaxHealth : Int = {
this.vehicleDef.MaxHealth
Definition.MaxHealth
}
def Shields : Int = {
@ -110,7 +111,7 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideServ
}
def MaxShields : Int = {
vehicleDef.MaxShields
Definition.MaxShields
}
def Drive : DriveState.Value = {
@ -118,7 +119,7 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideServ
}
def Drive_=(deploy : DriveState.Value) : DriveState.Value = {
if(vehicleDef.Deployment) {
if(Definition.Deployment) {
this.deployed = deploy
}
Drive
@ -153,9 +154,11 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideServ
* @return a seat number, or `None`
*/
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?
* @param group the group index
@ -225,8 +228,8 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideServ
}
}
def Seats : List[Seat] = {
seats.values.toList
def Seats : Map[Int, Seat] = {
seats
}
def SeatPermissionGroup(seatNumber : Int) : Option[AccessPermissionGroup.Value] = {
@ -428,27 +431,6 @@ object Vehicle {
*/
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.
* @param vehicleDef the vehicle's definition entry
@ -494,7 +476,7 @@ object Vehicle {
* @return the string output
*/
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)"
}
}

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}
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] = {
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
import net.psforever.objects.Player
import net.psforever.objects.definition.ImplantDefinition
import net.psforever.objects.equipment.Equipment
import net.psforever.objects.inventory.InventoryItem
import net.psforever.objects.serverobject.PlanetSideServerObject
@ -151,7 +152,24 @@ object Terminal {
*/
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
/**
* 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
/**

View file

@ -1,8 +1,8 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.vehicles
import akka.actor.Actor
import net.psforever.objects.Vehicle
import net.psforever.objects.mount.MountableControl
/**
* 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.
* @param vehicle the `Vehicle` object being governed
*/
class VehicleControl(private val vehicle : Vehicle) extends Actor {
def receive : Receive = {
class VehicleControl(private val vehicle : Vehicle) extends MountableControl(vehicle) {
override def receive : Receive = super[MountableControl].receive.orElse {
case Vehicle.PrepareForDeletion =>
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 _ => ;
}

View file

@ -45,16 +45,16 @@ class ZoneActor(zone : Zone) extends Actor {
}
catch {
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 {
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 {
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 {
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 {
if(!guid(pad_guid).get.isInstanceOf[VehicleSpawnPad]) {
@ -78,7 +78,30 @@ class ZoneActor(zone : Zone) extends Actor {
}
catch {
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) {
private var localObjects : List[ServerObjectBuilder[_]] = List()
private var linkTerminalPad : Map[Int, Int] = Map()
private var linkTerminalInterface : Map[Int, Int] = Map()
private var linkDoorLock : Map[Int, Int] = Map()
private var linkObjectBase : Map[Int, Int] = Map()
private var numBases : Int = 0
@ -74,4 +75,10 @@ class ZoneMap(private val name : String) {
def TerminalToSpawnPad(terminal_guid : Int, pad_guid : Int) : Unit = {
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.avatar => ConstructorData.genericCodec(CharacterData.codec, "avatar")
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.matrix_terminala => 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 obj = new Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)
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
case Some(slot) =>
slot.Installed mustEqual Some(testplant)
@ -126,15 +126,15 @@ class PlayerTest extends Specification {
val obj = new Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)
obj.Implants(0).Unlocked = true
obj.Implants(1).Unlocked = true
obj.InstallImplant(testplant1) mustEqual true
obj.InstallImplant(testplant2) mustEqual false
obj.InstallImplant(testplant1) mustEqual Some(0)
obj.InstallImplant(testplant2) mustEqual Some(1)
}
"uninstall implants" in {
val testplant : ImplantDefinition = ImplantDefinition(1)
val obj = new Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)
obj.Implants(0).Unlocked = true
obj.InstallImplant(testplant) mustEqual true
obj.InstallImplant(testplant) mustEqual Some(0)
obj.Implants(0).Installed mustEqual Some(testplant)
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)
fury_vehicle.Owner mustEqual None
fury_vehicle.Seats.size mustEqual 1
fury_vehicle.Seats.head.ArmorRestriction mustEqual SeatArmorRestriction.NoMax
fury_vehicle.Seats.head.isOccupied mustEqual false
fury_vehicle.Seats.head.Occupant mustEqual None
fury_vehicle.Seats.head.Bailable mustEqual true
fury_vehicle.Seats.head.ControlledWeapon mustEqual Some(1)
fury_vehicle.Seats(0).ArmorRestriction mustEqual SeatArmorRestriction.NoMax
fury_vehicle.Seats(0).isOccupied mustEqual false
fury_vehicle.Seats(0).Occupant mustEqual None
fury_vehicle.Seats(0).Bailable mustEqual true
fury_vehicle.Seats(0).ControlledWeapon mustEqual Some(1)
fury_vehicle.PermissionGroup(0) mustEqual Some(VehicleLockState.Locked) //driver
fury_vehicle.PermissionGroup(1) mustEqual Some(VehicleLockState.Empire) //gunner
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 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")

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.objects.zones._
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 org.slf4j
import org.fusesource.jansi.Ansi._
@ -233,10 +233,29 @@ object PsLogin {
LocalObject(DoorObjectBuilder(door, 330))
LocalObject(DoorObjectBuilder(door, 332))
LocalObject(DoorObjectBuilder(door, 362))
LocalObject(DoorObjectBuilder(door, 370))
LocalObject(DoorObjectBuilder(door, 371))
LocalObject(DoorObjectBuilder(door, 372))
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, 558))
LocalObject(TerminalObjectBuilder(cert_terminal, 186))
@ -245,6 +264,15 @@ object PsLogin {
LocalObject(TerminalObjectBuilder(order_terminal, 853))
LocalObject(TerminalObjectBuilder(order_terminal, 855))
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(VehicleSpawnPadObjectBuilder(spawn_pad, 500)) //TODO guid not correct
LocalObject(TerminalObjectBuilder(dropship_vehicle_terminal, 304))
@ -254,16 +282,36 @@ object PsLogin {
ObjectToBase(330, 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(558, 29)
ObjectToBase(1063, 29) //TODO unowned courtyard terminal?
ObjectToBase(500, 29) //TODO unowned courtyard spawnpad?
ObjectToBase(304, 29) //TODO unowned courtyard terminal?
ObjectToBase(501, 29) //TODO unowned courtyard spawnpad?
ObjectToBase(1081, 29)
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?
DoorToLock(330, 558)
DoorToLock(332, 556)
TerminalToSpawnPad(1063, 500)
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) {
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.guid.{GUIDTask, Task, TaskResolver}
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.doors.Door
import net.psforever.objects.serverobject.implantmech.ImplantTerminalMech
import net.psforever.objects.serverobject.locks.IFFLock
import net.psforever.objects.serverobject.pad.VehicleSpawnPad
import net.psforever.objects.serverobject.terminals.Terminal
import net.psforever.objects.vehicles.{AccessPermissionGroup, VehicleLockState}
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 services._
import services.avatar._
@ -342,6 +344,62 @@ class WorldSessionActor extends Actor with MDCContextAware {
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) =>
order match {
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)))
}
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) =>
continent.Map.TerminalToSpawnPad.get(msg.terminal_guid.guid) match {
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)))
}
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 =>
sendResponse(PacketCoding.CreateGamePacket(0, GenericObjectActionMessage(player.GUID, 36)))
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)))
vehicleService ! VehicleServiceMessage.UnscheduleDeconstruction(vehicle_guid) //cancel queue timeout delay
vehicleService ! VehicleServiceMessage.DelayedVehicleDeconstruction(vehicle, continent, 21L) //temporary drive away from pad delay
vehicle.Actor ! Vehicle.TrySeatPlayer(0, player)
vehicle.Actor ! Mountable.TryMount(player, 0)
case VehicleSpawnPad.PlayerSeatedInVehicle(vehicle) =>
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.GalaxyGunship
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(2).Equipment = Tool(suppressor)
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)
//TODO end temp player character auto-loading
self ! ListAccountCharacters
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global
clientKeepAlive.cancel
@ -982,25 +1072,58 @@ class WorldSessionActor extends Actor with MDCContextAware {
//load active vehicles in zone
continent.Vehicles.foreach(vehicle => {
val definition = vehicle.Definition
sendResponse(
PacketCoding.CreateGamePacket(0,
ObjectCreateMessage(
definition.ObjectId,
vehicle.GUID,
definition.Packet.ConstructorData(vehicle).get
)
sendResponse(PacketCoding.CreateGamePacket(0,
ObjectCreateMessage(
definition.ObjectId,
vehicle.GUID,
definition.Packet.ConstructorData(vehicle).get
)
)
))
//seat vehicle occupants
vehicle.Definition.MountPoints.values.foreach(seat_num => {
vehicle.Seat(seat_num).get.Occupant match {
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 => ;
}
})
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)
localService ! 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) =>
log.info("WarpgateRequest: " + msg)
case msg @ MountVehicleMsg(player_guid, vehicle_guid, unk) =>
//log.info("MountVehicleMsg: "+msg)
continent.GUID(vehicle_guid) match {
case Some(obj : Vehicle) =>
case msg @ MountVehicleMsg(player_guid, mountable_guid, unk) =>
log.info("MountVehicleMsg: "+msg)
continent.GUID(mountable_guid) match {
case Some(obj : Mountable) =>
obj.GetSeatFromMountPoint(unk) match {
case Some(seat_num) =>
obj.Actor ! Vehicle.TrySeatPlayer(seat_num, player)
obj.Actor ! Mountable.TryMount(player, seat_num)
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(_) =>
log.warn(s"MountVehicleMsg: not a vehicle")
log.warn(s"MountVehicleMsg: not a mountable thing")
}
case msg @ DismountVehicleMsg(player_guid, unk1, unk2) =>
@ -1443,31 +1566,37 @@ class WorldSessionActor extends Actor with MDCContextAware {
if(player.GUID == player_guid) {
//common warning for this section
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
player.VehicleSeated match {
case Some(vehicle_guid) =>
continent.GUID(vehicle_guid) match {
case Some(obj : Vehicle) =>
obj.Seats.find(seat => seat.Occupant.contains(player)) 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) =>
val vel = obj.Velocity.getOrElse(Vector3(0f, 0f, 0f))
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
seat.Occupant = None
if(obj.Seats.count(seat => seat.isOccupied) == 0) {
vehicleService ! VehicleServiceMessage.DelayedVehicleDeconstruction(obj, continent, 600L) //start vehicle decay (10m)
//special actions
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 =>
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 _ =>
dismountWarning(s"DismountVehicleMsg: can not find vehicle $vehicle_guid")
dismountWarning(s"DismountVehicleMsg: can not find mountable entity $obj_guid")
}
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
player.VehicleSeated = None
@ -1483,12 +1612,13 @@ class WorldSessionActor extends Actor with MDCContextAware {
if(tplayer.VehicleSeated.contains(vehicle_guid)) {
continent.GUID(vehicle_guid) match {
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) =>
seat.Occupant = None
tplayer.VehicleSeated = None
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)
}
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) = {
log.error(error)
sendResponse(PacketCoding.CreateControlPacket(ConnectionClose()))