mirror of
https://github.com/2revoemag/PSF-BotServer.git
synced 2026-04-28 23:35:23 +00:00
Merge pull request #211 from Fate-JH/prox-term-redo
Extracting the Proximity Effect / Repair Rearm Silos
This commit is contained in:
commit
901cf23e63
44 changed files with 1286 additions and 369 deletions
|
|
@ -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 = {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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._
|
||||||
|
|
|
||||||
|
|
@ -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`
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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) })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -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)
|
||||||
|
|
@ -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 })
|
||||||
|
|
|
||||||
|
|
@ -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()
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
}
|
||||||
|
|
@ -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")
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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]
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
16
common/src/main/scala/net/psforever/types/LoadoutType.scala
Normal file
16
common/src/main/scala/net/psforever/types/LoadoutType.scala
Normal 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)
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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])
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
170
common/src/test/scala/objects/terminal/ProximityTest.scala
Normal file
170
common/src/test/scala/objects/terminal/ProximityTest.scala
Normal 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
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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])
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ 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 +29,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 +39,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
|
||||||
|
|
@ -467,6 +468,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.KickPassenger(unk1, unk2, vehicle_guid) =>
|
case VehicleResponse.KickPassenger(unk1, unk2, vehicle_guid) =>
|
||||||
sendResponse(DismountVehicleMsg(guid, unk1, unk2))
|
sendResponse(DismountVehicleMsg(guid, unk1, unk2))
|
||||||
if(tplayer_guid == guid) {
|
if(tplayer_guid == guid) {
|
||||||
|
|
@ -818,7 +824,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)
|
||||||
|
|
@ -837,7 +843,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))
|
||||||
|
|
@ -881,7 +887,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)) {
|
||||||
|
|
@ -1058,7 +1110,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)
|
||||||
|
|
@ -1431,9 +1483,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
|
||||||
|
|
@ -1766,15 +1819,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
|
||||||
|
|
@ -1986,9 +2039,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])
|
||||||
|
|
@ -2080,7 +2133,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")
|
||||||
|
|
@ -2284,6 +2344,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))
|
||||||
}
|
}
|
||||||
|
|
@ -2306,13 +2375,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)
|
||||||
}
|
}
|
||||||
|
|
@ -2320,9 +2390,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) =>
|
||||||
|
|
@ -2359,20 +2429,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)
|
||||||
|
|
@ -2555,6 +2651,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)
|
||||||
|
|
||||||
|
|
@ -3296,32 +3395,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))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3354,27 +3473,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))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3382,14 +3501,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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3397,14 +3516,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
|
||||||
|
|
@ -3423,7 +3542,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))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3992,7 +4111,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
|
||||||
|
|
@ -4013,7 +4132,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
|
||||||
|
|
@ -4032,7 +4151,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._
|
||||||
|
|
@ -4077,7 +4196,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)
|
||||||
|
|
@ -4086,6 +4205,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 _ => ;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -4096,7 +4219,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)
|
||||||
}
|
}
|
||||||
|
|
@ -4120,7 +4243,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)
|
||||||
}
|
}
|
||||||
|
|
@ -4342,7 +4465,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)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -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, unk1 : Int, unk2 : Boolean) extends Action
|
final case class DismountVehicle(player_guid : PlanetSideGUID, unk1 : Int, 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
|
||||||
|
|
|
||||||
|
|
@ -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(unk1 : Int, unk2 : Boolean) extends Response
|
final case class DismountVehicle(unk1 : Int, 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(unk1 : Int, unk2 : Boolean, vehicle_guid : PlanetSideGUID) extends Response
|
final case class KickPassenger(unk1 : Int, unk2 : 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
|
||||||
|
|
|
||||||
|
|
@ -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, unk1, unk2, vehicle_guid) =>
|
case VehicleAction.KickPassenger(player_guid, unk1, unk2, vehicle_guid) =>
|
||||||
VehicleEvents.publish(
|
VehicleEvents.publish(
|
||||||
VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.KickPassenger(unk1, unk2, vehicle_guid))
|
VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.KickPassenger(unk1, unk2, vehicle_guid))
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue