Merge branch 'master' into feature/VehicleDismountImprovements

This commit is contained in:
Fate-JH 2018-05-21 08:34:29 -04:00 committed by GitHub
commit 12443c6aa5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
51 changed files with 2281 additions and 384 deletions

View file

@ -25,9 +25,11 @@ In order to use scala, you need the compiler `scalac`. This is equivalent to Jav
Install this on to your system and the compiler and Scala REPL will be added to your PATH.
### Downloading PSCrypto
The server requires PSCrypto in order to run. [Download the latest release](https://github.com/psforever/PSCrypto/releases/download/v1.1/pscrypto-lib-1.1.zip) and extract the ZIP in to the top level of your source directory. SBT, IDEA, and Java will automatically find the required libraries when running the server.
The server requires PSCrypto in order to run. [Download the latest release](https://github.com/psforever/PSCrypto/releases/download/v1.1/pscrypto-lib-1.1.zip) and extract the the approprate dll for your operating system to the top level of your source directory (the root directory, not /pslogin/src/main/scala). SBT, IDEA, and Java will automatically find the required libraries when running the server.
If you are not comfortable with compiled binaries, you can [build the libraries yourself](https://github.com/psforever/PSCrypto).
If you have any issues with PSCrypto being detected when trying to run the server try adding `-Djava.library.path=` (no path necessary) to your preferred IDE's build configuration, for example with IDEA: Run -> Edit Configuration -> VM Options
### Using an IDE
Scala code can be fairly complex and a good IDE helps you understand the code and what methods are available for certain types.
IntelliJ IDEA has some of the most mature support for Scala of any IDE today. It has advanced type introspection and excellent code completion. It's recommended for those who are new to Scala in order to get familiar with the syntax.

View file

@ -3,6 +3,7 @@ package net.psforever.objects
import net.psforever.objects.definition.{AvatarDefinition, ImplantDefinition}
import net.psforever.objects.equipment.EquipmentSize
import net.psforever.objects.loadouts.Loadout
import net.psforever.types.{CertificationType, CharacterGender, ImplantType, PlanetSideEmpire}
import scala.annotation.tailrec
@ -16,7 +17,7 @@ class Avatar(val name : String, val faction : PlanetSideEmpire.Value, val sex :
/** Certifications */
private val certs : mutable.Set[CertificationType.Value] = mutable.Set[CertificationType.Value]()
/** Implants<br>
* Unlike other objects, the maximum number of `ImplantSlots` are built into the `Avatar`.
* Unlike other objects, all `ImplantSlot` objects are already built into the `Avatar`.
* 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 terms externally used for the states of process is "installed" and "uninstalled."
@ -24,9 +25,12 @@ class Avatar(val name : String, val faction : PlanetSideEmpire.Value, val sex :
* @see `DetailedCharacterData.implants`
*/
private val implants : Array[ImplantSlot] = Array.fill[ImplantSlot](3)(new ImplantSlot)
/** Loadouts */
private val loadouts : Array[Option[Loadout]] = Array.fill[Option[Loadout]](10)(None)
/** Locker (inventory slot number five) */
/** Loadouts<br>
* 0-9 are Infantry loadouts
* 10-14 are Vehicle loadouts
*/
private val loadouts : Array[Option[Loadout]] = Array.fill[Option[Loadout]](15)(None)
/** Locker */
private val locker : LockerContainer = new LockerContainer() {
override def toString : String = {
s"$name's ${Definition.Name}"
@ -153,6 +157,12 @@ class Avatar(val name : String, val faction : PlanetSideEmpire.Value, val sex :
}
}
def SaveLoadout(owner : Vehicle, label : String, line : Int) : Unit = {
if(line > 9 && line < loadouts.length) {
loadouts(line) = Some(Loadout.Create(owner, label))
}
}
def LoadLoadout(line : Int) : Option[Loadout] = loadouts.lift(line).getOrElse(None)
def DeleteLoadout(line : Int) : Unit = {

View file

@ -524,6 +524,10 @@ object GlobalDefinitions {
val medical_terminal = new MedicalTerminalDefinition(529)
val pad_landing = new RepairRearmSiloDefinition(719)
val repair_silo = new RepairRearmSiloDefinition(729)
val spawn_pad = new VehicleSpawnPadDefinition
val mb_locker = new LockerDefinition

View file

@ -4,6 +4,7 @@ package net.psforever.objects
import net.psforever.objects.definition.AvatarDefinition
import net.psforever.objects.equipment.{Equipment, EquipmentSize}
import net.psforever.objects.inventory.{Container, GridInventory, InventoryItem}
import net.psforever.objects.loadouts.Loadout
import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.packet.game.PlanetSideGUID
import net.psforever.types._

View file

@ -349,6 +349,39 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideServ
def VisibleSlots : Set[Int] = weapons.keySet
override def Slot(slotNum : Int) : EquipmentSlot = {
weapons.get(slotNum)
// .orElse(utilities.get(slotNum) match {
// case Some(_) =>
// //TODO what do now?
// None
// case None => ;
// None
// })
.orElse(Some(Inventory.Slot(slotNum))).get
}
override def Find(guid : PlanetSideGUID) : Option[Int] = {
weapons.find({ case (_, obj) =>
obj.Equipment match {
case Some(item) =>
if(item.HasGUID && item.GUID == guid) {
true
}
else {
false
}
case None =>
false
}
}) match {
case Some((index, _)) =>
Some(index)
case None =>
Inventory.Find(guid)
}
}
/**
* A reference to the `Vehicle` `Trunk` space.
* @return this `Vehicle` `Trunk`

View file

@ -0,0 +1,102 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.loadouts
import net.psforever.types.ExoSuitType
/**
* A blueprint of a player's uniform, their holster items, and their inventory items, saved in a specific state.
* This previous state can be restored on any given player template
* by reconstructing the items (if permitted) and re-assigning the uniform (if available).<br>
* <br>
* The fifth tab on an `order_terminal` window is occupied by the list of "Favorite" `Loadout` blueprints.
* The ten-long list is initialized with `FavoritesMessage` packets assigned to the "Infantry" list.
* Specific entries are added or removed using `FavoritesRequest` packets,
* re-established using other conventional game packets.
* @param label the name by which this inventory will be known when displayed in a Favorites list;
* field gets inherited
* @param visible_slots simplified representation of the `Equipment` that can see "seen" on the target;
* field gets inherited
* @param inventory simplified representation of the `Equipment` in the target's inventory or trunk;
* field gets inherited
* @param exosuit the exo-suit in which the avatar will be dressed;
* may be restricted
* @param subtype the mechanized assault exo-suit specialization number that indicates whether the MAX performs:
* anti-infantry (1), anti-vehicular (2), or anti-air work (3);
* the default value is 0
*/
final case class InfantryLoadout(label : String,
visible_slots : List[Loadout.SimplifiedEntry],
inventory : List[Loadout.SimplifiedEntry],
exosuit : ExoSuitType.Value,
subtype : Int) extends Loadout(label, visible_slots, inventory)
object InfantryLoadout {
import net.psforever.objects.Player
import net.psforever.objects.GlobalDefinitions
import net.psforever.objects.equipment.Equipment
/**
* The sub-type of the player's uniform.
* Applicable to mechanized assault units, mainly.
* The subtype is reported as a number but indicates the specialization - anti-infantry, ani-vehicular, anti-air - of the suit
* as indicated by the arm weapon(s).
* @param player the player
* @return the numeric subtype
*/
def DetermineSubtype(player : Player) : Int = {
DetermineSubtypeA(player.ExoSuit, player.Slot(0).Equipment)
}
/**
* The sub-type of the player's uniform.
* Applicable to mechanized assault units, mainly.
* The subtype is reported as a number but indicates the specialization - anti-infantry, ani-vehicular, anti-air - of the suit
* as indicated by the arm weapon(s).
* @param suit the player's uniform;
* the target is for MAX armors
* @param weapon any weapon the player may have it his "first pistol slot;"
* to a MAX, that is its "primary weapon slot"
* @return the numeric subtype
*/
def DetermineSubtypeA(suit : ExoSuitType.Value, weapon : Option[Equipment]) : Int = {
if(suit == ExoSuitType.MAX) {
weapon match {
case Some(item) =>
item.Definition match {
case GlobalDefinitions.trhev_dualcycler | GlobalDefinitions.nchev_scattercannon | GlobalDefinitions.vshev_quasar =>
1
case GlobalDefinitions.trhev_pounder | GlobalDefinitions.nchev_falcon | GlobalDefinitions.vshev_comet =>
2
case GlobalDefinitions.trhev_burster | GlobalDefinitions.nchev_sparrow | GlobalDefinitions.vshev_starfire =>
3
case _ =>
0
}
case None =>
0
}
}
else {
0
}
}
/**
* The sub-type of the player's uniform, as used in `FavoritesMessage`.<br>
* <br>
* The values for `Standard`, `Infiltration`, and the generic `MAX` are not perfectly known.
* The latter-most exo-suit option is presumed.
* @param suit the player's uniform
* @param subtype the mechanized assault exo-suit subtype as determined by their arm weapons
* @return the numeric subtype
*/
def DetermineSubtypeB(suit : ExoSuitType.Value, subtype : Int) : Int = {
suit match {
case ExoSuitType.Standard => 0
case ExoSuitType.Agile => 1
case ExoSuitType.Reinforced => 2
case ExoSuitType.MAX => 3 + subtype //4, 5, 6
case ExoSuitType.Infiltration => 7
}
}
}

View file

@ -1,91 +1,42 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects
package net.psforever.objects.loadouts
import net.psforever.objects._
import net.psforever.objects.definition._
import net.psforever.objects.equipment.Equipment
import net.psforever.objects.inventory.InventoryItem
import net.psforever.types.ExoSuitType
import scala.annotation.tailrec
/**
* From a `Player` their current exo-suit and their `Equipment`, retain a set of instructions to reconstruct this arrangement.<br>
* The base of all specific kinds of blueprint containers.
* This previous state can be restored on any appropriate template from which the loadout was copied
* by reconstructing the items (if permitted).
* The three fields are the name assigned to the loadout,
* the visible items that are created (which obey different rules depending on the source),
* and the concealed items that are created and added to the source's `Inventory`.<br>
* For example, the `visible_slots` on a `Player`-borne loadout will transform into the form `Array[EquipmentSlot]`;
* `Vehicle`-originating loadouts transform into the form `Map[Int, Equipment]`.
* <br>
* `Loadout` objects are composed of the following information, as if a blueprint:<br>
* - the avatar's current exo-suit<br>
* - the type of specialization, called a "subtype" (mechanized assault exo-suits only)<br>
* - the contents of the avatar's occupied holster slots<br>
* - the contents of the avatar's occupied inventory<br>
* `Equipment` contents of the holsters and of the formal inventory region will be condensed into a simplified form.
* These are also "blueprints."
* At its most basic, this simplification will merely comprise the former object's `EquipmentDefinition`.
* For items that are already simple - `Kit` objects and `SimpleItem` objects - this form will not be too far removed.
* For more complicated affairs like `Tool` objects and `AmmoBox` objects, only essential information will be retained.<br>
* <br>
* The deconstructed blueprint can be applied to any avatar.
* They are, however, typically tied to unique users and unique characters.
* For reasons of certifications, however, permissions on that avatar may affect what `Equipment` can be distributed.
* Even a whole blueprint can be denied if the user lacks the necessary exo-suit certification.
* A completely new piece of `Equipment` is constructed when the `Loadout` is regurgitated.<br>
* <br>
* The fifth tab on an `order_terminal` window is for "Favorite" blueprints for `Loadout` entries.
* The ten-long list is initialized with `FavoritesMessage` packets.
* The lists of user-specific loadouts are initialized with `FavoritesMessage` packets.
* Specific entries are loaded or removed using `FavoritesRequest` packets.
* @param label the name by which this inventory will be known when displayed in a Favorites list
* @param visible_slots simplified representation of the `Equipment` that can see "seen" on the target
* @param inventory simplified representation of the `Equipment` in the target's inventory or trunk
* @param exosuit na
* @param subtype na
*/
final case class Loadout(private val label : String,
private val visible_slots : List[Loadout.SimplifiedEntry],
private val inventory : List[Loadout.SimplifiedEntry],
private val exosuit : ExoSuitType.Value,
private val subtype : Int) {
/**
* The label by which this `Loadout` is called.
* @return the label
*/
def Label : String = label
/**
* The exo-suit in which the avatar will be dressed.
* Might be restricted and, thus, restrict the rest of the `Equipment` from being constructed and given.
* @return the exo-suit
*/
def ExoSuit : ExoSuitType.Value = exosuit
/**
* The mechanized assault exo-suit specialization number that indicates whether the MAX performs:
* anti-infantry (1),
* anti-vehicular (2),
* or anti-air work (3).
* The major distinction is the type of arm weapons that MAX is equipped.
* When the blueprint doesn't call for a MAX, the number will be 0.
* @return the specialization number
*/
def Subtype : Int = subtype
/**
* The `Equipment` in the `Player`'s holster slots when this `Loadout` is created.
* @return a `List` of the holster item blueprints
*/
def VisibleSlots : List[Loadout.SimplifiedEntry] = visible_slots
/**
* The `Equipment` in the `Player`'s inventory region when this `Loadout` is created.
* @return a `List` of the inventory item blueprints
*/
def Inventory : List[Loadout.SimplifiedEntry] = inventory
}
abstract class Loadout(label : String,
visible_slots : List[Loadout.SimplifiedEntry],
inventory : List[Loadout.SimplifiedEntry])
object Loadout {
def apply(label : String, visible : List[SimplifiedEntry], inventory : List[SimplifiedEntry]) : Loadout = {
new Loadout(label, visible, inventory, ExoSuitType.Standard, 0)
}
/**
* Produce the blueprint on a player.
* @param player the player
* @param label the name of this loadout
* @return an `InfantryLoadout` object populated with appropriate information about the current state of the player
*/
def Create(player : Player, label : String) : Loadout = {
new Loadout(
InfantryLoadout(
label,
packageSimplifications(player.Holsters()),
packageSimplifications(player.Inventory.Items.values.toList),
@ -94,11 +45,18 @@ object Loadout {
)
}
/**
* Produce the blueprint of a vehicle.
* @param vehicle the vehicle
* @param label the name of this loadout
* @return a `VehicleLoadout` object populated with appropriate information about the current state of the vehicle
*/
def Create(vehicle : Vehicle, label : String) : Loadout = {
Loadout(
VehicleLoadout(
label,
packageSimplifications(vehicle.Weapons.map({ case ((index, weapon)) => InventoryItem(weapon.Equipment.get, index) }).toList),
packageSimplifications(vehicle.Trunk.Items.values.toList)
packageSimplifications(vehicle.Trunk.Items.values.toList),
vehicle.Definition
)
}
@ -152,35 +110,32 @@ object Loadout {
*/
final case class ShorthandKit(definition : KitDefinition) extends Simplification
/**
* The sub-type of the player's uniform.
* Applicable to mechanized assault units, mainly.
* The subtype is reported as a number but indicates the specialization - anti-infantry, ani-vehicular, anti-air - of the suit
* as indicated by the arm weapon(s).
* @param player the player
* @return the numeric subtype
*/
def DetermineSubtype(player : Player) : Int = {
if(player.ExoSuit == ExoSuitType.MAX) {
player.Slot(0).Equipment match {
case Some(item) =>
item.Definition match {
case GlobalDefinitions.trhev_dualcycler | GlobalDefinitions.nchev_scattercannon | GlobalDefinitions.vshev_quasar =>
1
case GlobalDefinitions.trhev_pounder | GlobalDefinitions.nchev_falcon | GlobalDefinitions.vshev_comet =>
2
case GlobalDefinitions.trhev_burster | GlobalDefinitions.nchev_sparrow | GlobalDefinitions.vshev_starfire =>
3
case _ =>
0
}
case None =>
0
}
}
else {
0
}
InfantryLoadout.DetermineSubtype(player)
}
/**
* The sub-type of the vehicle.
* Vehicle's have no subtype.
* @param vehicle the vehicle
* @return the numeric subtype, always 0
*/
def DetermineSubtype(vehicle : Vehicle) : Int = 0
/**
* Overloaded entry point for constructing simplified blueprints from holster slot equipment.
* @param equipment the holster slots
* @return a `List` of simplified `Equipment`
*/
private def packageSimplifications(equipment : Array[EquipmentSlot]) : List[SimplifiedEntry] = {
protected def packageSimplifications(equipment : Array[EquipmentSlot]) : List[SimplifiedEntry] = {
recursiveHolsterSimplifications(equipment.iterator)
}
@ -189,7 +144,7 @@ object Loadout {
* @param equipment the enumerated contents of the inventory
* @return a `List` of simplified `Equipment`
*/
private def packageSimplifications(equipment : List[InventoryItem]) : List[SimplifiedEntry] = {
protected def packageSimplifications(equipment : List[InventoryItem]) : List[SimplifiedEntry] = {
equipment.map(entry => { SimplifiedEntry(buildSimplification(entry.obj), entry.start) })
}

View file

@ -0,0 +1,27 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.loadouts
import net.psforever.objects.definition._
/**
* A blueprint of a vehicle's mounted weapons and its inventory items, saved in a specific state.
* This previous state can be restored on an apporpriate vehicle template
* by reconstructing the items (if permitted).
* Mismatched vehicles may produce no loadout or an imperfect loadout depending on specifications.<br>
* <br>
* The second tab on an `repair_silo` window is occupied by the list of "Favorite" `Loadout` blueprints.
* The five-long list is initialized with `FavoritesMessage` packets assigned to the "Vehicle" list.
* Specific entries are added or removed using `FavoritesRequest` packets,
* re-established using other conventional game packets.
* @param label the name by which this inventory will be known when displayed in a Favorites list;
* field gets inherited
* @param visible_slots simplified representation of the `Equipment` that can see "seen" on the target;
* field gets inherited
* @param inventory simplified representation of the `Equipment` in the target's inventory or trunk;
* field gets inherited
* @param vehicle_definition the original type of vehicle whose state is being populated
*/
final case class VehicleLoadout(label : String,
visible_slots : List[Loadout.SimplifiedEntry],
inventory : List[Loadout.SimplifiedEntry],
vehicle_definition : VehicleDefinition) extends Loadout(label, visible_slots, inventory)

View file

@ -4,6 +4,7 @@ package net.psforever.objects.serverobject.terminals
import net.psforever.objects._
import net.psforever.objects.definition._
import net.psforever.objects.equipment.Equipment
import net.psforever.objects.loadouts.Loadout
import net.psforever.packet.game.ItemTransactionMessage
import net.psforever.types.ExoSuitType
@ -336,7 +337,7 @@ object EquipmentTerminalDefinition {
* `TerminalDefinition.MakeKit`
*/
def BuildSimplifiedPattern(entry : Loadout.Simplification) : Equipment = {
import net.psforever.objects.Loadout._
import net.psforever.objects.loadouts.Loadout._
entry match {
case obj : ShorthandTool =>
val ammo : List[AmmoBoxDefinition] = obj.ammo.map(fmode => { fmode.ammo.definition })

View file

@ -1,18 +1,12 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.terminals
import net.psforever.objects.Player
import net.psforever.packet.game.ItemTransactionMessage
/**
* The definition for any `Terminal` that is of a type "medical_terminal".
* This includes the limited proximity-based functionality of the formal medical terminals
* and the actual proximity-based functionality of the cavern crystals.<br>
* <br>
* Do not confuse the "medical_terminal" category and the actual `medical_terminal` object (529).
* Objects created by this definition being linked by their use of `ProximityTerminalUseMessage` is more accurate.
* This includes the functionality of the formal medical terminals and some of the cavern crystals.
* Do not confuse the game's internal "medical_terminal" object category and the actual `medical_terminal` object (529).
*/
class MedicalTerminalDefinition(objectId : Int) extends TerminalDefinition(objectId) {
class MedicalTerminalDefinition(objectId : Int) extends TerminalDefinition(objectId) with ProximityDefinition {
Name = if(objectId == 38) {
"adv_med_terminal"
}
@ -31,6 +25,4 @@ class MedicalTerminalDefinition(objectId : Int) extends TerminalDefinition(objec
else {
throw new IllegalArgumentException("medical terminal must be either object id 38, 225, 226, 529, or 689")
}
def Buy(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = Terminal.NoDeal()
}

View file

@ -3,6 +3,7 @@ package net.psforever.objects.serverobject.terminals
import akka.actor.ActorContext
import net.psforever.objects.Player
import net.psforever.objects.loadouts.InfantryLoadout
import net.psforever.objects.inventory.InventoryItem
import net.psforever.objects.serverobject.structures.Amenity
import net.psforever.packet.game.ItemTransactionMessage
@ -41,7 +42,7 @@ class OrderTerminalABDefinition(object_id : Int) extends EquipmentTerminalDefini
override def Buy(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = buyFunc(player, msg)
/**
* Process a `TransactionType.InfantryLoadout` action by the user.
* Process a `TransactionType.Loadout` action by the user.
* `Loadout` objects are blueprints composed of exo-suit specifications and simplified `Equipment`-to-slot mappings.
* If a valid loadout is found, its data is transformed back into actual `Equipment` for return to the user.
* Loadouts that would suit the player into a mechanized assault exo-suit are not permitted.
@ -52,16 +53,16 @@ class OrderTerminalABDefinition(object_id : Int) extends EquipmentTerminalDefini
override def Loadout(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = {
if(msg.item_page == 4) { //Favorites tab
player.LoadLoadout(msg.unk1) match {
case Some(loadout) =>
if(loadout.ExoSuit != ExoSuitType.MAX) {
val holsters = loadout.VisibleSlots.map(entry => { InventoryItem(BuildSimplifiedPattern(entry.item), entry.index) })
val inventory = loadout.Inventory.map(entry => { InventoryItem(BuildSimplifiedPattern(entry.item), entry.index) })
Terminal.InfantryLoadout(loadout.ExoSuit, loadout.Subtype, holsters, inventory)
case Some(loadout : InfantryLoadout) =>
if(loadout.exosuit != ExoSuitType.MAX) {
val holsters = loadout.visible_slots.map(entry => { InventoryItem(BuildSimplifiedPattern(entry.item), entry.index) })
val inventory = loadout.inventory.map(entry => { InventoryItem(BuildSimplifiedPattern(entry.item), entry.index) })
Terminal.InfantryLoadout(loadout.exosuit, loadout.subtype, holsters, inventory)
}
else {
Terminal.NoDeal()
}
case None =>
case Some(_) | None =>
Terminal.NoDeal()
}
}

View file

@ -2,6 +2,7 @@
package net.psforever.objects.serverobject.terminals
import net.psforever.objects.Player
import net.psforever.objects.loadouts.InfantryLoadout
import net.psforever.objects.inventory.InventoryItem
import net.psforever.packet.game.ItemTransactionMessage
import net.psforever.objects.serverobject.terminals.EquipmentTerminalDefinition._
@ -27,7 +28,7 @@ class OrderTerminalDefinition extends EquipmentTerminalDefinition(612) {
override def Buy(player: Player, msg : ItemTransactionMessage) : Terminal.Exchange = buyFunc(player, msg)
/**
* Process a `TransactionType.InfantryLoadout` action by the user.
* Process a `TransactionType.Loadout` action by the user.
* `Loadout` objects are blueprints composed of exo-suit specifications and simplified `Equipment`-to-slot mappings.
* If a valid loadout is found, its data is transformed back into actual `Equipment` for return to the user.
* @param player the player
@ -37,11 +38,11 @@ class OrderTerminalDefinition extends EquipmentTerminalDefinition(612) {
override def Loadout(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = {
if(msg.item_page == 4) { //Favorites tab
player.LoadLoadout(msg.unk1) match {
case Some(loadout) =>
val holsters = loadout.VisibleSlots.map(entry => { InventoryItem(BuildSimplifiedPattern(entry.item), entry.index) })
val inventory = loadout.Inventory.map(entry => { InventoryItem(BuildSimplifiedPattern(entry.item), entry.index) })
Terminal.InfantryLoadout(loadout.ExoSuit, loadout.Subtype, holsters, inventory)
case None =>
case Some(loadout : InfantryLoadout) =>
val holsters = loadout.visible_slots.map(entry => { InventoryItem(BuildSimplifiedPattern(entry.item), entry.index) })
val inventory = loadout.inventory.map(entry => { InventoryItem(BuildSimplifiedPattern(entry.item), entry.index) })
Terminal.InfantryLoadout(loadout.exosuit, loadout.subtype, holsters, inventory)
case Some(_) | None =>
Terminal.NoDeal()
}
}

View file

@ -0,0 +1,15 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.terminals
import net.psforever.objects.Player
import net.psforever.packet.game.ItemTransactionMessage
/**
* The definition for any `Terminal` that possesses a proximity-based effect.
* This includes the limited proximity-based functionality of the formal medical terminals
* and the actual proximity-based functionality of the cavern crystals.
* Objects created by this definition being linked by their use of `ProximityTerminalUseMessage`.
*/
trait ProximityDefinition {
def Buy(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = Terminal.NoDeal()
}

View file

@ -1,8 +1,6 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.terminals
import net.psforever.packet.game.PlanetSideGUID
/**
* A server object that is a "terminal" that can be accessed for amenities and services,
* triggered when a certain distance from the unit itself (proximity-based).<br>
@ -11,28 +9,14 @@ import net.psforever.packet.game.PlanetSideGUID
* For example, the cavern crystals are considered owner-neutral elements that are not attached to a `Building` object.
* @param tdef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields
*/
class ProximityTerminal(tdef : MedicalTerminalDefinition) extends Terminal(tdef) {
private var users : Set[PlanetSideGUID] = Set.empty
def NumberUsers : Int = users.size
def AddUser(player_guid : PlanetSideGUID) : Int = {
users += player_guid
NumberUsers
}
def RemoveUser(player_guid : PlanetSideGUID) : Int = {
users -= player_guid
NumberUsers
}
}
class ProximityTerminal(tdef : TerminalDefinition with ProximityDefinition) extends Terminal(tdef) with ProximityUnit
object ProximityTerminal {
/**
* Overloaded constructor.
* @param tdef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields
*/
def apply(tdef : MedicalTerminalDefinition) : ProximityTerminal = {
def apply(tdef : TerminalDefinition with ProximityDefinition) : ProximityTerminal = {
new ProximityTerminal(tdef)
}
@ -45,7 +29,7 @@ object ProximityTerminal {
* @param context a context to allow the object to properly set up `ActorSystem` functionality
* @return the `Terminal` object
*/
def Constructor(tdef : MedicalTerminalDefinition)(id : Int, context : ActorContext) : Terminal = {
def Constructor(tdef : TerminalDefinition with ProximityDefinition)(id : Int, context : ActorContext) : Terminal = {
import akka.actor.Props
val obj = ProximityTerminal(tdef)
obj.Actor = context.actorOf(Props(classOf[ProximityTerminalControl], obj), s"${tdef.Name}_$id")

View file

@ -2,36 +2,24 @@
package net.psforever.objects.serverobject.terminals
import akka.actor.Actor
import net.psforever.objects.serverobject.CommonMessages
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
import net.psforever.objects.serverobject.terminals.Terminal.TerminalMessage
/**
*
* An `Actor` that handles messages being dispatched to a specific `ProximityTerminal`.
* Although this "terminal" itself does not accept the same messages as a normal `Terminal` object,
* it returns the same type of messages - wrapped in a `TerminalMessage` - to the `sender`.
* @param term the proximity unit (terminal)
*/
class ProximityTerminalControl(term : ProximityTerminal) extends Actor with FactionAffinityBehavior.Check {
class ProximityTerminalControl(term : Terminal with ProximityUnit) extends Actor with FactionAffinityBehavior.Check with ProximityUnit.Use {
def FactionObject : FactionAffinity = term
def receive : Receive = checkBehavior.orElse {
case CommonMessages.Use(player) =>
val hadNoUsers = term.NumberUsers == 0
if(term.AddUser(player.GUID) == 1 && hadNoUsers) {
sender ! TerminalMessage(player, null, Terminal.StartProximityEffect(term))
}
def TerminalObject : Terminal with ProximityUnit = term
case CommonMessages.Unuse(player) =>
val hadUsers = term.NumberUsers > 0
if(term.RemoveUser(player.GUID) == 0 && hadUsers) {
sender ! TerminalMessage(player, null, Terminal.StopProximityEffect(term))
}
case _ =>
sender ! Terminal.NoDeal()
}
def receive : Receive = checkBehavior
.orElse(proximityBehavior)
.orElse {
case _ => ;
}
override def toString : String = term.Definition.Name
}

View file

@ -0,0 +1,60 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.terminals
import net.psforever.objects.serverobject.CommonMessages
import net.psforever.objects.serverobject.terminals.Terminal.TerminalMessage
import net.psforever.packet.game.PlanetSideGUID
/**
* A server object that provides a service, triggered when a certain distance from the unit itself (proximity-based).
* Unlike conventional terminals, this one is not necessarily structure-owned.
* For example, the cavern crystals are considered owner-neutral elements that are not attached to a `Building` object.
*/
trait ProximityUnit {
this : Terminal =>
/**
* A list of targets that are currently affected by this proximity unit.
*/
private var targets : Set[PlanetSideGUID] = Set.empty
def NumberUsers : Int = targets.size
def AddUser(player_guid : PlanetSideGUID) : Int = {
targets += player_guid
NumberUsers
}
def RemoveUser(player_guid : PlanetSideGUID) : Int = {
targets -= player_guid
NumberUsers
}
}
object ProximityUnit {
import akka.actor.Actor
/**
* A mixin `trait` for an `Actor`'s `PartialFunction` that handles messages,
* in this case handling messages that controls the telegraphed state of the `ProximityUnit` object as the number of users changes.
*/
trait Use {
this : Actor =>
def TerminalObject : Terminal with ProximityUnit
val proximityBehavior : Receive = {
case CommonMessages.Use(player) =>
val hadNoUsers = TerminalObject.NumberUsers == 0
if(TerminalObject.AddUser(player.GUID) == 1 && hadNoUsers) {
sender ! TerminalMessage(player, null, Terminal.StartProximityEffect(TerminalObject))
}
case CommonMessages.Unuse(player) =>
val hadUsers = TerminalObject.NumberUsers > 0
if(TerminalObject.RemoveUser(player.GUID) == 0 && hadUsers) {
sender ! TerminalMessage(player, null, Terminal.StopProximityEffect(TerminalObject))
}
}
}
}

View file

@ -0,0 +1,44 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.terminals
import net.psforever.objects.Player
import net.psforever.objects.inventory.InventoryItem
import net.psforever.objects.loadouts.VehicleLoadout
import net.psforever.objects.serverobject.terminals.EquipmentTerminalDefinition.BuildSimplifiedPattern
import net.psforever.packet.game.ItemTransactionMessage
/**
* The `Definition` for any `Terminal` that is of a type "repair_silo."
* Has both proximity-based operation and direct access purchasing power.
*/
class RepairRearmSiloDefinition(objectId : Int) extends EquipmentTerminalDefinition(objectId) with ProximityDefinition {
Name = if(objectId == 719) {
"pad_landing"
}
else if(objectId == 729) {
"repair_silo"
}
else {
throw new IllegalArgumentException("repair re-arm terminal must be either object id 719 or 729")
}
private val buyFunc : (Player, ItemTransactionMessage)=>Terminal.Exchange = EquipmentTerminalDefinition.Buy(Map.empty, Map.empty, Map.empty)
override def Buy(player: Player, msg : ItemTransactionMessage) : Terminal.Exchange = buyFunc(player, msg)
override def Loadout(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = {
if(msg.item_page == 4) { //Favorites tab
player.LoadLoadout(msg.unk1 + 10) match {
case Some(loadout : VehicleLoadout) =>
val weapons = loadout.visible_slots.map(entry => { InventoryItem(BuildSimplifiedPattern(entry.item), entry.index) })
val inventory = loadout.inventory.map(entry => { InventoryItem(BuildSimplifiedPattern(entry.item), entry.index) })
Terminal.VehicleLoadout(loadout.vehicle_definition, weapons, inventory)
case _ =>
Terminal.NoDeal()
}
}
else {
Terminal.NoDeal()
}
}
}

View file

@ -2,6 +2,7 @@
package net.psforever.objects.serverobject.terminals
import net.psforever.objects.Player
import net.psforever.objects.definition.VehicleDefinition
import net.psforever.objects.serverobject.structures.Amenity
import net.psforever.packet.game.{ItemTransactionMessage, PlanetSideGUID}
import net.psforever.types.{TransactionType, Vector3}
@ -73,7 +74,7 @@ class Terminal(tdef : TerminalDefinition) extends Amenity {
case TransactionType.Sell =>
tdef.Sell(player, msg)
case TransactionType.InfantryLoadout =>
case TransactionType.Loadout =>
tdef.Loadout(player, msg)
case _ =>
@ -190,17 +191,19 @@ object Terminal {
*/
final case class InfantryLoadout(exosuit : ExoSuitType.Value, subtype : Int = 0, holsters : List[InventoryItem], inventory : List[InventoryItem]) extends Exchange
final case class VehicleLoadout(vehicle_definition : VehicleDefinition, weapons : List[InventoryItem], inventory : List[InventoryItem]) extends Exchange
/**
* Start the special effects caused by a proximity-base service.
* @param terminal the proximity-based unit
*/
final case class StartProximityEffect(terminal : ProximityTerminal) extends Exchange
final case class StartProximityEffect(terminal : Terminal with ProximityUnit) extends Exchange
/**
* Stop the special effects caused by a proximity-base service.
* @param terminal the proximity-based unit
*/
final case class StopProximityEffect(terminal : ProximityTerminal) extends Exchange
final case class StopProximityEffect(terminal : Terminal with ProximityUnit) extends Exchange
/**
* Overloaded constructor.

View file

@ -15,8 +15,7 @@ class TerminalControl(term : Terminal) extends Actor with FactionAffinityBehavio
case Terminal.Request(player, msg) =>
sender ! Terminal.TerminalMessage(player, msg, term.Request(player, msg))
case _ =>
sender ! Terminal.NoDeal()
case _ => ;
}
override def toString : String = term.Definition.Name

View file

@ -24,7 +24,7 @@ abstract class TerminalDefinition(objectId : Int) extends net.psforever.objects.
def Sell(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = Terminal.NoDeal()
/**
* The unimplemented functionality for this `Terminal`'s `TransactionType.InfantryLoadout` activity.
* The unimplemented functionality for this `Terminal`'s `TransactionType.Loadout` activity.
*/
def Loadout(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = Terminal.NoDeal()
}

View file

@ -3,6 +3,7 @@ package net.psforever.objects.serverobject.terminals
import net.psforever.objects.definition.VehicleDefinition
import net.psforever.objects.{Player, Vehicle}
import net.psforever.objects.loadouts.VehicleLoadout
import net.psforever.objects.inventory.InventoryItem
import net.psforever.packet.game.ItemTransactionMessage
@ -83,7 +84,7 @@ abstract class VehicleTerminalDefinition(objId : Int) extends TerminalDefinition
// "aphelion_flight" -> (()=>Unit)
)
import net.psforever.objects.{Loadout => _Loadout} //distinguish from Terminal.Loadout message
import net.psforever.objects.loadouts.{Loadout => _Loadout} //distinguish from Terminal.Loadout message
import _Loadout._
/**
* A `Map` of the default contents of a `Vehicle` inventory, called the trunk.
@ -101,29 +102,31 @@ abstract class VehicleTerminalDefinition(objId : Int) extends TerminalDefinition
val ammo_flux = ShorthandAmmoBox(flux_cannon_thresher_battery, flux_cannon_thresher_battery.Capacity)
val ammo_bomb = ShorthandAmmoBox(liberator_bomb, liberator_bomb.Capacity)
Map(
//"quadstealth" -> _Loadout("default_quadstealth", List(), List()),
"quadassault" -> _Loadout("default_quadassault", List(),
//"quadstealth" -> VehicleLoadout("default_quadstealth", List(), List(), quadstealth),
"quadassault" -> VehicleLoadout("default_quadassault", List(),
List(
SimplifiedEntry(ammo_12mm, 30),
SimplifiedEntry(ammo_12mm, 34),
SimplifiedEntry(ammo_12mm, 74),
SimplifiedEntry(ammo_12mm, 78)
)
),
quadassault
),
{
val ammo = ShorthandAmmoBox(hellfire_ammo, hellfire_ammo.Capacity)
"fury" -> _Loadout("default_fury", List(),
"fury" -> VehicleLoadout("default_fury", List(),
List(
SimplifiedEntry(ammo, 30),
SimplifiedEntry(ammo, 34),
SimplifiedEntry(ammo, 74),
SimplifiedEntry(ammo, 78)
)
),
fury
)
},
//"ant" -> _Loadout("default_ant", List(), List()),
//"ams" -> _Loadout("default_ams", List(), List()),
"two_man_assault_buggy" -> _Loadout("default_two_man_assault_buggy", List(),
//"ant" -> VehicleLoadout("default_ant", List(), List(), ant),
//"ams" -> VehicleLoadout("default_ams", List(), List(), ams),
"two_man_assault_buggy" -> VehicleLoadout("default_two_man_assault_buggy", List(),
List(
SimplifiedEntry(ammo_12mm, 30),
SimplifiedEntry(ammo_12mm, 34),
@ -131,11 +134,12 @@ abstract class VehicleTerminalDefinition(objId : Int) extends TerminalDefinition
SimplifiedEntry(ammo_12mm, 90),
SimplifiedEntry(ammo_12mm, 94),
SimplifiedEntry(ammo_12mm, 98)
)
),
two_man_assault_buggy
),
{
val ammo = ShorthandAmmoBox(skyguard_flak_cannon_ammo, skyguard_flak_cannon_ammo.Capacity)
"skyguard" -> _Loadout("default_skyguard", List(),
"skyguard" -> VehicleLoadout("default_skyguard", List(),
List(
SimplifiedEntry(ammo_12mm, 30),
SimplifiedEntry(ammo_12mm, 34),
@ -143,10 +147,11 @@ abstract class VehicleTerminalDefinition(objId : Int) extends TerminalDefinition
SimplifiedEntry(ammo, 90),
SimplifiedEntry(ammo, 94),
SimplifiedEntry(ammo, 98)
)
),
skyguard
)
},
"threemanheavybuggy" -> _Loadout("default_threemanheavybuggy", List(),
"threemanheavybuggy" -> VehicleLoadout("default_threemanheavybuggy", List(),
List(
SimplifiedEntry(ammo_12mm, 30),
SimplifiedEntry(ammo_12mm, 34),
@ -154,11 +159,12 @@ abstract class VehicleTerminalDefinition(objId : Int) extends TerminalDefinition
SimplifiedEntry(ammo_mortar, 90),
SimplifiedEntry(ammo_mortar, 94),
SimplifiedEntry(ammo_mortar, 98)
)
),
threemanheavybuggy
),
{
val ammo = ShorthandAmmoBox(firebird_missile, firebird_missile.Capacity)
"twomanheavybuggy" -> _Loadout("default_twomanheavybuggy", List(),
"twomanheavybuggy" -> VehicleLoadout("default_twomanheavybuggy", List(),
List(
SimplifiedEntry(ammo, 30),
SimplifiedEntry(ammo, 34),
@ -166,10 +172,11 @@ abstract class VehicleTerminalDefinition(objId : Int) extends TerminalDefinition
SimplifiedEntry(ammo, 90),
SimplifiedEntry(ammo, 94),
SimplifiedEntry(ammo, 98)
)
),
twomanheavybuggy
)
},
"twomanhoverbuggy" -> _Loadout("default_twomanhoverbuggy", List(),
"twomanhoverbuggy" -> VehicleLoadout("default_twomanhoverbuggy", List(),
List(
SimplifiedEntry(ammo_flux, 30),
SimplifiedEntry(ammo_flux, 34),
@ -177,9 +184,10 @@ abstract class VehicleTerminalDefinition(objId : Int) extends TerminalDefinition
SimplifiedEntry(ammo_flux, 90),
SimplifiedEntry(ammo_flux, 94),
SimplifiedEntry(ammo_flux, 98)
)
),
twomanhoverbuggy
),
"mediumtransport" -> _Loadout("default_mediumtransport", List(),
"mediumtransport" -> VehicleLoadout("default_mediumtransport", List(),
List(
SimplifiedEntry(ammo_20mm, 30),
SimplifiedEntry(ammo_20mm, 34),
@ -190,9 +198,10 @@ abstract class VehicleTerminalDefinition(objId : Int) extends TerminalDefinition
SimplifiedEntry(ammo_20mm, 150),
SimplifiedEntry(ammo_20mm, 154),
SimplifiedEntry(ammo_20mm, 158)
)
),
mediumtransport
),
"battlewagon" -> _Loadout("default_battlewagon", List(),
"battlewagon" -> VehicleLoadout("default_battlewagon", List(),
List(
SimplifiedEntry(ammo_15mm, 30),
SimplifiedEntry(ammo_15mm, 34),
@ -203,11 +212,12 @@ abstract class VehicleTerminalDefinition(objId : Int) extends TerminalDefinition
SimplifiedEntry(ammo_15mm, 150),
SimplifiedEntry(ammo_15mm, 154),
SimplifiedEntry(ammo_15mm, 158)
)
),
battlewagon
),
{
val ammo = ShorthandAmmoBox(gauss_cannon_ammo, gauss_cannon_ammo.Capacity)
"thunderer" -> _Loadout("default_thunderer", List(),
"thunderer" -> VehicleLoadout("default_thunderer", List(),
List(
SimplifiedEntry(ammo, 30),
SimplifiedEntry(ammo, 34),
@ -218,12 +228,13 @@ abstract class VehicleTerminalDefinition(objId : Int) extends TerminalDefinition
SimplifiedEntry(ammo, 150),
SimplifiedEntry(ammo, 154),
SimplifiedEntry(ammo, 158)
)
),
thunderer
)
},
{
val ammo = ShorthandAmmoBox(fluxpod_ammo, fluxpod_ammo.Capacity)
"aurora" -> _Loadout("default_aurora", List(),
"aurora" -> VehicleLoadout("default_aurora", List(),
List(
SimplifiedEntry(ammo, 30),
SimplifiedEntry(ammo, 34),
@ -234,10 +245,11 @@ abstract class VehicleTerminalDefinition(objId : Int) extends TerminalDefinition
SimplifiedEntry(ammo, 150),
SimplifiedEntry(ammo, 154),
SimplifiedEntry(ammo, 158)
)
),
aurora
)
},
"apc_tr" -> _Loadout("default_apc_tr", List(),
"apc_tr" -> VehicleLoadout("default_apc_tr", List(),
List(
SimplifiedEntry(ammo_75mm, 30),
SimplifiedEntry(ammo_75mm, 34),
@ -259,9 +271,10 @@ abstract class VehicleTerminalDefinition(objId : Int) extends TerminalDefinition
SimplifiedEntry(ammo_15mm, 278),
SimplifiedEntry(ammo_15mm, 282),
SimplifiedEntry(ammo_15mm, 286)
)
),
apc_tr
),
"apc_nc" -> _Loadout("default_apc_nc", List(),
"apc_nc" -> VehicleLoadout("default_apc_nc", List(),
List(
SimplifiedEntry(ammo_75mm, 30),
SimplifiedEntry(ammo_75mm, 34),
@ -283,9 +296,10 @@ abstract class VehicleTerminalDefinition(objId : Int) extends TerminalDefinition
SimplifiedEntry(ammo_20mm, 278),
SimplifiedEntry(ammo_20mm, 282),
SimplifiedEntry(ammo_20mm, 286)
)
),
apc_nc
),
"apc_vs" -> _Loadout("default_apc_vs", List(),
"apc_vs" -> VehicleLoadout("default_apc_vs", List(),
List(
SimplifiedEntry(ammo_75mm, 30),
SimplifiedEntry(ammo_75mm, 34),
@ -307,9 +321,10 @@ abstract class VehicleTerminalDefinition(objId : Int) extends TerminalDefinition
SimplifiedEntry(ammo_flux, 278),
SimplifiedEntry(ammo_flux, 282),
SimplifiedEntry(ammo_flux, 286)
)
),
apc_vs
),
"lightning" -> _Loadout("default_lightning", List(),
"lightning" -> VehicleLoadout("default_lightning", List(),
List(
SimplifiedEntry(ammo_25mm, 30),
SimplifiedEntry(ammo_25mm, 34),
@ -317,11 +332,12 @@ abstract class VehicleTerminalDefinition(objId : Int) extends TerminalDefinition
SimplifiedEntry(ammo_75mm, 90),
SimplifiedEntry(ammo_75mm, 94),
SimplifiedEntry(ammo_75mm, 98)
)
),
lightning
),
{
val ammo = ShorthandAmmoBox(bullet_105mm, bullet_105mm.Capacity)
"prowler" -> _Loadout("default_prowler", List(),
"prowler" -> VehicleLoadout("default_prowler", List(),
List(
SimplifiedEntry(ammo_15mm, 30),
SimplifiedEntry(ammo_15mm, 34),
@ -329,12 +345,13 @@ abstract class VehicleTerminalDefinition(objId : Int) extends TerminalDefinition
SimplifiedEntry(ammo, 90),
SimplifiedEntry(ammo, 94),
SimplifiedEntry(ammo, 98)
)
),
prowler
)
},
{
val ammo = ShorthandAmmoBox(bullet_150mm, bullet_150mm.Capacity)
"vanguard" -> _Loadout("default_vanguard", List(),
"vanguard" -> VehicleLoadout("default_vanguard", List(),
List(
SimplifiedEntry(ammo_20mm, 30),
SimplifiedEntry(ammo_20mm, 34),
@ -342,13 +359,14 @@ abstract class VehicleTerminalDefinition(objId : Int) extends TerminalDefinition
SimplifiedEntry(ammo, 90),
SimplifiedEntry(ammo, 94),
SimplifiedEntry(ammo, 98)
)
),
vanguard
)
},
{
val ammo1 = ShorthandAmmoBox(pulse_battery, pulse_battery.Capacity)
val ammo2 = ShorthandAmmoBox(heavy_rail_beam_battery, heavy_rail_beam_battery.Capacity)
"magrider" -> _Loadout("default_magrider", List(),
"magrider" -> VehicleLoadout("default_magrider", List(),
List(
SimplifiedEntry(ammo1, 30),
SimplifiedEntry(ammo1, 34),
@ -356,23 +374,25 @@ abstract class VehicleTerminalDefinition(objId : Int) extends TerminalDefinition
SimplifiedEntry(ammo2, 90),
SimplifiedEntry(ammo2, 94),
SimplifiedEntry(ammo2, 98)
)
),
magrider
)
},
//"flail" -> _Loadout("default_flail", List(), List()),
//"switchblade" -> _Loadout("default_switchblade", List(), List()),
//"router" -> _Loadout("default_router", List(), List()),
"mosquito" -> _Loadout("default_mosquito", List(),
//"flail" -> VehicleLoadout("default_flail", List(), List(), flail),
//"switchblade" -> VehicleLoadout("default_switchblade", List(), List(), switchblade),
//"router" -> VehicleLoadout("default_router", List(), List(), router),
"mosquito" -> VehicleLoadout("default_mosquito", List(),
List(
SimplifiedEntry(ammo_12mm, 30),
SimplifiedEntry(ammo_12mm, 34),
SimplifiedEntry(ammo_12mm, 74),
SimplifiedEntry(ammo_12mm, 78)
)
),
mosquito
),
{
val ammo = ShorthandAmmoBox(reaver_rocket, reaver_rocket.Capacity)
"lightgunship" -> _Loadout("default_lightgunship", List(),
"lightgunship" -> VehicleLoadout("default_lightgunship", List(),
List(
SimplifiedEntry(ammo, 30),
SimplifiedEntry(ammo, 34),
@ -380,22 +400,24 @@ abstract class VehicleTerminalDefinition(objId : Int) extends TerminalDefinition
SimplifiedEntry(ammo, 90),
SimplifiedEntry(ammo_20mm, 94),
SimplifiedEntry(ammo_20mm, 98)
)
),
lightgunship
)
},
{
val ammo1 = ShorthandAmmoBox(wasp_rocket_ammo, wasp_rocket_ammo.Capacity)
val ammo2 = ShorthandAmmoBox(wasp_gun_ammo, wasp_gun_ammo.Capacity)
"wasp" -> _Loadout("default_wasp", List(),
"wasp" -> VehicleLoadout("default_wasp", List(),
List(
SimplifiedEntry(ammo1, 30),
SimplifiedEntry(ammo1, 34),
SimplifiedEntry(ammo2, 74),
SimplifiedEntry(ammo2, 78)
)
),
wasp
)
},
"liberator" -> _Loadout("default_liberator", List(),
"liberator" -> VehicleLoadout("default_liberator", List(),
List(
SimplifiedEntry(ammo_35mm, 30),
SimplifiedEntry(ammo_35mm, 34),
@ -406,9 +428,10 @@ abstract class VehicleTerminalDefinition(objId : Int) extends TerminalDefinition
SimplifiedEntry(ammo_bomb, 150),
SimplifiedEntry(ammo_bomb, 154),
SimplifiedEntry(ammo_bomb, 158)
)
),
liberator
),
"vulture" -> _Loadout("default_vulture", List(),
"vulture" -> VehicleLoadout("default_vulture", List(),
List(
SimplifiedEntry(ammo_35mm, 30),
SimplifiedEntry(ammo_35mm, 34),
@ -418,9 +441,10 @@ abstract class VehicleTerminalDefinition(objId : Int) extends TerminalDefinition
SimplifiedEntry(ammo_bomb, 98),
SimplifiedEntry(ammo_bomb, 102),
SimplifiedEntry(ammo_bomb, 106)
) //TODO confirm
), //TODO confirm
vulture
),
"dropship" -> _Loadout("default_dropship", List(),
"dropship" -> VehicleLoadout("default_dropship", List(),
List(
SimplifiedEntry(ammo_20mm, 30),
SimplifiedEntry(ammo_20mm, 34),
@ -434,9 +458,10 @@ abstract class VehicleTerminalDefinition(objId : Int) extends TerminalDefinition
SimplifiedEntry(ammo_20mm, 162),
SimplifiedEntry(ammo_20mm, 166),
SimplifiedEntry(ammo_20mm, 170)
)
),
dropship
),
"galaxy_gunship" -> _Loadout("galaxy_gunship", List(),
"galaxy_gunship" -> VehicleLoadout("galaxy_gunship", List(),
List(
SimplifiedEntry(ammo_35mm, 30),
SimplifiedEntry(ammo_35mm, 34),
@ -450,10 +475,11 @@ abstract class VehicleTerminalDefinition(objId : Int) extends TerminalDefinition
SimplifiedEntry(ammo_mortar, 178),
SimplifiedEntry(ammo_mortar, 182),
SimplifiedEntry(ammo_mortar, 186)
)
),
galaxy_gunship
)
//"phantasm" -> _Loadout("default_phantasm", List(), List()),
//"lodestar" -> _Loadout("default_lodestar", List(), List()),
//"phantasm" -> VehicleLoadout("default_phantasm", List(), List(), phantasm),
//"lodestar" -> VehicleLoadout("default_lodestar", List(), List(), lodestar),
)
}
@ -461,12 +487,12 @@ abstract class VehicleTerminalDefinition(objId : Int) extends TerminalDefinition
vehicles.get(msg.item_name) match {
case Some(vehicle) =>
val (weapons, inventory) = trunk.get(msg.item_name) match {
case Some(loadout) =>
case Some(loadout : VehicleLoadout) =>
(
loadout.VisibleSlots.map(entry => { InventoryItem(EquipmentTerminalDefinition.BuildSimplifiedPattern(entry.item), entry.index) }),
loadout.Inventory.map(entry => { InventoryItem(EquipmentTerminalDefinition.BuildSimplifiedPattern(entry.item), entry.index) })
loadout.visible_slots.map(entry => { InventoryItem(EquipmentTerminalDefinition.BuildSimplifiedPattern(entry.item), entry.index) }),
loadout.inventory.map(entry => { InventoryItem(EquipmentTerminalDefinition.BuildSimplifiedPattern(entry.item), entry.index) })
)
case None =>
case _ =>
(List.empty, List.empty)
}
Terminal.BuyVehicle(vehicle(), weapons, inventory)

View file

@ -312,7 +312,7 @@ object GamePacketOpcode extends Enumeration {
= Value
private def noDecoder(opcode : GamePacketOpcode.Type) = (a : BitVector) =>
Attempt.failure(Err(s"Could not find a marshaller for game packet ${opcode}"))
Attempt.failure(Err(s"Could not find a marshaller for game packet $opcode"))
/// Mapping of packet IDs to decoders. Notice that we are using the @switch annotation which ensures that the Scala
/// compiler will be able to optimize this as a lookup table (switch statement). Microbenchmarks show a nearly 400x
@ -549,7 +549,7 @@ object GamePacketOpcode extends Enumeration {
// OPCODES 0xc0-cf
case 0xc0 => noDecoder(CaptureFlagUpdateMessage)
case 0xc1 => noDecoder(VanuModuleUpdateMessage)
case 0xc2 => noDecoder(FacilityBenefitShieldChargeRequestMessage)
case 0xc2 => game.FacilityBenefitShieldChargeRequestMessage.decode
case 0xc3 => game.ProximityTerminalUseMessage.decode
case 0xc4 => game.QuantityDeltaUpdateMessage.decode
case 0xc5 => noDecoder(ChainLashMessage)
@ -608,7 +608,7 @@ object GamePacketOpcode extends Enumeration {
case 0xf1 => game.MailMessage.decode
case 0xf2 => noDecoder(GameVarUpdate)
case 0xf3 => noDecoder(ClientCheatedMessage)
case default => noDecoder(opcode)
case _ => noDecoder(opcode)
}
implicit val codec: Codec[this.Value] = PacketHelpers.createEnumerationCodec(this, uint8L)

View file

@ -0,0 +1,24 @@
// Copyright (c) 2017 PSForever
package net.psforever.packet.game
import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket}
import scodec.Codec
import scodec.codecs._
/**
* Dispatched by the client when driving a vehicle in the sphere of influence of an allied base
* that is an amp station facility or that possesses the lattice-connected benefit of an amp station.
* The vehicle that is being driven will not have perfect fully-charged shields at the time.
* @param vehicle_guid the vehicle whose shield is being charged
*/
final case class FacilityBenefitShieldChargeRequestMessage(vehicle_guid : PlanetSideGUID)
extends PlanetSideGamePacket {
type Packet = FacilityBenefitShieldChargeRequestMessage
def opcode = GamePacketOpcode.FacilityBenefitShieldChargeRequestMessage
def encode = FacilityBenefitShieldChargeRequestMessage.encode(this)
}
object FacilityBenefitShieldChargeRequestMessage extends Marshallable[FacilityBenefitShieldChargeRequestMessage] {
implicit val codec : Codec[FacilityBenefitShieldChargeRequestMessage] =
("vehicle_guid" | PlanetSideGUID.codec).as[FacilityBenefitShieldChargeRequestMessage]
}

View file

@ -2,6 +2,7 @@
package net.psforever.packet.game
import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
import net.psforever.types.LoadoutType
import scodec.Codec
import scodec.codecs._
import shapeless.{::, HNil}
@ -18,12 +19,6 @@ import shapeless.{::, HNil}
* Infantry equipment favorites are appended with a code for the type of exo-suit that they will load on a player.
* This does not match the same two field numbering system as in `ArmorChangedMessage` packets.<br>
* <br>
* Lists:<br>
* `
* 0 - Equipment Terminal (infantry)<br>
* 1 - Repair/Rearm Silo (standard vehicles)<br>
* `
* <br>
* Armors:<br>
* `
* 1 - Agile<br>
@ -33,13 +28,7 @@ import shapeless.{::, HNil}
* 6 - AV MAX<br>
* `
* <br>
* Exploration 1:<br>
* The identifier for the list is two bits so four separated lists of `Favorites` are supportable.
* Two of the lists are common enough and we can assume one of the others is related to Battleframe Robotics.
* These lists also do not include `Squad Defintion...` presets.
* What are the unknown lists?<br>
* <br>
* Exploration 2:<br>
* Exploration:<br>
* There are three unaccounted exo-suit indices - 0, 3, and 7;
* and, there are two specific kinds of exo-suit that are not defined - Infiltration and Standard.
* It is possible that one of the indices also defines the generic MAX (see `ArmorChangedMessage`).
@ -50,11 +39,11 @@ import shapeless.{::, HNil}
* @param label the identifier for this entry
* @param armor the type of exo-suit, if an Infantry loadout
*/
final case class FavoritesMessage(list : Int,
final case class FavoritesMessage(list : LoadoutType.Value,
player_guid : PlanetSideGUID,
line : Int,
label : String,
armor : Option[Int] = None)
armor : Option[Int])
extends PlanetSideGamePacket {
type Packet = FavoritesMessage
def opcode = GamePacketOpcode.FavoritesMessage
@ -62,12 +51,36 @@ final case class FavoritesMessage(list : Int,
}
object FavoritesMessage extends Marshallable[FavoritesMessage] {
implicit val codec : Codec[FavoritesMessage] = (
("list" | uint2L) >>:~ { value =>
/**
* Overloaded constructor, for infantry loadouts specifically.
* @param list the destination list
* @param player_guid the player
* @param line the zero-indexed line number of this entry in its list
* @param label the identifier for this entry
* @param armor the type of exo-suit, if an Infantry loadout
* @return a `FavoritesMessage` object
*/
def apply(list : LoadoutType.Value, player_guid : PlanetSideGUID, line : Int, label : String, armor : Int) : FavoritesMessage = {
FavoritesMessage(list, player_guid, line, label, Some(armor))
}
/**
* Overloaded constructor, for vehicle loadouts specifically.
* @param list the destination list
* @param player_guid the player
* @param line the zero-indexed line number of this entry in its list
* @param label the identifier for this entry
* @return a `FavoritesMessage` object
*/
def apply(list : LoadoutType.Value, player_guid : PlanetSideGUID, line : Int, label : String) : FavoritesMessage = {
FavoritesMessage(list, player_guid, line, label, None)
}
implicit val codec : Codec[FavoritesMessage] = (
("list" | LoadoutType.codec) >>:~ { value =>
("player_guid" | PlanetSideGUID.codec) ::
("line" | uint4L) ::
("label" | PacketHelpers.encodedWideStringAligned(2)) ::
conditional(value == 0, "armor" | uintL(3))
conditional(value == LoadoutType.Infantry, "armor" | uintL(3))
}).xmap[FavoritesMessage] (
{
case lst :: guid :: ln :: str :: arm :: HNil =>
@ -75,7 +88,7 @@ object FavoritesMessage extends Marshallable[FavoritesMessage] {
},
{
case FavoritesMessage(lst, guid, ln, str, arm) =>
val armset : Option[Int] = if(lst == 0 && arm.isEmpty) { Some(0) } else { arm }
val armset : Option[Int] = if(lst == LoadoutType.Infantry && arm.isEmpty) { Some(0) } else { arm }
lst :: guid :: ln :: str :: armset :: HNil
}
)

View file

@ -2,21 +2,33 @@
package net.psforever.packet.game
import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
import net.psforever.types.LoadoutType
import scodec.Codec
import scodec.codecs._
object FavoritesAction extends Enumeration {
type Type = Value
val Unknown,
Save,
Delete = Value
val
Unknown,
Save,
Delete
= Value
implicit val codec = PacketHelpers.createEnumerationCodec(this, uint2L)
}
/**
* na
* @param player_guid the player
* @param list na
* @param action the behavior of this packet
* @param line what line of the applicable loadout ("Saved Favorites") list is modified
* @param label applicable when a load out is being saved;
* this is the string that will be displayed in the list of loadouts on that line
*/
final case class FavoritesRequest(player_guid : PlanetSideGUID,
unk : Int,
list : LoadoutType.Value,
action : FavoritesAction.Value,
line : Int,
label : Option[String])
@ -29,7 +41,7 @@ final case class FavoritesRequest(player_guid : PlanetSideGUID,
object FavoritesRequest extends Marshallable[FavoritesRequest] {
implicit val codec : Codec[FavoritesRequest] = (
("player_guid" | PlanetSideGUID.codec) ::
("unk" | uint2L) ::
("list" | LoadoutType.codec) ::
(("action" | FavoritesAction.codec) >>:~ { action =>
("line" | uint4L) ::
conditional(action == FavoritesAction.Save, "label" | PacketHelpers.encodedWideString)

View file

@ -0,0 +1,16 @@
// Copyright (c) 2017 PSForever
package net.psforever.types
import net.psforever.packet.PacketHelpers
import scodec.codecs.uint2L
object LoadoutType extends Enumeration {
type Type = Value
val
Infantry,
Vehicle
= Value
implicit val codec = PacketHelpers.createEnumerationCodec(this, uint2L)
}

View file

@ -12,7 +12,7 @@ object TransactionType extends Enumeration {
Sell, // or forget on certif term
Unk4,
Unk5,
InfantryLoadout,
Loadout,
Unk7
= Value

View file

@ -0,0 +1,28 @@
// Copyright (c) 2017 PSForever
package game
import org.specs2.mutable._
import net.psforever.packet._
import net.psforever.packet.game._
import scodec.bits._
class FacilityBenefitShieldChargeRequestMessageTest extends Specification {
val string = hex"C2 4C00"
"decode" in {
PacketCoding.DecodePacket(string).require match {
case FacilityBenefitShieldChargeRequestMessage(guid) =>
guid mustEqual PlanetSideGUID(76)
case _ =>
ko
}
}
"encode" in {
val msg = FacilityBenefitShieldChargeRequestMessage(PlanetSideGUID(76))
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string
}
}

View file

@ -4,6 +4,7 @@ package game
import org.specs2.mutable._
import net.psforever.packet._
import net.psforever.packet.game._
import net.psforever.types.LoadoutType
import scodec.bits._
class FavoritesMessageTest extends Specification {
@ -13,7 +14,7 @@ class FavoritesMessageTest extends Specification {
"decode (for infantry)" in {
PacketCoding.DecodePacket(stringInfantry).require match {
case FavoritesMessage(list, player_guid, line, label, armor) =>
list mustEqual 0
list mustEqual LoadoutType.Infantry
player_guid mustEqual PlanetSideGUID(3760)
line mustEqual 0
label mustEqual "Agile (basic)"
@ -25,7 +26,7 @@ class FavoritesMessageTest extends Specification {
}
"encode (for infantry)" in {
val msg = FavoritesMessage(0, PlanetSideGUID(3760), 0, "Agile (basic)", Option(1))
val msg = FavoritesMessage(LoadoutType.Infantry, PlanetSideGUID(3760), 0, "Agile (basic)", 1)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual stringInfantry
@ -34,7 +35,7 @@ class FavoritesMessageTest extends Specification {
"decode (for vehicles)" in {
PacketCoding.DecodePacket(stringVehicles).require match {
case FavoritesMessage(list, player_guid, line, label, armor) =>
list mustEqual 1
list mustEqual LoadoutType.Vehicle
player_guid mustEqual PlanetSideGUID(4210)
line mustEqual 0
label mustEqual "Skyguard"
@ -45,7 +46,7 @@ class FavoritesMessageTest extends Specification {
}
"encode (for vehicles)" in {
val msg = FavoritesMessage(1, PlanetSideGUID(4210), 0, "Skyguard")
val msg = FavoritesMessage(LoadoutType.Vehicle, PlanetSideGUID(4210), 0, "Skyguard")
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual stringVehicles

View file

@ -4,6 +4,7 @@ package game
import org.specs2.mutable._
import net.psforever.packet._
import net.psforever.packet.game._
import net.psforever.types.LoadoutType
import scodec.bits._
class FavoritesRequestTest extends Specification {
@ -11,9 +12,9 @@ class FavoritesRequestTest extends Specification {
"decode (for infantry)" in {
PacketCoding.DecodePacket(stringInfantry).require match {
case FavoritesRequest(player_guid, unk, action, line, label) =>
case FavoritesRequest(player_guid, list, action, line, label) =>
player_guid mustEqual PlanetSideGUID(75)
unk mustEqual 0
list mustEqual LoadoutType.Infantry
action mustEqual FavoritesAction.Save
line mustEqual 1
label.isDefined mustEqual true
@ -24,7 +25,7 @@ class FavoritesRequestTest extends Specification {
}
"encode (for infantry)" in {
val msg = FavoritesRequest(PlanetSideGUID(75), 0, FavoritesAction.Save, 1, Some("Example"))
val msg = FavoritesRequest(PlanetSideGUID(75), LoadoutType.Infantry, FavoritesAction.Save, 1, Some("Example"))
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual stringInfantry

View file

@ -3,6 +3,7 @@ package objects
import net.psforever.objects.GlobalDefinitions._
import net.psforever.objects._
import net.psforever.objects.loadouts._
import net.psforever.objects.definition.ImplantDefinition
import net.psforever.types.{CharacterGender, ImplantType, PlanetSideEmpire}
import org.specs2.mutable._
@ -291,13 +292,13 @@ class AvatarTest extends Specification {
avatar.SaveLoadout(obj, "test", 0)
avatar.LoadLoadout(0) match {
case Some(items) =>
items.Label mustEqual "test"
items.ExoSuit mustEqual obj.ExoSuit
items.Subtype mustEqual 0
case Some(items : InfantryLoadout) =>
items.label mustEqual "test"
items.exosuit mustEqual obj.ExoSuit
items.subtype mustEqual 0
items.VisibleSlots.length mustEqual 3
val holsters = items.VisibleSlots.sortBy(_.index)
items.visible_slots.length mustEqual 3
val holsters = items.visible_slots.sortBy(_.index)
holsters.head.index mustEqual 0
holsters.head.item.asInstanceOf[Loadout.ShorthandTool].definition mustEqual beamer
holsters.head.item.asInstanceOf[Loadout.ShorthandTool].ammo.head.ammo.capacity mustEqual 1 //we changed this
@ -307,8 +308,8 @@ class AvatarTest extends Specification {
holsters(2).index mustEqual 4
holsters(2).item.asInstanceOf[Loadout.ShorthandTool].definition mustEqual forceblade
items.Inventory.length mustEqual 6
val inventory = items.Inventory.sortBy(_.index)
items.inventory.length mustEqual 6
val inventory = items.inventory.sortBy(_.index)
inventory.head.index mustEqual 6
inventory.head.item.asInstanceOf[Loadout.ShorthandAmmoBox].definition mustEqual bullet_9mm
inventory(1).index mustEqual 9
@ -321,7 +322,7 @@ class AvatarTest extends Specification {
inventory(4).item.asInstanceOf[Loadout.ShorthandAmmoBox].definition mustEqual energy_cell
inventory(5).index mustEqual 39
inventory(5).item.asInstanceOf[Loadout.ShorthandSimpleItem].definition mustEqual remote_electronics_kit
case None =>
case _ =>
ko
}
}
@ -347,13 +348,13 @@ class AvatarTest extends Specification {
avatar.SaveLoadout(obj, "test", 0)
avatar.LoadLoadout(0) match {
case Some(items) =>
items.Label mustEqual "test"
items.ExoSuit mustEqual obj.ExoSuit
items.Subtype mustEqual 0
items.VisibleSlots.length mustEqual 3
items.Inventory.length mustEqual 0 //empty
case None =>
case Some(items : InfantryLoadout) =>
items.label mustEqual "test"
items.exosuit mustEqual obj.ExoSuit
items.subtype mustEqual 0
items.visible_slots.length mustEqual 3
items.inventory.length mustEqual 0 //empty
case _ =>
ko
}
}
@ -366,13 +367,13 @@ class AvatarTest extends Specification {
avatar.SaveLoadout(obj, "test", 0)
avatar.LoadLoadout(0) match {
case Some(items) =>
items.Label mustEqual "test"
items.ExoSuit mustEqual obj.ExoSuit
items.Subtype mustEqual 0
items.VisibleSlots.length mustEqual 0 //empty
items.Inventory.length mustEqual 6
case None =>
case Some(items : InfantryLoadout) =>
items.label mustEqual "test"
items.exosuit mustEqual obj.ExoSuit
items.subtype mustEqual 0
items.visible_slots.length mustEqual 0 //empty
items.inventory.length mustEqual 6
case _ =>
ko
}
}

View file

@ -2,6 +2,7 @@
package objects
import net.psforever.objects._
import net.psforever.objects.loadouts._
import net.psforever.types.{CharacterGender, ExoSuitType, PlanetSideEmpire}
import net.psforever.objects.GlobalDefinitions._
import org.specs2.mutable._
@ -36,14 +37,14 @@ class LoadoutTest extends Specification {
"create a loadout that contains a player's inventory" in {
val player = CreatePlayer()
val obj = Loadout.Create(player, "test")
val obj = Loadout.Create(player, "test").asInstanceOf[InfantryLoadout]
obj.Label mustEqual "test"
obj.ExoSuit mustEqual obj.ExoSuit
obj.Subtype mustEqual 0
obj.label mustEqual "test"
obj.exosuit mustEqual ExoSuitType.Standard
obj.subtype mustEqual 0
obj.VisibleSlots.length mustEqual 3
val holsters = obj.VisibleSlots.sortBy(_.index)
obj.visible_slots.length mustEqual 3
val holsters = obj.visible_slots.sortBy(_.index)
holsters.head.index mustEqual 0
holsters.head.item.asInstanceOf[Loadout.ShorthandTool].definition mustEqual beamer
holsters(1).index mustEqual 2
@ -51,8 +52,8 @@ class LoadoutTest extends Specification {
holsters(2).index mustEqual 4
holsters(2).item.asInstanceOf[Loadout.ShorthandTool].definition mustEqual forceblade
obj.Inventory.length mustEqual 5
val inventory = obj.Inventory.sortBy(_.index)
obj.inventory.length mustEqual 5
val inventory = obj.inventory.sortBy(_.index)
inventory.head.index mustEqual 6
inventory.head.item.asInstanceOf[Loadout.ShorthandConstructionItem].definition mustEqual ace
inventory(1).index mustEqual 9
@ -65,26 +66,86 @@ class LoadoutTest extends Specification {
inventory(4).item.asInstanceOf[Loadout.ShorthandSimpleItem].definition mustEqual remote_electronics_kit
}
"create a loadout that contains a vehicle's inventory" in {
val vehicle = Vehicle(mediumtransport)
vehicle.Inventory += 30 -> AmmoBox(bullet_9mm)
vehicle.Inventory += 33 -> AmmoBox(bullet_9mm_AP)
val obj = Loadout.Create(vehicle, "test").asInstanceOf[VehicleLoadout]
obj.label mustEqual "test"
obj.vehicle_definition mustEqual mediumtransport
obj.visible_slots.length mustEqual 2
val holsters = obj.visible_slots.sortBy(_.index)
holsters.head.index mustEqual 5
holsters.head.item.asInstanceOf[Loadout.ShorthandTool].definition mustEqual mediumtransport_weapon_systemA
holsters(1).index mustEqual 6
holsters(1).item.asInstanceOf[Loadout.ShorthandTool].definition mustEqual mediumtransport_weapon_systemB
obj.inventory.length mustEqual 2
val inventory = obj.inventory.sortBy(_.index)
inventory.head.index mustEqual 30
inventory.head.item.asInstanceOf[Loadout.ShorthandAmmoBox].definition mustEqual bullet_9mm
inventory(1).index mustEqual 33
inventory(1).item.asInstanceOf[Loadout.ShorthandAmmoBox].definition mustEqual bullet_9mm_AP
}
"distinguish MAX subtype information" in {
val player = CreatePlayer()
val slot = player.Slot(0)
slot.Equipment = None //only an unequipped slot can have its Equipment Size changed (Rifle -> Max)
Player.SuitSetup(player, ExoSuitType.MAX)
val ldout1 = Loadout.Create(player, "weaponless")
val ldout1 = Loadout.Create(player, "weaponless").asInstanceOf[InfantryLoadout]
slot.Equipment = None
slot.Equipment = Tool(trhev_dualcycler)
val ldout2 = Loadout.Create(player, "cycler")
val ldout2 = Loadout.Create(player, "cycler").asInstanceOf[InfantryLoadout]
slot.Equipment = None
slot.Equipment = Tool(trhev_pounder)
val ldout3 = Loadout.Create(player, "pounder")
val ldout3 = Loadout.Create(player, "pounder").asInstanceOf[InfantryLoadout]
slot.Equipment = None
slot.Equipment = Tool(trhev_burster)
val ldout4 = Loadout.Create(player, "burster")
val ldout4 = Loadout.Create(player, "burster").asInstanceOf[InfantryLoadout]
ldout1.Subtype mustEqual 0
ldout2.Subtype mustEqual 1
ldout3.Subtype mustEqual 2
ldout4.Subtype mustEqual 3
ldout1.subtype mustEqual 0
ldout2.subtype mustEqual 1
ldout3.subtype mustEqual 2
ldout4.subtype mustEqual InfantryLoadout.DetermineSubtype(player) //example
}
"players have additional uniform subtype" in {
val player = CreatePlayer()
val slot = player.Slot(0)
slot.Equipment = None //only an unequipped slot can have its Equipment Size changed (Rifle -> Max)
player.ExoSuit = ExoSuitType.Standard
val ldout0 = Loadout.Create(player, "standard").asInstanceOf[InfantryLoadout]
player.ExoSuit = ExoSuitType.Agile
val ldout1 = Loadout.Create(player, "agile").asInstanceOf[InfantryLoadout]
player.ExoSuit = ExoSuitType.Reinforced
val ldout2 = Loadout.Create(player, "rein").asInstanceOf[InfantryLoadout]
player.ExoSuit = ExoSuitType.Infiltration
val ldout7 = Loadout.Create(player, "inf").asInstanceOf[InfantryLoadout]
Player.SuitSetup(player, ExoSuitType.MAX)
val ldout3 = Loadout.Create(player, "weaponless").asInstanceOf[InfantryLoadout]
slot.Equipment = None
slot.Equipment = Tool(trhev_dualcycler)
val ldout4 = Loadout.Create(player, "cycler").asInstanceOf[InfantryLoadout]
slot.Equipment = None
slot.Equipment = Tool(trhev_pounder)
val ldout5 = Loadout.Create(player, "pounder").asInstanceOf[InfantryLoadout]
slot.Equipment = None
slot.Equipment = Tool(trhev_burster)
val ldout6 = Loadout.Create(player, "burster").asInstanceOf[InfantryLoadout]
InfantryLoadout.DetermineSubtypeB(ldout0.exosuit, ldout0.subtype) mustEqual 0
InfantryLoadout.DetermineSubtypeB(ldout1.exosuit, ldout1.subtype) mustEqual 1
InfantryLoadout.DetermineSubtypeB(ldout2.exosuit, ldout2.subtype) mustEqual 2
InfantryLoadout.DetermineSubtypeB(ldout3.exosuit, ldout3.subtype) mustEqual 3
InfantryLoadout.DetermineSubtypeB(ldout4.exosuit, ldout4.subtype) mustEqual 4
InfantryLoadout.DetermineSubtypeB(ldout5.exosuit, ldout5.subtype) mustEqual 5
InfantryLoadout.DetermineSubtypeB(ldout6.exosuit, ldout6.subtype) mustEqual 6
InfantryLoadout.DetermineSubtypeB(ldout7.exosuit, ldout7.subtype) mustEqual 7
}
}

View file

@ -277,6 +277,38 @@ class VehicleTest extends Specification {
filteredMap(0).UtilType mustEqual UtilityType.order_terminalb
filteredMap(2).UtilType mustEqual UtilityType.order_terminalb
}
"access its mounted weapons by Slot" in {
val harasser_vehicle = Vehicle(GlobalDefinitions.two_man_assault_buggy)
harasser_vehicle.Weapons(2).Equipment.get.GUID = PlanetSideGUID(10)
harasser_vehicle.Slot(2).Equipment.get.GUID mustEqual PlanetSideGUID(10)
}
"access its trunk by Slot" in {
val harasser_vehicle = Vehicle(GlobalDefinitions.two_man_assault_buggy)
val ammobox = AmmoBox(GlobalDefinitions.armor_canister)
ammobox.GUID = PlanetSideGUID(10)
harasser_vehicle.Inventory += 30 -> ammobox
harasser_vehicle.Slot(30).Equipment.get.GUID mustEqual PlanetSideGUID(10)
}
"find its mounted weapons by GUID" in {
val harasser_vehicle = Vehicle(GlobalDefinitions.two_man_assault_buggy)
harasser_vehicle.Weapons(2).Equipment.get.GUID = PlanetSideGUID(10)
harasser_vehicle.Find(PlanetSideGUID(10)) mustEqual Some(2)
}
"find items in its trunk by GUID" in {
val harasser_vehicle = Vehicle(GlobalDefinitions.two_man_assault_buggy)
val ammobox = AmmoBox(GlobalDefinitions.armor_canister)
ammobox.GUID = PlanetSideGUID(10)
harasser_vehicle.Inventory += 30 -> ammobox
harasser_vehicle.Find(PlanetSideGUID(10)) mustEqual Some(30)
}
}
}

View file

@ -68,10 +68,10 @@ class OrderTerminalABTest extends Specification {
player.ExoSuit = ExoSuitType.MAX
avatar.SaveLoadout(player, "test2", 1)
val msg1 = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.InfantryLoadout, 4, "", 0, PlanetSideGUID(0))
val msg1 = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Loadout, 4, "", 0, PlanetSideGUID(0))
terminal.Request(player, msg1) mustEqual Terminal.InfantryLoadout(ExoSuitType.Standard, 0, Nil, Nil)
val msg2 = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.InfantryLoadout, 4, "", 1, PlanetSideGUID(0))
val msg2 = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Loadout, 4, "", 1, PlanetSideGUID(0))
terminal.Request(player, msg2) mustEqual Terminal.NoDeal()
}
}

View file

@ -72,12 +72,54 @@ class OrderTerminalTest extends Specification {
terminal.Request(player, msg) mustEqual Terminal.NoDeal()
}
//TODO loudout tests
"player can not buy equipment from the wrong page ('9mmbullet_AP', page 1)" in {
val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Buy, 1, "9mmbullet_AP", 0, PlanetSideGUID(0))
terminal.Request(player, msg) mustEqual Terminal.NoDeal()
}
"player can retrieve an infantry loadout" in {
val avatar = Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)
val player2 = Player(avatar)
player2.ExoSuit = ExoSuitType.Agile
player2.Slot(0).Equipment = Tool(GlobalDefinitions.beamer)
player2.Slot(6).Equipment = Tool(GlobalDefinitions.beamer)
avatar.SaveLoadout(player2, "test", 0)
val msg = terminal.Request(player2, ItemTransactionMessage(PlanetSideGUID(10), TransactionType.Loadout, 4, "", 0, PlanetSideGUID(0)))
msg.isInstanceOf[Terminal.InfantryLoadout] mustEqual true
val loadout = msg.asInstanceOf[Terminal.InfantryLoadout]
loadout.exosuit mustEqual ExoSuitType.Agile
loadout.subtype mustEqual 0
loadout.holsters.size mustEqual 1
loadout.holsters.head.obj.Definition mustEqual GlobalDefinitions.beamer
loadout.holsters.head.start mustEqual 0
loadout.inventory.head.obj.Definition mustEqual GlobalDefinitions.beamer
loadout.inventory.head.start mustEqual 6
}
"player can not retrieve an infantry loadout from the wrong page" in {
val avatar = Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)
val player2 = Player(avatar)
player2.ExoSuit = ExoSuitType.Agile
player2.Slot(0).Equipment = Tool(GlobalDefinitions.beamer)
player2.Slot(6).Equipment = Tool(GlobalDefinitions.beamer)
avatar.SaveLoadout(player2, "test", 0)
val msg = terminal.Request(player2, ItemTransactionMessage(PlanetSideGUID(10), TransactionType.Loadout, 3, "", 0, PlanetSideGUID(0))) //page 3
msg.isInstanceOf[Terminal.NoDeal] mustEqual true
}
"player can not retrieve an infantry loadout from the wrong line" in {
val avatar = Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)
val player2 = Player(avatar)
player2.ExoSuit = ExoSuitType.Agile
player2.Slot(0).Equipment = Tool(GlobalDefinitions.beamer)
player2.Slot(6).Equipment = Tool(GlobalDefinitions.beamer)
avatar.SaveLoadout(player2, "test", 0)
val msg = terminal.Request(player2, ItemTransactionMessage(PlanetSideGUID(10), TransactionType.Loadout, 4, "", 1, PlanetSideGUID(0)))
msg.isInstanceOf[Terminal.NoDeal] mustEqual true
}
}
}

View file

@ -25,8 +25,7 @@ class ProximityTerminalControl2Test extends ActorTest() {
val (_, terminal) = TerminalControlTest.SetUpAgents(GlobalDefinitions.medical_terminal, PlanetSideEmpire.TR)
terminal.Actor !"hello"
val reply = receiveOne(Duration.create(500, "ms"))
assert(reply.isInstanceOf[Terminal.NoDeal])
expectNoMsg(Duration.create(500, "ms"))
}
}

View file

@ -0,0 +1,170 @@
// Copyright (c) 2017 PSForever
package objects.terminal
import akka.actor.Props
import net.psforever.objects.serverobject.CommonMessages
import net.psforever.objects.serverobject.terminals.Terminal.TerminalMessage
import net.psforever.objects.serverobject.terminals.{ProximityTerminal, ProximityTerminalControl, ProximityUnit, Terminal}
import net.psforever.objects.{Avatar, GlobalDefinitions, Player}
import net.psforever.packet.game.PlanetSideGUID
import net.psforever.types.{CharacterGender, PlanetSideEmpire}
import objects.ActorTest
import org.specs2.mutable.Specification
import scala.concurrent.duration._
class ProximityTest extends Specification {
"ProximityUnit" should {
"construct (with a Terminal object)" in {
val obj = new ProximityTest.SampleTerminal()
obj.NumberUsers mustEqual 0
}
"keep track of users (add)" in {
val obj = new ProximityTest.SampleTerminal()
obj.NumberUsers mustEqual 0
obj.AddUser(PlanetSideGUID(10)) mustEqual obj.NumberUsers
obj.NumberUsers mustEqual 1
obj.AddUser(PlanetSideGUID(20)) mustEqual obj.NumberUsers
obj.NumberUsers mustEqual 2
}
"keep track of users (remove)" in {
val obj = new ProximityTest.SampleTerminal()
obj.AddUser(PlanetSideGUID(10))
obj.AddUser(PlanetSideGUID(20))
obj.NumberUsers mustEqual 2
obj.RemoveUser(PlanetSideGUID(10)) mustEqual obj.NumberUsers
obj.NumberUsers mustEqual 1
obj.RemoveUser(PlanetSideGUID(20)) mustEqual obj.NumberUsers
obj.NumberUsers mustEqual 0
}
"can not add a user twice" in {
val obj = new ProximityTest.SampleTerminal()
obj.AddUser(PlanetSideGUID(10))
obj.NumberUsers mustEqual 1
obj.AddUser(PlanetSideGUID(10))
obj.NumberUsers mustEqual 1
}
"can not remove a user that was not added" in {
val obj = new ProximityTest.SampleTerminal()
obj.AddUser(PlanetSideGUID(10))
obj.NumberUsers mustEqual 1
obj.RemoveUser(PlanetSideGUID(20))
obj.NumberUsers mustEqual 1
}
}
"ProximityTerminal" should {
"construct" in {
ProximityTerminal(GlobalDefinitions.medical_terminal)
ok
}
}
}
class ProximityTerminalControl1bTest extends ActorTest {
"ProximityTerminalControl" should {
"send out a start message" in {
val obj = ProximityTerminal(GlobalDefinitions.medical_terminal)
obj.Actor = system.actorOf(Props(classOf[ProximityTerminalControl], obj), "prox-ctrl")
val player = Player(Avatar("TestCharacter", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0))
player.GUID = PlanetSideGUID(10)
assert(obj.NumberUsers == 0)
obj.Actor ! CommonMessages.Use(player)
val msg = receiveOne(200 milliseconds)
assert(obj.NumberUsers == 1)
assert(msg.isInstanceOf[TerminalMessage])
val msgout = msg.asInstanceOf[TerminalMessage]
assert(msgout.player == player)
assert(msgout.msg == null)
assert(msgout.response.isInstanceOf[Terminal.StartProximityEffect])
}
}
}
class ProximityTerminalControl2bTest extends ActorTest {
"ProximityTerminalControl" should {
"will not send out one start message unless first user" in {
val obj = ProximityTerminal(GlobalDefinitions.medical_terminal)
obj.Actor = system.actorOf(Props(classOf[ProximityTerminalControl], obj), "prox-ctrl")
val player1 = Player(Avatar("TestCharacter1", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0))
player1.GUID = PlanetSideGUID(10)
val player2 = Player(Avatar("TestCharacter2", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0))
player2.GUID = PlanetSideGUID(11)
assert(obj.NumberUsers == 0)
obj.Actor ! CommonMessages.Use(player1)
val msg = receiveOne(200 milliseconds)
assert(obj.NumberUsers == 1)
assert(msg.isInstanceOf[TerminalMessage])
assert(msg.asInstanceOf[TerminalMessage].response.isInstanceOf[Terminal.StartProximityEffect])
obj.Actor ! CommonMessages.Use(player2)
expectNoMsg(500 milliseconds)
assert(obj.NumberUsers == 2)
}
}
}
class ProximityTerminalControl3bTest extends ActorTest {
"ProximityTerminalControl" should {
"send out a stop message" in {
val obj = ProximityTerminal(GlobalDefinitions.medical_terminal)
obj.Actor = system.actorOf(Props(classOf[ProximityTerminalControl], obj), "prox-ctrl")
val player = Player(Avatar("TestCharacter", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0))
player.GUID = PlanetSideGUID(10)
assert(obj.NumberUsers == 0)
obj.Actor ! CommonMessages.Use(player)
receiveOne(200 milliseconds)
assert(obj.NumberUsers == 1)
obj.Actor ! CommonMessages.Unuse(player)
val msg = receiveOne(200 milliseconds)
assert(obj.NumberUsers == 0)
assert(msg.isInstanceOf[TerminalMessage])
val msgout = msg.asInstanceOf[TerminalMessage]
assert(msgout.player == player)
assert(msgout.msg == null)
assert(msgout.response.isInstanceOf[Terminal.StopProximityEffect])
}
}
}
class ProximityTerminalControl4bTest extends ActorTest {
"ProximityTerminalControl" should {
"will not send out one stop message until last user" in {
val obj = ProximityTerminal(GlobalDefinitions.medical_terminal)
obj.Actor = system.actorOf(Props(classOf[ProximityTerminalControl], obj), "prox-ctrl")
val player1 = Player(Avatar("TestCharacter1", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0))
player1.GUID = PlanetSideGUID(10)
val player2 = Player(Avatar("TestCharacter2", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0))
player2.GUID = PlanetSideGUID(11)
assert(obj.NumberUsers == 0)
obj.Actor ! CommonMessages.Use(player1)
receiveOne(200 milliseconds) //StartProximityEffect
assert(obj.NumberUsers == 1)
obj.Actor ! CommonMessages.Use(player2)
expectNoMsg(500 milliseconds)
assert(obj.NumberUsers == 2)
obj.Actor ! CommonMessages.Unuse(player1)
expectNoMsg(500 milliseconds)
assert(obj.NumberUsers == 1)
obj.Actor ! CommonMessages.Unuse(player2)
val msg = receiveOne(200 milliseconds)
assert(obj.NumberUsers == 0)
assert(msg.isInstanceOf[TerminalMessage])
val msgout = msg.asInstanceOf[TerminalMessage]
assert(msgout.player == player2)
assert(msgout.msg == null)
assert(msgout.response.isInstanceOf[Terminal.StopProximityEffect])
}
}
}
object ProximityTest {
class SampleTerminal extends Terminal(GlobalDefinitions.dropship_vehicle_terminal) with ProximityUnit
}

View file

@ -0,0 +1,91 @@
// Copyright (c) 2017 PSForever
package objects.terminal
import akka.actor.ActorRef
import net.psforever.objects.serverobject.structures.{Building, StructureType}
import net.psforever.objects._
import net.psforever.objects.serverobject.terminals.Terminal
import net.psforever.objects.zones.Zone
import net.psforever.packet.game.{ItemTransactionMessage, PlanetSideGUID}
import net.psforever.types.{CharacterGender, PlanetSideEmpire, TransactionType}
import org.specs2.mutable.Specification
class RepairRearmSiloTest extends Specification {
"RepairRearmSilo" should {
val player = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0))
val silo = Terminal(GlobalDefinitions.repair_silo)
silo.Owner = new Building(0, Zone.Nowhere, StructureType.Building)
silo.Owner.Faction = PlanetSideEmpire.TR
"define" in {
GlobalDefinitions.repair_silo.ObjectId mustEqual 729
}
"construct" in {
val obj = Terminal(GlobalDefinitions.repair_silo)
obj.Actor mustEqual ActorRef.noSender
}
"player can buy a box of ammunition ('bullet_35mm')" in {
val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Buy, 3, "35mmbullet", 0, PlanetSideGUID(0))
val reply = silo.Request(player, msg)
reply.isInstanceOf[Terminal.BuyEquipment] mustEqual true
val reply2 = reply.asInstanceOf[Terminal.BuyEquipment]
reply2.item.isInstanceOf[AmmoBox] mustEqual true
reply2.item.asInstanceOf[AmmoBox].Definition mustEqual GlobalDefinitions.bullet_35mm
reply2.item.asInstanceOf[AmmoBox].Capacity mustEqual 100
}
"player can not buy fake equipment ('sabot')" in {
val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Buy, 3, "sabot", 0, PlanetSideGUID(0))
silo.Request(player, msg) mustEqual Terminal.NoDeal()
}
"player can not buy equipment from the wrong page ('35mmbullet', page 1)" in {
val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Buy, 1, "35mmbullet", 0, PlanetSideGUID(0))
silo.Request(player, msg) mustEqual Terminal.NoDeal()
}
"player can retrieve a vehicle loadout" in {
val avatar = Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)
val player2 = Player(avatar)
val vehicle = Vehicle(GlobalDefinitions.fury)
vehicle.Slot(30).Equipment = AmmoBox(GlobalDefinitions.bullet_9mm)
avatar.SaveLoadout(vehicle, "test", 10)
val msg = silo.Request(player2, ItemTransactionMessage(PlanetSideGUID(10), TransactionType.Loadout, 4, "", 0, PlanetSideGUID(0)))
msg.isInstanceOf[Terminal.VehicleLoadout] mustEqual true
val loadout = msg.asInstanceOf[Terminal.VehicleLoadout]
loadout.vehicle_definition mustEqual GlobalDefinitions.fury
loadout.weapons.size mustEqual 1
loadout.weapons.head.obj.Definition mustEqual GlobalDefinitions.fury_weapon_systema
loadout.weapons.head.start mustEqual 1
loadout.inventory.head.obj.Definition mustEqual GlobalDefinitions.bullet_9mm
loadout.inventory.head.start mustEqual 30
}
"player can not retrieve a vehicle loadout from the wrong line" in {
val avatar = Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)
val player2 = Player(avatar)
val vehicle = Vehicle(GlobalDefinitions.fury)
vehicle.Slot(30).Equipment = AmmoBox(GlobalDefinitions.bullet_9mm)
avatar.SaveLoadout(vehicle, "test", 10)
val msg = silo.Request(player2, ItemTransactionMessage(PlanetSideGUID(10), TransactionType.Loadout, 3, "", 0, PlanetSideGUID(0))) //page 3
msg.isInstanceOf[Terminal.NoDeal] mustEqual true
}
"player can not retrieve a vehicle loadout from the wrong line" in {
val avatar = Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)
val player2 = Player(avatar)
val vehicle = Vehicle(GlobalDefinitions.fury)
vehicle.Slot(30).Equipment = AmmoBox(GlobalDefinitions.bullet_9mm)
avatar.SaveLoadout(vehicle, "test", 10)
val msg = silo.Request(player2, ItemTransactionMessage(PlanetSideGUID(10), TransactionType.Loadout, 4, "", 1, PlanetSideGUID(0))) //line 11
msg.isInstanceOf[Terminal.NoDeal] mustEqual true
}
}
}

View file

@ -26,8 +26,7 @@ class TerminalControl2Test extends ActorTest() {
val (_, terminal) = TerminalControlTest.SetUpAgents(GlobalDefinitions.cert_terminal, PlanetSideEmpire.TR)
terminal.Actor !"hello"
val reply = receiveOne(Duration.create(500, "ms"))
assert(reply.isInstanceOf[Terminal.NoDeal])
expectNoMsg(Duration.create(500, "ms"))
}
}

View file

@ -128,7 +128,7 @@ class LoginSessionActor extends Actor with MDCContextAware {
if(token.isDefined)
log.info(s"New login UN:$username Token:${token.get}. $clientVersion")
else
log.info(s"New login UN:$username PW:$password. $clientVersion")
log.info(s"New login UN:$username. $clientVersion")
// This is temporary until a schema has been developed
//val loginSucceeded = accountLookup(username, password.getOrElse(token.get))

View file

@ -110,9 +110,21 @@ object Maps {
LocalObject(1576, Terminal.Constructor(order_terminal))
LocalObject(1577, Terminal.Constructor(order_terminal))
LocalObject(1578, Terminal.Constructor(order_terminal))
LocalObject(1744, ProximityTerminal.Constructor(pad_landing)) //air pad A
LocalObject(1745, Terminal.Constructor(pad_landing)) //air pad A
LocalObject(1747, ProximityTerminal.Constructor(pad_landing)) //air pad B
LocalObject(1748, Terminal.Constructor(pad_landing)) //air pad B
LocalObject(1756, ProximityTerminal.Constructor(pad_landing)) //air pad C
LocalObject(1757, Terminal.Constructor(pad_landing)) //air pad C
LocalObject(1765, ProximityTerminal.Constructor(pad_landing)) //air pad D
LocalObject(1766, Terminal.Constructor(pad_landing)) //air pad D
LocalObject(2145, SpawnTube.Constructor(Vector3(3980.4062f, 4252.7656f, 257.5625f), Vector3(0, 0, 90)))
LocalObject(2146, SpawnTube.Constructor(Vector3(3980.4062f, 4259.992f, 257.5625f), Vector3(0, 0, 90)))
LocalObject(2147, SpawnTube.Constructor(Vector3(3980.4062f, 4267.3047f, 257.5625f), Vector3(0, 0, 90)))
LocalObject(2049, ProximityTerminal.Constructor(repair_silo)) //repair terminal A
LocalObject(2050, Terminal.Constructor(repair_silo)) //rearm terminal A
LocalObject(2061, ProximityTerminal.Constructor(repair_silo)) //repair terminal B
LocalObject(2062, Terminal.Constructor(repair_silo)) //rearm terminal B
LocalObject(2239, Terminal.Constructor(spawn_terminal))
LocalObject(2244, Terminal.Constructor(spawn_terminal))
LocalObject(2245, Terminal.Constructor(spawn_terminal))
@ -214,6 +226,18 @@ object Maps {
ObjectToBuilding(1576, 2)
ObjectToBuilding(1577, 2)
ObjectToBuilding(1578, 2)
ObjectToBuilding(1744, 2)
ObjectToBuilding(1745, 2)
ObjectToBuilding(1747, 2)
ObjectToBuilding(1748, 2)
ObjectToBuilding(1756, 2)
ObjectToBuilding(1757, 2)
ObjectToBuilding(1765, 2)
ObjectToBuilding(1766, 2)
ObjectToBuilding(2049, 2)
ObjectToBuilding(2050, 2)
ObjectToBuilding(2061, 2)
ObjectToBuilding(2062, 2)
ObjectToBuilding(2145, 2)
ObjectToBuilding(2146, 2)
ObjectToBuilding(2147, 2)
@ -351,6 +375,10 @@ object Maps {
LocalObject(1591, Terminal.Constructor(order_terminal))
LocalObject(1592, Terminal.Constructor(order_terminal))
LocalObject(1593, Terminal.Constructor(order_terminal))
LocalObject(1846, ProximityTerminal.Constructor(pad_landing)) //air pad S
LocalObject(1847, Terminal.Constructor(pad_landing)) //air pad S
LocalObject(1849, ProximityTerminal.Constructor(pad_landing)) //air pad N
LocalObject(1850, Terminal.Constructor(pad_landing)) //air pad N
LocalObject(2156, SpawnTube.Constructor(respawn_tube_tower, Vector3(4364.633f, 3994.125f, 228.1875f), Vector3(0, 0, 90)))
LocalObject(2157, SpawnTube.Constructor(respawn_tube_tower, Vector3(4364.633f, 3977.7266f, 228.1875f), Vector3(0, 0, 90)))
LocalObject(2333, Door.Constructor) //spawn tube door
@ -374,6 +402,10 @@ object Maps {
ObjectToBuilding(1591, 49)
ObjectToBuilding(1592, 49)
ObjectToBuilding(1593, 49)
ObjectToBuilding(1846, 49)
ObjectToBuilding(1847, 49)
ObjectToBuilding(1849, 49)
ObjectToBuilding(1850, 49)
ObjectToBuilding(2156, 49)
ObjectToBuilding(2157, 49)
ObjectToBuilding(2333, 49)
@ -428,6 +460,8 @@ object Maps {
def Building2() : Unit = {
//HART building C
LocalBuilding(2, FoundationBuilder(Building.Structure(StructureType.Building)))
LocalObject(12, ProximityTerminal.Constructor(repair_silo)) //repair terminal A
LocalObject(13, Terminal.Constructor(repair_silo)) //rearm terminal A //ItemTransaction: ItemTransactionMessage(PlanetSideGUID(2050),Buy,3,25mmbullet,0,PlanetSideGUID(0))
LocalObject(186, Terminal.Constructor(cert_terminal))
LocalObject(187, Terminal.Constructor(cert_terminal))
LocalObject(188, Terminal.Constructor(cert_terminal))
@ -475,6 +509,8 @@ object Maps {
LocalObject(1087, Terminal.Constructor(implant_terminal_interface)) //TODO guid not correct
LocalObject(1088, Terminal.Constructor(implant_terminal_interface)) //TODO guid not correct
LocalObject(1089, Terminal.Constructor(implant_terminal_interface)) //TODO guid not correct
ObjectToBuilding(12, 2)
ObjectToBuilding(13, 2)
ObjectToBuilding(186, 2)
ObjectToBuilding(187, 2)
ObjectToBuilding(188, 2)

View file

@ -9,12 +9,14 @@ import scodec.Attempt.{Failure, Successful}
import scodec.bits._
import org.log4s.MDC
import MDCContextAware.Implicits._
import csr.{CSRWarp, CSRZone, Traveler}
import net.psforever.objects.GlobalDefinitions._
import services.ServiceManager.Lookup
import net.psforever.objects._
import net.psforever.objects.definition.ToolDefinition
import net.psforever.objects.definition.converter.CorpseConverter
import net.psforever.objects.equipment._
import net.psforever.objects.loadouts._
import net.psforever.objects.guid.{GUIDTask, Task, TaskResolver}
import net.psforever.objects.inventory.{Container, GridInventory, InventoryItem}
import net.psforever.objects.serverobject.mount.Mountable
@ -28,8 +30,7 @@ import net.psforever.objects.serverobject.mblocker.Locker
import net.psforever.objects.serverobject.pad.{VehicleSpawnControl, VehicleSpawnPad}
import net.psforever.objects.serverobject.pad.process.{AutoDriveControls, VehicleSpawnControlGuided}
import net.psforever.objects.serverobject.structures.{Building, StructureType, WarpGate}
import net.psforever.objects.serverobject.terminals.{MatrixTerminalDefinition, ProximityTerminal, Terminal}
import net.psforever.objects.serverobject.terminals.Terminal
import net.psforever.objects.serverobject.terminals._
import net.psforever.objects.serverobject.terminals.Terminal.TerminalMessage
import net.psforever.objects.serverobject.tube.SpawnTube
import net.psforever.objects.vehicles.{AccessPermissionGroup, Utility, VehicleLockState}
@ -39,6 +40,7 @@ import net.psforever.types._
import services._
import services.avatar.{AvatarAction, AvatarResponse, AvatarServiceMessage, AvatarServiceResponse}
import services.local.{LocalAction, LocalResponse, LocalServiceMessage, LocalServiceResponse}
import services.vehicle.VehicleAction.UnstowEquipment
import services.vehicle.{VehicleAction, VehicleResponse, VehicleServiceMessage, VehicleServiceResponse}
import scala.annotation.tailrec
@ -70,6 +72,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
var usingProximityTerminal : Set[PlanetSideGUID] = Set.empty
var delayedProximityTerminalResets : Map[PlanetSideGUID, Cancellable] = Map.empty
var controlled : Option[Int] = None //keep track of avatar's ServerVehicleOverride state
var traveler : Traveler = null
var clientKeepAlive : Cancellable = DefaultCancellable.obj
var progressBarUpdate : Cancellable = DefaultCancellable.obj
@ -488,6 +491,11 @@ class WorldSessionActor extends Actor with MDCContextAware {
}
}
case VehicleResponse.InventoryState2(obj_guid, parent_guid, value) =>
if(tplayer_guid != guid) {
sendResponse(InventoryStateMessage(obj_guid, 0, parent_guid, value))
}
case VehicleResponse.LoadVehicle(vehicle, vtype, vguid, vdata) =>
//this is not be suitable for vehicles with people who are seated in it before it spawns (if that is possible)
if(tplayer_guid != guid) {
@ -829,7 +837,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
case Terminal.InfantryLoadout(exosuit, subtype, holsters, inventory) =>
//TODO optimizations against replacing Equipment with the exact same Equipment and potentially for recycling existing Equipment
log.info(s"$tplayer wants to change equipment loadout to their option #${msg.unk1 + 1}")
sendResponse(ItemTransactionResultMessage (msg.terminal_guid, TransactionType.InfantryLoadout, true))
sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Loadout, true))
val dropPred = DropPredicate(tplayer)
val (dropHolsters, beforeHolsters) = clearHolsters(tplayer.Holsters().iterator).partition(dropPred)
val (dropInventory, beforeInventory) = tplayer.Inventory.Clear().partition(dropPred)
@ -848,7 +856,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
taskResolver ! GUIDTask.UnregisterEquipment(elem.obj)(continent.GUID)
})
//report change
sendResponse(ArmorChangedMessage(tplayer.GUID, exosuit, 0))
sendResponse(ArmorChangedMessage(tplayer.GUID, exosuit, subtype))
avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.ArmorChanged(tplayer.GUID, exosuit, subtype))
sendResponse(PlanetsideAttributeMessage(tplayer.GUID, 4, tplayer.Armor))
avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.PlanetsideAttribute(tplayer.GUID, 4, tplayer.Armor))
@ -892,7 +900,53 @@ class WorldSessionActor extends Actor with MDCContextAware {
val objDef = obj.Definition
avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.EquipmentOnGround(tplayer.GUID, pos, orient, objDef.ObjectId, obj.GUID, objDef.Packet.ConstructorData(obj).get))
})
sendResponse(ItemTransactionResultMessage (msg.terminal_guid, TransactionType.InfantryLoadout, true))
case Terminal.VehicleLoadout(definition, weapons, inventory) =>
log.info(s"$tplayer wants to change their vehicle equipment loadout to their option #${msg.unk1 + 1}")
FindLocalVehicle match {
case Some(vehicle) =>
sendResponse(ItemTransactionResultMessage (msg.terminal_guid, TransactionType.Loadout, true))
val (_, afterInventory) = inventory.partition( DropPredicate(tplayer) ) //dropped items are lost
//remove old inventory
val deleteEquipment : (Int,Equipment)=>Unit = DeleteEquipmentFromVehicle(vehicle)
vehicle.Inventory.Clear().foreach({ case InventoryItem(obj, index) => deleteEquipment(index, obj) })
val stowEquipment : (Int,Equipment)=>TaskResolver.GiveTask = StowNewEquipmentInVehicle(vehicle)
(if(vehicle.Definition == definition) {
//vehicles are the same type; transfer over weapon ammo
//TODO ammo switching? no vehicle weapon does that currently but ...
//TODO want to completely swap weapons, but holster icon vanishes temporarily after swap
//TODO BFR arms must be swapped properly
val channel = s"${vehicle.Actor}"
weapons.foreach({ case InventoryItem(obj, index) =>
val savedWeapon = obj.asInstanceOf[Tool]
val existingWeapon = vehicle.Weapons(index).Equipment.get.asInstanceOf[Tool]
(0 until existingWeapon.MaxAmmoSlot).foreach({ index =>
val existingBox = existingWeapon.AmmoSlots(index).Box
existingBox.Capacity = savedWeapon.AmmoSlots(index).Box.Capacity
//use VehicleAction.InventoryState2; VehicleAction.InventoryState temporarily glitches ammo count in ui
vehicleService ! VehicleServiceMessage(channel, VehicleAction.InventoryState2(PlanetSideGUID(0), existingBox.GUID, existingWeapon.GUID, existingBox.Capacity))
})
})
afterInventory
}
else {
//do not transfer over weapon ammo
if(vehicle.Definition.TrunkSize == definition.TrunkSize && vehicle.Definition.TrunkOffset == definition.TrunkOffset) {
afterInventory
}
else {
//accommodate as much of inventory as possible
//TODO map x,y -> x,y rather than reorganize items
val (stow, _) = GridInventory.recoverInventory(afterInventory, vehicle.Inventory) //dropped items can be forgotten
stow
}
}).foreach({ case InventoryItem(obj, index) =>
taskResolver ! stowEquipment(index, obj)
})
case None =>
log.error(s"can not apply the loadout - can not find a vehicle")
sendResponse(ItemTransactionResultMessage (msg.terminal_guid, TransactionType.Loadout, false))
}
case Terminal.LearnCertification(cert, cost) =>
if(!tplayer.Certifications.contains(cert)) {
@ -1054,6 +1108,12 @@ class WorldSessionActor extends Actor with MDCContextAware {
case VehicleSpawnPad.StartPlayerSeatedInVehicle(vehicle, pad) =>
val vehicle_guid = vehicle.GUID
PlayerActionsToCancel()
if(player.VisibleSlots.contains(player.DrawnSlot)) {
player.DrawnSlot = Player.HandsDownSlot
sendResponse(ObjectHeldMessage(player.GUID, Player.HandsDownSlot, true))
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ObjectHeld(player.GUID, player.LastDrawnSlot))
}
sendResponse(PlanetsideAttributeMessage(vehicle_guid, 22, 1L)) //mount points off?
sendResponse(PlanetsideAttributeMessage(vehicle_guid, 21, player.GUID.guid)) //fte and ownership?
@ -1063,7 +1123,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
vehicleService ! VehicleServiceMessage.UnscheduleDeconstruction(vehicle_guid)
}
sendResponse(PlanetsideAttributeMessage(vehicle_guid, 22, 0L)) //mount points on?
//sendResponse(PlanetsideAttributeMessage(vehicle_guid, 0, vehicle.Definition.MaxHealth)))
//sendResponse(PlanetsideAttributeMessage(vehicle_guid, 0, 10))//vehicle.Definition.MaxHealth))
sendResponse(PlanetsideAttributeMessage(vehicle_guid, 68, 0L)) //???
sendResponse(PlanetsideAttributeMessage(vehicle_guid, 113, 0L)) //???
ReloadVehicleAccessPermissions(vehicle)
@ -1147,6 +1207,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
val popNC = poplist.count(_.faction == PlanetSideEmpire.NC)
val popVS = poplist.count(_.faction == PlanetSideEmpire.VS)
StartBundlingPackets()
zone.Buildings.foreach({ case(id, building) => initBuilding(continentNumber, id, building) })
sendResponse(ZonePopulationUpdateMessage(continentNumber, 414, 138, popTR, 138, popNC, 138, popVS, 138, popBO))
sendResponse(ContinentalLockUpdateMessage(continentNumber, PlanetSideEmpire.NEUTRAL))
@ -1231,7 +1292,9 @@ class WorldSessionActor extends Actor with MDCContextAware {
RequestSanctuaryZoneSpawn(player, zone_number)
case InterstellarCluster.ClientInitializationComplete() =>
StopBundlingPackets()
LivePlayerList.Add(sessionId, avatar)
traveler = new Traveler(self, continent.Id)
//PropertyOverrideMessage
sendResponse(PlanetsideAttributeMessage(PlanetSideGUID(0), 112, 1))
sendResponse(ReplicationStreamMessage(5, Some(6), Vector(SquadListing()))) //clear squad list
@ -1280,6 +1343,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
case SetCurrentAvatar(tplayer) =>
player = tplayer
val guid = tplayer.GUID
StartBundlingPackets()
sendResponse(SetCurrentAvatarMessage(guid,0,0))
sendResponse(PlayerStateShiftMessage(ShiftState(1, tplayer.Position, tplayer.Orientation.z)))
if(spectator) {
@ -1312,6 +1376,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
//SquadDefinitionActionMessage and SquadDetailDefinitionUpdateMessage
//MapObjectStateBlockMessage and ObjectCreateMessage
//TacticsMessage
StopBundlingPackets()
sendResponse(ChatMsg(ChatMessageType.CMT_EXPANSIONS, true, "", "1 on", None)) //CC on
@ -1432,9 +1497,10 @@ class WorldSessionActor extends Actor with MDCContextAware {
AwardBattleExperiencePoints(avatar, 1000000L)
player = new Player(avatar)
//player.Position = Vector3(3561.0f, 2854.0f, 90.859375f) //home3, HART C
//player.Orientation = Vector3(0f, 0f, 90f)
player.Position = Vector3(4262.211f ,4067.0625f ,262.35938f) //z6, Akna.tower
player.Orientation = Vector3(0f, 0f, 132.1875f)
player.Position = Vector3(3940.3984f, 4343.625f, 266.45312f)
player.Orientation = Vector3(0f, 0f, 90f)
//player.Position = Vector3(4262.211f ,4067.0625f ,262.35938f) //z6, Akna.tower
//player.Orientation = Vector3(0f, 0f, 132.1875f)
// player.ExoSuit = ExoSuitType.MAX //TODO strange issue; divide number above by 10 when uncommenting
player.Slot(0).Equipment = SimpleItem(remote_electronics_kit) //Tool(GlobalDefinitions.StandardPistol(player.Faction))
player.Slot(2).Equipment = Tool(punisher) //suppressor
@ -1477,6 +1543,8 @@ class WorldSessionActor extends Actor with MDCContextAware {
case msg @ BeginZoningMessage() =>
log.info("Reticulating splines ...")
traveler.zone = continent.Id
StartBundlingPackets()
configZone(continent)
sendResponse(TimeOfDayMessage(1191182336))
@ -1550,6 +1618,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
case _ => ;
}
})
StopBundlingPackets()
avatarService ! Service.Join(player.Continent)
localService ! Service.Join(player.Continent)
vehicleService ! Service.Join(player.Continent)
@ -1706,6 +1775,33 @@ class WorldSessionActor extends Actor with MDCContextAware {
}
}
CSRZone.read(traveler, msg) match {
case (true, zone, pos) =>
if(player.isAlive) {
player.Die //die to suspend client-driven position change updates
PlayerActionsToCancel()
player.Position = pos
traveler.zone = zone
continent.Population ! Zone.Population.Release(avatar)
continent.Population ! Zone.Population.Leave(avatar)
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ObjectDelete(player.GUID, player.GUID))
taskResolver ! TaskBeforeZoneChange(GUIDTask.UnregisterAvatar(player)(continent.GUID), zone)
}
case (false, _, _) => ;
}
CSRWarp.read(traveler, msg) match {
case (true, pos) =>
if(player.isAlive) {
PlayerActionsToCancel()
sendResponse(PlayerStateShiftMessage(ShiftState(0, pos, player.Orientation.z, None)))
player.Position = pos
}
case (false, _) => ;
}
// TODO: Prevents log spam, but should be handled correctly
if (messagetype != ChatMessageType.CMT_TOGGLE_GM) {
log.info("Chat: " + msg)
@ -1765,15 +1861,15 @@ class WorldSessionActor extends Actor with MDCContextAware {
case x :: xs =>
val (deleteFunc, modifyFunc) : ((Int, AmmoBox)=>Unit, (AmmoBox, Int)=>Unit) = obj match {
case (veh : Vehicle) =>
(DeleteAmmunitionInVehicle(veh), ModifyAmmunitionInVehicle(veh))
(DeleteEquipmentFromVehicle(veh), ModifyAmmunitionInVehicle(veh))
case _ =>
(DeleteAmmunition(obj), ModifyAmmunition(obj))
(DeleteEquipment(obj), ModifyAmmunition(obj))
}
val (stowFuncTask, stowFunc) : ((Int, AmmoBox)=>TaskResolver.GiveTask, (Int, AmmoBox)=>Unit) = obj match {
case (veh : Vehicle) =>
(StowNewAmmunitionInVehicles(veh), StowAmmunitionInVehicles(veh))
(StowNewEquipmentInVehicle(veh), StowEquipmentInVehicles(veh))
case _ =>
(StowNewAmmunition(obj), StowAmmunition(obj))
(StowNewEquipment(obj), StowEquipment(obj))
}
xs.foreach(item => {
obj.Inventory -= x.start
@ -1985,9 +2081,9 @@ class WorldSessionActor extends Actor with MDCContextAware {
case x :: xs =>
val (deleteFunc, modifyFunc) : ((Int, AmmoBox)=>Unit, (AmmoBox, Int)=>Unit) = obj match {
case (veh : Vehicle) =>
(DeleteAmmunitionInVehicle(veh), ModifyAmmunitionInVehicle(veh))
(DeleteEquipmentFromVehicle(veh), ModifyAmmunitionInVehicle(veh))
case _ =>
(DeleteAmmunition(obj), ModifyAmmunition(obj))
(DeleteEquipment(obj), ModifyAmmunition(obj))
}
xs.foreach(item => {
deleteFunc(item.start, item.obj.asInstanceOf[AmmoBox])
@ -2079,7 +2175,14 @@ class WorldSessionActor extends Actor with MDCContextAware {
findFunc(parent)
case None =>
None
}) match {
})
.orElse(FindLocalVehicle match {
case Some(parent) =>
findFunc(parent)
case None =>
None
})
match {
case Some((parent, Some(slot))) =>
taskResolver ! RemoveEquipmentFromSlot(parent, obj, slot)
log.info(s"RequestDestroy: equipment $object_guid")
@ -2283,6 +2386,15 @@ class WorldSessionActor extends Actor with MDCContextAware {
//TODO matrix spawn point; for now, just blindly bind to show work (and hope nothing breaks)
sendResponse(BindPlayerMessage(1, "@ams", true, true, 0, 0, 0, obj.Position))
}
else if(obj.Definition.isInstanceOf[RepairRearmSiloDefinition]) {
FindLocalVehicle match {
case Some(vehicle) =>
sendResponse(UseItemMessage(avatar_guid, unk1, object_guid, unk2, unk3, unk4, unk5, unk6, unk7, unk8, itemType))
sendResponse(UseItemMessage(avatar_guid, unk1, vehicle.GUID, unk2, unk3, unk4, unk5, unk6, unk7, unk8, vehicle.Definition.ObjectId))
case None =>
log.error("UseItem: expected seated vehicle, but found none")
}
}
else {
sendResponse(UseItemMessage(avatar_guid, unk1, object_guid, unk2, unk3, unk4, unk5, unk6, unk7, unk8, itemType))
}
@ -2305,13 +2417,14 @@ class WorldSessionActor extends Actor with MDCContextAware {
sendResponse(ObjectDeleteMessage(PlanetSideGUID(unk1), 2))
}
case None => ;
case None =>
log.error(s"UseItem: can not find object $object_guid")
}
case msg @ ProximityTerminalUseMessage(player_guid, object_guid, _) =>
log.info(s"ProximityTerminal: $msg")
log.info(s"ProximityTerminalUse: $msg")
continent.GUID(object_guid) match {
case Some(obj : ProximityTerminal) =>
case Some(obj : Terminal with ProximityUnit) =>
if(usingProximityTerminal.contains(object_guid)) {
SelectProximityUnit(obj)
}
@ -2319,9 +2432,9 @@ class WorldSessionActor extends Actor with MDCContextAware {
StartUsingProximityUnit(obj)
}
case Some(obj) => ;
log.warn(s"ProximityTerminal: object is not a terminal - $obj")
log.warn(s"ProximityTerminalUse: object does not have proximity effects - $obj")
case None =>
log.warn(s"ProximityTerminal: no object with guid $object_guid found")
log.warn(s"ProximityTerminalUse: no object with guid $object_guid found")
}
case msg @ UnuseItemMessage(player_guid, object_guid) =>
@ -2358,20 +2471,46 @@ class WorldSessionActor extends Actor with MDCContextAware {
log.error(s"ItemTransaction: $terminal_guid does not exist")
}
case msg @ FavoritesRequest(player_guid, unk, action, line, label) =>
case msg @ FavoritesRequest(player_guid, list, action, line, label) =>
log.info(s"FavoritesRequest: $msg")
if(player.GUID == player_guid) {
val name = label.getOrElse("missing_loadout_name")
val lineno = if(list == LoadoutType.Vehicle) { line + 10 } else { line }
val name = label.getOrElse(s"missing_loadout_${line+1}")
action match {
case FavoritesAction.Unknown => ;
case FavoritesAction.Save =>
avatar.SaveLoadout(player, name, line)
sendResponse(FavoritesMessage(0, player_guid, line, name))
(if(list == LoadoutType.Infantry) {
Some(player)
}
else if(list == LoadoutType.Vehicle) {
player.VehicleSeated match {
case Some(vehicle_guid) =>
continent.GUID(vehicle_guid)
case None =>
None
}
}
else {
None
}) match {
case Some(owner : Player) => //InfantryLoadout
avatar.SaveLoadout(owner, name, lineno)
import InfantryLoadout._
sendResponse(FavoritesMessage(list, player_guid, line, name, DetermineSubtypeB(player.ExoSuit, DetermineSubtype(player))))
case Some(owner : Vehicle) => //VehicleLoadout
avatar.SaveLoadout(owner, name, lineno)
sendResponse(FavoritesMessage(list, player_guid, line, name))
case Some(_) | None =>
log.error("FavoritesRequest: unexpected owner for favorites")
}
case FavoritesAction.Delete =>
avatar.DeleteLoadout(line)
sendResponse(FavoritesMessage(0, player_guid, line, ""))
avatar.DeleteLoadout(lineno)
sendResponse(FavoritesMessage(list, player_guid, line, ""))
case FavoritesAction.Unknown =>
log.warn("FavoritesRequest: unknown favorites action")
}
}
log.info("FavoritesRequest: " + msg)
case msg @ WeaponDelayFireMessage(seq_time, weapon_guid) =>
log.info("WeaponDelayFire: " + msg)
@ -2562,6 +2701,9 @@ class WorldSessionActor extends Actor with MDCContextAware {
sendResponse(PlanetsideAttributeMessage(object_guid, attribute_type, attribute_value))
}
case msg @ FacilityBenefitShieldChargeRequestMessage(guid) =>
//log.info(s"ShieldChargeRequest: $msg")
case msg @ BattleplanMessage(char_id, player_name, zonr_id, diagrams) =>
log.info("Battleplan: "+msg)
@ -3303,32 +3445,52 @@ class WorldSessionActor extends Actor with MDCContextAware {
}
/**
* Given an object that contains a box of amunition in its `Inventory` at a certain location,
* Get the current `Vehicle` object that the player is riding/driving.
* The vehicle must be found solely through use of `player.VehicleSeated`.
* @return the vehicle
*/
def FindLocalVehicle : Option[Vehicle] = {
player.VehicleSeated match {
case Some(vehicle_guid) =>
continent.GUID(vehicle_guid) match {
case Some(obj : Vehicle) =>
Some(obj)
case _ =>
None
}
case None =>
None
}
}
/**
* Given an object that contains an item (`Equipment`) in its `Inventory` at a certain location,
* remove it permanently.
* @param obj the `Container`
* @param start where the ammunition can be found
* @param item an object to unregister (should have been the ammunition that was removed);
* @param start where the item can be found
* @param item an object to unregister;
* not explicitly checked
*/
private def DeleteAmmunition(obj : PlanetSideGameObject with Container)(start : Int, item : AmmoBox) : Unit = {
private def DeleteEquipment(obj : PlanetSideGameObject with Container)(start : Int, item : Equipment) : Unit = {
val item_guid = item.GUID
obj.Inventory -= start
obj.Slot(start).Equipment = None
//obj.Inventory -= start
taskResolver ! GUIDTask.UnregisterEquipment(item)(continent.GUID)
sendResponse(ObjectDeleteMessage(item_guid, 0))
}
/**
* Given a vehicle that contains a box of amunition in its `Trunk` at a certain location,
* Given a vehicle that contains an item (`Equipment`) in its `Trunk` at a certain location,
* remove it permanently.
* @see `DeleteAmmunition`
* @see `DeleteEquipment`
* @param obj the `Vehicle`
* @param start where the ammunition can be found
* @param item an object to unregister (should have been the ammunition that was removed);
* @param start where the item can be found
* @param item an object to unregister;
* not explicitly checked
*/
private def DeleteAmmunitionInVehicle(obj : Vehicle)(start : Int, item : AmmoBox) : Unit = {
private def DeleteEquipmentFromVehicle(obj : Vehicle)(start : Int, item : Equipment) : Unit = {
val item_guid = item.GUID
DeleteAmmunition(obj)(start, item)
DeleteEquipment(obj)(start, item)
vehicleService ! VehicleServiceMessage(s"${obj.Actor}", VehicleAction.UnstowEquipment(player.GUID, item_guid))
}
@ -3361,27 +3523,27 @@ class WorldSessionActor extends Actor with MDCContextAware {
/**
* Announce that an already-registered `AmmoBox` object exists in a given position in some `Container` object's inventory.
* @see `StowAmmunitionInVehicles`
* @see `StowEquipmentInVehicles`
* @see `ChangeAmmoMessage`
* @param obj the `Container` object
* @param index an index in `obj`'s inventory
* @param item an `AmmoBox`
*/
def StowAmmunition(obj : PlanetSideGameObject with Container)(index : Int, item : AmmoBox) : Unit = {
def StowEquipment(obj : PlanetSideGameObject with Container)(index : Int, item : AmmoBox) : Unit = {
obj.Inventory += index -> item
sendResponse(ObjectAttachMessage(obj.GUID, item.GUID, index))
}
/**
* Announce that an already-registered `AmmoBox` object exists in a given position in some vehicle's inventory.
* @see `StowAmmunition`
* @see `StowEquipment`
* @see `ChangeAmmoMessage`
* @param obj the `Vehicle` object
* @param index an index in `obj`'s inventory
* @param item an `AmmoBox`
*/
def StowAmmunitionInVehicles(obj : Vehicle)(index : Int, item : AmmoBox) : Unit = {
StowAmmunition(obj)(index, item)
def StowEquipmentInVehicles(obj : Vehicle)(index : Int, item : AmmoBox) : Unit = {
StowEquipment(obj)(index, item)
vehicleService ! VehicleServiceMessage(s"${obj.Actor}", VehicleAction.StowEquipment(player.GUID, obj.GUID, index, item))
}
@ -3389,14 +3551,14 @@ class WorldSessionActor extends Actor with MDCContextAware {
* Prepare tasking that registers an `AmmoBox` object
* and announces that it exists in a given position in some `Container` object's inventory.
* `PutEquipmentInSlot` is the fastest way to achieve these goals.
* @see `StowNewAmmunitionInVehicles`
* @see `StowNewEquipmentInVehicle`
* @see `ChangeAmmoMessage`
* @param obj the `Container` object
* @param index an index in `obj`'s inventory
* @param item an `AmmoBox`
* @return a `TaskResolver.GiveTask` chain that executes the action
*/
def StowNewAmmunition(obj : PlanetSideGameObject with Container)(index : Int, item : AmmoBox) : TaskResolver.GiveTask = {
def StowNewEquipment(obj : PlanetSideGameObject with Container)(index : Int, item : Equipment) : TaskResolver.GiveTask = {
PutEquipmentInSlot(obj, item, index)
}
@ -3404,14 +3566,14 @@ class WorldSessionActor extends Actor with MDCContextAware {
* Prepare tasking that registers an `AmmoBox` object
* and announces that it exists in a given position in some vehicle's inventory.
* `PutEquipmentInSlot` is the fastest way to achieve these goals.
* @see `StowNewAmmunition`
* @see `StowNewEquipment`
* @see `ChangeAmmoMessage`
* @param obj the `Container` object
* @param index an index in `obj`'s inventory
* @param item an `AmmoBox`
* @return a `TaskResolver.GiveTask` chain that executes the action
*/
def StowNewAmmunitionInVehicles(obj : Vehicle)(index : Int, item : AmmoBox) : TaskResolver.GiveTask = {
def StowNewEquipmentInVehicle(obj : Vehicle)(index : Int, item : Equipment) : TaskResolver.GiveTask = {
TaskResolver.GiveTask(
new Task() {
private val localService = vehicleService
@ -3430,7 +3592,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
resolver ! scala.util.Success(this)
}
},
List(StowNewAmmunition(obj)(index, item))
List(StowNewEquipment(obj)(index, item))
)
}
@ -3860,11 +4022,6 @@ class WorldSessionActor extends Actor with MDCContextAware {
shooting = None
case None => ;
}
if(player != null && player.isAlive && player.VisibleSlots.contains(player.DrawnSlot)) {
player.DrawnSlot = Player.HandsDownSlot
sendResponse(ObjectHeldMessage(player.GUID, Player.HandsDownSlot, true))
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ObjectHeld(player.GUID, player.LastDrawnSlot))
}
if(flying) {
sendResponse(ChatMsg(ChatMessageType.CMT_FLY, false, "", "off", None))
flying = false
@ -4004,7 +4161,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
* Special note is warranted in the case of a medical terminal or an advanced medical terminal.
* @param terminal the proximity-based unit
*/
def StartUsingProximityUnit(terminal : ProximityTerminal) : Unit = {
def StartUsingProximityUnit(terminal : Terminal with ProximityUnit) : Unit = {
val term_guid = terminal.GUID
if(!usingProximityTerminal.contains(term_guid)) {
usingProximityTerminal += term_guid
@ -4025,7 +4182,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
* Other sorts of proximity-based units are put on a timer.
* @param terminal the proximity-based unit
*/
def StopUsingProximityUnit(terminal : ProximityTerminal) : Unit = {
def StopUsingProximityUnit(terminal : Terminal with ProximityUnit) : Unit = {
val term_guid = terminal.GUID
if(usingProximityTerminal.contains(term_guid)) {
usingProximityTerminal -= term_guid
@ -4044,7 +4201,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
* If this timer completes, a message will be sent that will attempt to disassociate from the target proximity unit.
* @param terminal the proximity-based unit
*/
def SetDelayedProximityUnitReset(terminal : ProximityTerminal) : Unit = {
def SetDelayedProximityUnitReset(terminal : Terminal with ProximityUnit) : Unit = {
val terminal_guid = terminal.GUID
ClearDelayedProximityUnitReset(terminal_guid)
import scala.concurrent.duration._
@ -4089,7 +4246,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
* and determinig which kind of unit is being utilized.
* @param terminal the proximity-based unit
*/
def SelectProximityUnit(terminal : ProximityTerminal) : Unit = {
def SelectProximityUnit(terminal : Terminal with ProximityUnit) : Unit = {
terminal.Definition match {
case GlobalDefinitions.adv_med_terminal | GlobalDefinitions.medical_terminal =>
ProximityMedicalTerminal(terminal)
@ -4098,6 +4255,10 @@ class WorldSessionActor extends Actor with MDCContextAware {
SetDelayedProximityUnitReset(terminal)
ProximityHealCrystal(terminal)
case GlobalDefinitions.repair_silo =>
SetDelayedProximityUnitReset(terminal)
//TODO insert vehicle repair here; see ProximityMedicalTerminal for example
case _ => ;
}
}
@ -4108,7 +4269,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
* If the player is both fully healed and fully repaired, stop using the terminal.
* @param unit the medical terminal
*/
def ProximityMedicalTerminal(unit : ProximityTerminal) : Unit = {
def ProximityMedicalTerminal(unit : Terminal with ProximityUnit) : Unit = {
val healthFull : Boolean = if(player.Health < player.MaxHealth) {
HealAction(player)
}
@ -4132,7 +4293,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
* If the player is fully healed, stop using the crystal.
* @param unit the healing crystal
*/
def ProximityHealCrystal(unit : ProximityTerminal) : Unit = {
def ProximityHealCrystal(unit : Terminal with ProximityUnit) : Unit = {
val healthFull : Boolean = if(player.Health < player.MaxHealth) {
HealAction(player)
}
@ -4232,12 +4393,95 @@ class WorldSessionActor extends Actor with MDCContextAware {
sendResponse(ConnectionClose())
}
def sendResponse(cont : PlanetSideControlPacket) : Unit = {
sendResponse(PacketCoding.CreateControlPacket(cont))
/**
* Persistent collector that intercepts `GamePacket` and `ControlPacket` messages that are being sent towards the network.
*/
private val packetBundlingCollector : MultiPacketCollector = new MultiPacketCollector()
/**
* Re-assigned function used to direct/intercept packets being sent towards the network.
* Defaults to directing the packets.
*/
private var packetBundlingFunc : (PlanetSidePacket)=>Option[PlanetSidePacket] = NoBundlingAction
/**
* Start packet bundling by assigning the appropriate function.
* @see `sendResponse(PlanetSidePacket) : Unit`
*/
def StartBundlingPackets() : Unit = {
log.trace("WORLD SEND: STARTED BUNDLING PACKETS")
packetBundlingFunc = PerformBundlingAction
}
def sendResponse(cont : PlanetSideGamePacket) : Unit = {
/**
* Stop packet bundling by assigning the appropriate function.
* If any bundles are in the collector's buffer, push that bundle out towards the network.
* @see `sendResponse(PlanetSidePacket) : Unit`
*/
def StopBundlingPackets() : Unit = {
log.trace("WORLD SEND: PACKET BUNDLING SUSPENDED")
packetBundlingFunc = NoBundlingAction
packetBundlingCollector.BundleOption match {
case Some(bundle) =>
sendResponse(bundle)
case None => ;
}
}
/**
* Transform the packet into either a `PlanetSideGamePacket` or a `PlanetSideControlPacket` and push it towards the network.
* @param cont the packet
* @return the same packet, to indicate it was sent
*/
private def NoBundlingAction(cont : PlanetSidePacket) : Option[PlanetSidePacket] = {
cont match {
case game : PlanetSideGamePacket =>
sendResponse(PacketCoding.CreateGamePacket(0, game))
case control : PlanetSideControlPacket =>
sendResponse(PacketCoding.CreateControlPacket(control))
case _ => ;
}
Some(cont)
}
/**
* Intercept the packet being sent towards the network and
* add it to a bundle that will eventually be sent to the network itself.
* @param cont the packet
* @return always `None`, to indicate the packet was not sent
*/
private def PerformBundlingAction(cont : PlanetSidePacket) : Option[PlanetSidePacket] = {
log.trace("WORLD SEND, BUNDLED: " + cont)
packetBundlingCollector.Add(cont)
None
}
/**
* Common entry point for transmitting packets to the network.
* Alternately, catch those packets and retain them to send out a bundled message.
* @param cont the packet
*/
def sendResponse(cont : PlanetSidePacket) : Unit = packetBundlingFunc(cont)
/**
* `KeepAliveMessage` is a special `PlanetSideGamePacket` that is excluded from being bundled when it is sent to the network.<br>
* <br>
* The risk of the server getting caught in a state where the packets dispatched to the client are alwaysd bundled is posible.
* Starting the bundling functionality but forgetting to transition into a state where it is deactivated can lead to this problem.
* No packets except for `KeepAliveMessage` will ever be sent until the ever-accumulating packets overflow.
* To avoid this state, whenever a `KeepAliveMessage` is sent, the packet collector empties its current contents to the network.
* @see `StartBundlingPackets`<br>
* `StopBundlingPackets`<br>
* `clientKeepAlive`
* @param cont a `KeepAliveMessage` packet
*/
def sendResponse(cont : KeepAliveMessage) : Unit = {
sendResponse(PacketCoding.CreateGamePacket(0, cont))
packetBundlingCollector.BundleOption match {
case Some(bundle) =>
log.trace("WORLD SEND: INTERMITTENT PACKET BUNDLE")
sendResponse(bundle)
case None => ;
}
}
def sendResponse(cont : PlanetSidePacketContainer) : Unit = {
@ -4246,7 +4490,6 @@ class WorldSessionActor extends Actor with MDCContextAware {
}
def sendResponse(cont : MultiPacketBundle) : Unit = {
log.trace("WORLD SEND: " + cont)
sendResponse(cont.asInstanceOf[Any])
}
@ -4272,7 +4515,7 @@ object WorldSessionActor {
private final case class ListAccountCharacters()
private final case class SetCurrentAvatar(tplayer : Player)
private final case class VehicleLoaded(vehicle : Vehicle)
private final case class DelayedProximityUnitStop(unit : ProximityTerminal)
private final case class DelayedProximityUnitStop(unit : Terminal with ProximityUnit)
private final case class UnregisterCorpseOnVehicleDisembark(corpse : Player)
/**

View file

@ -88,13 +88,13 @@ object Zones {
val c6 = new Zone("c6", Maps.ugd06, 28)
val i1 = new Zone("i1", Maps.map96, 29)
val i1 = new Zone("i1", Maps.map99, 29)
val i2 = new Zone("i2", Maps.map97, 30)
val i2 = new Zone("i2", Maps.map98, 30)
val i3 = new Zone("i3", Maps.map98, 31)
val i3 = new Zone("i3", Maps.map97, 31)
val i4 = new Zone("i4", Maps.map99, 32)
val i4 = new Zone("i4", Maps.map96, 32)
/**
* Get the zone identifier name for the sanctuary continent of a given empire.

View file

@ -0,0 +1,129 @@
package csr
import net.psforever.packet.PacketCoding
import net.psforever.packet.game.ChatMsg
import net.psforever.types.{ChatMessageType, Vector3}
import scala.collection.mutable.ArrayBuffer
import scala.util.Try
/*
The following is STILL for development and fun.
*/
/**
* An implementation of the CSR command `/warp`, highly modified to serve the purposes of the testing phases of the server.
* See `help()` for details.
*/
object CSRWarp {
/**
* Accept and confirm that a message sent to a player is a valid `/warp` invocation.
* If so, parse the message and send the player to whichever destination in this zone was requested.
* @param traveler the player
* @param msg the message the player received
* @return true, if the player is being transported to another place; false, otherwise
*/
def read(traveler : Traveler, msg : ChatMsg) : (Boolean, Vector3) = {
if(!isProperRequest(msg))
return (false, Vector3.Zero) //we do not handle this message
val buffer = decomposeMessage(msg.contents)
if(buffer.length == 0 || buffer(0).equals("") || buffer(0).equals("-help")) {
CSRWarp.help(traveler) //print usage information to chat
return (false, Vector3.Zero)
}
var destId : String = ""
var coords : ArrayBuffer[Int] = ArrayBuffer.empty[Int]
var list : Boolean = false
var failedCoordInput = false
for(o <- buffer) {
val toInt = Try(o.toInt)
if(toInt.isSuccess) {
coords += toInt.get
}
else if(coords.nonEmpty && coords.size < 3)
failedCoordInput = true
if(o.equals("-list"))
list = true
else if(destId.equals(""))
destId = o
}
if(failedCoordInput || (coords.nonEmpty && coords.size < 3)) {
CSRWarp.error(traveler, "Needs three integer components (<x> <y> <z>)")
return (false, Vector3.Zero)
}
else {
coords.slice(0, 3).foreach(x => {
if(x < 0 || x > 8191) {
CSRWarp.error(traveler, "Out of range - 0 < n < 8191, but n = " + x)
return (false, Vector3.Zero)
}
})
}
val zone = CSRZoneImpl.get(traveler.zone).get //the traveler is already in the appropriate zone
if(list && coords.isEmpty && destId.equals("")) {
CSRWarp.reply(traveler, CSRZoneImpl.listLocations(zone) + "; " + CSRZoneImpl.listWarpgates(zone))
return (false, Vector3.Zero)
}
val dest : Option[Vector3] = if(coords.nonEmpty) Some(Vector3(coords(0), coords(1), coords(2)))
else CSRZoneImpl.getWarpLocation(zone, destId) //coords before destId
if(dest.isEmpty) {
CSRWarp.error(traveler, "Invalid location")
return (false, Vector3.Zero)
}
(true, dest.get)
}
/**
* Check that the incoming message is an appropriate type for this command.
* @param msg the message
* @return true, if we will handle it; false, otherwise
*/
def isProperRequest(msg : ChatMsg) : Boolean = {
msg.messageType == ChatMessageType.CMT_WARP
}
/**
* Break the message in the packet down for parsing.
* @param msg the contents portion of the message, a space-separated `String`
* @return the contents portion of the message, transformed into an `Array`
*/
private def decomposeMessage(msg : String) : Array[String] = {
msg.trim.toLowerCase.split("\\s+")
}
/**
* Send a message back to the `Traveler` that will be printed into his chat window.
* @param traveler the player
* @param msg the message to be sent
*/
private def reply(traveler : Traveler, msg : String) : Unit = {
traveler ! PacketCoding.CreateGamePacket(0, ChatMsg(ChatMessageType.CMT_OPEN, true, "", msg, None))
}
/**
* Print usage information to the `Traveler`'s chat window.<br>
* <br>
* The "official" use information for help dictates the command should follow this format:
* `/warp &lt;x&gt;&lt;y&gt;&lt;z&gt; | to &lt;character&gt; | near &lt;object&gt; | above &lt;object&gt; | waypoint`.
* In our case, creating fixed coordinate points of interest is not terribly dissimilar from the "near" and "to" aspect.
* We can not currently implement most of the options for now, however.<br>
* <br>
* The destination prioritizes evaluation of the coordinates before the location string.
* When the user provides coordinates, he must provide all three components of the coordinate at once, else none will be accepted.
* If the coordinates are invalid, the location string will still be checked.
* "-list" is accepted while no serious attempt is made to indicate a destination (no location string or not enough coordinates).
* @param traveler the player
*/
private def help(traveler : Traveler) : Unit = {
CSRWarp.reply(traveler, "usage: /warp <location> | <gatename> | <x> <y> <z> | [-list]")
}
/**
* Print error information to the `Traveler`'s chat window.<br>
* The most common reason for error is the lack of information, or wrong information.
* @param traveler the player
*/
private def error(traveler : Traveler, msg : String) : Unit = {
CSRWarp.reply(traveler, "Error! " + msg)
}
}

View file

@ -0,0 +1,114 @@
package csr
import net.psforever.packet.PacketCoding
import net.psforever.packet.game.ChatMsg
import net.psforever.types.{ChatMessageType, Vector3}
/*
The following is STILL for development and fun.
*/
/**
* An implementation of the CSR command `/zone`, slightly modified to serve the purposes of the testing phases of the server.
*/
object CSRZone {
/**
* Accept and confirm that a message sent to a player is a valid `/zone` invocation.
* If so, parse the message and send the player to whichever zone was requested.
* @param traveler the player
* @param msg the message the player received
* @return true, if the player is being transported to another zone; false, otherwise
*/
def read(traveler : Traveler, msg : ChatMsg) : (Boolean, String , Vector3 ) = {
if(!isProperRequest(msg))
return (false,"", Vector3.Zero) //we do not handle this message
val buffer = decomposeMessage(msg.contents)
if(buffer.length == 0 || buffer(0).equals("-help")) {
CSRZone.help(traveler) //print usage information to chat
return (false,"", Vector3.Zero)
}
var zoneId = ""
var gateId = "" //the user can define which warpgate they may visit (actual keyword protocol missing)
var list = false //if the user wants a printed list of destination locations
for(o <- buffer) {
if(o.equals("-list")) {
if(zoneId.equals("") || gateId.equals("")) {
list = true
}
}
else if(zoneId.equals(""))
zoneId = o
else if(gateId.equals(""))
gateId = o
}
val zoneOpt = CSRZoneImpl.get(zoneId)
if(zoneOpt.isEmpty) {
if(list)
CSRZone.reply(traveler, CSRZoneImpl.list)
else
CSRZone.error(traveler, "Give a valid zonename (use '/zone -list')")
return (false,"", Vector3.Zero)
}
val zone = zoneOpt.get
var destination : Vector3 = CSRZoneImpl.selectRandom(zone) //the destination in the new zone starts as random
if(!gateId.equals("")) { //if we've defined a warpgate, and can find that warpgate, we re-assign the destination
val gateOpt = CSRZoneImpl.getWarpgate(zone, gateId)
if(gateOpt.isDefined)
destination = gateOpt.get
else
CSRZone.error(traveler, "Gate id not defined (use '/zone <zone> -list')")
}
else if(list) {
CSRZone.reply(traveler, CSRZoneImpl.listWarpgates(zone))
return (false,"",Vector3.Zero)
}
(true, zone.zonename, destination)
}
/**
* Check that the incoming message is an appropriate type for this command.
* @param msg the message
* @return true, if we will handle it; false, otherwise
*/
def isProperRequest(msg : ChatMsg) : Boolean ={
msg.messageType == ChatMessageType.CMT_ZONE
}
/**
* Break the message in the packet down for parsing.
* @param msg the contents portion of the message, a space-separated `String`
* @return the contents portion of the message, transformed into an `Array`
*/
private def decomposeMessage(msg : String) : Array[String] = {
msg.trim.toLowerCase.split("\\s+")
}
/**
* Send a message back to the `Traveler` that will be printed into his chat window.
* @param traveler the player
* @param msg the message to be sent
*/
private def reply(traveler : Traveler, msg : String) : Unit = {
traveler ! PacketCoding.CreateGamePacket(0, ChatMsg(ChatMessageType.CMT_OPEN,true,"", msg, None))
}
/**
* Print usage information to the `Traveler`'s chat window.
* @param traveler the player
*/
private def help(traveler : Traveler) : Unit = {
CSRZone.reply(traveler, "usage: /zone <zone> [gatename] | [-list]")
}
/**
* Print error information to the `Traveler`'s chat window.<br>
* The most common reason for error is the lack of information, or wrong information.
* @param traveler the player
*/
private def error(traveler : Traveler, msg : String) : Unit = {
CSRZone.reply(traveler, "Error! "+msg)
}
}

View file

@ -0,0 +1,578 @@
package csr
// Copyright (c) 2017 PSForever
import net.psforever.types.Vector3
import scala.collection.mutable
import scala.util.Random
/*
The following is STILL for development and fun.
*/
/**
* A crude representation of the information needed to describe a continent (hitherto, a "zone").
* The information is mainly catered to the simulation of the CSR commands `/zone` and `/warp`.
* (The exception is `alias` which is maintained for cosmetic purposes and clarification.)
* @param alias the common name of the zone
* @param map the map name of the zone (this map is loaded)
* @param zonename the zone's internal name
*/
class CSRZoneImpl(val alias : String, val map : String, val zonename : String) {
/**
* A listing of warpgates, geowarps, and island warpgates in this zone.
* The coordinates specified will only ever drop the user on a specific point within the protective bubble of the warpgate.
* This breaks from the expected zoning functionality where the user is placed in a random spot under the bubble.
* There is no prior usage details for the searchability format of this field's key values.
*/
private val gates : mutable.HashMap[String, Vector3] = mutable.HashMap()
/**
* A listing of special locations in this zone, i.e., major faciities, and some landmarks of interest.
* There is no prior usage details for the searchability format of this field's key values.
*/
private val locations : mutable.HashMap[String, Vector3] = mutable.HashMap()
}
object CSRZoneImpl {
/**
* A listing of all zones that can be visited by their internal name.
* The keys in this map should be directly usable by the `/zone` command.
*/
private val zones = Map[String, CSRZoneImpl](
"z1" -> CSRZoneImpl("Solsar", "map01", "z1"),
"z2" -> CSRZoneImpl("Hossin", "map02", "z2"),
"z3" -> CSRZoneImpl("Cyssor", "map03", "z3"),
"z4" -> CSRZoneImpl("Ishundar", "map04", "z4"),
"z5" -> CSRZoneImpl("Forseral", "map05", "z5"),
"z6" -> CSRZoneImpl("Ceryshen", "map06", "z6"),
"z7" -> CSRZoneImpl("Esamir", "map07", "z7"),
"z8" -> CSRZoneImpl("Oshur", "map08", "z8"),
"z9" -> CSRZoneImpl("Searhus", "map09", "z9"),
"z10" -> CSRZoneImpl("Amerish", "map10", "z10"),
"home1" -> CSRZoneImpl("NC Sanctuary", "map11", "home1"),
"home2" -> CSRZoneImpl("TR Sanctuary", "map12", "home2"),
"home3" -> CSRZoneImpl("VS Sanctuary", "map13", "home3"),
"tzshtr" -> CSRZoneImpl("VR Shooting Range TR", "map14", "tzshtr"),
"tzdrtr" -> CSRZoneImpl("VR Driving Range TR", "map15", "tzdrtr"),
"tzcotr" -> CSRZoneImpl("VR Combat csr.CSRZoneImpl TR", "map16", "tzcotr"),
"tzshvs" -> CSRZoneImpl("VR Shooting Range VS", "map14", "tzshvs"),
"tzdrvs" -> CSRZoneImpl("VR Driving Range VS", "map15", "tzdrvs"),
"tzcovs" -> CSRZoneImpl("VR Combat csr.CSRZoneImpl VS", "map16", "tzcovs"),
"tzshnc" -> CSRZoneImpl("VR Shooting Range NC", "map14", "tzshnc"),
"tzdrnc" -> CSRZoneImpl("VR Driving Range NC", "map15", "tzdrnc"),
"tzconc" -> CSRZoneImpl("VR Combat csr.CSRZoneImpl NC", "map16", "tzconc"),
"c1" -> CSRZoneImpl("Supai", "ugd01", "c1"),
"c2" -> CSRZoneImpl("Hunhau", "ugd02", "c2"),
"c3" -> CSRZoneImpl("Adlivun", "ugd03", "c3"),
"c4" -> CSRZoneImpl("Byblos", "ugd04", "c4"),
"c5" -> CSRZoneImpl("Annwn", "ugd05", "c5"),
"c6" -> CSRZoneImpl("Drugaskan", "ugd06", "c6"),
"i4" -> CSRZoneImpl("Nexus", "map96", "i4"),
"i3" -> CSRZoneImpl("Desolation", "map97", "i3"),
"i2" -> CSRZoneImpl("Ascension", "map98", "i2"),
"i1" -> CSRZoneImpl("Extinction", "map99", "i1"),
"homebo" -> CSRZoneImpl("Black_ops_hq", "Black_ops_hq", "homebo"),
"station1" -> CSRZoneImpl("TR Station", "Station1", "station1"),
"station2" -> CSRZoneImpl("NC Station", "Station2", "station2"),
"station3" -> CSRZoneImpl("VS Station", "Station3", "station3")
)
/**
* A listing of all zones that can be visited by their common name.
* The keys in this map should be directly usable by the `/zone` command.
* Though the behavior is undocumented, access to this alias list is for the benefit of the user.
*/
private val alias = Map[String, String](
"solsar" -> "z1",
"hossin" -> "z2",
"cyssor" -> "z3",
"ishundar" -> "z4",
"forseral" -> "z5",
"ceryshen" -> "z6",
"esamir" -> "z7",
"oshur" -> "z8",
"searhus" -> "z9",
"amerish" -> "z10",
"nc-sanctuary" -> "home1",
"tr-sanctuary" -> "home2",
"vs-sanctuary" -> "home3",
"tr-shooting" -> "tzshtr",
"tr-driving" -> "tzdrtr",
"tr-combat" -> "tzcotr",
"vs-shooting" -> "tzshvs",
"vs-driving" -> "tzdrvs",
"vs-combat" -> "tzcovs",
"nc-shooting" -> "tzshnc",
"nc-driving" -> "tzdrnc",
"nc-combat" -> "tzconc",
"supai" -> "c1",
"hunhau" -> "c2",
"adlivun" -> "c3",
"byblos" -> "c4",
"annwn" -> "c5",
"drugaskan" -> "c6",
"nexus" -> "i4",
"desolation" -> "i3",
"ascension" -> "i2",
"extinction" -> "i1",
"Black_ops_hq" -> "homebo",
"TR-Station" -> "station1",
"NC-Station" -> "station2",
"VS-Station" -> "station3"
)
/**
* A value used for selecting where to appear in a zone from the list of locations when the user has no indicated one.
*/
private val rand = Random
setup()
/**
* An abbreviated constructor for creating `CSRZone`s without invocation of `new`.
* @param alias the common name of the zone
* @param map the map name of the zone (this map is loaded)
* @param zonename the zone's internal name
*/
def apply(alias : String, map : String, zonename : String) : CSRZoneImpl = new CSRZoneImpl(alias, map, zonename)
/**
* Get a valid `CSRZone`'s information.
* @param zoneId a name that describes the zone and should be searchable
* @return the `CSRZone`, or `None`
*/
def get(zoneId : String) : Option[CSRZoneImpl] = {
var zId = zoneId.toLowerCase
if(alias.get(zId).isDefined)
zId = alias(zId)
zones.get(zId)
}
/**
* Get a location within the `CSRZone`.
* The location should be a facility or a warpgate or interesting.
* @param zone the `CSRZone`
* @param locId a name that describes a known location in the provided `CSRZone` and is searchable
* @return the coordinates of that location, or None
*/
def getWarpLocation(zone : CSRZoneImpl, locId : String) : Option[Vector3] = {
val low_locId = locId.toLowerCase
var location = zone.locations.get(low_locId)
if(location.isEmpty)
location = zone.gates.get(low_locId)
location
}
/**
* Get the position of a warpgate within the zone.
* @param zone the `CSRZone`
* @param gateId a name that describes a known warpgate in the provided `CSRZone` and is searchable
* @return the coordinates of that warpgate, or None
*/
def getWarpgate(zone : CSRZoneImpl, gateId : String) : Option[Vector3] = {
zone.gates.get(gateId.toLowerCase)
}
/**
* Get the names for all of the `CSRZones` that can be visited.
* @return all of the zonenames
*/
def list : String = {
"zonenames: z1 - z10, home1 - home3, tzshnc, tzdrnc, tzconc, tzshtr, tzdrtr, tzcotr, tzshvs, tzdrvs, tzcovs, c1 - c6, i1 - i4; zones are also aliased to their continent name"
}
/**
* Get the name for all of the locations that can be visited in this `CSRZone`, excluding warpgates.
* @param zone the `CSRZone`
* @return all of the location keys
*/
def listLocations(zone : CSRZoneImpl) : String = {
var out : String = "warps: "
if(zone.locations.nonEmpty) {
out += zone.locations.keys.toArray.sorted.mkString(", ")
}
else
out = "none"
out
}
/**
* Get the name for all of the warpgates that can be visited in this `CSRZone`.
* @param zone the `CSRZone`
* @return all of the warpgate keys
*/
def listWarpgates(zone : CSRZoneImpl) : String = {
var out : String = "gatenames: "
if(zone.gates.isEmpty)
out += "none"
else
out += zone.gates.keys.toArray.sorted.mkString(", ")
out
}
/**
* Select, of all the `CSRZone` locations and warpgates, a pseudorandom destination to spawn the player in the zone if none has been specified.
* @param zone the `CSRZone`
* @return the coordinates of the spawn point
*/
def selectRandom(zone : CSRZoneImpl) : Vector3 = {
var outlets = zone.locations //random location?
if(outlets.nonEmpty) {
return outlets.values.toArray.apply(rand.nextInt(outlets.size))
}
outlets = zone.gates //random warpgate?
if(outlets.nonEmpty) {
return outlets.values.toArray.apply(rand.nextInt(outlets.size))
}
Vector3.Zero //fallback coordinates (that will always be valid)
}
/**
* Load all zones with selected places of interest and the coordinates to place the player nearby that given place of interest.
* All of these keys should be searchable under the `/warp` command.
* Only the warpgate keys are searchable by the `/zone` command.
*/
def setup() : Unit = {
zones("z1").gates += (
"gate1" -> Vector3(4150, 7341, 82),
"gate2" -> Vector3(5698, 3404, 129),
"gate3" -> Vector3(2650, 5363, 176),
"gate4" -> Vector3(3022, 1225, 66),
"geowarp1" -> Vector3(3678, 2895, 108),
"geowarp2" -> Vector3(5672, 4750, 70)
)
zones("z1").locations += (
"amun" -> Vector3(4337, 2278, 68),
"aton" -> Vector3(3772, 5463, 54),
"bastet" -> Vector3(5412, 5588, 56),
"hapi" -> Vector3(4256, 4436, 59),
"horus" -> Vector3(3725, 2114, 73),
"mont" -> Vector3(3354, 4205, 83),
"seth" -> Vector3(4495, 6026, 58),
"sobek" -> Vector3(3094, 3027, 75),
"thoth" -> Vector3(4615, 3373, 53),
"lake" -> Vector3(4317, 4008, 37),
"monolith" -> Vector3(5551, 5047, 64)
)
zones("z2").gates += (
"gate1" -> Vector3(1881, 4873, 19),
"gate2" -> Vector3(4648, 4625, 28),
"gate3" -> Vector3(3296, 2045, 21),
"gate4" -> Vector3(5614, 1781, 32),
"geowarp1" -> Vector3(5199, 4869, 39),
"geowarp2" -> Vector3(3911, 2407, 15)
)
zones("z2").locations += (
"acan" -> Vector3(3534, 4015, 30),
"bitol" -> Vector3(4525, 2632, 30),
"chac" -> Vector3(4111, 5950, 39),
"ghanon" -> Vector3(2565, 3707, 41),
"hurakan" -> Vector3(1840, 2934, 38),
"ixtab" -> Vector3(3478, 3143, 40),
"kisin" -> Vector3(3356, 5374, 31),
"mulac" -> Vector3(5592, 2738, 37),
"naum" -> Vector3(5390, 3454, 28),
"voltan" -> Vector3(4529, 3414, 28),
"zotz" -> Vector3(6677, 2342, 129),
"monolith" -> Vector3(2938, 2485, 14)
)
zones("z3").gates += (
"gate1" -> Vector3(2616, 6567, 58),
"gate2" -> Vector3(6980, 5336, 57),
"gate3" -> Vector3(1199, 1332, 66),
"gate4" -> Vector3(5815, 1974, 63),
"geowarp1" -> Vector3(2403, 4278, 60),
"geowarp2" -> Vector3(4722, 2665, 78)
)
zones("z3").locations += (
"aja" -> Vector3(754, 5435, 48),
"chuku" -> Vector3(4208, 7021, 54),
"bomazi" -> Vector3(1198, 4492, 58),
"ekera" -> Vector3(5719, 6555, 51),
"faro" -> Vector3(5030, 5700, 57),
"gunuku" -> Vector3(4994, 4286, 54),
"honsi" -> Vector3(4042, 4588, 89),
"itan" -> Vector3(5175, 3393, 48),
"kaang" -> Vector3(5813, 3862, 62),
"leza" -> Vector3(2691, 1561, 64),
"mukuru" -> Vector3(661, 2380, 54),
"nzame" -> Vector3(1670, 2706, 45),
"orisha" -> Vector3(7060, 1327, 59),
"pamba" -> Vector3(7403, 3123, 63),
"shango" -> Vector3(6846, 2319, 63),
"tore" -> Vector3(3017, 2272, 58),
"wele" -> Vector3(436, 7040, 60),
"monolith" -> Vector3(4515, 4105, 38),
"peak" -> Vector3(3215, 5063, 579)
)
zones("z4").gates += (
"gate1" -> Vector3(4702, 6768, 30),
"gate2" -> Vector3(5515, 3368, 69),
"gate3" -> Vector3(1564, 3356, 46),
"gate4" -> Vector3(3889, 1118, 56),
"geowarp1" -> Vector3(4202, 4325, 68),
"geowarp2" -> Vector3(2384, 1925, 37)
)
zones("z4").locations += (
"akkan" -> Vector3(2746, 4260, 39),
"baal" -> Vector3(825, 5470, 72),
"dagon" -> Vector3(1739, 5681, 40),
"enkidu" -> Vector3(3217, 3574, 37),
"girru" -> Vector3(4475, 5853, 78),
"hanish" -> Vector3(3794, 5540, 89),
"irkall" -> Vector3(4742, 5270, 66),
"kusag" -> Vector3(6532, 4692, 46),
"lahar" -> Vector3(6965, 5306, 38),
"marduk" -> Vector3(3059, 2144, 70),
"neti" -> Vector3(3966, 2417, 80),
"zaqar" -> Vector3(4796, 2177, 75),
"monolith" -> Vector3(5165, 4083, 35),
"stonehenge" -> Vector3(4992, 3776, 56)
)
zones("z5").gates += (
"gate1" -> Vector3(3432, 6498, 73),
"gate2" -> Vector3(7196, 3917, 47),
"gate3" -> Vector3(1533, 3540, 56),
"gate4" -> Vector3(3197, 1390, 45),
"geowarp1" -> Vector3(4899, 5633, 38),
"geowarp2" -> Vector3(5326, 2558, 54)
)
zones("z5").locations += (
"anu" -> Vector3(3479, 2556, 56),
"bel" -> Vector3(3665, 4626, 58),
"caer" -> Vector3(4570, 2601, 56),
"dagd" -> Vector3(5825, 4449, 55),
"eadon" -> Vector3(2725, 2853, 53),
"gwydion" -> Vector3(5566, 3739, 61),
"lugh" -> Vector3(6083, 5069, 72),
"neit" -> Vector3(4345, 4319, 76),
"ogma" -> Vector3(3588, 3227, 114),
"pwyll" -> Vector3(4683, 4764, 104),
"monolith" -> Vector3(3251, 3245, 160),
"islands1" -> Vector3(6680, 6217, 125),
"islands2" -> Vector3(1059, 6213, 120)
)
zones("z6").gates += (
"gate1" -> Vector3(5040, 4327, 46),
"gate2" -> Vector3(2187, 5338, 30),
"gate3" -> Vector3(4960, 1922, 15),
"gate4" -> Vector3(2464, 3088, 189),
"geowarp1" -> Vector3(3221, 5328, 242),
"geowarp2" -> Vector3(2237, 1783, 238)
)
zones("z6").locations += (
"akna" -> Vector3(4509, 3732, 219),
"anguta" -> Vector3(3999, 4170, 266),
"igaluk" -> Vector3(3241, 5658, 235),
"keelut" -> Vector3(3630, 1904, 265),
"nerrivik" -> Vector3(3522, 3703, 322),
"pinga" -> Vector3(5938, 3545, 96),
"sedna" -> Vector3(3932, 5160, 232),
"tarqaq" -> Vector3(2980, 2155, 237),
"tootega" -> Vector3(5171, 3251, 217),
"monolith" -> Vector3(4011, 4851, 32),
"bridge" -> Vector3(3729, 4859, 234)
)
zones("z7").gates += (
"gate1" -> Vector3(1516, 6448, 61),
"gate2" -> Vector3(5249, 3819, 69),
"gate3" -> Vector3(2763, 2961, 86),
"gate4" -> Vector3(6224, 1152, 78),
"geowarp1" -> Vector3(6345, 4802, 90),
"geowarp2" -> Vector3(3800, 2197, 64)
)
zones("z7").locations += (
"andvari" -> Vector3(3233, 7207, 78),
"dagur" -> Vector3(4026, 6191, 60),
"eisa" -> Vector3(3456, 4513, 75),
"freyr" -> Vector3(2853, 3840, 56),
"gjallar" -> Vector3(1056, 2656, 74),
"helheim" -> Vector3(5542, 2532, 53),
"jarl" -> Vector3(1960, 5462, 68),
"kvasir" -> Vector3(4096, 1571, 69),
"mani" -> Vector3(5057, 4989, 58),
"nott" -> Vector3(6783, 4329, 46),
"ran" -> Vector3(2378, 1919, 85),
"vidar" -> Vector3(3772, 3024, 67),
"ymir" -> Vector3(1911, 4008, 69),
"monolith" -> Vector3(6390, 1622, 63)
)
zones("z8").gates += (
"gate1" -> Vector3(5437, 5272, 32),
"gate2" -> Vector3(3251, 5650, 60),
"gate3" -> Vector3(5112, 2616, 40),
"gate4" -> Vector3(2666, 1665, 45),
"geowarp1" -> Vector3(3979, 5370, 47),
"geowarp2" -> Vector3(6018, 3136, 35)
)
zones("z8").locations += (
"atar" -> Vector3(3609, 2730, 47),
"dahaka" -> Vector3(4633, 5379, 54),
"hvar" -> Vector3(3857, 4764, 49),
"izha" -> Vector3(5396, 3852, 51),
"jamshid" -> Vector3(2371, 3378, 52),
"mithra" -> Vector3(2480, 4456, 44),
"rashnu" -> Vector3(3098, 3961, 59),
"yazata" -> Vector3(4620, 3983, 62),
"zal" -> Vector3(3966, 2164, 61),
"arch1" -> Vector3(4152, 3285, 31),
"arch2" -> Vector3(4688, 5272, 68),
"pride" -> Vector3(2913, 4412, 63)
)
zones("z9").gates += (
"gate1" -> Vector3(1505, 6981, 65),
"gate2" -> Vector3(6835, 3517, 56),
"gate3" -> Vector3(1393, 1376, 53),
"geowarp1" -> Vector3(7081, 5552, 46),
"geowarp2" -> Vector3(3776, 1092, 49)
)
zones("z9").locations += (
"akua" -> Vector3(5258, 4041, 346),
"drakulu" -> Vector3(3806, 2647, 151),
"hiro" -> Vector3(4618, 5761, 190),
"iva" -> Vector3(6387, 5199, 55),
"karihi" -> Vector3(3879, 5574, 236),
"laka" -> Vector3(4720, 6718, 49),
"matagi" -> Vector3(5308, 5093, 239),
"ngaru" -> Vector3(4103, 4077, 205),
"oro" -> Vector3(4849, 4456, 208),
"pele" -> Vector3(4549, 3712, 208),
"rehua" -> Vector3(3843, 2195, 60),
"sina" -> Vector3(5919, 2177, 91),
"tara" -> Vector3(1082, 4225, 60),
"wakea" -> Vector3(1785, 5241, 63),
"monolith" -> Vector3(3246, 6507, 105)
)
zones("z10").gates += (
"gate1" -> Vector3(6140, 6599, 71),
"gate2" -> Vector3(4814, 4608, 59),
"gate3" -> Vector3(3152, 3480, 54),
"gate4" -> Vector3(1605, 1446, 40),
"geowarp1" -> Vector3(3612, 6918, 38),
"geowarp2" -> Vector3(3668, 3327, 55)
)
zones("z10").locations += (
"azeban" -> Vector3(6316, 5160, 62),
"cetan" -> Vector3(3587, 2522, 48),
"heyoka" -> Vector3(4395, 2327, 47),
"ikanam" -> Vector3(2740, 2412, 57),
"kyoi" -> Vector3(5491, 2284, 62),
"mekala" -> Vector3(6087, 2925, 59),
"onatha" -> Vector3(3397, 5799, 48),
"qumu" -> Vector3(3990, 5152, 46),
"sungrey" -> Vector3(4609, 5624, 72),
"tumas" -> Vector3(4687, 6392, 69),
"verica" -> Vector3(4973, 3459, 47),
"xelas" -> Vector3(6609, 4479, 56),
"monolith" -> Vector3(5651, 6024, 38)
)
zones("home1").gates += (
"gate1" -> Vector3(4158, 6344, 44),
"gate2" -> Vector3(2214, 5797, 48),
"gate3" -> Vector3(5032, 3241, 53)
)
zones("home1").locations += "hart_c" -> Vector3(2352, 5523, 66)
zones("home2").gates += (
"gate1" -> Vector3(5283, 4317, 44),
"gate2" -> Vector3(3139, 4809, 40),
"gate3" -> Vector3(3659, 2894, 26)
)
zones("home2").locations += "hart_c" -> Vector3(3125, 2864, 35)
zones("home3").gates += (
"gate1" -> Vector3(5657, 4681, 98),
"gate2" -> Vector3(2639, 5366, 57),
"gate3" -> Vector3(4079, 2467, 155)
)
zones("home3").locations += "hart_c" -> Vector3(3675, 2727, 91)
zones("tzshtr").locations += "roof" -> Vector3(499, 1568, 25)
zones("tzcotr").locations += "spawn" -> Vector3(960, 1002, 32)
zones("tzdrtr").locations += (
"start" -> Vector3(2457, 1864, 23),
"air_pad" -> Vector3(1700, 1900, 32)
)
zones("tzshvs").locations += "roof" -> Vector3(499, 1568, 25)
zones("tzcovs").locations += "spawn" -> Vector3(960, 1002, 32)
zones("tzdrvs").locations += (
"start" -> Vector3(2457, 1864, 23),
"air_pad" -> Vector3(1700, 1900, 32)
)
zones("tzshnc").locations += "roof" -> Vector3(499, 1568, 25)
zones("tzconc").locations += "spawn" -> Vector3(960, 1002, 32)
zones("tzdrnc").locations += (
"start" -> Vector3(2457, 1864, 23),
"air_pad" -> Vector3(1700, 1900, 32)
)
zones("c1").gates += (
"geowarp1" -> Vector3(998, 2038, 103),
"geowarp2" -> Vector3(231, 1026, 82),
"geowarp3" -> Vector3(2071, 1405, 102),
"geowarp4" -> Vector3(1051, 370, 103)
)
zones("c2").gates += (
"geowarp1" -> Vector3(999, 2386, 243),
"geowarp2" -> Vector3(283, 1249, 172),
"geowarp3" -> Vector3(1887, 1307, 192),
"geowarp4" -> Vector3(1039, 155, 143)
)
zones("c3").gates += (
"geowarp1" -> Vector3(1095, 1725, 25),
"geowarp2" -> Vector3(226, 832, 42),
"geowarp3" -> Vector3(1832, 1026, 43),
"geowarp4" -> Vector3(981, 320, 46)
)
zones("c4").gates += (
"geowarp1" -> Vector3(902, 1811, 93),
"geowarp2" -> Vector3(185, 922, 113),
"geowarp3" -> Vector3(1696, 1188, 92),
"geowarp4" -> Vector3(887, 227, 115)
)
zones("c5").gates += (
"geowarp1" -> Vector3(1195, 1752, 244),
"geowarp2" -> Vector3(290, 1104, 235),
"geowarp3" -> Vector3(1803, 899, 243),
"geowarp4" -> Vector3(1042, 225, 246)
)
zones("c6").gates += (
"geowarp1" -> Vector3(1067, 2044, 95),
"geowarp2" -> Vector3(290, 693, 73),
"geowarp3" -> Vector3(1922, 928, 33),
"geowarp4" -> Vector3(1174, 249, 114)
)
zones("i3").gates += (
"gate1" -> Vector3(1219, 2580, 30),
"gate2" -> Vector3(2889, 2919, 33),
"gate3" -> Vector3(2886, 1235, 32)
)
zones("i3").locations += (
"dahaka" -> Vector3(1421, 2216, 30),
"jamshid" -> Vector3(2500, 2543, 30),
"izha" -> Vector3(2569, 1544, 30),
"oasis" -> Vector3(2084, 1935, 40)
)
zones("i2").gates += (
"gate1" -> Vector3(1243, 1393, 12),
"gate2" -> Vector3(2510, 2544, 12),
"gate3" -> Vector3(2634, 1477, 12)
)
zones("i2").locations += (
"rashnu" -> Vector3(1709, 1802, 91),
"sraosha" -> Vector3(2729, 2349, 91),
"zal" -> Vector3(1888, 2728, 91),
"center" -> Vector3(2082, 2192, 160),
"vpad" -> Vector3(1770, 2686, 92)
)
zones("i1").gates += (
"gate1" -> Vector3(1225, 2036, 67),
"gate2" -> Vector3(2548, 2801, 65),
"gate3" -> Vector3(2481, 1194, 89)
)
zones("i1").locations += (
"hvar" -> Vector3(1559, 1268, 88),
"mithra" -> Vector3(2855, 2850, 89),
"yazata" -> Vector3(1254, 2583, 88),
"south_of_volcano" -> Vector3(2068, 1686, 88)
)
zones("i4").gates += (
"gate1" -> Vector3(2359, 2717, 36),
"gate2" -> Vector3(2732, 1355, 36),
"geowarp" -> Vector3(1424, 1640, 45)
)
zones("i4").locations += "atar" -> Vector3(1915, 1936, 43)
}
}

View file

@ -0,0 +1,37 @@
package csr
// Copyright (c) 2017 PSForever
import akka.actor.ActorRef
import net.psforever.packet.PlanetSidePacketContainer
/*
The following is STILL for development and fun.
*/
/**
* The traveler is synonymous with the player.
* The primary purpose of the object is to keep track of but not expose the player's session so that packets may be relayed back to him.
* csr.Traveler also keeps track of which zone the player currently occupies.
* @param session the player's session
*/
class Traveler(private val session : ActorRef, var zone : String) {
/**
* `sendToSelf` is a call that permits the session to gain access to its internal `rightRef` so that it can dispatch a packet.
* @param msg the byte-code translation of a packet
*/
def sendToSelf(msg : PlanetSidePacketContainer) : Unit = {
// this.session.sendResponse(msg)
}
def !(msg : Any) : Unit = {
session ! msg
}
}
object Traveler {
/**
* An abbreviated constructor for creating `csr.Traveler`s without invocation of `new`.
* @param session the player's session
* @return a traveler object for this player
*/
def apply(session : ActorRef, zoneId : String) : Traveler = new Traveler(session, zoneId)
}

View file

@ -16,6 +16,7 @@ object VehicleAction {
final case class DeployRequest(player_guid : PlanetSideGUID, object_guid : PlanetSideGUID, state : DriveState.Value, unk1 : Int, unk2 : Boolean, pos : Vector3) extends Action
final case class DismountVehicle(player_guid : PlanetSideGUID, bailType : BailType.Value, unk2 : Boolean) extends Action
final case class InventoryState(player_guid : PlanetSideGUID, obj : PlanetSideGameObject, parent_guid : PlanetSideGUID, start : Int, con_data : ConstructorData) extends Action
final case class InventoryState2(player_guid : PlanetSideGUID, obj_guid : PlanetSideGUID, parent_guid : PlanetSideGUID, value : Int) extends Action
final case class KickPassenger(player_guid : PlanetSideGUID, unk1 : Int, unk2 : Boolean, vehicle_guid : PlanetSideGUID) extends Action
final case class LoadVehicle(player_guid : PlanetSideGUID, vehicle : Vehicle, vtype : Int, vguid : PlanetSideGUID, vdata : ConstructorData) extends Action
final case class MountVehicle(player_guid : PlanetSideGUID, object_guid : PlanetSideGUID, seat : Int) extends Action

View file

@ -17,6 +17,7 @@ object VehicleResponse {
final case class DetachFromRails(vehicle_guid : PlanetSideGUID, rails_guid : PlanetSideGUID, rails_pos : Vector3, rails_rot : Float) extends Response
final case class DismountVehicle(bailType : BailType.Value , unk2 : Boolean) extends Response
final case class InventoryState(obj : PlanetSideGameObject, parent_guid : PlanetSideGUID, start : Int, con_data : ConstructorData) extends Response
final case class InventoryState2(obj_guid : PlanetSideGUID, parent_guid : PlanetSideGUID, value : Int) extends Response
final case class KickPassenger(seat_num : Int, kickedByDriver : Boolean, vehicle_guid : PlanetSideGUID) extends Response
final case class LoadVehicle(vehicle : Vehicle, vtype : Int, vguid : PlanetSideGUID, vdata : ConstructorData) extends Response
final case class MountVehicle(object_guid : PlanetSideGUID, seat : Int) extends Response

View file

@ -60,6 +60,10 @@ class VehicleService extends Actor {
VehicleEvents.publish(
VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.InventoryState(obj, parent_guid, start, con_data))
)
case VehicleAction.InventoryState2(player_guid, obj_guid, parent_guid, value) =>
VehicleEvents.publish(
VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.InventoryState2(obj_guid, parent_guid, value))
)
case VehicleAction.KickPassenger(player_guid, seat_num, kickedByDriver, vehicle_guid) =>
VehicleEvents.publish(
VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.KickPassenger(seat_num, kickedByDriver, vehicle_guid))

View file

@ -136,6 +136,22 @@ class InventoryStateTest extends ActorTest {
}
}
class InventoryState2Test extends ActorTest {
ServiceManager.boot(system)
val tool = Tool(GlobalDefinitions.beamer)
tool.AmmoSlots.head.Box.GUID = PlanetSideGUID(13)
val cdata = tool.Definition.Packet.ConstructorData(tool).get
"VehicleService" should {
"pass InventoryState2" in {
val service = system.actorOf(Props[VehicleService], "v-service")
service ! Service.Join("test")
service ! VehicleServiceMessage("test", VehicleAction.InventoryState2(PlanetSideGUID(10), PlanetSideGUID(11), PlanetSideGUID(12), 13))
expectMsg(VehicleServiceResponse("/test/Vehicle", PlanetSideGUID(10), VehicleResponse.InventoryState2(PlanetSideGUID(11), PlanetSideGUID(12), 13)))
}
}
}
class KickPassengerTest extends ActorTest {
ServiceManager.boot(system)