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. Install this on to your system and the compiler and Scala REPL will be added to your PATH.
### Downloading PSCrypto ### 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 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 ### 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. 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. 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.definition.{AvatarDefinition, ImplantDefinition}
import net.psforever.objects.equipment.EquipmentSize import net.psforever.objects.equipment.EquipmentSize
import net.psforever.objects.loadouts.Loadout
import net.psforever.types.{CertificationType, CharacterGender, ImplantType, PlanetSideEmpire} import net.psforever.types.{CertificationType, CharacterGender, ImplantType, PlanetSideEmpire}
import scala.annotation.tailrec import scala.annotation.tailrec
@ -16,7 +17,7 @@ class Avatar(val name : String, val faction : PlanetSideEmpire.Value, val sex :
/** Certifications */ /** Certifications */
private val certs : mutable.Set[CertificationType.Value] = mutable.Set[CertificationType.Value]() private val certs : mutable.Set[CertificationType.Value] = mutable.Set[CertificationType.Value]()
/** Implants<br> /** 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. * 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 `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." * 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` * @see `DetailedCharacterData.implants`
*/ */
private val implants : Array[ImplantSlot] = Array.fill[ImplantSlot](3)(new ImplantSlot) private val implants : Array[ImplantSlot] = Array.fill[ImplantSlot](3)(new ImplantSlot)
/** Loadouts */ /** Loadouts<br>
private val loadouts : Array[Option[Loadout]] = Array.fill[Option[Loadout]](10)(None) * 0-9 are Infantry loadouts
/** Locker (inventory slot number five) */ * 10-14 are Vehicle loadouts
*/
private val loadouts : Array[Option[Loadout]] = Array.fill[Option[Loadout]](15)(None)
/** Locker */
private val locker : LockerContainer = new LockerContainer() { private val locker : LockerContainer = new LockerContainer() {
override def toString : String = { override def toString : String = {
s"$name's ${Definition.Name}" 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 LoadLoadout(line : Int) : Option[Loadout] = loadouts.lift(line).getOrElse(None)
def DeleteLoadout(line : Int) : Unit = { def DeleteLoadout(line : Int) : Unit = {

View file

@ -524,6 +524,10 @@ object GlobalDefinitions {
val medical_terminal = new MedicalTerminalDefinition(529) val medical_terminal = new MedicalTerminalDefinition(529)
val pad_landing = new RepairRearmSiloDefinition(719)
val repair_silo = new RepairRearmSiloDefinition(729)
val spawn_pad = new VehicleSpawnPadDefinition val spawn_pad = new VehicleSpawnPadDefinition
val mb_locker = new LockerDefinition 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.definition.AvatarDefinition
import net.psforever.objects.equipment.{Equipment, EquipmentSize} import net.psforever.objects.equipment.{Equipment, EquipmentSize}
import net.psforever.objects.inventory.{Container, GridInventory, InventoryItem} import net.psforever.objects.inventory.{Container, GridInventory, InventoryItem}
import net.psforever.objects.loadouts.Loadout
import net.psforever.objects.serverobject.affinity.FactionAffinity import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.packet.game.PlanetSideGUID import net.psforever.packet.game.PlanetSideGUID
import net.psforever.types._ import net.psforever.types._

View file

@ -349,6 +349,39 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideServ
def VisibleSlots : Set[Int] = weapons.keySet 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. * A reference to the `Vehicle` `Trunk` space.
* @return this `Vehicle` `Trunk` * @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 // 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.definition._
import net.psforever.objects.equipment.Equipment import net.psforever.objects.equipment.Equipment
import net.psforever.objects.inventory.InventoryItem import net.psforever.objects.inventory.InventoryItem
import net.psforever.types.ExoSuitType
import scala.annotation.tailrec 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> * <br>
* `Loadout` objects are composed of the following information, as if a blueprint:<br> * The lists of user-specific loadouts are initialized with `FavoritesMessage` packets.
* - 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.
* Specific entries are loaded or removed using `FavoritesRequest` 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 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 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 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, abstract class Loadout(label : String,
private val visible_slots : List[Loadout.SimplifiedEntry], visible_slots : List[Loadout.SimplifiedEntry],
private val inventory : List[Loadout.SimplifiedEntry], 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
}
object Loadout { 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 = { def Create(player : Player, label : String) : Loadout = {
new Loadout( InfantryLoadout(
label, label,
packageSimplifications(player.Holsters()), packageSimplifications(player.Holsters()),
packageSimplifications(player.Inventory.Items.values.toList), 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 = { def Create(vehicle : Vehicle, label : String) : Loadout = {
Loadout( VehicleLoadout(
label, label,
packageSimplifications(vehicle.Weapons.map({ case ((index, weapon)) => InventoryItem(weapon.Equipment.get, index) }).toList), 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 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 = { def DetermineSubtype(player : Player) : Int = {
if(player.ExoSuit == ExoSuitType.MAX) { InfantryLoadout.DetermineSubtype(player)
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
}
} }
/**
* 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. * Overloaded entry point for constructing simplified blueprints from holster slot equipment.
* @param equipment the holster slots * @param equipment the holster slots
* @return a `List` of simplified `Equipment` * @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) recursiveHolsterSimplifications(equipment.iterator)
} }
@ -189,7 +144,7 @@ object Loadout {
* @param equipment the enumerated contents of the inventory * @param equipment the enumerated contents of the inventory
* @return a `List` of simplified `Equipment` * @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) }) 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._
import net.psforever.objects.definition._ import net.psforever.objects.definition._
import net.psforever.objects.equipment.Equipment import net.psforever.objects.equipment.Equipment
import net.psforever.objects.loadouts.Loadout
import net.psforever.packet.game.ItemTransactionMessage import net.psforever.packet.game.ItemTransactionMessage
import net.psforever.types.ExoSuitType import net.psforever.types.ExoSuitType
@ -336,7 +337,7 @@ object EquipmentTerminalDefinition {
* `TerminalDefinition.MakeKit` * `TerminalDefinition.MakeKit`
*/ */
def BuildSimplifiedPattern(entry : Loadout.Simplification) : Equipment = { def BuildSimplifiedPattern(entry : Loadout.Simplification) : Equipment = {
import net.psforever.objects.Loadout._ import net.psforever.objects.loadouts.Loadout._
entry match { entry match {
case obj : ShorthandTool => case obj : ShorthandTool =>
val ammo : List[AmmoBoxDefinition] = obj.ammo.map(fmode => { fmode.ammo.definition }) val ammo : List[AmmoBoxDefinition] = obj.ammo.map(fmode => { fmode.ammo.definition })

View file

@ -1,18 +1,12 @@
// Copyright (c) 2017 PSForever // Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.terminals 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". * The definition for any `Terminal` that is of a type "medical_terminal".
* This includes the limited proximity-based functionality of the formal medical terminals * This includes the functionality of the formal medical terminals and some of the cavern crystals.
* and the actual proximity-based functionality of the cavern crystals.<br> * Do not confuse the game's internal "medical_terminal" object category and the actual `medical_terminal` object (529).
* <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.
*/ */
class MedicalTerminalDefinition(objectId : Int) extends TerminalDefinition(objectId) { class MedicalTerminalDefinition(objectId : Int) extends TerminalDefinition(objectId) with ProximityDefinition {
Name = if(objectId == 38) { Name = if(objectId == 38) {
"adv_med_terminal" "adv_med_terminal"
} }
@ -31,6 +25,4 @@ class MedicalTerminalDefinition(objectId : Int) extends TerminalDefinition(objec
else { else {
throw new IllegalArgumentException("medical terminal must be either object id 38, 225, 226, 529, or 689") 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 akka.actor.ActorContext
import net.psforever.objects.Player import net.psforever.objects.Player
import net.psforever.objects.loadouts.InfantryLoadout
import net.psforever.objects.inventory.InventoryItem import net.psforever.objects.inventory.InventoryItem
import net.psforever.objects.serverobject.structures.Amenity import net.psforever.objects.serverobject.structures.Amenity
import net.psforever.packet.game.ItemTransactionMessage 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) 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. * `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. * 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. * 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 = { override def Loadout(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = {
if(msg.item_page == 4) { //Favorites tab if(msg.item_page == 4) { //Favorites tab
player.LoadLoadout(msg.unk1) match { player.LoadLoadout(msg.unk1) match {
case Some(loadout) => case Some(loadout : InfantryLoadout) =>
if(loadout.ExoSuit != ExoSuitType.MAX) { if(loadout.exosuit != ExoSuitType.MAX) {
val holsters = loadout.VisibleSlots.map(entry => { InventoryItem(BuildSimplifiedPattern(entry.item), entry.index) }) 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) }) val inventory = loadout.inventory.map(entry => { InventoryItem(BuildSimplifiedPattern(entry.item), entry.index) })
Terminal.InfantryLoadout(loadout.ExoSuit, loadout.Subtype, holsters, inventory) Terminal.InfantryLoadout(loadout.exosuit, loadout.subtype, holsters, inventory)
} }
else { else {
Terminal.NoDeal() Terminal.NoDeal()
} }
case None => case Some(_) | None =>
Terminal.NoDeal() Terminal.NoDeal()
} }
} }

View file

@ -2,6 +2,7 @@
package net.psforever.objects.serverobject.terminals package net.psforever.objects.serverobject.terminals
import net.psforever.objects.Player import net.psforever.objects.Player
import net.psforever.objects.loadouts.InfantryLoadout
import net.psforever.objects.inventory.InventoryItem import net.psforever.objects.inventory.InventoryItem
import net.psforever.packet.game.ItemTransactionMessage import net.psforever.packet.game.ItemTransactionMessage
import net.psforever.objects.serverobject.terminals.EquipmentTerminalDefinition._ 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) 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. * `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. * If a valid loadout is found, its data is transformed back into actual `Equipment` for return to the user.
* @param player the player * @param player the player
@ -37,11 +38,11 @@ class OrderTerminalDefinition extends EquipmentTerminalDefinition(612) {
override def Loadout(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = { override def Loadout(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = {
if(msg.item_page == 4) { //Favorites tab if(msg.item_page == 4) { //Favorites tab
player.LoadLoadout(msg.unk1) match { player.LoadLoadout(msg.unk1) match {
case Some(loadout) => case Some(loadout : InfantryLoadout) =>
val holsters = loadout.VisibleSlots.map(entry => { InventoryItem(BuildSimplifiedPattern(entry.item), entry.index) }) 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) }) val inventory = loadout.inventory.map(entry => { InventoryItem(BuildSimplifiedPattern(entry.item), entry.index) })
Terminal.InfantryLoadout(loadout.ExoSuit, loadout.Subtype, holsters, inventory) Terminal.InfantryLoadout(loadout.exosuit, loadout.subtype, holsters, inventory)
case None => case Some(_) | None =>
Terminal.NoDeal() 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 // Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.terminals 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, * 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> * 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. * 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 * @param tdef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields
*/ */
class ProximityTerminal(tdef : MedicalTerminalDefinition) extends Terminal(tdef) { class ProximityTerminal(tdef : TerminalDefinition with ProximityDefinition) extends Terminal(tdef) with ProximityUnit
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
}
}
object ProximityTerminal { object ProximityTerminal {
/** /**
* Overloaded constructor. * Overloaded constructor.
* @param tdef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields * @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) new ProximityTerminal(tdef)
} }
@ -45,7 +29,7 @@ object ProximityTerminal {
* @param context a context to allow the object to properly set up `ActorSystem` functionality * @param context a context to allow the object to properly set up `ActorSystem` functionality
* @return the `Terminal` object * @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 import akka.actor.Props
val obj = ProximityTerminal(tdef) val obj = ProximityTerminal(tdef)
obj.Actor = context.actorOf(Props(classOf[ProximityTerminalControl], obj), s"${tdef.Name}_$id") obj.Actor = context.actorOf(Props(classOf[ProximityTerminalControl], obj), s"${tdef.Name}_$id")

View file

@ -2,36 +2,24 @@
package net.psforever.objects.serverobject.terminals package net.psforever.objects.serverobject.terminals
import akka.actor.Actor import akka.actor.Actor
import net.psforever.objects.serverobject.CommonMessages
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior} 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`. * 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, * 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`. * it returns the same type of messages - wrapped in a `TerminalMessage` - to the `sender`.
* @param term the proximity unit (terminal) * @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 FactionObject : FactionAffinity = term
def receive : Receive = checkBehavior.orElse { def TerminalObject : Terminal with ProximityUnit = term
case CommonMessages.Use(player) =>
val hadNoUsers = term.NumberUsers == 0
if(term.AddUser(player.GUID) == 1 && hadNoUsers) {
sender ! TerminalMessage(player, null, Terminal.StartProximityEffect(term))
}
case CommonMessages.Unuse(player) => def receive : Receive = checkBehavior
val hadUsers = term.NumberUsers > 0 .orElse(proximityBehavior)
if(term.RemoveUser(player.GUID) == 0 && hadUsers) { .orElse {
sender ! TerminalMessage(player, null, Terminal.StopProximityEffect(term)) case _ => ;
} }
case _ =>
sender ! Terminal.NoDeal()
}
override def toString : String = term.Definition.Name 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 package net.psforever.objects.serverobject.terminals
import net.psforever.objects.Player import net.psforever.objects.Player
import net.psforever.objects.definition.VehicleDefinition
import net.psforever.objects.serverobject.structures.Amenity import net.psforever.objects.serverobject.structures.Amenity
import net.psforever.packet.game.{ItemTransactionMessage, PlanetSideGUID} import net.psforever.packet.game.{ItemTransactionMessage, PlanetSideGUID}
import net.psforever.types.{TransactionType, Vector3} import net.psforever.types.{TransactionType, Vector3}
@ -73,7 +74,7 @@ class Terminal(tdef : TerminalDefinition) extends Amenity {
case TransactionType.Sell => case TransactionType.Sell =>
tdef.Sell(player, msg) tdef.Sell(player, msg)
case TransactionType.InfantryLoadout => case TransactionType.Loadout =>
tdef.Loadout(player, msg) tdef.Loadout(player, msg)
case _ => 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 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. * Start the special effects caused by a proximity-base service.
* @param terminal the proximity-based unit * @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. * Stop the special effects caused by a proximity-base service.
* @param terminal the proximity-based unit * @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. * Overloaded constructor.

View file

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

View file

@ -312,7 +312,7 @@ object GamePacketOpcode extends Enumeration {
= Value = Value
private def noDecoder(opcode : GamePacketOpcode.Type) = (a : BitVector) => 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 /// 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 /// 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 // OPCODES 0xc0-cf
case 0xc0 => noDecoder(CaptureFlagUpdateMessage) case 0xc0 => noDecoder(CaptureFlagUpdateMessage)
case 0xc1 => noDecoder(VanuModuleUpdateMessage) case 0xc1 => noDecoder(VanuModuleUpdateMessage)
case 0xc2 => noDecoder(FacilityBenefitShieldChargeRequestMessage) case 0xc2 => game.FacilityBenefitShieldChargeRequestMessage.decode
case 0xc3 => game.ProximityTerminalUseMessage.decode case 0xc3 => game.ProximityTerminalUseMessage.decode
case 0xc4 => game.QuantityDeltaUpdateMessage.decode case 0xc4 => game.QuantityDeltaUpdateMessage.decode
case 0xc5 => noDecoder(ChainLashMessage) case 0xc5 => noDecoder(ChainLashMessage)
@ -608,7 +608,7 @@ object GamePacketOpcode extends Enumeration {
case 0xf1 => game.MailMessage.decode case 0xf1 => game.MailMessage.decode
case 0xf2 => noDecoder(GameVarUpdate) case 0xf2 => noDecoder(GameVarUpdate)
case 0xf3 => noDecoder(ClientCheatedMessage) case 0xf3 => noDecoder(ClientCheatedMessage)
case default => noDecoder(opcode) case _ => noDecoder(opcode)
} }
implicit val codec: Codec[this.Value] = PacketHelpers.createEnumerationCodec(this, uint8L) 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 package net.psforever.packet.game
import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket} import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
import net.psforever.types.LoadoutType
import scodec.Codec import scodec.Codec
import scodec.codecs._ import scodec.codecs._
import shapeless.{::, HNil} 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. * 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> * This does not match the same two field numbering system as in `ArmorChangedMessage` packets.<br>
* <br> * <br>
* Lists:<br>
* `
* 0 - Equipment Terminal (infantry)<br>
* 1 - Repair/Rearm Silo (standard vehicles)<br>
* `
* <br>
* Armors:<br> * Armors:<br>
* ` * `
* 1 - Agile<br> * 1 - Agile<br>
@ -33,13 +28,7 @@ import shapeless.{::, HNil}
* 6 - AV MAX<br> * 6 - AV MAX<br>
* ` * `
* <br> * <br>
* Exploration 1:<br> * Exploration:<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>
* There are three unaccounted exo-suit indices - 0, 3, and 7; * 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. * 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`). * 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 label the identifier for this entry
* @param armor the type of exo-suit, if an Infantry loadout * @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, player_guid : PlanetSideGUID,
line : Int, line : Int,
label : String, label : String,
armor : Option[Int] = None) armor : Option[Int])
extends PlanetSideGamePacket { extends PlanetSideGamePacket {
type Packet = FavoritesMessage type Packet = FavoritesMessage
def opcode = GamePacketOpcode.FavoritesMessage def opcode = GamePacketOpcode.FavoritesMessage
@ -62,12 +51,36 @@ final case class FavoritesMessage(list : Int,
} }
object FavoritesMessage extends Marshallable[FavoritesMessage] { 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) :: ("player_guid" | PlanetSideGUID.codec) ::
("line" | uint4L) :: ("line" | uint4L) ::
("label" | PacketHelpers.encodedWideStringAligned(2)) :: ("label" | PacketHelpers.encodedWideStringAligned(2)) ::
conditional(value == 0, "armor" | uintL(3)) conditional(value == LoadoutType.Infantry, "armor" | uintL(3))
}).xmap[FavoritesMessage] ( }).xmap[FavoritesMessage] (
{ {
case lst :: guid :: ln :: str :: arm :: HNil => case lst :: guid :: ln :: str :: arm :: HNil =>
@ -75,7 +88,7 @@ object FavoritesMessage extends Marshallable[FavoritesMessage] {
}, },
{ {
case FavoritesMessage(lst, guid, ln, str, arm) => 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 lst :: guid :: ln :: str :: armset :: HNil
} }
) )

View file

@ -2,21 +2,33 @@
package net.psforever.packet.game package net.psforever.packet.game
import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket} import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
import net.psforever.types.LoadoutType
import scodec.Codec import scodec.Codec
import scodec.codecs._ import scodec.codecs._
object FavoritesAction extends Enumeration { object FavoritesAction extends Enumeration {
type Type = Value type Type = Value
val Unknown, val
Save, Unknown,
Delete = Value Save,
Delete
= Value
implicit val codec = PacketHelpers.createEnumerationCodec(this, uint2L) 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, final case class FavoritesRequest(player_guid : PlanetSideGUID,
unk : Int, list : LoadoutType.Value,
action : FavoritesAction.Value, action : FavoritesAction.Value,
line : Int, line : Int,
label : Option[String]) label : Option[String])
@ -29,7 +41,7 @@ final case class FavoritesRequest(player_guid : PlanetSideGUID,
object FavoritesRequest extends Marshallable[FavoritesRequest] { object FavoritesRequest extends Marshallable[FavoritesRequest] {
implicit val codec : Codec[FavoritesRequest] = ( implicit val codec : Codec[FavoritesRequest] = (
("player_guid" | PlanetSideGUID.codec) :: ("player_guid" | PlanetSideGUID.codec) ::
("unk" | uint2L) :: ("list" | LoadoutType.codec) ::
(("action" | FavoritesAction.codec) >>:~ { action => (("action" | FavoritesAction.codec) >>:~ { action =>
("line" | uint4L) :: ("line" | uint4L) ::
conditional(action == FavoritesAction.Save, "label" | PacketHelpers.encodedWideString) 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 Sell, // or forget on certif term
Unk4, Unk4,
Unk5, Unk5,
InfantryLoadout, Loadout,
Unk7 Unk7
= Value = 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 org.specs2.mutable._
import net.psforever.packet._ import net.psforever.packet._
import net.psforever.packet.game._ import net.psforever.packet.game._
import net.psforever.types.LoadoutType
import scodec.bits._ import scodec.bits._
class FavoritesMessageTest extends Specification { class FavoritesMessageTest extends Specification {
@ -13,7 +14,7 @@ class FavoritesMessageTest extends Specification {
"decode (for infantry)" in { "decode (for infantry)" in {
PacketCoding.DecodePacket(stringInfantry).require match { PacketCoding.DecodePacket(stringInfantry).require match {
case FavoritesMessage(list, player_guid, line, label, armor) => case FavoritesMessage(list, player_guid, line, label, armor) =>
list mustEqual 0 list mustEqual LoadoutType.Infantry
player_guid mustEqual PlanetSideGUID(3760) player_guid mustEqual PlanetSideGUID(3760)
line mustEqual 0 line mustEqual 0
label mustEqual "Agile (basic)" label mustEqual "Agile (basic)"
@ -25,7 +26,7 @@ class FavoritesMessageTest extends Specification {
} }
"encode (for infantry)" in { "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 val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual stringInfantry pkt mustEqual stringInfantry
@ -34,7 +35,7 @@ class FavoritesMessageTest extends Specification {
"decode (for vehicles)" in { "decode (for vehicles)" in {
PacketCoding.DecodePacket(stringVehicles).require match { PacketCoding.DecodePacket(stringVehicles).require match {
case FavoritesMessage(list, player_guid, line, label, armor) => case FavoritesMessage(list, player_guid, line, label, armor) =>
list mustEqual 1 list mustEqual LoadoutType.Vehicle
player_guid mustEqual PlanetSideGUID(4210) player_guid mustEqual PlanetSideGUID(4210)
line mustEqual 0 line mustEqual 0
label mustEqual "Skyguard" label mustEqual "Skyguard"
@ -45,7 +46,7 @@ class FavoritesMessageTest extends Specification {
} }
"encode (for vehicles)" in { "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 val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual stringVehicles pkt mustEqual stringVehicles

View file

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

View file

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

View file

@ -2,6 +2,7 @@
package objects package objects
import net.psforever.objects._ import net.psforever.objects._
import net.psforever.objects.loadouts._
import net.psforever.types.{CharacterGender, ExoSuitType, PlanetSideEmpire} import net.psforever.types.{CharacterGender, ExoSuitType, PlanetSideEmpire}
import net.psforever.objects.GlobalDefinitions._ import net.psforever.objects.GlobalDefinitions._
import org.specs2.mutable._ import org.specs2.mutable._
@ -36,14 +37,14 @@ class LoadoutTest extends Specification {
"create a loadout that contains a player's inventory" in { "create a loadout that contains a player's inventory" in {
val player = CreatePlayer() val player = CreatePlayer()
val obj = Loadout.Create(player, "test") val obj = Loadout.Create(player, "test").asInstanceOf[InfantryLoadout]
obj.Label mustEqual "test" obj.label mustEqual "test"
obj.ExoSuit mustEqual obj.ExoSuit obj.exosuit mustEqual ExoSuitType.Standard
obj.Subtype mustEqual 0 obj.subtype mustEqual 0
obj.VisibleSlots.length mustEqual 3 obj.visible_slots.length mustEqual 3
val holsters = obj.VisibleSlots.sortBy(_.index) val holsters = obj.visible_slots.sortBy(_.index)
holsters.head.index mustEqual 0 holsters.head.index mustEqual 0
holsters.head.item.asInstanceOf[Loadout.ShorthandTool].definition mustEqual beamer holsters.head.item.asInstanceOf[Loadout.ShorthandTool].definition mustEqual beamer
holsters(1).index mustEqual 2 holsters(1).index mustEqual 2
@ -51,8 +52,8 @@ class LoadoutTest extends Specification {
holsters(2).index mustEqual 4 holsters(2).index mustEqual 4
holsters(2).item.asInstanceOf[Loadout.ShorthandTool].definition mustEqual forceblade holsters(2).item.asInstanceOf[Loadout.ShorthandTool].definition mustEqual forceblade
obj.Inventory.length mustEqual 5 obj.inventory.length mustEqual 5
val inventory = obj.Inventory.sortBy(_.index) val inventory = obj.inventory.sortBy(_.index)
inventory.head.index mustEqual 6 inventory.head.index mustEqual 6
inventory.head.item.asInstanceOf[Loadout.ShorthandConstructionItem].definition mustEqual ace inventory.head.item.asInstanceOf[Loadout.ShorthandConstructionItem].definition mustEqual ace
inventory(1).index mustEqual 9 inventory(1).index mustEqual 9
@ -65,26 +66,86 @@ class LoadoutTest extends Specification {
inventory(4).item.asInstanceOf[Loadout.ShorthandSimpleItem].definition mustEqual remote_electronics_kit 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 { "distinguish MAX subtype information" in {
val player = CreatePlayer() val player = CreatePlayer()
val slot = player.Slot(0) val slot = player.Slot(0)
slot.Equipment = None //only an unequipped slot can have its Equipment Size changed (Rifle -> Max) slot.Equipment = None //only an unequipped slot can have its Equipment Size changed (Rifle -> Max)
Player.SuitSetup(player, ExoSuitType.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 = None
slot.Equipment = Tool(trhev_dualcycler) slot.Equipment = Tool(trhev_dualcycler)
val ldout2 = Loadout.Create(player, "cycler") val ldout2 = Loadout.Create(player, "cycler").asInstanceOf[InfantryLoadout]
slot.Equipment = None slot.Equipment = None
slot.Equipment = Tool(trhev_pounder) slot.Equipment = Tool(trhev_pounder)
val ldout3 = Loadout.Create(player, "pounder") val ldout3 = Loadout.Create(player, "pounder").asInstanceOf[InfantryLoadout]
slot.Equipment = None slot.Equipment = None
slot.Equipment = Tool(trhev_burster) slot.Equipment = Tool(trhev_burster)
val ldout4 = Loadout.Create(player, "burster") val ldout4 = Loadout.Create(player, "burster").asInstanceOf[InfantryLoadout]
ldout1.Subtype mustEqual 0 ldout1.subtype mustEqual 0
ldout2.Subtype mustEqual 1 ldout2.subtype mustEqual 1
ldout3.Subtype mustEqual 2 ldout3.subtype mustEqual 2
ldout4.Subtype mustEqual 3 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(0).UtilType mustEqual UtilityType.order_terminalb
filteredMap(2).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 player.ExoSuit = ExoSuitType.MAX
avatar.SaveLoadout(player, "test2", 1) 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) 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() terminal.Request(player, msg2) mustEqual Terminal.NoDeal()
} }
} }

View file

@ -72,12 +72,54 @@ class OrderTerminalTest extends Specification {
terminal.Request(player, msg) mustEqual Terminal.NoDeal() terminal.Request(player, msg) mustEqual Terminal.NoDeal()
} }
//TODO loudout tests
"player can not buy equipment from the wrong page ('9mmbullet_AP', page 1)" in { "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)) val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Buy, 1, "9mmbullet_AP", 0, PlanetSideGUID(0))
terminal.Request(player, msg) mustEqual Terminal.NoDeal() 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) val (_, terminal) = TerminalControlTest.SetUpAgents(GlobalDefinitions.medical_terminal, PlanetSideEmpire.TR)
terminal.Actor !"hello" terminal.Actor !"hello"
val reply = receiveOne(Duration.create(500, "ms")) expectNoMsg(Duration.create(500, "ms"))
assert(reply.isInstanceOf[Terminal.NoDeal])
} }
} }

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) val (_, terminal) = TerminalControlTest.SetUpAgents(GlobalDefinitions.cert_terminal, PlanetSideEmpire.TR)
terminal.Actor !"hello" terminal.Actor !"hello"
val reply = receiveOne(Duration.create(500, "ms")) expectNoMsg(Duration.create(500, "ms"))
assert(reply.isInstanceOf[Terminal.NoDeal])
} }
} }

View file

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

View file

@ -110,9 +110,21 @@ object Maps {
LocalObject(1576, Terminal.Constructor(order_terminal)) LocalObject(1576, Terminal.Constructor(order_terminal))
LocalObject(1577, Terminal.Constructor(order_terminal)) LocalObject(1577, Terminal.Constructor(order_terminal))
LocalObject(1578, 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(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(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(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(2239, Terminal.Constructor(spawn_terminal))
LocalObject(2244, Terminal.Constructor(spawn_terminal)) LocalObject(2244, Terminal.Constructor(spawn_terminal))
LocalObject(2245, Terminal.Constructor(spawn_terminal)) LocalObject(2245, Terminal.Constructor(spawn_terminal))
@ -214,6 +226,18 @@ object Maps {
ObjectToBuilding(1576, 2) ObjectToBuilding(1576, 2)
ObjectToBuilding(1577, 2) ObjectToBuilding(1577, 2)
ObjectToBuilding(1578, 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(2145, 2)
ObjectToBuilding(2146, 2) ObjectToBuilding(2146, 2)
ObjectToBuilding(2147, 2) ObjectToBuilding(2147, 2)
@ -351,6 +375,10 @@ object Maps {
LocalObject(1591, Terminal.Constructor(order_terminal)) LocalObject(1591, Terminal.Constructor(order_terminal))
LocalObject(1592, Terminal.Constructor(order_terminal)) LocalObject(1592, Terminal.Constructor(order_terminal))
LocalObject(1593, 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(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(2157, SpawnTube.Constructor(respawn_tube_tower, Vector3(4364.633f, 3977.7266f, 228.1875f), Vector3(0, 0, 90)))
LocalObject(2333, Door.Constructor) //spawn tube door LocalObject(2333, Door.Constructor) //spawn tube door
@ -374,6 +402,10 @@ object Maps {
ObjectToBuilding(1591, 49) ObjectToBuilding(1591, 49)
ObjectToBuilding(1592, 49) ObjectToBuilding(1592, 49)
ObjectToBuilding(1593, 49) ObjectToBuilding(1593, 49)
ObjectToBuilding(1846, 49)
ObjectToBuilding(1847, 49)
ObjectToBuilding(1849, 49)
ObjectToBuilding(1850, 49)
ObjectToBuilding(2156, 49) ObjectToBuilding(2156, 49)
ObjectToBuilding(2157, 49) ObjectToBuilding(2157, 49)
ObjectToBuilding(2333, 49) ObjectToBuilding(2333, 49)
@ -428,6 +460,8 @@ object Maps {
def Building2() : Unit = { def Building2() : Unit = {
//HART building C //HART building C
LocalBuilding(2, FoundationBuilder(Building.Structure(StructureType.Building))) 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(186, Terminal.Constructor(cert_terminal))
LocalObject(187, Terminal.Constructor(cert_terminal)) LocalObject(187, Terminal.Constructor(cert_terminal))
LocalObject(188, 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(1087, Terminal.Constructor(implant_terminal_interface)) //TODO guid not correct
LocalObject(1088, 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 LocalObject(1089, Terminal.Constructor(implant_terminal_interface)) //TODO guid not correct
ObjectToBuilding(12, 2)
ObjectToBuilding(13, 2)
ObjectToBuilding(186, 2) ObjectToBuilding(186, 2)
ObjectToBuilding(187, 2) ObjectToBuilding(187, 2)
ObjectToBuilding(188, 2) ObjectToBuilding(188, 2)

View file

@ -9,12 +9,14 @@ import scodec.Attempt.{Failure, Successful}
import scodec.bits._ import scodec.bits._
import org.log4s.MDC import org.log4s.MDC
import MDCContextAware.Implicits._ import MDCContextAware.Implicits._
import csr.{CSRWarp, CSRZone, Traveler}
import net.psforever.objects.GlobalDefinitions._ import net.psforever.objects.GlobalDefinitions._
import services.ServiceManager.Lookup import services.ServiceManager.Lookup
import net.psforever.objects._ import net.psforever.objects._
import net.psforever.objects.definition.ToolDefinition import net.psforever.objects.definition.ToolDefinition
import net.psforever.objects.definition.converter.CorpseConverter import net.psforever.objects.definition.converter.CorpseConverter
import net.psforever.objects.equipment._ import net.psforever.objects.equipment._
import net.psforever.objects.loadouts._
import net.psforever.objects.guid.{GUIDTask, Task, TaskResolver} import net.psforever.objects.guid.{GUIDTask, Task, TaskResolver}
import net.psforever.objects.inventory.{Container, GridInventory, InventoryItem} import net.psforever.objects.inventory.{Container, GridInventory, InventoryItem}
import net.psforever.objects.serverobject.mount.Mountable 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.{VehicleSpawnControl, VehicleSpawnPad}
import net.psforever.objects.serverobject.pad.process.{AutoDriveControls, VehicleSpawnControlGuided} import net.psforever.objects.serverobject.pad.process.{AutoDriveControls, VehicleSpawnControlGuided}
import net.psforever.objects.serverobject.structures.{Building, StructureType, WarpGate} import net.psforever.objects.serverobject.structures.{Building, StructureType, WarpGate}
import net.psforever.objects.serverobject.terminals.{MatrixTerminalDefinition, ProximityTerminal, Terminal} import net.psforever.objects.serverobject.terminals._
import net.psforever.objects.serverobject.terminals.Terminal
import net.psforever.objects.serverobject.terminals.Terminal.TerminalMessage import net.psforever.objects.serverobject.terminals.Terminal.TerminalMessage
import net.psforever.objects.serverobject.tube.SpawnTube import net.psforever.objects.serverobject.tube.SpawnTube
import net.psforever.objects.vehicles.{AccessPermissionGroup, Utility, VehicleLockState} import net.psforever.objects.vehicles.{AccessPermissionGroup, Utility, VehicleLockState}
@ -39,6 +40,7 @@ import net.psforever.types._
import services._ import services._
import services.avatar.{AvatarAction, AvatarResponse, AvatarServiceMessage, AvatarServiceResponse} import services.avatar.{AvatarAction, AvatarResponse, AvatarServiceMessage, AvatarServiceResponse}
import services.local.{LocalAction, LocalResponse, LocalServiceMessage, LocalServiceResponse} import services.local.{LocalAction, LocalResponse, LocalServiceMessage, LocalServiceResponse}
import services.vehicle.VehicleAction.UnstowEquipment
import services.vehicle.{VehicleAction, VehicleResponse, VehicleServiceMessage, VehicleServiceResponse} import services.vehicle.{VehicleAction, VehicleResponse, VehicleServiceMessage, VehicleServiceResponse}
import scala.annotation.tailrec import scala.annotation.tailrec
@ -70,6 +72,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
var usingProximityTerminal : Set[PlanetSideGUID] = Set.empty var usingProximityTerminal : Set[PlanetSideGUID] = Set.empty
var delayedProximityTerminalResets : Map[PlanetSideGUID, Cancellable] = Map.empty var delayedProximityTerminalResets : Map[PlanetSideGUID, Cancellable] = Map.empty
var controlled : Option[Int] = None //keep track of avatar's ServerVehicleOverride state var controlled : Option[Int] = None //keep track of avatar's ServerVehicleOverride state
var traveler : Traveler = null
var clientKeepAlive : Cancellable = DefaultCancellable.obj var clientKeepAlive : Cancellable = DefaultCancellable.obj
var progressBarUpdate : 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) => 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) //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) { if(tplayer_guid != guid) {
@ -829,7 +837,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
case Terminal.InfantryLoadout(exosuit, subtype, holsters, inventory) => case Terminal.InfantryLoadout(exosuit, subtype, holsters, inventory) =>
//TODO optimizations against replacing Equipment with the exact same Equipment and potentially for recycling existing Equipment //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}") 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 dropPred = DropPredicate(tplayer)
val (dropHolsters, beforeHolsters) = clearHolsters(tplayer.Holsters().iterator).partition(dropPred) val (dropHolsters, beforeHolsters) = clearHolsters(tplayer.Holsters().iterator).partition(dropPred)
val (dropInventory, beforeInventory) = tplayer.Inventory.Clear().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) taskResolver ! GUIDTask.UnregisterEquipment(elem.obj)(continent.GUID)
}) })
//report change //report change
sendResponse(ArmorChangedMessage(tplayer.GUID, exosuit, 0)) sendResponse(ArmorChangedMessage(tplayer.GUID, exosuit, subtype))
avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.ArmorChanged(tplayer.GUID, exosuit, subtype)) avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.ArmorChanged(tplayer.GUID, exosuit, subtype))
sendResponse(PlanetsideAttributeMessage(tplayer.GUID, 4, tplayer.Armor)) sendResponse(PlanetsideAttributeMessage(tplayer.GUID, 4, tplayer.Armor))
avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.PlanetsideAttribute(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 val objDef = obj.Definition
avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.EquipmentOnGround(tplayer.GUID, pos, orient, objDef.ObjectId, obj.GUID, objDef.Packet.ConstructorData(obj).get)) 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) => case Terminal.LearnCertification(cert, cost) =>
if(!tplayer.Certifications.contains(cert)) { if(!tplayer.Certifications.contains(cert)) {
@ -1054,6 +1108,12 @@ class WorldSessionActor extends Actor with MDCContextAware {
case VehicleSpawnPad.StartPlayerSeatedInVehicle(vehicle, pad) => case VehicleSpawnPad.StartPlayerSeatedInVehicle(vehicle, pad) =>
val vehicle_guid = vehicle.GUID 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, 22, 1L)) //mount points off?
sendResponse(PlanetsideAttributeMessage(vehicle_guid, 21, player.GUID.guid)) //fte and ownership? 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) vehicleService ! VehicleServiceMessage.UnscheduleDeconstruction(vehicle_guid)
} }
sendResponse(PlanetsideAttributeMessage(vehicle_guid, 22, 0L)) //mount points on? 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, 68, 0L)) //???
sendResponse(PlanetsideAttributeMessage(vehicle_guid, 113, 0L)) //??? sendResponse(PlanetsideAttributeMessage(vehicle_guid, 113, 0L)) //???
ReloadVehicleAccessPermissions(vehicle) ReloadVehicleAccessPermissions(vehicle)
@ -1147,6 +1207,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
val popNC = poplist.count(_.faction == PlanetSideEmpire.NC) val popNC = poplist.count(_.faction == PlanetSideEmpire.NC)
val popVS = poplist.count(_.faction == PlanetSideEmpire.VS) val popVS = poplist.count(_.faction == PlanetSideEmpire.VS)
StartBundlingPackets()
zone.Buildings.foreach({ case(id, building) => initBuilding(continentNumber, id, building) }) zone.Buildings.foreach({ case(id, building) => initBuilding(continentNumber, id, building) })
sendResponse(ZonePopulationUpdateMessage(continentNumber, 414, 138, popTR, 138, popNC, 138, popVS, 138, popBO)) sendResponse(ZonePopulationUpdateMessage(continentNumber, 414, 138, popTR, 138, popNC, 138, popVS, 138, popBO))
sendResponse(ContinentalLockUpdateMessage(continentNumber, PlanetSideEmpire.NEUTRAL)) sendResponse(ContinentalLockUpdateMessage(continentNumber, PlanetSideEmpire.NEUTRAL))
@ -1231,7 +1292,9 @@ class WorldSessionActor extends Actor with MDCContextAware {
RequestSanctuaryZoneSpawn(player, zone_number) RequestSanctuaryZoneSpawn(player, zone_number)
case InterstellarCluster.ClientInitializationComplete() => case InterstellarCluster.ClientInitializationComplete() =>
StopBundlingPackets()
LivePlayerList.Add(sessionId, avatar) LivePlayerList.Add(sessionId, avatar)
traveler = new Traveler(self, continent.Id)
//PropertyOverrideMessage //PropertyOverrideMessage
sendResponse(PlanetsideAttributeMessage(PlanetSideGUID(0), 112, 1)) sendResponse(PlanetsideAttributeMessage(PlanetSideGUID(0), 112, 1))
sendResponse(ReplicationStreamMessage(5, Some(6), Vector(SquadListing()))) //clear squad list sendResponse(ReplicationStreamMessage(5, Some(6), Vector(SquadListing()))) //clear squad list
@ -1280,6 +1343,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
case SetCurrentAvatar(tplayer) => case SetCurrentAvatar(tplayer) =>
player = tplayer player = tplayer
val guid = tplayer.GUID val guid = tplayer.GUID
StartBundlingPackets()
sendResponse(SetCurrentAvatarMessage(guid,0,0)) sendResponse(SetCurrentAvatarMessage(guid,0,0))
sendResponse(PlayerStateShiftMessage(ShiftState(1, tplayer.Position, tplayer.Orientation.z))) sendResponse(PlayerStateShiftMessage(ShiftState(1, tplayer.Position, tplayer.Orientation.z)))
if(spectator) { if(spectator) {
@ -1312,6 +1376,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
//SquadDefinitionActionMessage and SquadDetailDefinitionUpdateMessage //SquadDefinitionActionMessage and SquadDetailDefinitionUpdateMessage
//MapObjectStateBlockMessage and ObjectCreateMessage //MapObjectStateBlockMessage and ObjectCreateMessage
//TacticsMessage //TacticsMessage
StopBundlingPackets()
sendResponse(ChatMsg(ChatMessageType.CMT_EXPANSIONS, true, "", "1 on", None)) //CC on sendResponse(ChatMsg(ChatMessageType.CMT_EXPANSIONS, true, "", "1 on", None)) //CC on
@ -1432,9 +1497,10 @@ class WorldSessionActor extends Actor with MDCContextAware {
AwardBattleExperiencePoints(avatar, 1000000L) AwardBattleExperiencePoints(avatar, 1000000L)
player = new Player(avatar) player = new Player(avatar)
//player.Position = Vector3(3561.0f, 2854.0f, 90.859375f) //home3, HART C //player.Position = Vector3(3561.0f, 2854.0f, 90.859375f) //home3, HART C
//player.Orientation = Vector3(0f, 0f, 90f) player.Position = Vector3(3940.3984f, 4343.625f, 266.45312f)
player.Position = Vector3(4262.211f ,4067.0625f ,262.35938f) //z6, Akna.tower player.Orientation = Vector3(0f, 0f, 90f)
player.Orientation = Vector3(0f, 0f, 132.1875f) //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.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(0).Equipment = SimpleItem(remote_electronics_kit) //Tool(GlobalDefinitions.StandardPistol(player.Faction))
player.Slot(2).Equipment = Tool(punisher) //suppressor player.Slot(2).Equipment = Tool(punisher) //suppressor
@ -1477,6 +1543,8 @@ class WorldSessionActor extends Actor with MDCContextAware {
case msg @ BeginZoningMessage() => case msg @ BeginZoningMessage() =>
log.info("Reticulating splines ...") log.info("Reticulating splines ...")
traveler.zone = continent.Id
StartBundlingPackets()
configZone(continent) configZone(continent)
sendResponse(TimeOfDayMessage(1191182336)) sendResponse(TimeOfDayMessage(1191182336))
@ -1550,6 +1618,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
case _ => ; case _ => ;
} }
}) })
StopBundlingPackets()
avatarService ! Service.Join(player.Continent) avatarService ! Service.Join(player.Continent)
localService ! Service.Join(player.Continent) localService ! Service.Join(player.Continent)
vehicleService ! Service.Join(player.Continent) vehicleService ! Service.Join(player.Continent)
@ -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 // TODO: Prevents log spam, but should be handled correctly
if (messagetype != ChatMessageType.CMT_TOGGLE_GM) { if (messagetype != ChatMessageType.CMT_TOGGLE_GM) {
log.info("Chat: " + msg) log.info("Chat: " + msg)
@ -1765,15 +1861,15 @@ class WorldSessionActor extends Actor with MDCContextAware {
case x :: xs => case x :: xs =>
val (deleteFunc, modifyFunc) : ((Int, AmmoBox)=>Unit, (AmmoBox, Int)=>Unit) = obj match { val (deleteFunc, modifyFunc) : ((Int, AmmoBox)=>Unit, (AmmoBox, Int)=>Unit) = obj match {
case (veh : Vehicle) => case (veh : Vehicle) =>
(DeleteAmmunitionInVehicle(veh), ModifyAmmunitionInVehicle(veh)) (DeleteEquipmentFromVehicle(veh), ModifyAmmunitionInVehicle(veh))
case _ => case _ =>
(DeleteAmmunition(obj), ModifyAmmunition(obj)) (DeleteEquipment(obj), ModifyAmmunition(obj))
} }
val (stowFuncTask, stowFunc) : ((Int, AmmoBox)=>TaskResolver.GiveTask, (Int, AmmoBox)=>Unit) = obj match { val (stowFuncTask, stowFunc) : ((Int, AmmoBox)=>TaskResolver.GiveTask, (Int, AmmoBox)=>Unit) = obj match {
case (veh : Vehicle) => case (veh : Vehicle) =>
(StowNewAmmunitionInVehicles(veh), StowAmmunitionInVehicles(veh)) (StowNewEquipmentInVehicle(veh), StowEquipmentInVehicles(veh))
case _ => case _ =>
(StowNewAmmunition(obj), StowAmmunition(obj)) (StowNewEquipment(obj), StowEquipment(obj))
} }
xs.foreach(item => { xs.foreach(item => {
obj.Inventory -= x.start obj.Inventory -= x.start
@ -1985,9 +2081,9 @@ class WorldSessionActor extends Actor with MDCContextAware {
case x :: xs => case x :: xs =>
val (deleteFunc, modifyFunc) : ((Int, AmmoBox)=>Unit, (AmmoBox, Int)=>Unit) = obj match { val (deleteFunc, modifyFunc) : ((Int, AmmoBox)=>Unit, (AmmoBox, Int)=>Unit) = obj match {
case (veh : Vehicle) => case (veh : Vehicle) =>
(DeleteAmmunitionInVehicle(veh), ModifyAmmunitionInVehicle(veh)) (DeleteEquipmentFromVehicle(veh), ModifyAmmunitionInVehicle(veh))
case _ => case _ =>
(DeleteAmmunition(obj), ModifyAmmunition(obj)) (DeleteEquipment(obj), ModifyAmmunition(obj))
} }
xs.foreach(item => { xs.foreach(item => {
deleteFunc(item.start, item.obj.asInstanceOf[AmmoBox]) deleteFunc(item.start, item.obj.asInstanceOf[AmmoBox])
@ -2079,7 +2175,14 @@ class WorldSessionActor extends Actor with MDCContextAware {
findFunc(parent) findFunc(parent)
case None => case None =>
None None
}) match { })
.orElse(FindLocalVehicle match {
case Some(parent) =>
findFunc(parent)
case None =>
None
})
match {
case Some((parent, Some(slot))) => case Some((parent, Some(slot))) =>
taskResolver ! RemoveEquipmentFromSlot(parent, obj, slot) taskResolver ! RemoveEquipmentFromSlot(parent, obj, slot)
log.info(s"RequestDestroy: equipment $object_guid") 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) //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)) 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 { else {
sendResponse(UseItemMessage(avatar_guid, unk1, object_guid, unk2, unk3, unk4, unk5, unk6, unk7, unk8, itemType)) 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)) 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, _) => case msg @ ProximityTerminalUseMessage(player_guid, object_guid, _) =>
log.info(s"ProximityTerminal: $msg") log.info(s"ProximityTerminalUse: $msg")
continent.GUID(object_guid) match { continent.GUID(object_guid) match {
case Some(obj : ProximityTerminal) => case Some(obj : Terminal with ProximityUnit) =>
if(usingProximityTerminal.contains(object_guid)) { if(usingProximityTerminal.contains(object_guid)) {
SelectProximityUnit(obj) SelectProximityUnit(obj)
} }
@ -2319,9 +2432,9 @@ class WorldSessionActor extends Actor with MDCContextAware {
StartUsingProximityUnit(obj) StartUsingProximityUnit(obj)
} }
case Some(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 => 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) => 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") 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) { 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 { action match {
case FavoritesAction.Unknown => ;
case FavoritesAction.Save => case FavoritesAction.Save =>
avatar.SaveLoadout(player, name, line) (if(list == LoadoutType.Infantry) {
sendResponse(FavoritesMessage(0, player_guid, line, name)) 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 => case FavoritesAction.Delete =>
avatar.DeleteLoadout(line) avatar.DeleteLoadout(lineno)
sendResponse(FavoritesMessage(0, player_guid, line, "")) 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) => case msg @ WeaponDelayFireMessage(seq_time, weapon_guid) =>
log.info("WeaponDelayFire: " + msg) log.info("WeaponDelayFire: " + msg)
@ -2562,6 +2701,9 @@ class WorldSessionActor extends Actor with MDCContextAware {
sendResponse(PlanetsideAttributeMessage(object_guid, attribute_type, attribute_value)) 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) => case msg @ BattleplanMessage(char_id, player_name, zonr_id, diagrams) =>
log.info("Battleplan: "+msg) 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. * remove it permanently.
* @param obj the `Container` * @param obj the `Container`
* @param start where the ammunition can be found * @param start where the item can be found
* @param item an object to unregister (should have been the ammunition that was removed); * @param item an object to unregister;
* not explicitly checked * 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 val item_guid = item.GUID
obj.Inventory -= start obj.Slot(start).Equipment = None
//obj.Inventory -= start
taskResolver ! GUIDTask.UnregisterEquipment(item)(continent.GUID) taskResolver ! GUIDTask.UnregisterEquipment(item)(continent.GUID)
sendResponse(ObjectDeleteMessage(item_guid, 0)) 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. * remove it permanently.
* @see `DeleteAmmunition` * @see `DeleteEquipment`
* @param obj the `Vehicle` * @param obj the `Vehicle`
* @param start where the ammunition can be found * @param start where the item can be found
* @param item an object to unregister (should have been the ammunition that was removed); * @param item an object to unregister;
* not explicitly checked * 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 val item_guid = item.GUID
DeleteAmmunition(obj)(start, item) DeleteEquipment(obj)(start, item)
vehicleService ! VehicleServiceMessage(s"${obj.Actor}", VehicleAction.UnstowEquipment(player.GUID, item_guid)) 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. * Announce that an already-registered `AmmoBox` object exists in a given position in some `Container` object's inventory.
* @see `StowAmmunitionInVehicles` * @see `StowEquipmentInVehicles`
* @see `ChangeAmmoMessage` * @see `ChangeAmmoMessage`
* @param obj the `Container` object * @param obj the `Container` object
* @param index an index in `obj`'s inventory * @param index an index in `obj`'s inventory
* @param item an `AmmoBox` * @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 obj.Inventory += index -> item
sendResponse(ObjectAttachMessage(obj.GUID, item.GUID, index)) sendResponse(ObjectAttachMessage(obj.GUID, item.GUID, index))
} }
/** /**
* Announce that an already-registered `AmmoBox` object exists in a given position in some vehicle's inventory. * Announce that an already-registered `AmmoBox` object exists in a given position in some vehicle's inventory.
* @see `StowAmmunition` * @see `StowEquipment`
* @see `ChangeAmmoMessage` * @see `ChangeAmmoMessage`
* @param obj the `Vehicle` object * @param obj the `Vehicle` object
* @param index an index in `obj`'s inventory * @param index an index in `obj`'s inventory
* @param item an `AmmoBox` * @param item an `AmmoBox`
*/ */
def StowAmmunitionInVehicles(obj : Vehicle)(index : Int, item : AmmoBox) : Unit = { def StowEquipmentInVehicles(obj : Vehicle)(index : Int, item : AmmoBox) : Unit = {
StowAmmunition(obj)(index, item) StowEquipment(obj)(index, item)
vehicleService ! VehicleServiceMessage(s"${obj.Actor}", VehicleAction.StowEquipment(player.GUID, obj.GUID, 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 * Prepare tasking that registers an `AmmoBox` object
* and announces that it exists in a given position in some `Container` object's inventory. * and announces that it exists in a given position in some `Container` object's inventory.
* `PutEquipmentInSlot` is the fastest way to achieve these goals. * `PutEquipmentInSlot` is the fastest way to achieve these goals.
* @see `StowNewAmmunitionInVehicles` * @see `StowNewEquipmentInVehicle`
* @see `ChangeAmmoMessage` * @see `ChangeAmmoMessage`
* @param obj the `Container` object * @param obj the `Container` object
* @param index an index in `obj`'s inventory * @param index an index in `obj`'s inventory
* @param item an `AmmoBox` * @param item an `AmmoBox`
* @return a `TaskResolver.GiveTask` chain that executes the action * @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) PutEquipmentInSlot(obj, item, index)
} }
@ -3404,14 +3566,14 @@ class WorldSessionActor extends Actor with MDCContextAware {
* Prepare tasking that registers an `AmmoBox` object * Prepare tasking that registers an `AmmoBox` object
* and announces that it exists in a given position in some vehicle's inventory. * and announces that it exists in a given position in some vehicle's inventory.
* `PutEquipmentInSlot` is the fastest way to achieve these goals. * `PutEquipmentInSlot` is the fastest way to achieve these goals.
* @see `StowNewAmmunition` * @see `StowNewEquipment`
* @see `ChangeAmmoMessage` * @see `ChangeAmmoMessage`
* @param obj the `Container` object * @param obj the `Container` object
* @param index an index in `obj`'s inventory * @param index an index in `obj`'s inventory
* @param item an `AmmoBox` * @param item an `AmmoBox`
* @return a `TaskResolver.GiveTask` chain that executes the action * @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( TaskResolver.GiveTask(
new Task() { new Task() {
private val localService = vehicleService private val localService = vehicleService
@ -3430,7 +3592,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
resolver ! scala.util.Success(this) 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 shooting = None
case 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) { if(flying) {
sendResponse(ChatMsg(ChatMessageType.CMT_FLY, false, "", "off", None)) sendResponse(ChatMsg(ChatMessageType.CMT_FLY, false, "", "off", None))
flying = false 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. * Special note is warranted in the case of a medical terminal or an advanced medical terminal.
* @param terminal the proximity-based unit * @param terminal the proximity-based unit
*/ */
def StartUsingProximityUnit(terminal : ProximityTerminal) : Unit = { def StartUsingProximityUnit(terminal : Terminal with ProximityUnit) : Unit = {
val term_guid = terminal.GUID val term_guid = terminal.GUID
if(!usingProximityTerminal.contains(term_guid)) { if(!usingProximityTerminal.contains(term_guid)) {
usingProximityTerminal += 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. * Other sorts of proximity-based units are put on a timer.
* @param terminal the proximity-based unit * @param terminal the proximity-based unit
*/ */
def StopUsingProximityUnit(terminal : ProximityTerminal) : Unit = { def StopUsingProximityUnit(terminal : Terminal with ProximityUnit) : Unit = {
val term_guid = terminal.GUID val term_guid = terminal.GUID
if(usingProximityTerminal.contains(term_guid)) { if(usingProximityTerminal.contains(term_guid)) {
usingProximityTerminal -= 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. * 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 * @param terminal the proximity-based unit
*/ */
def SetDelayedProximityUnitReset(terminal : ProximityTerminal) : Unit = { def SetDelayedProximityUnitReset(terminal : Terminal with ProximityUnit) : Unit = {
val terminal_guid = terminal.GUID val terminal_guid = terminal.GUID
ClearDelayedProximityUnitReset(terminal_guid) ClearDelayedProximityUnitReset(terminal_guid)
import scala.concurrent.duration._ import scala.concurrent.duration._
@ -4089,7 +4246,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
* and determinig which kind of unit is being utilized. * and determinig which kind of unit is being utilized.
* @param terminal the proximity-based unit * @param terminal the proximity-based unit
*/ */
def SelectProximityUnit(terminal : ProximityTerminal) : Unit = { def SelectProximityUnit(terminal : Terminal with ProximityUnit) : Unit = {
terminal.Definition match { terminal.Definition match {
case GlobalDefinitions.adv_med_terminal | GlobalDefinitions.medical_terminal => case GlobalDefinitions.adv_med_terminal | GlobalDefinitions.medical_terminal =>
ProximityMedicalTerminal(terminal) ProximityMedicalTerminal(terminal)
@ -4098,6 +4255,10 @@ class WorldSessionActor extends Actor with MDCContextAware {
SetDelayedProximityUnitReset(terminal) SetDelayedProximityUnitReset(terminal)
ProximityHealCrystal(terminal) ProximityHealCrystal(terminal)
case GlobalDefinitions.repair_silo =>
SetDelayedProximityUnitReset(terminal)
//TODO insert vehicle repair here; see ProximityMedicalTerminal for example
case _ => ; 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. * If the player is both fully healed and fully repaired, stop using the terminal.
* @param unit the medical 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) { val healthFull : Boolean = if(player.Health < player.MaxHealth) {
HealAction(player) HealAction(player)
} }
@ -4132,7 +4293,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
* If the player is fully healed, stop using the crystal. * If the player is fully healed, stop using the crystal.
* @param unit the healing 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) { val healthFull : Boolean = if(player.Health < player.MaxHealth) {
HealAction(player) HealAction(player)
} }
@ -4232,12 +4393,95 @@ class WorldSessionActor extends Actor with MDCContextAware {
sendResponse(ConnectionClose()) 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)) 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 = { def sendResponse(cont : PlanetSidePacketContainer) : Unit = {
@ -4246,7 +4490,6 @@ class WorldSessionActor extends Actor with MDCContextAware {
} }
def sendResponse(cont : MultiPacketBundle) : Unit = { def sendResponse(cont : MultiPacketBundle) : Unit = {
log.trace("WORLD SEND: " + cont)
sendResponse(cont.asInstanceOf[Any]) sendResponse(cont.asInstanceOf[Any])
} }
@ -4272,7 +4515,7 @@ object WorldSessionActor {
private final case class ListAccountCharacters() private final case class ListAccountCharacters()
private final case class SetCurrentAvatar(tplayer : Player) private final case class SetCurrentAvatar(tplayer : Player)
private final case class VehicleLoaded(vehicle : Vehicle) 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) private final case class UnregisterCorpseOnVehicleDisembark(corpse : Player)
/** /**

View file

@ -88,13 +88,13 @@ object Zones {
val c6 = new Zone("c6", Maps.ugd06, 28) 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. * 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 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 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 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 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 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 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 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 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 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 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 LoadVehicle(vehicle : Vehicle, vtype : Int, vguid : PlanetSideGUID, vdata : ConstructorData) extends Response
final case class MountVehicle(object_guid : PlanetSideGUID, seat : Int) 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( VehicleEvents.publish(
VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.InventoryState(obj, parent_guid, start, con_data)) 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) => case VehicleAction.KickPassenger(player_guid, seat_num, kickedByDriver, vehicle_guid) =>
VehicleEvents.publish( VehicleEvents.publish(
VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.KickPassenger(seat_num, kickedByDriver, vehicle_guid)) 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 { class KickPassengerTest extends ActorTest {
ServiceManager.boot(system) ServiceManager.boot(system)