Operational vehicle terminals:

Vehicles can now be pulled from assigned and initialized terminals.  The vehicle's chosen spawn pad controls (or paces) all aspects of the spawning process.  Support Actors ensure that a fully-realized Vehicle will be unloaded and unregistered if left alone, either right after spawning on the pad or after an extended period of time.  The latter half of the procedure used for spawning vehicles is a temporary workaround until future analysis and functionality of the server vehicle override packet is incorporated.

Weapons:

Weapons will now construct their own default magazines thanks to a switch from Ammo.Value to AmmoBoxDefinition in the ToolDefinition.

GenericObjectActionMessage :

The only thing this packet does, at the moment, is obscure the player when he is being promoted into the owner of a vehicle.
This commit is contained in:
FateJH 2017-11-08 21:00:46 -05:00
parent 73d0553b2c
commit 5428bbbfbf
57 changed files with 2957 additions and 538 deletions

View file

@ -0,0 +1,18 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects
/**
* Used to initialize the value of a re-usable `Cancellable` object.
* By convention, it always acts like it has been cancelled before and can be cancelled.
* Should be replaced with pertinent `Cancellable` logic through the initialization of an executor.
*/
object DefaultCancellable {
import akka.actor.Cancellable
protected class InternalCancellable extends Cancellable {
override def cancel : Boolean = true
override def isCancelled : Boolean = true
}
final val obj : Cancellable = new InternalCancellable
}

View file

@ -65,7 +65,7 @@ class ExoSuitDefinition(private val suitType : ExoSuitType.Value) {
object ExoSuitDefinition { object ExoSuitDefinition {
final val Standard = ExoSuitDefinition(ExoSuitType.Standard) final val Standard = ExoSuitDefinition(ExoSuitType.Standard)
Standard.MaxArmor = 50 Standard.MaxArmor = 50
Standard.InventoryScale = new InventoryTile(9,6) Standard.InventoryScale = InventoryTile.Tile96
Standard.InventoryOffset = 6 Standard.InventoryOffset = 6
Standard.Holster(0, EquipmentSize.Pistol) Standard.Holster(0, EquipmentSize.Pistol)
Standard.Holster(2, EquipmentSize.Rifle) Standard.Holster(2, EquipmentSize.Rifle)
@ -73,7 +73,7 @@ object ExoSuitDefinition {
final val Agile = ExoSuitDefinition(ExoSuitType.Agile) final val Agile = ExoSuitDefinition(ExoSuitType.Agile)
Agile.MaxArmor = 100 Agile.MaxArmor = 100
Agile.InventoryScale = new InventoryTile(9,9) Agile.InventoryScale = InventoryTile.Tile99
Agile.InventoryOffset = 6 Agile.InventoryOffset = 6
Agile.Holster(0, EquipmentSize.Pistol) Agile.Holster(0, EquipmentSize.Pistol)
Agile.Holster(1, EquipmentSize.Pistol) Agile.Holster(1, EquipmentSize.Pistol)
@ -83,7 +83,7 @@ object ExoSuitDefinition {
final val Reinforced = ExoSuitDefinition(ExoSuitType.Reinforced) final val Reinforced = ExoSuitDefinition(ExoSuitType.Reinforced)
Reinforced.permission = 1 Reinforced.permission = 1
Reinforced.MaxArmor = 200 Reinforced.MaxArmor = 200
Reinforced.InventoryScale = new InventoryTile(12,9) Reinforced.InventoryScale = InventoryTile.Tile1209
Reinforced.InventoryOffset = 6 Reinforced.InventoryOffset = 6
Reinforced.Holster(0, EquipmentSize.Pistol) Reinforced.Holster(0, EquipmentSize.Pistol)
Reinforced.Holster(1, EquipmentSize.Pistol) Reinforced.Holster(1, EquipmentSize.Pistol)
@ -94,7 +94,7 @@ object ExoSuitDefinition {
final val Infiltration = ExoSuitDefinition(ExoSuitType.Standard) final val Infiltration = ExoSuitDefinition(ExoSuitType.Standard)
Infiltration.permission = 1 Infiltration.permission = 1
Infiltration.MaxArmor = 0 Infiltration.MaxArmor = 0
Infiltration.InventoryScale = new InventoryTile(6,6) Infiltration.InventoryScale = InventoryTile.Tile66
Infiltration.InventoryOffset = 6 Infiltration.InventoryOffset = 6
Infiltration.Holster(0, EquipmentSize.Pistol) Infiltration.Holster(0, EquipmentSize.Pistol)
Infiltration.Holster(4, EquipmentSize.Melee) Infiltration.Holster(4, EquipmentSize.Melee)
@ -102,7 +102,7 @@ object ExoSuitDefinition {
final val MAX = ExoSuitDefinition(ExoSuitType.MAX) final val MAX = ExoSuitDefinition(ExoSuitType.MAX)
MAX.permission = 1 MAX.permission = 1
MAX.MaxArmor = 650 MAX.MaxArmor = 650
MAX.InventoryScale = new InventoryTile(16,12) MAX.InventoryScale = InventoryTile.Tile1612
MAX.InventoryOffset = 6 MAX.InventoryOffset = 6
MAX.Holster(0, EquipmentSize.Max) MAX.Holster(0, EquipmentSize.Max)
MAX.Holster(4, EquipmentSize.Melee) MAX.Holster(4, EquipmentSize.Melee)

View file

@ -11,7 +11,7 @@ import scala.annotation.tailrec
/** /**
* From a `Player` their current exo-suit and their `Equipment`, retain a set of instructions to reconstruct this arrangement.<br> * From a `Player` their current exo-suit and their `Equipment`, retain a set of instructions to reconstruct this arrangement.<br>
* <br> * <br>
* `InfantryLoadout` objects are composed of the following information, as if a blueprint:<br> * `Loadout` objects are composed of the following information, as if a blueprint:<br>
* - the avatar's current exo-suit<br> * - the avatar's current exo-suit<br>
* - the type of specialization, called a "subtype" (mechanized assault exo-suits only)<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 holster slots<br>
@ -28,25 +28,24 @@ import scala.annotation.tailrec
* Even a whole blueprint can be denied if the user lacks the necessary exo-suit certification. * 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> * A completely new piece of `Equipment` is constructed when the `Loadout` is regurgitated.<br>
* <br> * <br>
* The fifth tab on an `order_terminal` window is for "Favorite" blueprints for `InfantryLoadout` entries. * The fifth tab on an `order_terminal` window is for "Favorite" blueprints for `Loadout` entries.
* The ten-long list is initialized with `FavoritesMessage` packets. * The 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 player the player * @param player the player
* @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
*/ */
class InfantryLoadout(player : Player, private val label : String) { class Loadout(player : Player, private val label : String) {
/** the exo-suit */ /** the exo-suit */
private val exosuit : ExoSuitType.Value = player.ExoSuit private val exosuit : ExoSuitType.Value = player.ExoSuit
/** the MAX specialization, to differentiate the three types of MAXes who all use the same exo-suit name */ /** the MAX specialization, to differentiate the three types of MAXes who all use the same exo-suit name */
private val subtype = private val subtype =
if(exosuit == ExoSuitType.MAX) { if(exosuit == ExoSuitType.MAX) {
import net.psforever.packet.game.objectcreate.ObjectClass player.Holsters().head.Equipment.get.Definition match {
player.Holsters().head.Equipment.get.Definition.ObjectId match { case GlobalDefinitions.trhev_dualcycler | GlobalDefinitions.nchev_scattercannon | GlobalDefinitions.vshev_quasar =>
case ObjectClass.trhev_dualcycler | ObjectClass.nchev_scattercannon | ObjectClass.vshev_quasar =>
1 1
case ObjectClass.trhev_pounder | ObjectClass.nchev_falcon | ObjectClass.vshev_comet => case GlobalDefinitions.trhev_pounder | GlobalDefinitions.nchev_falcon | GlobalDefinitions.vshev_comet =>
2 2
case ObjectClass.trhev_burster | ObjectClass.nchev_sparrow | ObjectClass.vshev_starfire => case GlobalDefinitions.trhev_burster | GlobalDefinitions.nchev_sparrow | GlobalDefinitions.vshev_starfire =>
3 3
case _ => case _ =>
0 0
@ -56,14 +55,14 @@ class InfantryLoadout(player : Player, private val label : String) {
0 0
} }
/** simplified representation of the holster `Equipment` */ /** simplified representation of the holster `Equipment` */
private val holsters : List[InfantryLoadout.SimplifiedEntry] = private val holsters : List[Loadout.SimplifiedEntry] =
InfantryLoadout.packageSimplifications(player.Holsters()) Loadout.packageSimplifications(player.Holsters())
/** simplified representation of the inventory `Equipment` */ /** simplified representation of the inventory `Equipment` */
private val inventory : List[InfantryLoadout.SimplifiedEntry] = private val inventory : List[Loadout.SimplifiedEntry] =
InfantryLoadout.packageSimplifications(player.Inventory.Items.values.toList) Loadout.packageSimplifications(player.Inventory.Items.values.toList)
/** /**
* The label by which this `InfantryLoadout` is called. * The label by which this `Loadout` is called.
* @return the label * @return the label
*/ */
def Label : String = label def Label : String = label
@ -87,26 +86,26 @@ class InfantryLoadout(player : Player, private val label : String) {
def Subtype : Int = subtype def Subtype : Int = subtype
/** /**
* The `Equipment` in the `Player`'s holster slots when this `InfantryLoadout` is created. * The `Equipment` in the `Player`'s holster slots when this `Loadout` is created.
* @return a `List` of the holster item blueprints * @return a `List` of the holster item blueprints
*/ */
def Holsters : List[InfantryLoadout.SimplifiedEntry] = holsters def Holsters : List[Loadout.SimplifiedEntry] = holsters
/** /**
* The `Equipment` in the `Player`'s inventory region when this `InfantryLoadout` is created. * The `Equipment` in the `Player`'s inventory region when this `Loadout` is created.
* @return a `List` of the inventory item blueprints * @return a `List` of the inventory item blueprints
*/ */
def Inventory : List[InfantryLoadout.SimplifiedEntry] = inventory def Inventory : List[Loadout.SimplifiedEntry] = inventory
} }
object InfantryLoadout { object Loadout {
/** /**
* A basic `Trait` connecting all of the `Equipment` blueprints. * A basic `Trait` connecting all of the `Equipment` blueprints.
*/ */
sealed trait Simplification sealed trait Simplification
/** /**
* An entry in the `InfantryLoadout`, wrapping around a slot index and what is in the slot index. * An entry in the `Loadout`, wrapping around a slot index and what is in the slot index.
* @param item the `Equipment` * @param item the `Equipment`
* @param index the slot number where the `Equipment` is to be stowed * @param index the slot number where the `Equipment` is to be stowed
* @see `InventoryItem` * @see `InventoryItem`
@ -163,7 +162,7 @@ object InfantryLoadout {
* @return a `List` of simplified `Equipment` * @return a `List` of simplified `Equipment`
*/ */
private def packageSimplifications(equipment : List[InventoryItem]) : List[SimplifiedEntry] = { private def packageSimplifications(equipment : List[InventoryItem]) : List[SimplifiedEntry] = {
recursiveInventorySimplifications(equipment.iterator) equipment.map(entry => { SimplifiedEntry(buildSimplification(entry.obj), entry.start) })
} }
/** /**
@ -193,23 +192,6 @@ object InfantryLoadout {
} }
} }
/**
* Traverse a `Player`'s inventory and transform `Equipment` into simplified blueprints.
* @param iter an `Iterator`
* @param list an updating `List` of simplified `Equipment` blueprints;
* empty, by default
* @return a `List` of simplified `Equipment` blueprints
*/
@tailrec private def recursiveInventorySimplifications(iter : Iterator[InventoryItem], list : List[SimplifiedEntry] = Nil) : List[SimplifiedEntry] = {
if(!iter.hasNext) {
list
}
else {
val entry = iter.next
recursiveInventorySimplifications(iter, list :+ SimplifiedEntry(buildSimplification(entry.obj), entry.start))
}
}
/** /**
* Ammunition slots are internal connection points where `AmmoBox` units and their characteristics represent a `Tool`'s magazine. * Ammunition slots are internal connection points where `AmmoBox` units and their characteristics represent a `Tool`'s magazine.
* Their simplification process has a layer of complexity that ensures that the content of the slot matches the type of content that should be in the slot. * Their simplification process has a layer of complexity that ensures that the content of the slot matches the type of content that should be in the slot.
@ -235,7 +217,7 @@ object InfantryLoadout {
else { else {
ShorthandAmmotSlot( ShorthandAmmotSlot(
entry.AmmoTypeIndex, entry.AmmoTypeIndex,
ShorthandAmmoBox(AmmoBoxDefinition(entry.Tool.Definition.AmmoTypes(entry.Definition.AmmoTypeIndices.head).id), 1) ShorthandAmmoBox(entry.Tool.AmmoTypes(entry.Definition.AmmoTypeIndices.head), 1)
) )
} }
recursiveFireModeSimplications(iter, list :+ fmodeSimp) recursiveFireModeSimplications(iter, list :+ fmodeSimp)

View file

@ -32,7 +32,7 @@ class Player(private val name : String,
private var drawnSlot : Int = Player.HandsDownSlot private var drawnSlot : Int = Player.HandsDownSlot
private var lastDrawnSlot : Int = 0 private var lastDrawnSlot : Int = 0
private val loadouts : Array[Option[InfantryLoadout]] = Array.fill[Option[InfantryLoadout]](10)(None) private val loadouts : Array[Option[Loadout]] = Array.fill[Option[Loadout]](10)(None)
private var bep : Long = 0 private var bep : Long = 0
private var cep : Long = 0 private var cep : Long = 0
@ -230,10 +230,10 @@ class Player(private val name : String,
} }
def SaveLoadout(label : String, line : Int) : Unit = { def SaveLoadout(label : String, line : Int) : Unit = {
loadouts(line) = Some(new InfantryLoadout(this, label)) loadouts(line) = Some(new Loadout(this, label))
} }
def LoadLoadout(line : Int) : Option[InfantryLoadout] = loadouts(line) def LoadLoadout(line : Int) : Option[Loadout] = loadouts(line)
def DeleteLoadout(line : Int) : Unit = { def DeleteLoadout(line : Int) : Unit = {
loadouts(line) = None loadouts(line) = None
@ -551,7 +551,7 @@ object Player {
player.ExoSuit = eSuit player.ExoSuit = eSuit
//inventory //inventory
player.Inventory.Clear() player.Inventory.Clear()
player.Inventory.Resize(esuitDef.InventoryScale.width, esuitDef.InventoryScale.height) player.Inventory.Resize(esuitDef.InventoryScale.Width, esuitDef.InventoryScale.Height)
player.Inventory.Offset = esuitDef.InventoryOffset player.Inventory.Offset = esuitDef.InventoryOffset
//holsters //holsters
(0 until 5).foreach(index => { player.Slot(index).Size = esuitDef.Holster(index) }) (0 until 5).foreach(index => { player.Slot(index).Size = esuitDef.Holster(index) })

View file

@ -1,9 +1,8 @@
// Copyright (c) 2017 PSForever // Copyright (c) 2017 PSForever
package net.psforever.objects package net.psforever.objects
import net.psforever.objects.definition.{AmmoBoxDefinition, ToolDefinition} import net.psforever.objects.definition.ToolDefinition
import net.psforever.objects.equipment.{Ammo, Equipment, FireModeDefinition, FireModeSwitch} import net.psforever.objects.equipment.{Ammo, Equipment, FireModeDefinition, FireModeSwitch}
import net.psforever.packet.game.PlanetSideGUID
import scala.annotation.tailrec import scala.annotation.tailrec
@ -13,14 +12,15 @@ import scala.annotation.tailrec
* "Tool" is a very mechanical name while this class is intended for various weapons and support items. * "Tool" is a very mechanical name while this class is intended for various weapons and support items.
* The primary trait of a `Tool` is that it has something that counts as an "ammunition," * The primary trait of a `Tool` is that it has something that counts as an "ammunition,"
* depleted as the `Tool` is used, replaceable as long as one has an appropriate type of `AmmoBox` object. * depleted as the `Tool` is used, replaceable as long as one has an appropriate type of `AmmoBox` object.
* (The former is always called "consuming;" the latter, "reloading.")<br> * (The former is always called "consuming;" the latter, "reloading.")
* <br>
* Some weapons Chainblade have ammunition but do not consume it. * Some weapons Chainblade have ammunition but do not consume it.
* @param toolDef the `ObjectDefinition` that constructs this item and maintains some of its immutable fields * @param toolDef the `ObjectDefinition` that constructs this item and maintains some of its immutable fields
*/ */
class Tool(private val toolDef : ToolDefinition) extends Equipment with FireModeSwitch[FireModeDefinition] { class Tool(private val toolDef : ToolDefinition) extends Equipment with FireModeSwitch[FireModeDefinition] {
/** index of the current fire mode on the `ToolDefinition`'s list of fire modes */
private var fireModeIndex : Int = 0 private var fireModeIndex : Int = 0
private val ammoSlot : List[Tool.FireModeSlot] = Tool.LoadDefinition(this) /** current ammunition slot being used by this fire mode */
private val ammoSlots : List[Tool.FireModeSlot] = Tool.LoadDefinition(this)
def FireModeIndex : Int = fireModeIndex def FireModeIndex : Int = fireModeIndex
@ -36,24 +36,24 @@ class Tool(private val toolDef : ToolDefinition) extends Equipment with FireMode
FireMode FireMode
} }
def AmmoTypeIndex : Int = ammoSlot(fireModeIndex).AmmoTypeIndex def AmmoTypeIndex : Int = FireMode.AmmoTypeIndices(AmmoSlot.AmmoTypeIndex)
def AmmoTypeIndex_=(index : Int) : Int = { def AmmoTypeIndex_=(index : Int) : Int = {
ammoSlot(fireModeIndex).AmmoTypeIndex = index % FireMode.AmmoTypeIndices.length AmmoSlot.AmmoTypeIndex = index % FireMode.AmmoTypeIndices.length
AmmoTypeIndex AmmoTypeIndex
} }
def AmmoType : Ammo.Value = toolDef.AmmoTypes(AmmoTypeIndex) def AmmoType : Ammo.Value = toolDef.AmmoTypes(AmmoTypeIndex).AmmoType
def NextAmmoType : Ammo.Value = { def NextAmmoType : Ammo.Value = {
AmmoTypeIndex = AmmoTypeIndex + 1 AmmoSlot.AmmoTypeIndex = AmmoSlot.AmmoTypeIndex + 1
AmmoType AmmoType
} }
def Magazine : Int = ammoSlot(fireModeIndex).Magazine def Magazine : Int = AmmoSlot.Magazine
def Magazine_=(mag : Int) : Int = { def Magazine_=(mag : Int) : Int = {
ammoSlot(fireModeIndex).Magazine = Math.min(Math.max(0, mag), MaxMagazine) AmmoSlot.Magazine = Math.min(Math.max(0, mag), MaxMagazine)
Magazine Magazine
} }
@ -61,15 +61,15 @@ class Tool(private val toolDef : ToolDefinition) extends Equipment with FireMode
def NextDischarge : Int = math.min(Magazine, FireMode.Chamber) def NextDischarge : Int = math.min(Magazine, FireMode.Chamber)
def AmmoSlots : List[Tool.FireModeSlot] = ammoSlot def AmmoSlot : Tool.FireModeSlot = ammoSlots(FireMode.AmmoSlotIndex)
def MaxAmmoSlot : Int = ammoSlot.length def AmmoSlots : List[Tool.FireModeSlot] = ammoSlots
def MaxAmmoSlot : Int = ammoSlots.length
def Definition : ToolDefinition = toolDef def Definition : ToolDefinition = toolDef
override def toString : String = { override def toString : String = Tool.toString(this)
Tool.toString(this)
}
} }
object Tool { object Tool {
@ -77,12 +77,6 @@ object Tool {
new Tool(toolDef) new Tool(toolDef)
} }
def apply(guid : PlanetSideGUID, toolDef : ToolDefinition) : Tool = {
val obj = new Tool(toolDef)
obj.GUID = guid
obj
}
/** /**
* Use the `*Definition` that was provided to this object to initialize its fields and settings. * Use the `*Definition` that was provided to this object to initialize its fields and settings.
* @param tool the `Tool` being initialized * @param tool the `Tool` being initialized
@ -90,10 +84,10 @@ object Tool {
def LoadDefinition(tool : Tool) : List[FireModeSlot] = { def LoadDefinition(tool : Tool) : List[FireModeSlot] = {
val tdef : ToolDefinition = tool.Definition val tdef : ToolDefinition = tool.Definition
val maxSlot = tdef.FireModes.maxBy(fmode => fmode.AmmoSlotIndex).AmmoSlotIndex val maxSlot = tdef.FireModes.maxBy(fmode => fmode.AmmoSlotIndex).AmmoSlotIndex
buildFireModes(tool, (0 to maxSlot).iterator, tdef.FireModes.toList) buildFireModes(tdef, (0 to maxSlot).iterator, tdef.FireModes.toList)
} }
@tailrec private def buildFireModes(tool : Tool, iter : Iterator[Int], fmodes : List[FireModeDefinition], list : List[FireModeSlot] = Nil) : List[FireModeSlot] = { @tailrec private def buildFireModes(tdef : ToolDefinition, iter : Iterator[Int], fmodes : List[FireModeDefinition], list : List[FireModeSlot] = Nil) : List[FireModeSlot] = {
if(!iter.hasNext) { if(!iter.hasNext) {
list list
} }
@ -101,9 +95,9 @@ object Tool {
val index = iter.next val index = iter.next
fmodes.filter(fmode => fmode.AmmoSlotIndex == index) match { fmodes.filter(fmode => fmode.AmmoSlotIndex == index) match {
case fmode :: _ => case fmode :: _ =>
buildFireModes(tool, iter, fmodes, list :+ new FireModeSlot(tool, fmode)) buildFireModes(tdef, iter, fmodes, list :+ new FireModeSlot(tdef, fmode))
case Nil => case Nil =>
throw new IllegalArgumentException(s"tool ${tool.Definition.Name} ammo slot #$index is missing a fire mode specification; do not skip") throw new IllegalArgumentException(s"tool ${tdef.Name} ammo slot #$index is missing a fire mode specification; do not skip")
} }
} }
} }
@ -113,32 +107,45 @@ object Tool {
} }
/** /**
* A hidden class that manages the specifics of the given ammunition for the current fire mode of this tool. * The `FireModeSlot` can be called the "magazine feed," an abstracted "ammunition slot."
* It operates much closer to an "ammunition feed" rather than a fire mode. * Most weapons will have only one ammunition slot and swap different ammunition into it as needed.
* The relationship to fire modes is at least one-to-one and at most one-to-many. * In general to swap ammunition means to unload the onld ammunition and load the new ammunition.
* Many weapons also have one ammunition slot and multiple fire modes using the same list of ammunition
* This slot manages either of two ammunitions where one does not need to unload to be swapped to the other;
* however, the fire mod has most likely been changed.
* The Punisher -
* six ammunition types in total,
* two uniquely different types without unloading,
* two exclusive groups of ammunition divided into 2 cycled types and 4 cycled types -
* is an example of a weapon that benefits from this implementation.
*/ */
class FireModeSlot(private val tool : Tool, private val fdef : FireModeDefinition) { class FireModeSlot(private val tdef : ToolDefinition, private val fdef : FireModeDefinition) {
/* /**
By way of demonstration: * if this fire mode has multiple types of ammunition
Suppressors have one fire mode, two types of ammunition, one slot (2) * this is the index of the fire mode's ammo List, not a reference to the tool's ammo List
MA Pistols have two fire modes, one type of ammunition, one slot (1) */
Jackhammers have two fire modes, two types of ammunition, one slot (2) private var ammoTypeIndex : Int = 0
Punishers have two fire modes, five types of ammunition, two slots (2, 3) /** a reference to the actual `AmmoBox` of this slot */
*/ private var box : AmmoBox = AmmoBox(tdef.AmmoTypes(ammoTypeIndex), fdef.Magazine)
/** if this fire mode has multiple types of ammunition */
private var ammoTypeIndex : Int = fdef.AmmoTypeIndices.head
/** a reference to the actual `AmmoBox` of this slot; will not synch up with `AmmoType` immediately */
private var box : AmmoBox = AmmoBox(AmmoBoxDefinition(AmmoType)) //defaults to box of one round of the default type for this slot
def AmmoTypeIndex : Int = ammoTypeIndex def AmmoTypeIndex : Int = ammoTypeIndex
def AmmoTypeIndex_=(index : Int) : Int = { def AmmoTypeIndex_=(index : Int) : Int = {
ammoTypeIndex = index ammoTypeIndex = index % fdef.AmmoTypeIndices.length
AmmoTypeIndex AmmoTypeIndex
} }
def AmmoType : Ammo.Value = tool.Definition.AmmoTypes(ammoTypeIndex) /**
* This is a reference to the `Ammo.Value` whose `AmmoBoxDefinition` should be loaded into `box`.
* It may not be the correct `Ammo.Value` whose `AmmoBoxDefinition` is loaded into `box` such as is the case during ammunition swaps.
* Generally, convert from this index, to the index in the fire mode's ammunition list, to the index in the `ToolDefinition`'s ammunition list.
* @return the `Ammo` type that should be loaded into the magazine right now
*/
def AmmoType : Ammo.Value = tdef.AmmoTypes(fdef.AmmoTypeIndices(ammoTypeIndex)).AmmoType
def AllAmmoTypes : List[Ammo.Value] = {
fdef.AmmoTypeIndices.map(index => tdef.AmmoTypes(fdef.AmmoTypeIndices(index)).AmmoType).toList
}
def Magazine : Int = box.Capacity def Magazine : Int = box.Capacity
@ -159,7 +166,7 @@ object Tool {
} }
} }
def Tool : Tool = tool def Tool : ToolDefinition = tdef
def Definition : FireModeDefinition = fdef def Definition : FireModeDefinition = fdef
} }

View file

@ -3,7 +3,7 @@ package net.psforever.objects
import net.psforever.objects.definition.VehicleDefinition import net.psforever.objects.definition.VehicleDefinition
import net.psforever.objects.equipment.{Equipment, EquipmentSize} import net.psforever.objects.equipment.{Equipment, EquipmentSize}
import net.psforever.objects.inventory.GridInventory import net.psforever.objects.inventory.{GridInventory, InventoryTile}
import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.vehicles.{AccessPermissionGroup, Seat, Utility, VehicleLockState} import net.psforever.objects.vehicles.{AccessPermissionGroup, Seat, Utility, VehicleLockState}
import net.psforever.packet.game.PlanetSideGUID import net.psforever.packet.game.PlanetSideGUID
@ -480,8 +480,12 @@ object Vehicle {
vehicle.Utilities += Utility.Select(i, vehicle) vehicle.Utilities += Utility.Select(i, vehicle)
} }
//trunk //trunk
vehicle.trunk.Resize(vdef.TrunkSize.width, vdef.TrunkSize.height) vdef.TrunkSize match {
vehicle.trunk.Offset = vdef.TrunkOffset case InventoryTile.None => ;
case dim =>
vehicle.trunk.Resize(dim.Width, dim.Height)
vehicle.trunk.Offset = vdef.TrunkOffset
}
vehicle vehicle
} }

View file

@ -2,17 +2,17 @@
package net.psforever.objects.definition package net.psforever.objects.definition
import net.psforever.objects.definition.converter.ToolConverter import net.psforever.objects.definition.converter.ToolConverter
import net.psforever.objects.equipment.{Ammo, FireModeDefinition} import net.psforever.objects.equipment.FireModeDefinition
import scala.collection.mutable import scala.collection.mutable
class ToolDefinition(objectId : Int) extends EquipmentDefinition(objectId) { class ToolDefinition(objectId : Int) extends EquipmentDefinition(objectId) {
private val ammoTypes : mutable.ListBuffer[Ammo.Value] = new mutable.ListBuffer[Ammo.Value] private val ammoTypes : mutable.ListBuffer[AmmoBoxDefinition] = new mutable.ListBuffer[AmmoBoxDefinition]
private val fireModes : mutable.ListBuffer[FireModeDefinition] = new mutable.ListBuffer[FireModeDefinition] private val fireModes : mutable.ListBuffer[FireModeDefinition] = new mutable.ListBuffer[FireModeDefinition]
Name = "tool" Name = "tool"
Packet = new ToolConverter() Packet = new ToolConverter()
def AmmoTypes : mutable.ListBuffer[Ammo.Value] = ammoTypes def AmmoTypes : mutable.ListBuffer[AmmoBoxDefinition] = ammoTypes
def FireModes : mutable.ListBuffer[FireModeDefinition] = fireModes def FireModes : mutable.ListBuffer[FireModeDefinition] = fireModes
} }

View file

@ -43,7 +43,7 @@ object Ammo extends Enumeration {
final val fluxpod_ammo = Value(310) final val fluxpod_ammo = Value(310)
final val frag_cartridge = Value(327) final val frag_cartridge = Value(327)
final val frag_grenade_ammo = Value(331) final val frag_grenade_ammo = Value(331)
final val gauss_cannon_ammo = Value(345) final val gauss_cannon_ammo = Value(347)
final val grenade = Value(370) final val grenade = Value(370)
final val health_canister = Value(389) final val health_canister = Value(389)
final val heavy_grenade_mortar = Value(391) final val heavy_grenade_mortar = Value(391)

View file

@ -68,7 +68,7 @@ class GridInventory {
* @return the number of free cells * @return the number of free cells
*/ */
def Capacity : Int = { def Capacity : Int = {
TotalCapacity - items.values.foldLeft(0)((cnt, item) => cnt + (item.obj.Tile.width * item.obj.Tile.height)) TotalCapacity - items.values.foldLeft(0)((cnt, item) => cnt + (item.obj.Tile.Width * item.obj.Tile.Height))
} }
/** /**
@ -112,7 +112,7 @@ class GridInventory {
*/ */
def CheckCollisions(start : Int, item : Equipment) : Try[List[Int]] = { def CheckCollisions(start : Int, item : Equipment) : Try[List[Int]] = {
val tile : InventoryTile = item.Tile val tile : InventoryTile = item.Tile
CheckCollisions(start, tile.width, tile.height) CheckCollisions(start, tile.Width, tile.Height)
} }
/** /**
@ -185,8 +185,8 @@ class GridInventory {
val itemx : Int = actualItemStart % width val itemx : Int = actualItemStart % width
val itemy : Int = actualItemStart / width val itemy : Int = actualItemStart / width
val tile = item.obj.Tile val tile = item.obj.Tile
val clipsOnX : Boolean = if(itemx < startx) { itemx + tile.width > startx } else { itemx <= startw } val clipsOnX : Boolean = if(itemx < startx) { itemx + tile.Width > startx } else { itemx <= startw }
val clipsOnY : Boolean = if(itemy < starty) { itemy + tile.height > starty } else { itemy <= starth } val clipsOnY : Boolean = if(itemy < starty) { itemy + tile.Height > starty } else { itemy <= starth }
if(clipsOnX && clipsOnY) { if(clipsOnX && clipsOnY) {
collisions += item collisions += item
} }
@ -237,8 +237,8 @@ class GridInventory {
* @return the grid index of the upper left corner where equipment to which the `tile` belongs should be placed * @return the grid index of the upper left corner where equipment to which the `tile` belongs should be placed
*/ */
def Fit(tile : InventoryTile) : Option[Int] = { def Fit(tile : InventoryTile) : Option[Int] = {
val tWidth = tile.width val tWidth = tile.Width
val tHeight = tile.height val tHeight = tile.Height
val gridIter = (0 until (grid.length - (tHeight - 1) * width)) val gridIter = (0 until (grid.length - (tHeight - 1) * width))
.filter(cell => grid(cell) == -1 && (width - cell%width >= tWidth)) .filter(cell => grid(cell) == -1 && (width - cell%width >= tWidth))
.iterator .iterator
@ -325,7 +325,7 @@ class GridInventory {
val card = InventoryItem(obj, start) val card = InventoryItem(obj, start)
items += key -> card items += key -> card
val tile = obj.Tile val tile = obj.Tile
SetCells(start, tile.width, tile.height, key) SetCells(start, tile.Width, tile.Height, key)
true true
case _ => case _ =>
false false
@ -348,7 +348,7 @@ class GridInventory {
items.remove(key) match { items.remove(key) match {
case Some(item) => case Some(item) =>
val tile = item.obj.Tile val tile = item.obj.Tile
SetCells(item.start, tile.width, tile.height) SetCells(item.start, tile.Width, tile.Height)
true true
case None => case None =>
false false
@ -362,7 +362,7 @@ class GridInventory {
case Some(index) => case Some(index) =>
val item = items.remove(index).get val item = items.remove(index).get
val tile = item.obj.Tile val tile = item.obj.Tile
SetCells(item.start, tile.width, tile.height) SetCells(item.start, tile.Width, tile.Height)
true true
case None => case None =>
false false
@ -492,11 +492,11 @@ object GridInventory {
(a, b) => { (a, b) => {
val aTile = a.obj.Tile val aTile = a.obj.Tile
val bTile = b.obj.Tile val bTile = b.obj.Tile
if(aTile.width == bTile.width) { if(aTile.Width == bTile.Width) {
aTile.height > bTile.height aTile.Height > bTile.Height
} }
else { else {
aTile.width > bTile.width aTile.Width > bTile.Width
} }
} }
@ -513,9 +513,9 @@ object GridInventory {
private def sortKnapsack(list : List[InventoryItem], width : Int, height : Int) : Unit = { private def sortKnapsack(list : List[InventoryItem], width : Int, height : Int) : Unit = {
val root = new KnapsackNode(0, 0, width, height) val root = new KnapsackNode(0, 0, width, height)
list.foreach(item => { list.foreach(item => {
findKnapsackSpace(root, item.obj.Tile.width, item.obj.Tile.height) match { findKnapsackSpace(root, item.obj.Tile.Width, item.obj.Tile.Height) match {
case Some(node) => case Some(node) =>
splitKnapsackSpace(node, item.obj.Tile.width, item.obj.Tile.height) splitKnapsackSpace(node, item.obj.Tile.Width, item.obj.Tile.Height)
item.start = node.y * width + node.x item.start = node.y * width + node.x
case _ => ; case _ => ;
item.start = -1 item.start = -1

View file

@ -8,7 +8,7 @@ package net.psforever.objects.inventory
* @param height the height of the tile * @param height the height of the tile
* @throws IllegalArgumentException if either the width or the height are less than zero * @throws IllegalArgumentException if either the width or the height are less than zero
*/ */
class InventoryTile(val width : Int, val height : Int) { class InventoryTile(private val width : Int, private val height : Int) {
if(width < 0 || height < 0) if(width < 0 || height < 0)
throw new IllegalArgumentException(s"tile has no area - width: $width, height: $height") throw new IllegalArgumentException(s"tile has no area - width: $width, height: $height")
@ -19,15 +19,27 @@ class InventoryTile(val width : Int, val height : Int) {
object InventoryTile { object InventoryTile {
final val None = InventoryTile(0,0) //technically invalid; used to indicate a vehicle with no trunk final val None = InventoryTile(0,0) //technically invalid; used to indicate a vehicle with no trunk
final val Tile11 = InventoryTile(1,1) //placeholder size final val Tile11 = InventoryTile(1,1) //occasional placeholder
final val Tile22 = InventoryTile(2,2) //grenades, boomer trigger final val Tile22 = InventoryTile(2,2) //grenades, boomer trigger
final val Tile23 = InventoryTile(2,3) //canister ammo final val Tile23 = InventoryTile(2,3) //canister ammo
final val Tile42 = InventoryTile(4,2) //medkit final val Tile42 = InventoryTile(4,2) //medkit
final val Tile33 = InventoryTile(3,3) //ammo box, pistols, ace final val Tile33 = InventoryTile(3,3) //ammo box, pistols, ace
final val Tile44 = InventoryTile(4,4) //large ammo box final val Tile44 = InventoryTile(4,4) //large ammo box
final val Tile55 = InventoryTile(5,5) //bfr ammo box final val Tile55 = InventoryTile(5,5) //bfr ammo box
final val Tile66 = InventoryTile(6,6) //standard assault inventory
final val Tile63 = InventoryTile(6,3) //rifles final val Tile63 = InventoryTile(6,3) //rifles
final val Tile93 = InventoryTile(9,3) //long-body weapons final val Tile93 = InventoryTile(9,3) //long-body weapons
final val Tile96 = InventoryTile(9,6) //standard exo-suit
final val Tile99 = InventoryTile(9,9) //agile exo-suit
final val Tile1107 = InventoryTile(11, 7) //uncommon small trunk capacity - phantasm
final val Tile1111 = InventoryTile(11,11) //common small trunk capacity
final val Tile1209 = InventoryTile(12, 9) //reinforced exo-suit
final val Tile1511 = InventoryTile(15,11) //common medium trunk capacity
final val Tile1515 = InventoryTile(15,15) //common large trunk capacity
final val Tile1611 = InventoryTile(16,11) //uncommon medium trunk capacity - vulture
final val Tile1612 = InventoryTile(16,12) //MAX; uncommon medium trunk capacity - lodestar
final val Tile1816 = InventoryTile(18,16) //uncommon massive trunk capacity - galaxy_gunship
final val Tile2016 = InventoryTile(20,16) //uncommon massive trunk capacity - apc
def apply(w : Int, h : Int) : InventoryTile = { def apply(w : Int, h : Int) : InventoryTile = {
new InventoryTile(w, h) new InventoryTile(w, h)

View file

@ -24,7 +24,7 @@ abstract class PlanetSideServerObject extends PlanetSideGameObject {
* @return the current internal `ActorRef` * @return the current internal `ActorRef`
*/ */
def Actor_=(control : ActorRef) : ActorRef = { def Actor_=(control : ActorRef) : ActorRef = {
if(actor == ActorRef.noSender) { if(actor == ActorRef.noSender || control == ActorRef.noSender) {
actor = control actor = control
} }
actor actor

View file

@ -0,0 +1,35 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.builders
import akka.actor.Props
import net.psforever.objects.definition.ObjectDefinition
import net.psforever.objects.serverobject.pad.{VehicleSpawnControl, VehicleSpawnPad}
/**
* Wrapper `Class` designed to instantiate a `VehicleSpawnPad` server object.
* @param spdef an `ObjectDefinition` object ...
* @param id the globally unique identifier to which this `VehicleSpawnPad` will be registered
*/
class VehicleSpawnPadObjectBuilder(private val spdef : ObjectDefinition, private val id : Int) extends ServerObjectBuilder[VehicleSpawnPad] {
import akka.actor.ActorContext
import net.psforever.objects.guid.NumberPoolHub
def Build(implicit context : ActorContext, guid : NumberPoolHub) : VehicleSpawnPad = {
val obj = VehicleSpawnPad(spdef)
guid.register(obj, id) //non-Actor GUID registration
obj.Actor = context.actorOf(Props(classOf[VehicleSpawnControl], obj), s"${spdef.Name}_${obj.GUID.guid}")
obj
}
}
object VehicleSpawnPadObjectBuilder {
/**
* Overloaded constructor for a `DoorObjectBuilder`.
* @param spdef an `ObjectDefinition` object
* @param id a globally unique identifier
* @return a `VehicleSpawnPadObjectBuilder` object
*/
def apply(spdef : ObjectDefinition, id : Int) : VehicleSpawnPadObjectBuilder = {
new VehicleSpawnPadObjectBuilder(spdef, id)
}
}

View file

@ -0,0 +1,203 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.pad
import akka.actor.{Actor, ActorRef, Cancellable}
import net.psforever.objects.{DefaultCancellable, Player, Vehicle}
import net.psforever.types.Vector3
import scala.concurrent.duration._
/**
* An `Actor` that handles messages being dispatched to a specific `VehicleSpawnPad`.<br>
* <br>
* A spawn pad receives vehicle orders from an attached `Terminal` object.
* At the time when the order is received, the player who submitted the order is completely visible
* and waiting back by the said `Terminal` from where the order was submitted.
* Assuming no other orders are currently being processed, the repeated self message will retrieve this as the next order.
* The player character is first made transparent with a `GenericObjectActionMessage` packet.
* The vehicle model itself is then introduced to the game and three things happen with the following order, more or less:<br>
* 1. the vehicle is attached to a lifting platform that is designed to introduce the vehicle;<br>
* 2. the player is seated in the vehicle's driver seat (seat 0) and is thus declared the owner; <br>
* 3. various properties of the player, the vehicle, and the spawn pad itself are set `PlanetsideAttributesMessage`.<br>
* When this step is finished, the lifting platform raises the vehicle and the mounted player into the game world.
* The vehicle detaches and is made to roll off the spawn pad a certain distance before being released to user control.
* That is what is supposed to happen within a certain measure of timing.<br>
* <br>
* The orders that are submitted to the spawn pad must be composed of at least three elements:
* 1. a player, specifically the one that submitted the order and will be declared the "owner;"
* 2. a vehicle;
* 3. a callback location for sending messages.
* @param pad the `VehicleSpawnPad` object being governed
*/
class VehicleSpawnControl(pad : VehicleSpawnPad) extends Actor {
/** an executor for progressing a vehicle order through the normal spawning logic */
private var process : Cancellable = DefaultCancellable.obj
/** a list of vehicle orders that have been submitted for this spawn pad */
private var orders : List[VehicleSpawnControl.OrderEntry] = List.empty[VehicleSpawnControl.OrderEntry]
/** the current vehicle order being acted upon */
private var trackedOrder : Option[VehicleSpawnControl.OrderEntry] = None
/** how many times a spawned vehicle (spatially) disrupted the next vehicle from being spawned */
private var blockingViolations : Int = 0
private[this] val log = org.log4s.getLogger
private[this] def trace(msg : String) : Unit = log.trace(msg)
def receive : Receive = {
case VehicleSpawnPad.VehicleOrder(player, vehicle) =>
trace(s"order from $player for $vehicle received")
orders = orders :+ VehicleSpawnControl.OrderEntry(player, vehicle, sender)
if(trackedOrder.isEmpty && orders.length == 1) {
self ! VehicleSpawnControl.Process.GetOrder
}
case VehicleSpawnControl.Process.GetOrder =>
process.cancel
blockingViolations = 0
val (completeOrder, remainingOrders) : (Option[VehicleSpawnControl.OrderEntry], List[VehicleSpawnControl.OrderEntry]) = orders match {
case x :: Nil =>
(Some(x), Nil)
case x :: b =>
(Some(x), b)
case Nil =>
(None, Nil)
}
orders = remainingOrders
completeOrder match {
case Some(entry) =>
trace(s"processing order $entry")
trackedOrder = completeOrder
import scala.concurrent.ExecutionContext.Implicits.global
process = context.system.scheduler.scheduleOnce(VehicleSpawnControl.concealPlayerTimeout, self, VehicleSpawnControl.Process.ConcealPlayer)
case None => ;
}
case VehicleSpawnControl.Process.ConcealPlayer =>
process.cancel
trackedOrder match {
case Some(entry) =>
if(entry.player.isAlive && entry.vehicle.Actor != ActorRef.noSender && entry.sendTo != ActorRef.noSender && entry.player.VehicleSeated.isEmpty) {
trace(s"hiding player: ${entry.player}")
entry.sendTo ! VehicleSpawnPad.ConcealPlayer
import scala.concurrent.ExecutionContext.Implicits.global
process = context.system.scheduler.scheduleOnce(VehicleSpawnControl.loadVehicleTimeout, self, VehicleSpawnControl.Process.LoadVehicle)
}
else {
trace("integral component lost; abort order fulfillment")
//TODO Unregister vehicle ... somehow
trackedOrder = None
self ! VehicleSpawnControl.Process.GetOrder
}
case None =>
self ! VehicleSpawnControl.Process.GetOrder
}
case VehicleSpawnControl.Process.LoadVehicle =>
process.cancel
trackedOrder match {
case Some(entry) =>
if(entry.vehicle.Actor != ActorRef.noSender && entry.sendTo != ActorRef.noSender) {
trace(s"loading vehicle: ${entry.vehicle} defined in order")
entry.sendTo ! VehicleSpawnPad.LoadVehicle(entry.vehicle, pad)
import scala.concurrent.ExecutionContext.Implicits.global
process = context.system.scheduler.scheduleOnce(VehicleSpawnControl.awaitSeatedTimeout, self, VehicleSpawnControl.Process.AwaitSeated)
}
else {
trace("owner or vehicle lost; abort order fulfillment")
//TODO Unregister vehicle ... somehow
trackedOrder = None
self ! VehicleSpawnControl.Process.GetOrder
}
case None =>
self ! VehicleSpawnControl.Process.GetOrder
}
case VehicleSpawnControl.Process.AwaitSeated =>
process.cancel
trackedOrder match {
case Some(entry) =>
if(entry.sendTo != ActorRef.noSender) {
trace("owner seated in vehicle")
import scala.concurrent.ExecutionContext.Implicits.global
process = if(entry.player.VehicleOwned.contains(entry.vehicle.GUID)) {
entry.sendTo ! VehicleSpawnPad.PlayerSeatedInVehicle(entry.vehicle)
context.system.scheduler.scheduleOnce(VehicleSpawnControl.awaitClearanceTimeout, self, VehicleSpawnControl.Process.AwaitClearance)
}
else {
context.system.scheduler.scheduleOnce(VehicleSpawnControl.awaitSeatedTimeout, self, VehicleSpawnControl.Process.AwaitSeated)
}
}
else {
trace("owner lost; abort order fulfillment")
trackedOrder = None
self ! VehicleSpawnControl.Process.GetOrder
}
case None =>
self ! VehicleSpawnControl.Process.GetOrder
}
//TODO raise spawn pad rails from ground
//TODO start auto drive away
//TODO release auto drive away
case VehicleSpawnControl.Process.AwaitClearance =>
process.cancel
trackedOrder match {
case Some(entry) =>
if(entry.sendTo == ActorRef.noSender || entry.vehicle.Actor == ActorRef.noSender) {
trace("integral component lost, but order fulfilled; process next order")
trackedOrder = None
self ! VehicleSpawnControl.Process.GetOrder
}
else if(Vector3.DistanceSquared(entry.vehicle.Position, pad.Position) > 100.0f) { //10m away from pad
trace("pad cleared; process next order")
trackedOrder = None
entry.sendTo ! VehicleSpawnPad.SpawnPadUnblocked(entry.vehicle.GUID)
self ! VehicleSpawnControl.Process.GetOrder
}
else {
trace(s"pad blocked by ${entry.vehicle} ...")
blockingViolations += 1
entry.sendTo ! VehicleSpawnPad.SpawnPadBlockedWarning(entry.vehicle, blockingViolations)
import scala.concurrent.ExecutionContext.Implicits.global
process = context.system.scheduler.scheduleOnce(VehicleSpawnControl.awaitClearanceTimeout, self, VehicleSpawnControl.Process.AwaitClearance)
}
case None =>
self ! VehicleSpawnControl.Process.GetOrder
}
case _ => ;
}
}
object VehicleSpawnControl {
final val concealPlayerTimeout : FiniteDuration = 2000000000L nanoseconds //2s
final val loadVehicleTimeout : FiniteDuration = 1000000000L nanoseconds //1s
final val awaitSeatedTimeout : FiniteDuration = 1000000000L nanoseconds //1s
final val awaitClearanceTimeout : FiniteDuration = 5000000000L nanoseconds //5s
/**
* An `Enumeration` of the stages of a full vehicle spawning process, associated with certain messages passed.
* Some stages are currently TEMPORARY.
* @see VehicleSpawnPad
*/
object Process extends Enumeration {
val
GetOrder,
ConcealPlayer,
LoadVehicle,
AwaitSeated,
AwaitClearance
= Value
}
/**
* An entry that stores vehicle spawn pad spawning tasks (called "orders").
* @param player the player
* @param vehicle the vehicle
* @param sendTo a callback `Actor` associated with the player (in other words, `WorldSessionActor`)
*/
private final case class OrderEntry(player : Player, vehicle : Vehicle, sendTo : ActorRef)
}

View file

@ -0,0 +1,89 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.pad
import net.psforever.objects.{Player, Vehicle}
import net.psforever.objects.definition.ObjectDefinition
import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.packet.game.PlanetSideGUID
/**
* A structure-owned server object that is a "spawn pad" for vehicles.<br>
* <br>
* Spawn pads have no purpose on their own but
* maintain the operative queue that introduces the vehicle into the game world and applies initial activity to it and
* maintain a position and a direction where the vehicle will be made to appear (as a `PlanetSideServerObject`).
* The actual functionality managed by this object is wholly found on its accompanying `Actor`.
* @param spDef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields
* @see `VehicleSpawnControl`
*/
class VehicleSpawnPad(spDef : ObjectDefinition) extends PlanetSideServerObject {
def Definition : ObjectDefinition = spDef
}
object VehicleSpawnPad {
/**
* Communicate to the spawn pad that it should enqueue the following vehicle.
* This is the entry point to vehicle spawn pad functionality.
* @param player the player who submitted the order (the "owner")
* @param vehicle the vehicle produced from the order
*/
final case class VehicleOrder(player : Player, vehicle : Vehicle)
/**
* The first callback step in spawning the vehicle.
* An packet `GenericObjectActionMessage(/player/, 36)`, when used on a player character,
* will cause that player character's model to fade into transparency.
*/
final case class ConcealPlayer()
/**
* A callback step in spawning the vehicle.
* The vehicle is properly introduced into the game world.
* If information about the vehicle itself that is important to its spawning has not yet been set,
* this callback is the last ideal situation to set that properties without having to adjust the vehicle visually.
* The primary operation that should occur is a content-appropriate `ObjectCreateMessage` packet and
* having the player sit down in the driver's seat (seat 0) of the vehicle.
* @param vehicle the vehicle being spawned
* @param pad the pad
*/
final case class LoadVehicle(vehicle : Vehicle, pad : VehicleSpawnPad)
/**
* A TEMPORARY callback step in spawning the vehicle.
* From a state of transparency, while the vehicle is attached to the lifting platform of the spawn pad,
* the player designated the "owner" by callback is made to sit in the driver's seat (always seat 0).
* This message is the next step after that.
* @param vehicle the vehicle being spawned
*/
final case class PlayerSeatedInVehicle(vehicle : Vehicle)
/**
* A TEMPORARY callback step in (successfully) spawning the vehicle.
* While the vehicle is still occupying the pad just after being spawned and its driver seat mounted,
* that vehicle is considered blocking the pad from being used for further spawning operations.
* This message allows the user to be made known about this blockage.
* @param vehicle the vehicle
* @param warning_count the number of times a warning period has occurred
*/
final case class SpawnPadBlockedWarning(vehicle : Vehicle, warning_count : Int)
/**
* A TEMPORARY callback step in (successfully) spawning the vehicle.
* While the vehicle is still occupying the pad just after being spawned and its driver seat mounted,
* that vehicle is considered blocking the pad from being used for further spawning operations.
* A timeout will begin counting until the vehicle is despawned automatically for its driver's negligence.
* This message is used to clear the deconstruction countdown, primarily.
* @param vehicle_guid the vehicle
*/
final case class SpawnPadUnblocked(vehicle_guid : PlanetSideGUID)
/**
* Overloaded constructor.
* @param spDef the spawn pad's definition entry
* @return a `VehicleSpawnPad` object
*/
def apply(spDef : ObjectDefinition) : VehicleSpawnPad = {
new VehicleSpawnPad(spDef)
}
}

View file

@ -0,0 +1,18 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.terminals
import net.psforever.objects.Player
import net.psforever.packet.game.ItemTransactionMessage
class AirVehicleTerminalDefinition extends TerminalDefinition(43) {
Name = "air_vehicle_terminal"
def Buy(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = {
flight1Vehicles.get(msg.item_name) match {
case Some(vehicle) =>
Terminal.BuyVehicle(vehicle(), Nil)
case None =>
Terminal.NoDeal()
}
}
}

View file

@ -0,0 +1,19 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.terminals
import net.psforever.objects.Player
import net.psforever.packet.game.ItemTransactionMessage
class BFRTerminalDefinition extends TerminalDefinition(143) {
Name = "bfr_terminal"
def Buy(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = {
bfrVehicles.get(msg.item_name) match {
case Some(vehicle) =>
//Terminal.BuyVehicle(vehicle, Nil)
Terminal.NoDeal()
case None =>
Terminal.NoDeal()
}
}
}

View file

@ -81,7 +81,7 @@ class CertTerminalDefinition extends TerminalDefinition(171) {
* @param msg the original packet carrying the request * @param msg the original packet carrying the request
* @return an actionable message that explains how to process the request * @return an actionable message that explains how to process the request
*/ */
def Sell(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = { override def Sell(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = {
certificationList.get(msg.item_name) match { certificationList.get(msg.item_name) match {
case Some((cert, cost)) => case Some((cert, cost)) =>
Terminal.SellCertification(cert, cost) Terminal.SellCertification(cert, cost)
@ -89,12 +89,4 @@ class CertTerminalDefinition extends TerminalDefinition(171) {
Terminal.NoDeal() Terminal.NoDeal()
} }
} }
/**
* This action is not supported by this type of `Terminal`.
* @param player the player
* @param msg the original packet carrying the request
* @return `Terminal.NoEvent` always
*/
def Loadout(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = Terminal.NoDeal()
} }

View file

@ -0,0 +1,19 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.terminals
import net.psforever.objects.Player
import net.psforever.packet.game.ItemTransactionMessage
class DropshipVehicleTerminalDefinition extends TerminalDefinition(263) {
private val flightVehicles = flight1Vehicles ++ flight2Vehicles
Name = "dropship_vehicle_terminal"
def Buy(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = {
flightVehicles.get(msg.item_name) match {
case Some(vehicle) =>
Terminal.BuyVehicle(vehicle(), Nil)
case None =>
Terminal.NoDeal()
}
}
}

View file

@ -0,0 +1,18 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.terminals
import net.psforever.objects.Player
import net.psforever.packet.game.ItemTransactionMessage
class GroundVehicleTerminalDefinition extends TerminalDefinition(386) {
Name = "ground_vehicle_terminal"
def Buy(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = {
groundVehicles.get(msg.item_name) match {
case Some(vehicle) =>
Terminal.BuyVehicle(vehicle(), Nil)
case None =>
Terminal.NoDeal()
}
}
}

View file

@ -1,7 +1,7 @@
// Copyright (c) 2017 PSForever // Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.terminals package net.psforever.objects.serverobject.terminals
import net.psforever.objects.InfantryLoadout.Simplification import net.psforever.objects.Loadout.Simplification
import net.psforever.objects.{Player, Tool} import net.psforever.objects.{Player, Tool}
import net.psforever.objects.definition._ import net.psforever.objects.definition._
import net.psforever.objects.equipment.Equipment import net.psforever.objects.equipment.Equipment
@ -13,7 +13,7 @@ import scala.annotation.switch
/** /**
* The definition for any `Terminal` that is of a type "order_terminal". * The definition for any `Terminal` that is of a type "order_terminal".
* `Buy` and `Sell` `Equipment` items and `AmmoBox` items. * `Buy` and `Sell` `Equipment` items and `AmmoBox` items.
* Change `ExoSuitType` and retrieve `InfantryLoadout` entries. * Change `ExoSuitType` and retrieve `Loadout` entries.
*/ */
class OrderTerminalDefinition extends TerminalDefinition(612) { class OrderTerminalDefinition extends TerminalDefinition(612) {
Name = "order_terminal" Name = "order_terminal"
@ -75,19 +75,19 @@ class OrderTerminalDefinition extends TerminalDefinition(612) {
* @param msg the original packet carrying the request * @param msg the original packet carrying the request
* @return an actionable message that explains how to process the request * @return an actionable message that explains how to process the request
*/ */
def Sell(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = { override def Sell(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = {
Terminal.SellEquipment() Terminal.SellEquipment()
} }
/** /**
* Process a `TransactionType.InfantryLoadout` action by the user. * Process a `TransactionType.InfantryLoadout` action by the user.
* `InfantryLoadout` 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
* @param msg the original packet carrying the request * @param msg the original packet carrying the request
* @return an actionable message that explains how to process the request * @return an actionable message that explains how to process the request
*/ */
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) =>
@ -105,7 +105,7 @@ class OrderTerminalDefinition extends TerminalDefinition(612) {
/** /**
* Accept a simplified blueprint for some piece of `Equipment` and create an actual piece of `Equipment` based on it. * Accept a simplified blueprint for some piece of `Equipment` and create an actual piece of `Equipment` based on it.
* Used specifically for the reconstruction of `Equipment` via an `InfantryLoadout`. * Used specifically for the reconstruction of `Equipment` via an `Loadout`.
* @param entry the simplified blueprint * @param entry the simplified blueprint
* @return some `Equipment` object * @return some `Equipment` object
* @see `TerminalDefinition.MakeTool`<br> * @see `TerminalDefinition.MakeTool`<br>
@ -115,7 +115,7 @@ class OrderTerminalDefinition extends TerminalDefinition(612) {
* `TerminalDefinition.MakeKit` * `TerminalDefinition.MakeKit`
*/ */
private def BuildSimplifiedPattern(entry : Simplification) : Equipment = { private def BuildSimplifiedPattern(entry : Simplification) : Equipment = {
import net.psforever.objects.InfantryLoadout._ import net.psforever.objects.Loadout._
entry match { entry match {
case obj : ShorthandTool => case obj : ShorthandTool =>
val ammo : List[AmmoBoxDefinition] = obj.ammo.map(fmode => { fmode.ammo.adef }) val ammo : List[AmmoBoxDefinition] = obj.ammo.map(fmode => { fmode.ammo.adef })

View file

@ -136,10 +136,24 @@ object Terminal {
final case class SellEquipment() extends Exchange final case class SellEquipment() extends Exchange
import net.psforever.types.CertificationType import net.psforever.types.CertificationType
/**
* Provide the certification type unlocked by the player.
* @param cert the certification unlocked
* @param cost the certification point cost
*/
final case class LearnCertification(cert : CertificationType.Value, cost : Int) extends Exchange final case class LearnCertification(cert : CertificationType.Value, cost : Int) extends Exchange
/**
* Provide the certification type freed-up by the player.
* @param cert the certification returned
* @param cost the certification point cost
*/
final case class SellCertification(cert : CertificationType.Value, cost : Int) extends Exchange final case class SellCertification(cert : CertificationType.Value, cost : Int) extends Exchange
import net.psforever.objects.Vehicle
final case class BuyVehicle(vehicle : Vehicle, loadout: List[Any]) extends Exchange
/** /**
* Recover a former exo-suit and `Equipment` configuration that the `Player` possessed. * Recover a former exo-suit and `Equipment` configuration that the `Player` possessed.
* A result of a processed request. * A result of a processed request.

View file

@ -8,10 +8,11 @@ import net.psforever.packet.game.ItemTransactionMessage
import net.psforever.types.ExoSuitType import net.psforever.types.ExoSuitType
/** /**
* The definition for any `Terminal`. * The basic definition for any `Terminal`.
* @param objectId the object's identifier number * @param objectId the object's identifier number
*/ */
abstract class TerminalDefinition(objectId : Int) extends ObjectDefinition(objectId) { abstract class TerminalDefinition(objectId : Int) extends ObjectDefinition(objectId) {
private[this] val log = org.log4s.getLogger("TerminalDefinition")
Name = "terminal" Name = "terminal"
/** /**
@ -22,12 +23,12 @@ abstract class TerminalDefinition(objectId : Int) extends ObjectDefinition(objec
/** /**
* The unimplemented functionality for this `Terminal`'s `TransactionType.Sell` activity. * The unimplemented functionality for this `Terminal`'s `TransactionType.Sell` activity.
*/ */
def Sell(player: Player, msg : ItemTransactionMessage) : Terminal.Exchange 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.InfantryLoadout` activity.
*/ */
def Loadout(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange def Loadout(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = Terminal.NoDeal()
/** /**
* A `Map` of information for changing exo-suits. * A `Map` of information for changing exo-suits.
@ -128,42 +129,42 @@ abstract class TerminalDefinition(objectId : Int) extends ObjectDefinition(objec
* value - a curried function that builds the object * value - a curried function that builds the object
*/ */
protected val infantryWeapons : Map[String, ()=>Equipment] = Map( protected val infantryWeapons : Map[String, ()=>Equipment] = Map(
"ilc9" -> MakeTool(ilc9, bullet_9mm), "ilc9" -> MakeTool(ilc9),
"repeater" -> MakeTool(repeater, bullet_9mm), "repeater" -> MakeTool(repeater),
"isp" -> MakeTool(isp, shotgun_shell), //amp "isp" -> MakeTool(isp), //amp
"beamer" -> MakeTool(beamer, energy_cell), "beamer" -> MakeTool(beamer),
"suppressor" -> MakeTool(suppressor, bullet_9mm), "suppressor" -> MakeTool(suppressor),
"anniversary_guna" -> MakeTool(anniversary_guna, anniversary_ammo), //tr stinger "anniversary_guna" -> MakeTool(anniversary_guna), //tr stinger
"anniversary_gun" -> MakeTool(anniversary_gun, anniversary_ammo), //nc spear "anniversary_gun" -> MakeTool(anniversary_gun), //nc spear
"anniversary_gunb" -> MakeTool(anniversary_gunb, anniversary_ammo), //vs eraser "anniversary_gunb" -> MakeTool(anniversary_gunb), //vs eraser
"cycler" -> MakeTool(cycler, bullet_9mm), "cycler" -> MakeTool(cycler),
"gauss" -> MakeTool(gauss, bullet_9mm), "gauss" -> MakeTool(gauss),
"pulsar" -> MakeTool(pulsar, energy_cell), "pulsar" -> MakeTool(pulsar),
"punisher" -> MakeTool(punisher, List(bullet_9mm, rocket)), "punisher" -> MakeTool(punisher),
"flechette" -> MakeTool(flechette, shotgun_shell), "flechette" -> MakeTool(flechette),
"spiker" -> MakeTool(spiker, ancient_ammo_combo), "spiker" -> MakeTool(spiker),
"frag_grenade" -> MakeTool(frag_grenade, frag_grenade_ammo), "frag_grenade" -> MakeTool(frag_grenade),
"jammer_grenade" -> MakeTool(jammer_grenade, jammer_grenade_ammo), "jammer_grenade" -> MakeTool(jammer_grenade),
"plasma_grenade" -> MakeTool(plasma_grenade, plasma_grenade_ammo), "plasma_grenade" -> MakeTool(plasma_grenade),
"katana" -> MakeTool(katana, melee_ammo), "katana" -> MakeTool(katana),
"chainblade" -> MakeTool(chainblade, melee_ammo), "chainblade" -> MakeTool(chainblade),
"magcutter" -> MakeTool(magcutter, melee_ammo), "magcutter" -> MakeTool(magcutter),
"forceblade" -> MakeTool(forceblade, melee_ammo), "forceblade" -> MakeTool(forceblade),
"mini_chaingun" -> MakeTool(mini_chaingun, bullet_9mm), "mini_chaingun" -> MakeTool(mini_chaingun),
"r_shotgun" -> MakeTool(r_shotgun, shotgun_shell), //jackhammer "r_shotgun" -> MakeTool(r_shotgun), //jackhammer
"lasher" -> MakeTool(lasher, energy_cell), "lasher" -> MakeTool(lasher),
"maelstrom" -> MakeTool(maelstrom, maelstrom_ammo), "maelstrom" -> MakeTool(maelstrom),
"striker" -> MakeTool(striker, striker_missile_ammo), "striker" -> MakeTool(striker),
"hunterseeker" -> MakeTool(hunterseeker, hunter_seeker_missile), //phoenix "hunterseeker" -> MakeTool(hunterseeker), //phoenix
"lancer" -> MakeTool(lancer, lancer_cartridge), "lancer" -> MakeTool(lancer),
"phoenix" -> MakeTool(phoenix, phoenix_missile), //decimator "phoenix" -> MakeTool(phoenix), //decimator
"rocklet" -> MakeTool(rocklet, rocket), "rocklet" -> MakeTool(rocklet),
"thumper" -> MakeTool(thumper, frag_cartridge), "thumper" -> MakeTool(thumper),
"radiator" -> MakeTool(radiator, ancient_ammo_combo), "radiator" -> MakeTool(radiator),
"heavy_sniper" -> MakeTool(heavy_sniper, bolt), //hsr "heavy_sniper" -> MakeTool(heavy_sniper), //hsr
"bolt_driver" -> MakeTool(bolt_driver, bolt), "bolt_driver" -> MakeTool(bolt_driver),
"oicw" -> MakeTool(oicw, oicw_ammo), //scorpion "oicw" -> MakeTool(oicw), //scorpion
"flamethrower" -> MakeTool(flamethrower, flamethrower_ammo) "flamethrower" -> MakeTool(flamethrower)
) )
/** /**
@ -176,20 +177,98 @@ abstract class TerminalDefinition(objectId : Int) extends ObjectDefinition(objec
"super_medkit" -> MakeKit(super_medkit), "super_medkit" -> MakeKit(super_medkit),
"super_armorkit" -> MakeKit(super_armorkit), "super_armorkit" -> MakeKit(super_armorkit),
"super_staminakit" -> MakeKit(super_staminakit), "super_staminakit" -> MakeKit(super_staminakit),
"medicalapplicator" -> MakeTool(medicalapplicator, health_canister), "medicalapplicator" -> MakeTool(medicalapplicator),
"bank" -> MakeTool(bank, armor_canister), "bank" -> MakeTool(bank, armor_canister),
"nano_dispenser" -> MakeTool(nano_dispenser, armor_canister), "nano_dispenser" -> MakeTool(nano_dispenser),
//TODO "ace" -> MakeConstructionItem(ace), //TODO "ace" -> MakeConstructionItem(ace),
//TODO "advanced_ace" -> MakeConstructionItem(advanced_ace), //TODO "advanced_ace" -> MakeConstructionItem(advanced_ace),
"remote_electronics_kit" -> MakeSimpleItem(remote_electronics_kit), "remote_electronics_kit" -> MakeSimpleItem(remote_electronics_kit),
"trek" -> MakeTool(trek, trek_ammo), "trek" -> MakeTool(trek),
"command_detonater" -> MakeSimpleItem(command_detonater), "command_detonater" -> MakeSimpleItem(command_detonater),
"flail_targeting_laser" -> MakeSimpleItem(flail_targeting_laser) "flail_targeting_laser" -> MakeSimpleItem(flail_targeting_laser)
) )
/**
* A `Map` of operations for producing a ground-based `Vehicle`.
* key - an identification string sent by the client
* value - a curried function that builds the object
*/
protected val groundVehicles : Map[String, ()=>Vehicle] = Map(
"quadassault" -> MakeVehicle(quadassault),
"fury" -> MakeVehicle(fury),
"quadstealth" -> MakeVehicle(quadstealth),
"ant" -> MakeVehicle(ant),
"ams" -> MakeVehicle(ams),
"mediumtransport" -> MakeVehicle(mediumtransport),
"two_man_assault_buggy" -> MakeVehicle(two_man_assault_buggy),
"skyguard" -> MakeVehicle(skyguard),
"lightning" -> MakeVehicle(lightning),
"threemanheavybuggy" -> MakeVehicle(threemanheavybuggy),
"battlewagon" -> MakeVehicle(battlewagon),
"apc_tr" -> MakeVehicle(apc_tr),
"prowler" -> MakeVehicle(prowler),
"twomanheavybuggy" -> MakeVehicle(twomanheavybuggy),
"thunderer" -> MakeVehicle(thunderer),
"apc_nc" -> MakeVehicle(apc_nc),
"vanguard" -> MakeVehicle(vanguard),
"twomanhoverbuggy" -> MakeVehicle(twomanhoverbuggy),
"aurora" -> MakeVehicle(aurora),
"apc_vs" -> MakeVehicle(apc_vs),
"magrider" -> MakeVehicle(magrider),
"flail" -> MakeVehicle(flail),
"switchblade" -> MakeVehicle(switchblade),
"router" -> MakeVehicle(router)
)
/**
* A `Map` of operations for producing most flight-based `Vehicle`.
* key - an identification string sent by the client
* value - a curried function that builds the object
*/
protected val flight1Vehicles : Map[String, ()=>Vehicle] = Map(
"mosquito" -> MakeVehicle(mosquito),
"lightgunship" -> MakeVehicle(lightgunship),
"wasp" -> MakeVehicle(wasp),
"phantasm" -> MakeVehicle(phantasm),
"vulture" -> MakeVehicle(vulture),
"liberator" -> MakeVehicle(liberator)
)
/**
* A `Map` of operations for producing a flight-based `Vehicle` specific to the dropship terminal.
* key - an identification string sent by the client
* value - a curried function that builds the object
*/
protected val flight2Vehicles : Map[String, ()=>Vehicle] = Map(
"dropship" -> MakeVehicle(dropship),
"galaxy_gunship" -> MakeVehicle(galaxy_gunship),
"lodestar" -> MakeVehicle(lodestar)
)
/**
* A `Map` of operations for producing a ground-based `Vehicle` specific to the bfr terminal.
* key - an identification string sent by the client
* value - a curried function that builds the object
*/
protected val bfrVehicles : Map[String, ()=>Vehicle] = Map(
// "colossus_gunner" -> (()=>Unit),
// "colossus_flight" -> (()=>Unit),
// "peregrine_gunner" -> (()=>Unit),
// "peregrine_flight" -> (()=>Unit),
// "aphelion_gunner" -> (()=>Unit),
// "aphelion_flight" -> (()=>Unit)
)
/** /**
* Create a new `Tool` from provided `EquipmentDefinition` objects. * Create a new `Tool` from provided `EquipmentDefinition` objects.
* @param tdef the `ToolDefinition` objects * @param tdef the `ToolDefinition` object
* @return a partial function that, when called, creates the piece of `Equipment`
*/
protected def MakeTool(tdef : ToolDefinition)() : Tool = MakeTool(tdef, Nil)
/**
* Create a new `Tool` from provided `EquipmentDefinition` objects.
* @param tdef the `ToolDefinition` object
* @param adef an `AmmoBoxDefinition` object * @param adef an `AmmoBoxDefinition` object
* @return a partial function that, when called, creates the piece of `Equipment` * @return a partial function that, when called, creates the piece of `Equipment`
*/ */
@ -200,24 +279,55 @@ abstract class TerminalDefinition(objectId : Int) extends ObjectDefinition(objec
* Only use this function to create default `Tools` with the default parameters. * Only use this function to create default `Tools` with the default parameters.
* For example, loadouts can retain `Tool` information that utilizes alternate, valid ammunition types; * For example, loadouts can retain `Tool` information that utilizes alternate, valid ammunition types;
* and, this method function will not construct a complete object if provided that information. * and, this method function will not construct a complete object if provided that information.
* @param tdef the `ToolDefinition` objects * @param tdef the `ToolDefinition` object
* @param adefs a `List` of `AmmoBoxDefinition` objects * @param adefs a `List` of `AmmoBoxDefinition` objects
* @return a curried function that, when called, creates the piece of `Equipment` * @return a curried function that, when called, creates the piece of `Equipment`
* @see `GlobalDefinitions` * @see `GlobalDefinitions`
* @see `OrderTerminalDefinition.BuildSimplifiedPattern` * @see `OrderTerminalDefinition.BuildSimplifiedPattern`
*/ */
protected def MakeTool(tdef : ToolDefinition, adefs : List[AmmoBoxDefinition])() : Tool = { protected def MakeTool(tdef : ToolDefinition, adefs : List[AmmoBoxDefinition])() : Tool = {
val obj = Tool(tdef) val obj = Tool(tdef)
(0 until obj.MaxAmmoSlot).foreach(index => { adefs match {
val aType = adefs(index) case _ :: _ =>
val ammo = MakeAmmoBox(aType, Some(obj.Definition.FireModes(index).Magazine)) //make internal magazine, full LoadAmmunitionIntoWeapon(obj, adefs)
(obj.AmmoSlots(index).Box = ammo) match { case Nil => ; //as-is
case Some(_) => ; //this means it worked }
case None => obj
org.log4s.getLogger("TerminalDefinition").warn(s"plans do not match definition: trying to feed ${ammo.AmmoType} ammunition into Tool (${obj.Definition.ObjectId} @ $index)") }
/**
* Given a weapon, and custom ammunition profiles, attempt to load those boxes of ammunition into the weapon.<br>
* <br>
* This is a customization function that should normally go unused.
* All of the information necessary to generate a `Tool` from a `Terminal` request should be available on the `ToolDefinition` object.
* The ammunition information, regardless of "customization," must satisfy the type limits of the original definition.
* As thus, to introduce very strange ammunition to a give `Tool`,
* either the definition must be modified or a different definition must be used.
* The custom ammunition is organized into order of ammunition slots based on the `FireModeDefinition` objects.
* That is:
* the first custom element is processed by the first ammunition slot;
* the second custom element is processed by the second ammunition slot; and, so forth.
* @param weapon the `Tool` object
* @param adefs a sequential `List` of ammunition to be loaded into weapon
* @see `AmmoBoxDefinition`
* @see `FireModeDefinition`
*/
private def LoadAmmunitionIntoWeapon(weapon : Tool, adefs : List[AmmoBoxDefinition]) : Unit = {
val definition = weapon.Definition
(0 until math.min(weapon.MaxAmmoSlot, adefs.length)).foreach(index => {
val ammoSlot = weapon.AmmoSlots(index)
adefs.lift(index) match {
case Some(aType) =>
ammoSlot.AllAmmoTypes.indexOf(aType.AmmoType) match {
case -1 =>
log.warn(s"terminal plans do not match definition: can not feed ${aType.AmmoType} ammunition into Tool (${definition.ObjectId} @ ammo $index)")
case n =>
ammoSlot.AmmoTypeIndex = n
ammoSlot.Box = MakeAmmoBox(aType, Some(definition.FireModes(index).Magazine)) //make new internal magazine, full
}
case None => ;
} }
}) })
obj
} }
/** /**
@ -229,11 +339,12 @@ abstract class TerminalDefinition(objectId : Int) extends ObjectDefinition(objec
* @see `GlobalDefinitions` * @see `GlobalDefinitions`
*/ */
protected def MakeAmmoBox(adef : AmmoBoxDefinition, capacity : Option[Int] = None)() : AmmoBox = { protected def MakeAmmoBox(adef : AmmoBoxDefinition, capacity : Option[Int] = None)() : AmmoBox = {
val obj = AmmoBox(adef) capacity match {
if(capacity.isDefined) { case Some(cap) =>
obj.Capacity = capacity.get AmmoBox(adef, cap)
case None =>
AmmoBox(adef)
} }
obj
} }
/** /**
@ -259,4 +370,12 @@ abstract class TerminalDefinition(objectId : Int) extends ObjectDefinition(objec
* @see `GlobalDefinitions` * @see `GlobalDefinitions`
*/ */
protected def MakeConstructionItem(cdef : ConstructionItemDefinition)() : ConstructionItem = ConstructionItem(cdef) protected def MakeConstructionItem(cdef : ConstructionItemDefinition)() : ConstructionItem = ConstructionItem(cdef)
/**
* Create a new `Vehicle` from provided `VehicleDefinition` objects.
* @param vdef the `VehicleDefinition` object
* @return a curried function that, when called, creates the `Vehicle`
* @see `GlobalDefinitions`
*/
protected def MakeVehicle(vdef : VehicleDefinition)() : Vehicle = Vehicle(vdef)
} }

View file

@ -0,0 +1,19 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.terminals
import net.psforever.objects.Player
import net.psforever.packet.game.ItemTransactionMessage
class VehicleTerminalCombinedDefinition extends TerminalDefinition(952) {
private val vehicles = groundVehicles ++ flight1Vehicles
Name = "vehicle_terminal_combined"
def Buy(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = {
vehicles.get(msg.item_name) match {
case Some(vehicle) =>
Terminal.BuyVehicle(vehicle(), Nil)
case None =>
Terminal.NoDeal()
}
}
}

View file

@ -2,7 +2,6 @@
package net.psforever.objects.zones package net.psforever.objects.zones
import akka.actor.Actor import akka.actor.Actor
import net.psforever.objects.serverobject.locks.IFFLock
/** /**
* na * na
@ -35,8 +34,9 @@ class ZoneActor(zone : Zone) extends Actor {
} }
}) })
//check door to locks association //check door to lock association
import net.psforever.objects.serverobject.doors.Door import net.psforever.objects.serverobject.doors.Door
import net.psforever.objects.serverobject.locks.IFFLock
map.DoorToLock.foreach({ case((door_guid, lock_guid)) => map.DoorToLock.foreach({ case((door_guid, lock_guid)) =>
try { try {
if(!guid(door_guid).get.isInstanceOf[Door]) { if(!guid(door_guid).get.isInstanceOf[Door]) {
@ -45,7 +45,7 @@ class ZoneActor(zone : Zone) extends Actor {
} }
catch { catch {
case _ : Exception => case _ : Exception =>
slog.error(s"expected a door, but looking for uninitialized object $door_guid") slog.error(s"expected a door at id $door_guid, but looking for uninitialized object")
} }
try { try {
if(!guid(lock_guid).get.isInstanceOf[IFFLock]) { if(!guid(lock_guid).get.isInstanceOf[IFFLock]) {
@ -54,7 +54,31 @@ class ZoneActor(zone : Zone) extends Actor {
} }
catch { catch {
case _ : Exception => case _ : Exception =>
slog.error(s"expected an IFF locks, but looking for uninitialized object $lock_guid") slog.error(s"expected an IFF locks at id $lock_guid, but looking for uninitialized object")
}
})
//check vehicle terminal to spawn pad association
import net.psforever.objects.serverobject.pad.VehicleSpawnPad
import net.psforever.objects.serverobject.terminals.Terminal
map.TerminalToSpawnPad.foreach({ case ((term_guid, pad_guid)) =>
try {
if(!guid(term_guid).get.isInstanceOf[Terminal]) { //TODO check is vehicle terminal
slog.error(s"expected id $term_guid to be a terminal, but it was not")
}
}
catch {
case _ : Exception =>
slog.error(s"expected a terminal at id $term_guid, but looking for uninitialized object")
}
try {
if(!guid(pad_guid).get.isInstanceOf[VehicleSpawnPad]) {
slog.error(s"expected id $pad_guid to be a spawn pad, but it was not")
}
}
catch {
case _ : Exception =>
slog.error(s"expected a spawn pad at id $pad_guid, but looking for uninitialized object")
} }
}) })
} }

View file

@ -25,6 +25,7 @@ import net.psforever.objects.serverobject.builders.ServerObjectBuilder
*/ */
class ZoneMap(private val name : String) { class ZoneMap(private val name : String) {
private var localObjects : List[ServerObjectBuilder[_]] = List() private var localObjects : List[ServerObjectBuilder[_]] = List()
private var linkTerminalPad : Map[Int, Int] = Map()
private var linkDoorLock : Map[Int, Int] = Map() private var linkDoorLock : Map[Int, Int] = Map()
private var linkObjectBase : Map[Int, Int] = Map() private var linkObjectBase : Map[Int, Int] = Map()
private var numBases : Int = 0 private var numBases : Int = 0
@ -64,7 +65,13 @@ class ZoneMap(private val name : String) {
def DoorToLock : Map[Int, Int] = linkDoorLock def DoorToLock : Map[Int, Int] = linkDoorLock
def DoorToLock(door_guid : Int, lock_guid : Int) = { def DoorToLock(door_guid : Int, lock_guid : Int) : Unit = {
linkDoorLock = linkDoorLock ++ Map(door_guid -> lock_guid) linkDoorLock = linkDoorLock ++ Map(door_guid -> lock_guid)
} }
def TerminalToSpawnPad : Map[Int, Int] = linkTerminalPad
def TerminalToSpawnPad(terminal_guid : Int, pad_guid : Int) : Unit = {
linkTerminalPad = linkTerminalPad ++ Map(terminal_guid -> pad_guid)
}
} }

View file

@ -420,7 +420,7 @@ object GamePacketOpcode extends Enumeration {
case 0x53 => noDecoder(DroppodLaunchRequestMessage) case 0x53 => noDecoder(DroppodLaunchRequestMessage)
case 0x54 => game.HackMessage.decode case 0x54 => game.HackMessage.decode
case 0x55 => noDecoder(DroppodLaunchResponseMessage) case 0x55 => noDecoder(DroppodLaunchResponseMessage)
case 0x56 => noDecoder(GenericObjectActionMessage) case 0x56 => game.GenericObjectActionMessage.decode
case 0x57 => game.AvatarVehicleTimerMessage.decode case 0x57 => game.AvatarVehicleTimerMessage.decode
// 0x58 // 0x58
case 0x58 => game.AvatarImplantMessage.decode case 0x58 => game.AvatarImplantMessage.decode

View file

@ -0,0 +1,39 @@
// Copyright (c) 2017 PSForever
package net.psforever.packet.game
import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket}
import scodec.bits.BitVector
import scodec.{Attempt, Codec}
import scodec.codecs._
import shapeless.{::, HNil}
/**
* Dispatched by the server to enact an effect on some game object.
* (Write more some other time.)
* @param object_guid the target object
* @param code the action code
*/
final case class GenericObjectActionMessage(object_guid : PlanetSideGUID,
code : Int)
extends PlanetSideGamePacket {
type Packet = GenericObjectActionMessage
def opcode = GamePacketOpcode.GenericObjectActionMessage
def encode = GenericObjectActionMessage.encode(this)
}
object GenericObjectActionMessage extends Marshallable[GenericObjectActionMessage] {
implicit val codec : Codec[GenericObjectActionMessage] = (
("object_guid" | PlanetSideGUID.codec) ::
("code" | uint8L) ::
("ex" | bits) //"code" may extract at odd sizes
).exmap[GenericObjectActionMessage] (
{
case guid :: code :: _ :: HNil =>
Attempt.Successful(GenericObjectActionMessage(guid, code))
},
{
case GenericObjectActionMessage(guid, code) =>
Attempt.Successful(guid :: code :: BitVector.empty :: HNil)
}
)
}

View file

@ -89,7 +89,7 @@ import scodec.codecs._
* `106 - Custom Head`<br> * `106 - Custom Head`<br>
* <br> * <br>
* Vehicles:<br> * Vehicles:<br>
* 0 - Vehicle health<br> * 0 - Vehicle base health<br>
* 10 - Driver seat permissions (0 = Locked, 1 = Group, 3 = Empire)<br> * 10 - Driver seat permissions (0 = Locked, 1 = Group, 3 = Empire)<br>
* 11 - Gunner seat(s) permissions (same)<br> * 11 - Gunner seat(s) permissions (same)<br>
* 12 - Passenger seat(s) permissions (same) <br> * 12 - Passenger seat(s) permissions (same) <br>

View file

@ -347,6 +347,8 @@ object ObjectClass {
final val quadstealth_destroyed = 711 final val quadstealth_destroyed = 711
final val router = 741 final val router = 741
final val router_destroyed = 742 final val router_destroyed = 742
final val skyguard = 784
final val skyguard_destroyed = 785
final val switchblade = 847 final val switchblade = 847
final val switchblade_destroyed = 848 final val switchblade_destroyed = 848
final val threemanheavybuggy = 862 //marauder final val threemanheavybuggy = 862 //marauder
@ -845,7 +847,7 @@ object ObjectClass {
case ObjectClass.lancer => ConstructorData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.lancer => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.lasher => ConstructorData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.lasher => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.liberator_25mm_cannon => ConstructorData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.liberator_25mm_cannon => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.liberator_bomb_bay => ConstructorData.genericCodec(WeaponData.codec(2), "weapon") case ObjectClass.liberator_bomb_bay => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.liberator_weapon_system => ConstructorData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.liberator_weapon_system => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.lightgunship_weapon_system => ConstructorData.genericCodec(WeaponData.codec(2), "weapon") case ObjectClass.lightgunship_weapon_system => ConstructorData.genericCodec(WeaponData.codec(2), "weapon")
case ObjectClass.lightning_weapon_system => ConstructorData.genericCodec(WeaponData.codec(2), "weapon") case ObjectClass.lightning_weapon_system => ConstructorData.genericCodec(WeaponData.codec(2), "weapon")
@ -888,7 +890,7 @@ object ObjectClass {
case ObjectClass.rocklet => ConstructorData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.rocklet => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.rotarychaingun_mosquito => ConstructorData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.rotarychaingun_mosquito => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.scythe => ConstructorData.genericCodec(WeaponData.codec(2), "weapon") case ObjectClass.scythe => ConstructorData.genericCodec(WeaponData.codec(2), "weapon")
case ObjectClass.skyguard_weapon_system => ConstructorData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.skyguard_weapon_system => ConstructorData.genericCodec(WeaponData.codec(2), "weapon")
case ObjectClass.spiker => ConstructorData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.spiker => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.spitfire_aa_weapon => ConstructorData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.spitfire_aa_weapon => ConstructorData.genericCodec(WeaponData.codec, "weapon")
case ObjectClass.spitfire_weapon => ConstructorData.genericCodec(WeaponData.codec, "weapon") case ObjectClass.spitfire_weapon => ConstructorData.genericCodec(WeaponData.codec, "weapon")
@ -1245,6 +1247,8 @@ object ObjectClass {
case ObjectClass.quadstealth_destroyed => ConstructorData.genericCodec(DestroyedVehicleData.codec, "wreckage") case ObjectClass.quadstealth_destroyed => ConstructorData.genericCodec(DestroyedVehicleData.codec, "wreckage")
case ObjectClass.router => ConstructorData.genericCodec(VehicleData.codec(VehicleFormat.Variant), "vehicle") case ObjectClass.router => ConstructorData.genericCodec(VehicleData.codec(VehicleFormat.Variant), "vehicle")
case ObjectClass.router_destroyed => ConstructorData.genericCodec(DestroyedVehicleData.codec, "wreckage") case ObjectClass.router_destroyed => ConstructorData.genericCodec(DestroyedVehicleData.codec, "wreckage")
case ObjectClass.skyguard => ConstructorData.genericCodec(VehicleData.codec, "vehicle")
case ObjectClass.skyguard_destroyed => ConstructorData.genericCodec(DestroyedVehicleData.codec, "wreckage")
case ObjectClass.switchblade => ConstructorData.genericCodec(VehicleData.codec(VehicleFormat.Variant), "vehicle") case ObjectClass.switchblade => ConstructorData.genericCodec(VehicleData.codec(VehicleFormat.Variant), "vehicle")
case ObjectClass.switchblade_destroyed => ConstructorData.genericCodec(DestroyedVehicleData.codec, "wreckage") case ObjectClass.switchblade_destroyed => ConstructorData.genericCodec(DestroyedVehicleData.codec, "wreckage")
case ObjectClass.threemanheavybuggy => ConstructorData.genericCodec(VehicleData.codec, "vehicle") case ObjectClass.threemanheavybuggy => ConstructorData.genericCodec(VehicleData.codec, "vehicle")

View file

@ -27,6 +27,81 @@ object Prefab {
VehicleData(CommonFieldData(loc, faction, 0), 0, health, false, false, driveState, false, false, false, Some(UtilityVehicleData(0)), None)(VehicleFormat.Utility) VehicleData(CommonFieldData(loc, faction, 0), 0, health, false, false, driveState, false, false, false, Some(UtilityVehicleData(0)), None)(VehicleFormat.Utility)
} }
def apc_nc(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon1_guid : PlanetSideGUID, ammo1_guid : PlanetSideGUID, weapon2_guid : PlanetSideGUID, ammo2_guid : PlanetSideGUID, weapon3_guid : PlanetSideGUID, ammo3_guid : PlanetSideGUID, weapon4_guid : PlanetSideGUID, ammo4_guid : PlanetSideGUID, weapon5_guid : PlanetSideGUID, ammo5_guid : PlanetSideGUID, weapon6_guid : PlanetSideGUID, ammo6_guid : PlanetSideGUID) : VehicleData = {
VehicleData(CommonFieldData(loc, faction, 2), 0, health, false, false, DriveState.State7, true, false, false, None,
Some(InventoryData(
InventoryItemData(ObjectClass.apc_weapon_systemc_nc, weapon1_guid, 11,
WeaponData(0x6, 0x8, 0, ObjectClass.bullet_20mm, ammo1_guid, 0, AmmoBoxData(8))
) ::
InventoryItemData(ObjectClass.apc_weapon_systemb, weapon2_guid, 12,
WeaponData(0x6, 0x8, 0, ObjectClass.bullet_75mm, ammo2_guid, 0, AmmoBoxData(8))
) ::
InventoryItemData(ObjectClass.apc_weapon_systema, weapon3_guid, 13,
WeaponData(0x6, 0x8, 0, ObjectClass.bullet_75mm, ammo3_guid, 0, AmmoBoxData(8))
) ::
InventoryItemData(ObjectClass.apc_weapon_systemd_nc, weapon4_guid, 14,
WeaponData(0x6, 0x8, 0, ObjectClass.bullet_20mm, ammo4_guid, 0, AmmoBoxData(8))
) ::
InventoryItemData(ObjectClass.apc_ballgun_r, weapon5_guid, 15,
WeaponData(0x6, 0x8, 0, ObjectClass.bullet_12mm, ammo5_guid, 0, AmmoBoxData(8))
) ::
InventoryItemData(ObjectClass.apc_ballgun_l, weapon6_guid, 16,
WeaponData(0x6, 0x8, 0, ObjectClass.bullet_12mm, ammo6_guid, 0, AmmoBoxData(8))
) :: Nil
))
)(VehicleFormat.Normal)
}
def apc_tr(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon1_guid : PlanetSideGUID, ammo1_guid : PlanetSideGUID, weapon2_guid : PlanetSideGUID, ammo2_guid : PlanetSideGUID, weapon3_guid : PlanetSideGUID, ammo3_guid : PlanetSideGUID, weapon4_guid : PlanetSideGUID, ammo4_guid : PlanetSideGUID, weapon5_guid : PlanetSideGUID, ammo5_guid : PlanetSideGUID, weapon6_guid : PlanetSideGUID, ammo6_guid : PlanetSideGUID) : VehicleData = {
VehicleData(CommonFieldData(loc, faction, 2), 0, health, false, false, DriveState.State7, true, false, false, None,
Some(InventoryData(
InventoryItemData(ObjectClass.apc_weapon_systemc_tr, weapon1_guid, 11,
WeaponData(0x6, 0x8, 0, ObjectClass.bullet_15mm, ammo1_guid, 0, AmmoBoxData(8))
) ::
InventoryItemData(ObjectClass.apc_weapon_systemb, weapon2_guid, 12,
WeaponData(0x6, 0x8, 0, ObjectClass.bullet_75mm, ammo2_guid, 0, AmmoBoxData(8))
) ::
InventoryItemData(ObjectClass.apc_weapon_systema, weapon3_guid, 13,
WeaponData(0x6, 0x8, 0, ObjectClass.bullet_75mm, ammo3_guid, 0, AmmoBoxData(8))
) ::
InventoryItemData(ObjectClass.apc_weapon_systemd_tr, weapon4_guid, 14,
WeaponData(0x6, 0x8, 0, ObjectClass.bullet_15mm, ammo4_guid, 0, AmmoBoxData(8))
) ::
InventoryItemData(ObjectClass.apc_ballgun_r, weapon5_guid, 15,
WeaponData(0x6, 0x8, 0, ObjectClass.bullet_12mm, ammo5_guid, 0, AmmoBoxData(8))
) ::
InventoryItemData(ObjectClass.apc_ballgun_l, weapon6_guid, 16,
WeaponData(0x6, 0x8, 0, ObjectClass.bullet_12mm, ammo6_guid, 0, AmmoBoxData(8))
) :: Nil
))
)(VehicleFormat.Normal)
}
def apc_vs(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon1_guid : PlanetSideGUID, ammo1_guid : PlanetSideGUID, weapon2_guid : PlanetSideGUID, ammo2_guid : PlanetSideGUID, weapon3_guid : PlanetSideGUID, ammo3_guid : PlanetSideGUID, weapon4_guid : PlanetSideGUID, ammo4_guid : PlanetSideGUID, weapon5_guid : PlanetSideGUID, ammo5_guid : PlanetSideGUID, weapon6_guid : PlanetSideGUID, ammo6_guid : PlanetSideGUID) : VehicleData = {
VehicleData(CommonFieldData(loc, faction, 2), 0, health, false, false, DriveState.State7, true, false, false, None,
Some(InventoryData(
InventoryItemData(ObjectClass.apc_weapon_systemc_vs, weapon1_guid, 11,
WeaponData(0x6, 0x8, 0, ObjectClass.flux_cannon_thresher_battery, ammo1_guid, 0, AmmoBoxData(8))
) ::
InventoryItemData(ObjectClass.apc_weapon_systemb, weapon2_guid, 12,
WeaponData(0x6, 0x8, 0, ObjectClass.bullet_75mm, ammo2_guid, 0, AmmoBoxData(8))
) ::
InventoryItemData(ObjectClass.apc_weapon_systema, weapon3_guid, 13,
WeaponData(0x6, 0x8, 0, ObjectClass.bullet_75mm, ammo3_guid, 0, AmmoBoxData(8))
) ::
InventoryItemData(ObjectClass.apc_weapon_systemd_vs, weapon4_guid, 14,
WeaponData(0x6, 0x8, 0, ObjectClass.flux_cannon_thresher_battery, ammo4_guid, 0, AmmoBoxData(8))
) ::
InventoryItemData(ObjectClass.apc_ballgun_r, weapon5_guid, 15,
WeaponData(0x6, 0x8, 0, ObjectClass.bullet_12mm, ammo5_guid, 0, AmmoBoxData(8))
) ::
InventoryItemData(ObjectClass.apc_ballgun_l, weapon6_guid, 16,
WeaponData(0x6, 0x8, 0, ObjectClass.bullet_12mm, ammo6_guid, 0, AmmoBoxData(8))
) :: Nil
))
)(VehicleFormat.Normal)
}
def aurora(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon1_guid : PlanetSideGUID, ammo11_guid : PlanetSideGUID, ammo12_guid : PlanetSideGUID, weapon2_guid : PlanetSideGUID, ammo21_guid : PlanetSideGUID, ammo22_guid : PlanetSideGUID) : VehicleData = { def aurora(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon1_guid : PlanetSideGUID, ammo11_guid : PlanetSideGUID, ammo12_guid : PlanetSideGUID, weapon2_guid : PlanetSideGUID, ammo21_guid : PlanetSideGUID, ammo22_guid : PlanetSideGUID) : VehicleData = {
VehicleData(CommonFieldData(loc, faction, 0), 0, health, false, false, DriveState.State7, true, false, false, None, VehicleData(CommonFieldData(loc, faction, 0), 0, health, false, false, DriveState.State7, true, false, false, None,
Some(InventoryData( Some(InventoryData(
@ -118,56 +193,6 @@ object Prefab {
)(VehicleFormat.Variant) )(VehicleFormat.Variant)
} }
def juggernaut(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon1_guid : PlanetSideGUID, ammo1_guid : PlanetSideGUID, weapon2_guid : PlanetSideGUID, ammo2_guid : PlanetSideGUID, weapon3_guid : PlanetSideGUID, ammo3_guid : PlanetSideGUID, weapon4_guid : PlanetSideGUID, ammo4_guid : PlanetSideGUID, weapon5_guid : PlanetSideGUID, ammo5_guid : PlanetSideGUID, weapon6_guid : PlanetSideGUID, ammo6_guid : PlanetSideGUID) : VehicleData = {
VehicleData(CommonFieldData(loc, faction, 2), 0, health, false, false, DriveState.State7, true, false, false, None,
Some(InventoryData(
InventoryItemData(ObjectClass.apc_weapon_systemc_tr, weapon1_guid, 11,
WeaponData(0x6, 0x8, 0, ObjectClass.bullet_15mm, ammo1_guid, 0, AmmoBoxData(8))
) ::
InventoryItemData(ObjectClass.apc_weapon_systemb, weapon2_guid, 12,
WeaponData(0x6, 0x8, 0, ObjectClass.bullet_75mm, ammo2_guid, 0, AmmoBoxData(8))
) ::
InventoryItemData(ObjectClass.apc_weapon_systema, weapon3_guid, 13,
WeaponData(0x6, 0x8, 0, ObjectClass.bullet_75mm, ammo3_guid, 0, AmmoBoxData(8))
) ::
InventoryItemData(ObjectClass.apc_weapon_systemd_tr, weapon4_guid, 14,
WeaponData(0x6, 0x8, 0, ObjectClass.bullet_15mm, ammo4_guid, 0, AmmoBoxData(8))
) ::
InventoryItemData(ObjectClass.apc_ballgun_r, weapon5_guid, 15,
WeaponData(0x6, 0x8, 0, ObjectClass.bullet_12mm, ammo5_guid, 0, AmmoBoxData(8))
) ::
InventoryItemData(ObjectClass.apc_ballgun_l, weapon6_guid, 16,
WeaponData(0x6, 0x8, 0, ObjectClass.bullet_12mm, ammo6_guid, 0, AmmoBoxData(8))
) :: Nil
))
)(VehicleFormat.Normal)
}
def leviathan(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon1_guid : PlanetSideGUID, ammo1_guid : PlanetSideGUID, weapon2_guid : PlanetSideGUID, ammo2_guid : PlanetSideGUID, weapon3_guid : PlanetSideGUID, ammo3_guid : PlanetSideGUID, weapon4_guid : PlanetSideGUID, ammo4_guid : PlanetSideGUID, weapon5_guid : PlanetSideGUID, ammo5_guid : PlanetSideGUID, weapon6_guid : PlanetSideGUID, ammo6_guid : PlanetSideGUID) : VehicleData = {
VehicleData(CommonFieldData(loc, faction, 2), 0, health, false, false, DriveState.State7, true, false, false, None,
Some(InventoryData(
InventoryItemData(ObjectClass.apc_weapon_systemc_vs, weapon1_guid, 11,
WeaponData(0x6, 0x8, 0, ObjectClass.flux_cannon_thresher_battery, ammo1_guid, 0, AmmoBoxData(8))
) ::
InventoryItemData(ObjectClass.apc_weapon_systemb, weapon2_guid, 12,
WeaponData(0x6, 0x8, 0, ObjectClass.bullet_75mm, ammo2_guid, 0, AmmoBoxData(8))
) ::
InventoryItemData(ObjectClass.apc_weapon_systema, weapon3_guid, 13,
WeaponData(0x6, 0x8, 0, ObjectClass.bullet_75mm, ammo3_guid, 0, AmmoBoxData(8))
) ::
InventoryItemData(ObjectClass.apc_weapon_systemd_vs, weapon4_guid, 14,
WeaponData(0x6, 0x8, 0, ObjectClass.flux_cannon_thresher_battery, ammo4_guid, 0, AmmoBoxData(8))
) ::
InventoryItemData(ObjectClass.apc_ballgun_r, weapon5_guid, 15,
WeaponData(0x6, 0x8, 0, ObjectClass.bullet_12mm, ammo5_guid, 0, AmmoBoxData(8))
) ::
InventoryItemData(ObjectClass.apc_ballgun_l, weapon6_guid, 16,
WeaponData(0x6, 0x8, 0, ObjectClass.bullet_12mm, ammo6_guid, 0, AmmoBoxData(8))
) :: Nil
))
)(VehicleFormat.Normal)
}
def liberator(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon1_guid : PlanetSideGUID, ammo1_guid : PlanetSideGUID, weapon2_guid : PlanetSideGUID, ammo2_guid : PlanetSideGUID, ammo3_guid : PlanetSideGUID, weapon3_guid : PlanetSideGUID, ammo4_guid : PlanetSideGUID) : VehicleData = { def liberator(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon1_guid : PlanetSideGUID, ammo1_guid : PlanetSideGUID, weapon2_guid : PlanetSideGUID, ammo2_guid : PlanetSideGUID, ammo3_guid : PlanetSideGUID, weapon3_guid : PlanetSideGUID, ammo4_guid : PlanetSideGUID) : VehicleData = {
VehicleData(CommonFieldData(loc, faction, 2), 0, health, false, false, DriveState.State7, true, false, false, Some(VariantVehicleData(0)), VehicleData(CommonFieldData(loc, faction, 2), 0, health, false, false, DriveState.State7, true, false, false, Some(VariantVehicleData(0)),
Some(InventoryData( Some(InventoryData(
@ -187,8 +212,8 @@ object Prefab {
def lightgunship(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon_guid : PlanetSideGUID, ammo1_guid : PlanetSideGUID, ammo2_guid : PlanetSideGUID) : VehicleData = { def lightgunship(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon_guid : PlanetSideGUID, ammo1_guid : PlanetSideGUID, ammo2_guid : PlanetSideGUID) : VehicleData = {
VehicleData(CommonFieldData(loc, faction, 2), 0, health, false, false, DriveState.Mobile, false, false, false, Some(VariantVehicleData(0)), VehicleData(CommonFieldData(loc, faction, 2), 0, health, false, false, DriveState.Mobile, false, false, false, Some(VariantVehicleData(0)),
Some(InventoryData( Some(InventoryData(
InventoryItemData(445, weapon_guid, 1, InventoryItemData(ObjectClass.lightgunship_weapon_system, weapon_guid, 1,
WeaponData(0x6, 0x8, 0, 16, ammo1_guid, 0, AmmoBoxData(8), 722, ammo2_guid,1, AmmoBoxData(8)) WeaponData(0x6, 0x8, 0, ObjectClass.bullet_20mm, ammo1_guid, 0, AmmoBoxData(8), ObjectClass.reaver_rocket, ammo2_guid,1, AmmoBoxData(8))
) :: Nil ) :: Nil
)) ))
)(VehicleFormat.Variant) )(VehicleFormat.Variant)
@ -270,14 +295,6 @@ object Prefab {
)(VehicleFormat.Normal) )(VehicleFormat.Normal)
} }
def router(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, terminal_guid : PlanetSideGUID) : VehicleData = {
VehicleData(CommonFieldData(loc, faction, 2), 0, health, false, false, DriveState.Mobile, false, false, false, Some(VariantVehicleData(0)),
Some(InventoryData(
InventoryItemData(ObjectClass.teleportpad_terminal, terminal_guid, 1, CommonTerminalData(faction, 2)) :: Nil
))
)(VehicleFormat.Variant)
}
def quadassault(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon_guid : PlanetSideGUID, ammo_guid : PlanetSideGUID) : VehicleData = { def quadassault(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon_guid : PlanetSideGUID, ammo_guid : PlanetSideGUID) : VehicleData = {
VehicleData(CommonFieldData(loc, faction, 0), 0, health, false, false, DriveState.State7, true, false, false, None, VehicleData(CommonFieldData(loc, faction, 0), 0, health, false, false, DriveState.State7, true, false, false, None,
Some(InventoryData( Some(InventoryData(
@ -292,6 +309,24 @@ object Prefab {
VehicleData(CommonFieldData(loc, faction, 0), 0, health, false, false, DriveState.State7, false, false, false, None, None)(VehicleFormat.Normal) VehicleData(CommonFieldData(loc, faction, 0), 0, health, false, false, DriveState.State7, false, false, false, None, None)(VehicleFormat.Normal)
} }
def router(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, terminal_guid : PlanetSideGUID) : VehicleData = {
VehicleData(CommonFieldData(loc, faction, 2), 0, health, false, false, DriveState.Mobile, false, false, false, Some(VariantVehicleData(0)),
Some(InventoryData(
InventoryItemData(ObjectClass.teleportpad_terminal, terminal_guid, 1, CommonTerminalData(faction, 2)) :: Nil
))
)(VehicleFormat.Variant)
}
def skyguard(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon_guid : PlanetSideGUID, ammo1_guid : PlanetSideGUID, ammo2_guid : PlanetSideGUID) : VehicleData = {
VehicleData(CommonFieldData(loc, faction, 2), 0, health, false, false, DriveState.State7, true, false, false, None,
Some(InventoryData(
InventoryItemData(ObjectClass.skyguard_weapon_system, weapon_guid, 2,
WeaponData(0x6, 0x8, 0, ObjectClass.skyguard_flak_cannon_ammo, ammo1_guid, 0, AmmoBoxData(8), ObjectClass.bullet_12mm, ammo2_guid, 1, AmmoBoxData(8))
) :: Nil
))
)(VehicleFormat.Normal)
}
def switchblade(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, driveState : DriveState.Value, weapon_guid : PlanetSideGUID, ammo1_guid : PlanetSideGUID, ammo2_guid : PlanetSideGUID) : VehicleData = { def switchblade(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, driveState : DriveState.Value, weapon_guid : PlanetSideGUID, ammo1_guid : PlanetSideGUID, ammo2_guid : PlanetSideGUID) : VehicleData = {
VehicleData(CommonFieldData(loc, faction, 0), 0, health, false, false, DriveState.State7, true, false, false, Some(VariantVehicleData(0)), VehicleData(CommonFieldData(loc, faction, 0), 0, health, false, false, DriveState.State7, true, false, false, Some(VariantVehicleData(0)),
Some(InventoryData( Some(InventoryData(
@ -368,31 +403,6 @@ object Prefab {
)(VehicleFormat.Normal) )(VehicleFormat.Normal)
} }
def vindicator(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon1_guid : PlanetSideGUID, ammo1_guid : PlanetSideGUID, weapon2_guid : PlanetSideGUID, ammo2_guid : PlanetSideGUID, weapon3_guid : PlanetSideGUID, ammo3_guid : PlanetSideGUID, weapon4_guid : PlanetSideGUID, ammo4_guid : PlanetSideGUID, weapon5_guid : PlanetSideGUID, ammo5_guid : PlanetSideGUID, weapon6_guid : PlanetSideGUID, ammo6_guid : PlanetSideGUID) : VehicleData = {
VehicleData(CommonFieldData(loc, faction, 2), 0, health, false, false, DriveState.State7, true, false, false, None,
Some(InventoryData(
InventoryItemData(ObjectClass.apc_weapon_systemc_nc, weapon1_guid, 11,
WeaponData(0x6, 0x8, 0, ObjectClass.bullet_20mm, ammo1_guid, 0, AmmoBoxData(8))
) ::
InventoryItemData(ObjectClass.apc_weapon_systemb, weapon2_guid, 12,
WeaponData(0x6, 0x8, 0, ObjectClass.bullet_75mm, ammo2_guid, 0, AmmoBoxData(8))
) ::
InventoryItemData(ObjectClass.apc_weapon_systema, weapon3_guid, 13,
WeaponData(0x6, 0x8, 0, ObjectClass.bullet_75mm, ammo3_guid, 0, AmmoBoxData(8))
) ::
InventoryItemData(ObjectClass.apc_weapon_systemd_nc, weapon4_guid, 14,
WeaponData(0x6, 0x8, 0, ObjectClass.bullet_20mm, ammo4_guid, 0, AmmoBoxData(8))
) ::
InventoryItemData(ObjectClass.apc_ballgun_r, weapon5_guid, 15,
WeaponData(0x6, 0x8, 0, ObjectClass.bullet_12mm, ammo5_guid, 0, AmmoBoxData(8))
) ::
InventoryItemData(ObjectClass.apc_ballgun_l, weapon6_guid, 16,
WeaponData(0x6, 0x8, 0, ObjectClass.bullet_12mm, ammo6_guid, 0, AmmoBoxData(8))
) :: Nil
))
)(VehicleFormat.Normal)
}
def vulture(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon1_guid : PlanetSideGUID, ammo1_guid : PlanetSideGUID, weapon2_guid : PlanetSideGUID, ammo2_guid : PlanetSideGUID, weapon3_guid : PlanetSideGUID, ammo3_guid : PlanetSideGUID) : VehicleData = { def vulture(loc : PlacementData, faction : PlanetSideEmpire.Value, health : Int, weapon1_guid : PlanetSideGUID, ammo1_guid : PlanetSideGUID, weapon2_guid : PlanetSideGUID, ammo2_guid : PlanetSideGUID, weapon3_guid : PlanetSideGUID, ammo3_guid : PlanetSideGUID) : VehicleData = {
VehicleData(CommonFieldData(loc, faction, 2), 0, health, false, false, DriveState.State7, true, false, false, Some(VariantVehicleData(0)), VehicleData(CommonFieldData(loc, faction, 2), 0, health, false, false, DriveState.State7, true, false, false, Some(VariantVehicleData(0)),
Some(InventoryData( Some(InventoryData(

View file

@ -4,11 +4,41 @@ package net.psforever.types
import net.psforever.newcodecs._ import net.psforever.newcodecs._
import scodec.Codec import scodec.Codec
import scodec.codecs._ import scodec.codecs._
import shapeless.{::, HNil}
final case class Vector3(x : Float, final case class Vector3(x : Float,
y : Float, y : Float,
z : Float) z : Float) {
/**
* Operator override for vector addition, treating `Vector3` objects as actual mathematical vectors.
* The application of this overload is "vector1 + vector2."
* @param vec the other `Vector3` object
* @return a new `Vector3` object with the summed values
*/
def +(vec : Vector3) : Vector3 = {
new Vector3(x + vec.x, y + vec.y, z + vec.z)
}
/**
* Operator override for vector subtraction, treating `Vector3` objects as actual mathematical vectors.
* The application of this overload is "vector1 - vector2."
* @param vec the other `Vector3` object
* @return a new `Vector3` object with the difference values
*/
def -(vec : Vector3) : Vector3 = {
new Vector3(x - vec.x, y - vec.y, z - vec.z)
}
/**
* Operator override for vector scaling, treating `Vector3` objects as actual mathematical vectors.
* The application of this overload is "vector * scalar" exclusively.
* "scalar * vector" is invalid.
* @param scalar the value to multiply this vector
* @return a new `Vector3` object
*/
def *(scalar : Float) : Vector3 = {
new Vector3(x*scalar, y*scalar, z*scalar)
}
}
object Vector3 { object Vector3 {
implicit val codec_pos : Codec[Vector3] = ( implicit val codec_pos : Codec[Vector3] = (
@ -28,4 +58,48 @@ object Vector3 {
("y" | floatL) :: ("y" | floatL) ::
("z" | floatL) ("z" | floatL)
).as[Vector3] ).as[Vector3]
/**
* Calculate the actual distance between two points.
* @param pos1 the first point
* @param pos2 the second point
* @return the distance
*/
def Distance(pos1 : Vector3, pos2 : Vector3) : Float = {
math.sqrt(DistanceSquared(pos1, pos2)).toFloat
}
/**
* Calculate the squared distance between two points.
* Though some time is saved care must be taken that any comparative distance is also squared.
* @param pos1 the first point
* @param pos2 the second point
* @return the distance
*/
def DistanceSquared(pos1 : Vector3, pos2 : Vector3) : Float = {
val dvec : Vector3 = pos1 - pos2
(dvec.x * dvec.x) + (dvec.y * dvec.y) + (dvec.z * dvec.z)
}
/**
* Calculate the actual magnitude of a vector.
* @param vec the vector
* @return the magnitude
*/
def Magnitude(vec : Vector3) : Float = {
math.sqrt(MagnitudeSquared(vec)).toFloat
}
/**
* Calculate the squared magnitude of a vector.
* Though some time is saved care must be taken that any comparative magnitude is also squared.
* @param vec the vector
* @return the magnitude
*/
def MagnitudeSquared(vec : Vector3) : Float = {
val dx : Float = vec.x
val dy : Float = vec.y
val dz : Float = vec.z
(dx * dx) + (dy * dy) + (dz * dz)
}
} }

View file

@ -0,0 +1,68 @@
// Copyright (c) 2017 PSForever
import org.specs2.mutable._
import net.psforever.types.Vector3
class Vector3Test extends Specification {
val vec = Vector3(1.3f, -2.6f, 3.9f)
"Vector3" should {
"construct" in {
vec.x mustEqual 1.3f
vec.y mustEqual -2.6f
vec.z mustEqual 3.9f
}
"calculate magnitude (like a vector) 1" in {
val obj = Vector3(2.0f, 0.0f, 0.0f)
Vector3.Magnitude(obj) mustEqual 2.0f
}
"calculate magnitude (like a vector) 2" in {
val obj = Vector3(3.0f, 4.0f, 0.0f)
Vector3.Magnitude(obj) mustEqual 5.0f
}
"calculate magnitude (like a vector) 3" in {
Vector3.Magnitude(vec) mustEqual 4.864155f
}
"calculate square magnitude (like a vector)" in {
Vector3.MagnitudeSquared(vec) mustEqual 23.66f
}
"calculate distance 1" in {
val obj1 = Vector3(0.0f, 0.0f, 0.0f)
val obj2 = Vector3(2.0f, 0.0f, 0.0f)
Vector3.Distance(obj1, obj2) mustEqual 2.0f
}
"calculate distance 2" in {
val obj1 = Vector3(0.0f, 0.0f, 0.0f)
val obj2 = Vector3(2.0f, 0.0f, 0.0f)
Vector3.Distance(obj1, obj2) mustEqual Vector3.Magnitude(obj2)
}
"calculate distance 3" in {
val obj1 = Vector3(3.0f, 4.0f, 5.0f)
val obj2 = Vector3(3.0f, 4.0f, 5.0f)
Vector3.Distance(obj1, obj2) mustEqual 0f
}
"addition" in {
val obj1 = Vector3(3.0f, 4.0f, 5.0f)
val obj2 = Vector3(3.0f, 4.0f, 5.0f)
obj1 + obj2 mustEqual Vector3(6f, 8f, 10f)
}
"subtraction" in {
val obj1 = Vector3(3.0f, 4.0f, 5.0f)
val obj2 = Vector3(3.0f, 4.0f, 5.0f)
obj1 - obj2 mustEqual Vector3(0f, 0f, 0f)
}
"scalar" in {
vec * 3f mustEqual Vector3(3.8999999f, -7.7999997f, 11.700001f)
}
}
}

View file

@ -0,0 +1,28 @@
// Copyright (c) 2017 PSForever
package game
import org.specs2.mutable._
import net.psforever.packet._
import net.psforever.packet.game._
import scodec.bits._
class GenericObjectActionMessageTest extends Specification {
val string = hex"56 B501 24"
"decode" in {
PacketCoding.DecodePacket(string).require match {
case GenericObjectActionMessage(object_guid, action) =>
object_guid mustEqual PlanetSideGUID(437)
action mustEqual 36
case _ =>
ko
}
}
"encode" in {
val msg = GenericObjectActionMessage(PlanetSideGUID(437), 36)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string
}
}

View file

@ -40,16 +40,15 @@ class ConverterTest extends Specification {
"convert to packet" in { "convert to packet" in {
val tdef = ToolDefinition(1076) val tdef = ToolDefinition(1076)
tdef.Size = EquipmentSize.Rifle tdef.Size = EquipmentSize.Rifle
tdef.AmmoTypes += Ammo.shotgun_shell tdef.AmmoTypes += GlobalDefinitions.shotgun_shell
tdef.AmmoTypes += Ammo.shotgun_shell_AP tdef.AmmoTypes += GlobalDefinitions.shotgun_shell_AP
tdef.FireModes += new FireModeDefinition tdef.FireModes += new FireModeDefinition
tdef.FireModes.head.AmmoTypeIndices += 0 tdef.FireModes.head.AmmoTypeIndices += 0
tdef.FireModes.head.AmmoTypeIndices += 1 tdef.FireModes.head.AmmoTypeIndices += 1
tdef.FireModes.head.AmmoSlotIndex = 0 tdef.FireModes.head.AmmoSlotIndex = 0
tdef.FireModes.head.Magazine = 30
val obj : Tool = Tool(tdef) val obj : Tool = Tool(tdef)
val box = AmmoBox(PlanetSideGUID(90), new AmmoBoxDefinition(Ammo.shotgun_shell.id)) obj.AmmoSlot.Box.GUID = PlanetSideGUID(90)
obj.AmmoSlots.head.Box = box
obj.AmmoSlots.head.Magazine = 30
obj.Definition.Packet.DetailedConstructorData(obj) match { obj.Definition.Packet.DetailedConstructorData(obj) match {
case Success(pkt) => case Success(pkt) =>
@ -139,24 +138,22 @@ class ConverterTest extends Specification {
Give the Player's Holster (2) the Tool Give the Player's Holster (2) the Tool
Place the remaining AmmoBox into the Player's inventory in the third slot (8) Place the remaining AmmoBox into the Player's inventory in the third slot (8)
*/ */
val bullet_9mm = AmmoBoxDefinition(28)
bullet_9mm.Capacity = 50
val box1 = AmmoBox(PlanetSideGUID(90), bullet_9mm)
val box2 = AmmoBox(PlanetSideGUID(91), bullet_9mm)
val tdef = ToolDefinition(1076) val tdef = ToolDefinition(1076)
tdef.Name = "sample_weapon" tdef.Name = "sample_weapon"
tdef.Size = EquipmentSize.Rifle tdef.Size = EquipmentSize.Rifle
tdef.AmmoTypes += Ammo.bullet_9mm tdef.AmmoTypes += GlobalDefinitions.bullet_9mm
tdef.FireModes += new FireModeDefinition tdef.FireModes += new FireModeDefinition
tdef.FireModes.head.AmmoTypeIndices += 0 tdef.FireModes.head.AmmoTypeIndices += 0
tdef.FireModes.head.AmmoSlotIndex = 0 tdef.FireModes.head.AmmoSlotIndex = 0
tdef.FireModes.head.Magazine = 18 tdef.FireModes.head.Magazine = 18
val tool = Tool(PlanetSideGUID(92), tdef) val tool = Tool(tdef)
tool.AmmoSlots.head.Box = box1 tool.GUID = PlanetSideGUID(92)
tool.AmmoSlot.Box.GUID = PlanetSideGUID(90)
val obj = Player(PlanetSideGUID(93), "Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) val obj = Player(PlanetSideGUID(93), "Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)
obj.Slot(2).Equipment = tool obj.Slot(2).Equipment = tool
obj.Slot(5).Equipment.get.GUID = PlanetSideGUID(94) obj.Slot(5).Equipment.get.GUID = PlanetSideGUID(94)
obj.Inventory += 8 -> box2 obj.Inventory += 8 -> AmmoBox(GlobalDefinitions.bullet_9mm)
obj.Slot(8).Equipment.get.GUID = PlanetSideGUID(91)
obj obj
} }
val converter = new CharacterSelectConverter val converter = new CharacterSelectConverter
@ -263,7 +260,7 @@ class ConverterTest extends Specification {
val fury_weapon_systema_def = ToolDefinition(ObjectClass.fury_weapon_systema) val fury_weapon_systema_def = ToolDefinition(ObjectClass.fury_weapon_systema)
fury_weapon_systema_def.Size = EquipmentSize.VehicleWeapon fury_weapon_systema_def.Size = EquipmentSize.VehicleWeapon
fury_weapon_systema_def.AmmoTypes += Ammo.hellfire_ammo fury_weapon_systema_def.AmmoTypes += GlobalDefinitions.hellfire_ammo
fury_weapon_systema_def.FireModes += new FireModeDefinition fury_weapon_systema_def.FireModes += new FireModeDefinition
fury_weapon_systema_def.FireModes.head.AmmoTypeIndices += 0 fury_weapon_systema_def.FireModes.head.AmmoTypeIndices += 0
fury_weapon_systema_def.FireModes.head.AmmoSlotIndex = 0 fury_weapon_systema_def.FireModes.head.AmmoSlotIndex = 0

View file

@ -10,7 +10,6 @@ import net.psforever.objects.GlobalDefinitions._
import org.specs2.mutable._ import org.specs2.mutable._
class EquipmentTest extends Specification { class EquipmentTest extends Specification {
"AmmoBox" should { "AmmoBox" should {
"define" in { "define" in {
val obj = AmmoBoxDefinition(86) val obj = AmmoBoxDefinition(86)
@ -19,8 +18,8 @@ class EquipmentTest extends Specification {
obj.AmmoType mustEqual Ammo.aphelion_immolation_cannon_ammo obj.AmmoType mustEqual Ammo.aphelion_immolation_cannon_ammo
obj.Capacity mustEqual 300 obj.Capacity mustEqual 300
obj.Tile.width mustEqual InventoryTile.Tile44.width obj.Tile.Width mustEqual InventoryTile.Tile44.Width
obj.Tile.height mustEqual InventoryTile.Tile44.height obj.Tile.Height mustEqual InventoryTile.Tile44.Height
obj.ObjectId mustEqual 86 obj.ObjectId mustEqual 86
} }
@ -58,8 +57,8 @@ class EquipmentTest extends Specification {
val obj = ToolDefinition(1076) val obj = ToolDefinition(1076)
obj.Name = "sample_weapon" obj.Name = "sample_weapon"
obj.Size = EquipmentSize.Rifle obj.Size = EquipmentSize.Rifle
obj.AmmoTypes += Ammo.shotgun_shell obj.AmmoTypes += GlobalDefinitions.shotgun_shell
obj.AmmoTypes += Ammo.shotgun_shell_AP obj.AmmoTypes += GlobalDefinitions.shotgun_shell_AP
obj.FireModes += new FireModeDefinition obj.FireModes += new FireModeDefinition
obj.FireModes.head.AmmoTypeIndices += 0 obj.FireModes.head.AmmoTypeIndices += 0
obj.FireModes.head.AmmoTypeIndices += 1 obj.FireModes.head.AmmoTypeIndices += 1
@ -74,9 +73,10 @@ class EquipmentTest extends Specification {
obj.FireModes(1).Magazine = 18 obj.FireModes(1).Magazine = 18
obj.Tile = InventoryTile.Tile93 obj.Tile = InventoryTile.Tile93
obj.ObjectId mustEqual 1076 obj.ObjectId mustEqual 1076
obj.Name mustEqual "sample_weapon" obj.Name mustEqual "sample_weapon"
obj.AmmoTypes.head mustEqual Ammo.shotgun_shell obj.AmmoTypes.head mustEqual GlobalDefinitions.shotgun_shell
obj.AmmoTypes(1) mustEqual Ammo.shotgun_shell_AP obj.AmmoTypes(1) mustEqual GlobalDefinitions.shotgun_shell_AP
obj.FireModes.head.AmmoTypeIndices.head mustEqual 0 obj.FireModes.head.AmmoTypeIndices.head mustEqual 0
obj.FireModes.head.AmmoTypeIndices(1) mustEqual 1 obj.FireModes.head.AmmoTypeIndices(1) mustEqual 1
obj.FireModes.head.AmmoSlotIndex mustEqual 0 obj.FireModes.head.AmmoSlotIndex mustEqual 0
@ -89,8 +89,8 @@ class EquipmentTest extends Specification {
obj.FireModes(1).Chamber mustEqual 3 obj.FireModes(1).Chamber mustEqual 3
obj.FireModes(1).Magazine mustEqual 18 obj.FireModes(1).Magazine mustEqual 18
obj.FireModes(1).ResetAmmoIndexOnSwap mustEqual false obj.FireModes(1).ResetAmmoIndexOnSwap mustEqual false
obj.Tile.width mustEqual InventoryTile.Tile93.width obj.Tile.Width mustEqual InventoryTile.Tile93.Width
obj.Tile.height mustEqual InventoryTile.Tile93.height obj.Tile.Height mustEqual InventoryTile.Tile93.Height
} }
"construct" in { "construct" in {
@ -118,8 +118,8 @@ class EquipmentTest extends Specification {
//explanation: sample_weapon has two fire modes; adjusting the FireMode changes between them //explanation: sample_weapon has two fire modes; adjusting the FireMode changes between them
val tdef = ToolDefinition(1076) val tdef = ToolDefinition(1076)
tdef.Size = EquipmentSize.Rifle tdef.Size = EquipmentSize.Rifle
tdef.AmmoTypes += Ammo.shotgun_shell tdef.AmmoTypes += GlobalDefinitions.shotgun_shell
tdef.AmmoTypes += Ammo.shotgun_shell_AP tdef.AmmoTypes += GlobalDefinitions.shotgun_shell_AP
tdef.FireModes += new FireModeDefinition tdef.FireModes += new FireModeDefinition
tdef.FireModes.head.AmmoTypeIndices += 0 tdef.FireModes.head.AmmoTypeIndices += 0
tdef.FireModes.head.AmmoSlotIndex = 0 tdef.FireModes.head.AmmoSlotIndex = 0
@ -149,8 +149,8 @@ class EquipmentTest extends Specification {
//explanation: obj has one fire mode and two ammunitions; adjusting the AmmoType changes between them //explanation: obj has one fire mode and two ammunitions; adjusting the AmmoType changes between them
val tdef = ToolDefinition(1076) val tdef = ToolDefinition(1076)
tdef.Size = EquipmentSize.Rifle tdef.Size = EquipmentSize.Rifle
tdef.AmmoTypes += Ammo.shotgun_shell tdef.AmmoTypes += GlobalDefinitions.shotgun_shell
tdef.AmmoTypes += Ammo.shotgun_shell_AP tdef.AmmoTypes += GlobalDefinitions.shotgun_shell_AP
tdef.FireModes += new FireModeDefinition tdef.FireModes += new FireModeDefinition
tdef.FireModes.head.AmmoTypeIndices += 0 tdef.FireModes.head.AmmoTypeIndices += 0
tdef.FireModes.head.AmmoTypeIndices += 1 tdef.FireModes.head.AmmoTypeIndices += 1
@ -168,14 +168,54 @@ class EquipmentTest extends Specification {
obj.AmmoTypeIndex mustEqual 0 obj.AmmoTypeIndex mustEqual 0
obj.AmmoType mustEqual Ammo.shotgun_shell obj.AmmoType mustEqual Ammo.shotgun_shell
} }
"multiple ammo types and multiple fire modes, split (Punisher)" in {
val obj = Tool(GlobalDefinitions.punisher)
//ammo = 0, fmode = 0
obj.FireModeIndex mustEqual 0
obj.AmmoTypeIndex mustEqual 0
obj.AmmoType mustEqual Ammo.bullet_9mm
//ammo = 2, fmode = 1
obj.NextFireMode
obj.FireModeIndex mustEqual 1
obj.AmmoTypeIndex mustEqual 2
obj.AmmoType mustEqual Ammo.rocket
//ammo = 3, fmode = 1
obj.NextAmmoType
obj.AmmoTypeIndex mustEqual 3
obj.AmmoType mustEqual Ammo.frag_cartridge
//ammo = 4, fmode = 1
obj.NextAmmoType
obj.AmmoTypeIndex mustEqual 4
obj.AmmoType mustEqual Ammo.jammer_cartridge
//ammo = 0, fmode = 0
obj.NextFireMode
obj.FireModeIndex mustEqual 0
obj.AmmoTypeIndex mustEqual 0
obj.AmmoType mustEqual Ammo.bullet_9mm
//ammo = 1, fmode = 0
obj.NextAmmoType
obj.AmmoTypeIndex mustEqual 1
obj.AmmoType mustEqual Ammo.bullet_9mm_AP
//ammo = 5, fmode = 1
obj.NextFireMode
obj.NextAmmoType
obj.FireModeIndex mustEqual 1
obj.AmmoTypeIndex mustEqual 5
obj.AmmoType mustEqual Ammo.plasma_cartridge
//ammo = 2, fmode = 1
obj.NextAmmoType
obj.AmmoTypeIndex mustEqual 2
obj.AmmoType mustEqual Ammo.rocket
}
} }
"Kit" should { "Kit" should {
"define" in { "define" in {
val sample = KitDefinition(Kits.medkit) val sample = KitDefinition(Kits.medkit)
sample.ObjectId mustEqual medkit.ObjectId sample.ObjectId mustEqual medkit.ObjectId
sample.Tile.width mustEqual medkit.Tile.width sample.Tile.Width mustEqual medkit.Tile.Width
sample.Tile.height mustEqual medkit.Tile.height sample.Tile.Height mustEqual medkit.Tile.Height
} }
"construct" in { "construct" in {
@ -200,8 +240,8 @@ class EquipmentTest extends Specification {
sample.Modes.head mustEqual DeployedItem.tank_traps sample.Modes.head mustEqual DeployedItem.tank_traps
sample.Modes(1) mustEqual DeployedItem.portable_manned_turret_tr sample.Modes(1) mustEqual DeployedItem.portable_manned_turret_tr
sample.Modes(2) mustEqual DeployedItem.deployable_shield_generator sample.Modes(2) mustEqual DeployedItem.deployable_shield_generator
sample.Tile.width mustEqual InventoryTile.Tile63.width sample.Tile.Width mustEqual InventoryTile.Tile63.Width
sample.Tile.height mustEqual InventoryTile.Tile63.height sample.Tile.Height mustEqual InventoryTile.Tile63.Height
} }
"construct" in { "construct" in {

View file

@ -57,8 +57,8 @@ class InventoryTest extends Specification {
val obj : GridInventory = GridInventory(9, 6) val obj : GridInventory = GridInventory(9, 6)
obj += 0 -> bullet9mmBox1 obj += 0 -> bullet9mmBox1
obj.Capacity mustEqual 45 obj.Capacity mustEqual 45
val w = bullet9mmBox2.Tile.width val w = bullet9mmBox2.Tile.Width
val h = bullet9mmBox2.Tile.height val h = bullet9mmBox2.Tile.Height
val list0 = obj.CheckCollisionsAsList(0, w, h) val list0 = obj.CheckCollisionsAsList(0, w, h)
list0 match { list0 match {
case scala.util.Success(list) => list.length mustEqual 1 case scala.util.Success(list) => list.length mustEqual 1
@ -91,8 +91,8 @@ class InventoryTest extends Specification {
val obj : GridInventory = GridInventory(9, 6) val obj : GridInventory = GridInventory(9, 6)
obj += 3 -> bullet9mmBox1 obj += 3 -> bullet9mmBox1
obj.Capacity mustEqual 45 obj.Capacity mustEqual 45
val w = bullet9mmBox2.Tile.width val w = bullet9mmBox2.Tile.Width
val h = bullet9mmBox2.Tile.height val h = bullet9mmBox2.Tile.Height
val list0 = obj.CheckCollisionsAsList(3, w, h) val list0 = obj.CheckCollisionsAsList(3, w, h)
list0 match { list0 match {
case scala.util.Success(list) => list.length mustEqual 1 case scala.util.Success(list) => list.length mustEqual 1
@ -125,8 +125,8 @@ class InventoryTest extends Specification {
val obj : GridInventory = GridInventory(9, 6) val obj : GridInventory = GridInventory(9, 6)
obj += 0 -> bullet9mmBox1 obj += 0 -> bullet9mmBox1
obj.Capacity mustEqual 45 obj.Capacity mustEqual 45
val w = bullet9mmBox2.Tile.width val w = bullet9mmBox2.Tile.Width
val h = bullet9mmBox2.Tile.height val h = bullet9mmBox2.Tile.Height
val list0 = obj.CheckCollisionsAsList(0, w, h) val list0 = obj.CheckCollisionsAsList(0, w, h)
list0 match { list0 match {
case scala.util.Success(list) => list.length mustEqual 1 case scala.util.Success(list) => list.length mustEqual 1
@ -159,8 +159,8 @@ class InventoryTest extends Specification {
val obj : GridInventory = GridInventory(9, 6) val obj : GridInventory = GridInventory(9, 6)
obj += 27 -> bullet9mmBox1 obj += 27 -> bullet9mmBox1
obj.Capacity mustEqual 45 obj.Capacity mustEqual 45
val w = bullet9mmBox2.Tile.width val w = bullet9mmBox2.Tile.Width
val h = bullet9mmBox2.Tile.height val h = bullet9mmBox2.Tile.Height
val list0 = obj.CheckCollisionsAsList(27, w, h) val list0 = obj.CheckCollisionsAsList(27, w, h)
list0 match { list0 match {
case scala.util.Success(list) => list.length mustEqual 1 case scala.util.Success(list) => list.length mustEqual 1
@ -205,8 +205,8 @@ class InventoryTest extends Specification {
val obj : GridInventory = GridInventory(12, 9) val obj : GridInventory = GridInventory(12, 9)
obj += 39 -> bullet9mmBox1 obj += 39 -> bullet9mmBox1
obj.Capacity mustEqual 99 //108 - 9 obj.Capacity mustEqual 99 //108 - 9
val w = bullet9mmBox2.Tile.width val w = bullet9mmBox2.Tile.Width
val h = bullet9mmBox2.Tile.height val h = bullet9mmBox2.Tile.Height
val list0 = obj.CheckCollisionsAsList(0, w, h) val list0 = obj.CheckCollisionsAsList(0, w, h)
list0 match { list0 match {
case scala.util.Success(list) => list.isEmpty mustEqual true case scala.util.Success(list) => list.isEmpty mustEqual true

View file

@ -0,0 +1,110 @@
// Copyright (c) 2017 PSForever
package objects
import akka.actor.{ActorRef, Props}
import net.psforever.objects.serverobject.pad.{VehicleSpawnControl, VehicleSpawnPad}
import net.psforever.objects.vehicles.VehicleControl
import net.psforever.objects.{GlobalDefinitions, Player, Vehicle}
import net.psforever.packet.game.PlanetSideGUID
import net.psforever.types.{CharacterGender, PlanetSideEmpire, Vector3}
import org.specs2.mutable.Specification
import scala.concurrent.duration.Duration
class VehicleSpawnPadTest extends Specification {
"VehicleSpawnPadDefinition" should {
"define" in {
GlobalDefinitions.spawn_pad.ObjectId mustEqual 800
}
}
"VehicleSpawnPad" should {
"construct" in {
val obj = VehicleSpawnPad(GlobalDefinitions.spawn_pad)
obj.Actor mustEqual ActorRef.noSender
obj.Definition mustEqual GlobalDefinitions.spawn_pad
}
}
}
class VehicleSpawnControl1Test extends ActorTest() {
"VehicleSpawnControl" should {
"construct" in {
val obj = VehicleSpawnPad(GlobalDefinitions.spawn_pad)
obj.Actor = system.actorOf(Props(classOf[VehicleSpawnControl], obj), "door")
assert(obj.Actor != ActorRef.noSender)
}
}
}
class VehicleSpawnControl2Test extends ActorTest() {
"VehicleSpawnControl" should {
"spawn a vehicle" in {
val obj = VehicleSpawnPad(GlobalDefinitions.spawn_pad)
obj.Actor = system.actorOf(Props(classOf[VehicleSpawnControl], obj), "door")
val player = Player("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)
player.Spawn
val vehicle = Vehicle(GlobalDefinitions.two_man_assault_buggy)
vehicle.GUID = PlanetSideGUID(1)
vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle")
obj.Actor ! VehicleSpawnPad.VehicleOrder(player, vehicle)
val reply = receiveOne(Duration.create(10000, "ms"))
assert(reply == VehicleSpawnPad.ConcealPlayer) //explicit: isInstanceOf does not work
val reply2 = receiveOne(Duration.create(10000, "ms"))
assert(reply2.isInstanceOf[VehicleSpawnPad.LoadVehicle])
assert(reply2.asInstanceOf[VehicleSpawnPad.LoadVehicle].vehicle == vehicle)
assert(reply2.asInstanceOf[VehicleSpawnPad.LoadVehicle].pad == obj)
player.VehicleOwned = vehicle
val reply3 = receiveOne(Duration.create(10000, "ms"))
assert(reply3.isInstanceOf[VehicleSpawnPad.PlayerSeatedInVehicle])
assert(reply3.asInstanceOf[VehicleSpawnPad.PlayerSeatedInVehicle].vehicle == vehicle)
val reply4 = receiveOne(Duration.create(10000, "ms"))
assert(reply4.isInstanceOf[VehicleSpawnPad.SpawnPadBlockedWarning])
assert(reply4.asInstanceOf[VehicleSpawnPad.SpawnPadBlockedWarning].vehicle == vehicle)
assert(reply4.asInstanceOf[VehicleSpawnPad.SpawnPadBlockedWarning].warning_count > 0)
vehicle.Position = Vector3(11f, 0f, 0f) //greater than 10m
val reply5 = receiveOne(Duration.create(10000, "ms"))
assert(reply5.isInstanceOf[VehicleSpawnPad.SpawnPadUnblocked])
assert(reply5.asInstanceOf[VehicleSpawnPad.SpawnPadUnblocked].vehicle_guid == vehicle.GUID)
}
}
}
class VehicleSpawnControl3Test extends ActorTest() {
"VehicleSpawnControl" should {
"not spawn a vehicle if player is dead" in {
val obj = VehicleSpawnPad(GlobalDefinitions.spawn_pad)
obj.Actor = system.actorOf(Props(classOf[VehicleSpawnControl], obj), "door")
val player = Player("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)
val vehicle = Vehicle(GlobalDefinitions.two_man_assault_buggy)
vehicle.GUID = PlanetSideGUID(1)
vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle")
obj.Actor ! VehicleSpawnPad.VehicleOrder(player, vehicle)
val reply = receiveOne(Duration.create(5000, "ms"))
assert(reply == null)
}
}
}
class VehicleSpawnControl4Test extends ActorTest() {
"VehicleSpawnControl" should {
"not spawn a vehicle if vehicle Actor is missing" in {
val obj = VehicleSpawnPad(GlobalDefinitions.spawn_pad)
obj.Actor = system.actorOf(Props(classOf[VehicleSpawnControl], obj), "door")
val player = Player("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)
player.Spawn
val vehicle = Vehicle(GlobalDefinitions.two_man_assault_buggy)
vehicle.GUID = PlanetSideGUID(1)
obj.Actor ! VehicleSpawnPad.VehicleOrder(player, vehicle)
val reply = receiveOne(Duration.create(5000, "ms"))
assert(reply == null)
}
}
}

View file

@ -44,8 +44,8 @@ class VehicleTest extends Specification {
fury.Weapons.size mustEqual 1 fury.Weapons.size mustEqual 1
fury.Weapons.get(0) mustEqual None fury.Weapons.get(0) mustEqual None
fury.Weapons.get(1) mustEqual Some(GlobalDefinitions.fury_weapon_systema) fury.Weapons.get(1) mustEqual Some(GlobalDefinitions.fury_weapon_systema)
fury.TrunkSize.width mustEqual 11 fury.TrunkSize.Width mustEqual 11
fury.TrunkSize.height mustEqual 11 fury.TrunkSize.Height mustEqual 11
fury.TrunkOffset mustEqual 30 fury.TrunkOffset mustEqual 30
} }
} }

View file

@ -0,0 +1,37 @@
// Copyright (c) 2017 PSForever
package objects.terminal
import akka.actor.ActorRef
import net.psforever.objects.{GlobalDefinitions, Player, Tool}
import net.psforever.objects.serverobject.terminals.Terminal
import net.psforever.packet.game.{ItemTransactionMessage, PlanetSideGUID}
import net.psforever.types.{CharacterGender, PlanetSideEmpire, TransactionType}
import org.specs2.mutable.Specification
class AirVehicleTerminalTest extends Specification {
"Air_Vehicle_Terminal" should {
val player = Player("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)
"construct" in {
val terminal = Terminal(GlobalDefinitions.air_vehicle_terminal)
terminal.Actor mustEqual ActorRef.noSender
}
"player can buy a reaver ('lightgunship')" in {
val terminal = Terminal(GlobalDefinitions.air_vehicle_terminal)
val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Buy, 0, "lightgunship", 0, PlanetSideGUID(0))
val reply = terminal.Request(player, msg)
reply.isInstanceOf[Terminal.BuyVehicle] mustEqual true
val reply2 = reply.asInstanceOf[Terminal.BuyVehicle]
reply2.vehicle.Definition mustEqual GlobalDefinitions.lightgunship
reply2.loadout mustEqual Nil //TODO
}
"player can not buy a fake vehicle ('reaver')" in {
val terminal = Terminal(GlobalDefinitions.ground_vehicle_terminal)
val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Buy, 0, "reaver", 0, PlanetSideGUID(0))
terminal.Request(player, msg) mustEqual Terminal.NoDeal()
}
}
}

View file

@ -0,0 +1,37 @@
// Copyright (c) 2017 PSForever
package objects.terminal
import akka.actor.ActorRef
import net.psforever.objects.{GlobalDefinitions, Player}
import net.psforever.objects.serverobject.terminals.Terminal
import net.psforever.packet.game.{ItemTransactionMessage, PlanetSideGUID}
import net.psforever.types.{CharacterGender, PlanetSideEmpire, TransactionType}
import org.specs2.mutable.Specification
class DropshipVehicleTerminalTest extends Specification {
"Dropship_Vehicle_Terminal" should {
val player = Player("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)
"construct" in {
val terminal = Terminal(GlobalDefinitions.dropship_vehicle_terminal)
terminal.Actor mustEqual ActorRef.noSender
}
"player can buy a galaxy ('dropship')" in {
val terminal = Terminal(GlobalDefinitions.dropship_vehicle_terminal)
val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Buy, 0, "dropship", 0, PlanetSideGUID(0))
val reply = terminal.Request(player, msg)
reply.isInstanceOf[Terminal.BuyVehicle] mustEqual true
val reply2 = reply.asInstanceOf[Terminal.BuyVehicle]
reply2.vehicle.Definition mustEqual GlobalDefinitions.dropship
reply2.loadout mustEqual Nil //TODO
}
"player can not buy a fake vehicle ('galaxy')" in {
val terminal = Terminal(GlobalDefinitions.dropship_vehicle_terminal)
val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Buy, 0, "galaxy", 0, PlanetSideGUID(0))
terminal.Request(player, msg) mustEqual Terminal.NoDeal()
}
}
}

View file

@ -0,0 +1,37 @@
// Copyright (c) 2017 PSForever
package objects.terminal
import akka.actor.ActorRef
import net.psforever.objects.{GlobalDefinitions, Player}
import net.psforever.objects.serverobject.terminals.Terminal
import net.psforever.packet.game.{ItemTransactionMessage, PlanetSideGUID}
import net.psforever.types.{CharacterGender, PlanetSideEmpire, TransactionType}
import org.specs2.mutable.Specification
class GroundVehicleTerminalTest extends Specification {
"Ground_Vehicle_Terminal" should {
val player = Player("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)
"construct" in {
val terminal = Terminal(GlobalDefinitions.ground_vehicle_terminal)
terminal.Actor mustEqual ActorRef.noSender
}
"player can buy a harasser ('two_man_assault_buggy')" in {
val terminal = Terminal(GlobalDefinitions.ground_vehicle_terminal)
val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Buy, 0, "two_man_assault_buggy", 0, PlanetSideGUID(0))
val reply = terminal.Request(player, msg)
reply.isInstanceOf[Terminal.BuyVehicle] mustEqual true
val reply2 = reply.asInstanceOf[Terminal.BuyVehicle]
reply2.vehicle.Definition mustEqual GlobalDefinitions.two_man_assault_buggy
reply2.loadout mustEqual Nil //TODO
}
"player can not buy a fake vehicle ('harasser')" in {
val terminal = Terminal(GlobalDefinitions.ground_vehicle_terminal)
val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Buy, 0, "harasser", 0, PlanetSideGUID(0))
terminal.Request(player, msg) mustEqual Terminal.NoDeal()
}
}
}

View file

@ -71,3 +71,40 @@ class CertTerminalControl3Test extends ActorTest() {
assert(reply2.response == Terminal.SellCertification(CertificationType.MediumAssault, 2)) assert(reply2.response == Terminal.SellCertification(CertificationType.MediumAssault, 2))
} }
} }
class VehicleTerminalControl1Test extends ActorTest() {
"TerminalControl can be used to buy a vehicle ('two_man_assault_buggy')" in {
val terminal = Terminal(GlobalDefinitions.ground_vehicle_terminal)
terminal.Actor = system.actorOf(Props(classOf[TerminalControl], terminal), "test-cert-term")
val player = Player("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)
val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Buy, 0, "two_man_assault_buggy", 0, PlanetSideGUID(0))
terminal.Actor ! Terminal.Request(player, msg)
val reply = receiveOne(Duration.create(500, "ms"))
assert(reply.isInstanceOf[Terminal.TerminalMessage])
val reply2 = reply.asInstanceOf[Terminal.TerminalMessage]
assert(reply2.player == player)
assert(reply2.msg == msg)
assert(reply2.response.isInstanceOf[Terminal.BuyVehicle])
val reply3 = reply2.response.asInstanceOf[Terminal.BuyVehicle]
assert(reply3.vehicle.Definition == GlobalDefinitions.two_man_assault_buggy)
assert(reply3.loadout == Nil) //TODO
}
}
class VehicleTerminalControl2Test extends ActorTest() {
"TerminalControl can be used to warn about not buy a vehicle ('harasser')" in {
val terminal = Terminal(GlobalDefinitions.ground_vehicle_terminal)
terminal.Actor = system.actorOf(Props(classOf[TerminalControl], terminal), "test-cert-term")
val player = Player("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)
val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Buy, 0, "harasser", 0, PlanetSideGUID(0))
terminal.Actor ! Terminal.Request(player, msg)
val reply = receiveOne(Duration.create(500, "ms"))
assert(reply.isInstanceOf[Terminal.TerminalMessage])
val reply2 = reply.asInstanceOf[Terminal.TerminalMessage]
assert(reply2.player == player)
assert(reply2.msg == msg)
assert(reply2.response == Terminal.NoDeal())
}
}

View file

@ -0,0 +1,47 @@
// Copyright (c) 2017 PSForever
package objects.terminal
import akka.actor.ActorRef
import net.psforever.objects.{GlobalDefinitions, Player}
import net.psforever.objects.serverobject.terminals.Terminal
import net.psforever.packet.game.{ItemTransactionMessage, PlanetSideGUID}
import net.psforever.types.{CharacterGender, PlanetSideEmpire, TransactionType}
import org.specs2.mutable.Specification
class VehicleTerminalCombinedTest extends Specification {
"Ground_Vehicle_Terminal" should {
val player = Player("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)
"construct" in {
val terminal = Terminal(GlobalDefinitions.vehicle_terminal_combined)
terminal.Actor mustEqual ActorRef.noSender
}
"player can buy a ground vehicle, the harasser ('two_man_assault_buggy')" in {
val terminal = Terminal(GlobalDefinitions.vehicle_terminal_combined)
val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Buy, 0, "two_man_assault_buggy", 0, PlanetSideGUID(0))
val reply = terminal.Request(player, msg)
reply.isInstanceOf[Terminal.BuyVehicle] mustEqual true
val reply2 = reply.asInstanceOf[Terminal.BuyVehicle]
reply2.vehicle.Definition mustEqual GlobalDefinitions.two_man_assault_buggy
reply2.loadout mustEqual Nil //TODO
}
"player can buy a flying vehicle, the reaver ('lightgunship')" in {
val terminal = Terminal(GlobalDefinitions.vehicle_terminal_combined)
val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Buy, 0, "lightgunship", 0, PlanetSideGUID(0))
val reply = terminal.Request(player, msg)
reply.isInstanceOf[Terminal.BuyVehicle] mustEqual true
val reply2 = reply.asInstanceOf[Terminal.BuyVehicle]
reply2.vehicle.Definition mustEqual GlobalDefinitions.lightgunship
reply2.loadout mustEqual Nil //TODO
}
"player can not buy a fake vehicle ('harasser')" in {
val terminal = Terminal(GlobalDefinitions.vehicle_terminal_combined)
val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Buy, 0, "harasser", 0, PlanetSideGUID(0))
terminal.Request(player, msg) mustEqual Terminal.NoDeal()
}
}
}

View file

@ -14,7 +14,8 @@ import com.typesafe.config.ConfigFactory
import net.psforever.crypto.CryptoInterface import net.psforever.crypto.CryptoInterface
import net.psforever.objects.zones._ import net.psforever.objects.zones._
import net.psforever.objects.guid.TaskResolver import net.psforever.objects.guid.TaskResolver
import net.psforever.objects.serverobject.builders.{DoorObjectBuilder, IFFLockObjectBuilder, TerminalObjectBuilder} import net.psforever.objects.serverobject.builders.{DoorObjectBuilder, IFFLockObjectBuilder, TerminalObjectBuilder, VehicleSpawnPadObjectBuilder}
import net.psforever.types.Vector3
import org.slf4j import org.slf4j
import org.fusesource.jansi.Ansi._ import org.fusesource.jansi.Ansi._
import org.fusesource.jansi.Ansi.Color._ import org.fusesource.jansi.Ansi.Color._
@ -244,6 +245,10 @@ object PsLogin {
LocalObject(TerminalObjectBuilder(order_terminal, 853)) LocalObject(TerminalObjectBuilder(order_terminal, 853))
LocalObject(TerminalObjectBuilder(order_terminal, 855)) LocalObject(TerminalObjectBuilder(order_terminal, 855))
LocalObject(TerminalObjectBuilder(order_terminal, 860)) LocalObject(TerminalObjectBuilder(order_terminal, 860))
LocalObject(TerminalObjectBuilder(ground_vehicle_terminal, 1063))
LocalObject(VehicleSpawnPadObjectBuilder(spawn_pad, 500)) //TODO guid not correct
LocalObject(TerminalObjectBuilder(dropship_vehicle_terminal, 304))
LocalObject(VehicleSpawnPadObjectBuilder(spawn_pad, 501)) //TODO guid not correct
LocalBases = 30 LocalBases = 30
@ -251,8 +256,14 @@ object PsLogin {
ObjectToBase(332, 29) ObjectToBase(332, 29)
ObjectToBase(556, 29) ObjectToBase(556, 29)
ObjectToBase(558, 29) ObjectToBase(558, 29)
ObjectToBase(1063, 29) //TODO unowned courtyard terminal?
ObjectToBase(500, 29) //TODO unowned courtyard spawnpad?
ObjectToBase(304, 29) //TODO unowned courtyard terminal?
ObjectToBase(501, 29) //TODO unowned courtyard spawnpad?
DoorToLock(330, 558) DoorToLock(330, 558)
DoorToLock(332, 556) DoorToLock(332, 556)
TerminalToSpawnPad(1063, 500)
TerminalToSpawnPad(304, 501)
} }
val home3 = new Zone("home3", map13, 13) { val home3 = new Zone("home3", map13, 13) {
override def Init(implicit context : ActorContext) : Unit = { override def Init(implicit context : ActorContext) : Unit = {
@ -261,6 +272,19 @@ object PsLogin {
import net.psforever.types.PlanetSideEmpire import net.psforever.types.PlanetSideEmpire
Base(2).get.Faction = PlanetSideEmpire.VS //HART building C Base(2).get.Faction = PlanetSideEmpire.VS //HART building C
Base(29).get.Faction = PlanetSideEmpire.NC //South Villa Gun Tower Base(29).get.Faction = PlanetSideEmpire.NC //South Villa Gun Tower
GUID(500) match {
case Some(pad) =>
pad.Position = Vector3(3506.0f, 2820.0f, 92.0f)
pad.Orientation = Vector3(0f, 0f, 270.0f)
case None => ;
}
GUID(501) match {
case Some(pad) =>
pad.Position = Vector3(3508.9844f, 2895.961f, 92.296875f)
pad.Orientation = Vector3(0f, 0f, 270.0f)
case None => ;
}
} }
} }

View file

@ -17,8 +17,9 @@ import net.psforever.objects.inventory.{GridInventory, InventoryItem}
import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject} import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject}
import net.psforever.objects.serverobject.doors.Door import net.psforever.objects.serverobject.doors.Door
import net.psforever.objects.serverobject.locks.IFFLock import net.psforever.objects.serverobject.locks.IFFLock
import net.psforever.objects.serverobject.pad.VehicleSpawnPad
import net.psforever.objects.serverobject.terminals.Terminal import net.psforever.objects.serverobject.terminals.Terminal
import net.psforever.objects.vehicles.{AccessPermissionGroup, Seat, VehicleLockState} import net.psforever.objects.vehicles.{AccessPermissionGroup, VehicleLockState}
import net.psforever.objects.zones.{InterstellarCluster, Zone} import net.psforever.objects.zones.{InterstellarCluster, Zone}
import net.psforever.packet.game.objectcreate._ import net.psforever.packet.game.objectcreate._
import net.psforever.types._ import net.psforever.types._
@ -45,25 +46,43 @@ class WorldSessionActor extends Actor with MDCContextAware {
var continent : Zone = null var continent : Zone = null
var progressBarValue : Option[Float] = None var progressBarValue : Option[Float] = None
var clientKeepAlive : Cancellable = WorldSessionActor.DefaultCancellable var clientKeepAlive : Cancellable = DefaultCancellable.obj
var progressBarUpdate : Cancellable = WorldSessionActor.DefaultCancellable var progressBarUpdate : Cancellable = DefaultCancellable.obj
override def postStop() = { override def postStop() = {
if(clientKeepAlive != null) if(clientKeepAlive != null)
clientKeepAlive.cancel() clientKeepAlive.cancel()
localService ! Service.Leave()
vehicleService ! Service.Leave()
avatarService ! Service.Leave()
LivePlayerList.Remove(sessionId) match {
case Some(tplayer) =>
tplayer.VehicleSeated match {
case Some(vehicle_guid) =>
vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.KickPassenger(tplayer.GUID, 0, true))
case None => ;
}
tplayer.VehicleOwned match {
case Some(vehicle_guid) =>
continent.GUID(vehicle_guid) match {
case Some(vehicle : Vehicle) =>
vehicle.Owner = None
//TODO temporary solution; to un-own, permit driver seat to Empire access level
vehicle.PermissionGroup(10, VehicleLockState.Empire.id)
vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.SeatPermissions(tplayer.GUID, vehicle_guid, 10, VehicleLockState.Empire.id))
case _ => ;
}
case None => ;
}
avatarService ! Service.Leave() if(tplayer.HasGUID) {
localService ! Service.Leave() val guid = tplayer.GUID
vehicleService ! Service.Leave() avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.ObjectDelete(guid, guid))
LivePlayerList.Remove(sessionId) match { taskResolver ! GUIDTask.UnregisterAvatar(tplayer)(continent.GUID)
case Some(tplayer) => //TODO normally, the actual player avatar persists a minute or so after the user disconnects
if(tplayer.HasGUID) { }
val guid = tplayer.GUID
avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.ObjectDelete(guid, guid)) case None => ;
taskResolver ! GUIDTask.UnregisterAvatar(tplayer)(continent.GUID)
//TODO normally, the actual player avatar persists a minute or so after the user disconnects
}
case None => ;
} }
} }
@ -124,6 +143,11 @@ class WorldSessionActor extends Actor with MDCContextAware {
sendResponse(PacketCoding.CreateGamePacket(0, ArmorChangedMessage(guid, suit, subtype))) sendResponse(PacketCoding.CreateGamePacket(0, ArmorChangedMessage(guid, suit, subtype)))
} }
case AvatarResponse.ConcealPlayer() =>
if(player.GUID != guid) {
sendResponse(PacketCoding.CreateGamePacket(0, GenericObjectActionMessage(guid, 36)))
}
case AvatarResponse.EquipmentInHand(slot, item) => case AvatarResponse.EquipmentInHand(slot, item) =>
if(player.GUID != guid) { if(player.GUID != guid) {
val definition = item.Definition val definition = item.Definition
@ -186,7 +210,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
} }
else { else {
val before = player.lastSeenStreamMessage(guid.guid) val before = player.lastSeenStreamMessage(guid.guid)
val dist = WorldSessionActor.DistanceSquared(player.Position, msg.pos) val dist = Vector3.DistanceSquared(player.Position, msg.pos)
(msg.pos, now - before, dist) (msg.pos, now - before, dist)
} }
@ -280,9 +304,6 @@ class WorldSessionActor extends Actor with MDCContextAware {
case VehicleResponse.MountVehicle(vehicle_guid, seat) => case VehicleResponse.MountVehicle(vehicle_guid, seat) =>
if(player.GUID != guid) { if(player.GUID != guid) {
sendResponse(PacketCoding.CreateGamePacket(0, ObjectAttachMessage(vehicle_guid, guid, seat))) sendResponse(PacketCoding.CreateGamePacket(0, ObjectAttachMessage(vehicle_guid, guid, seat)))
if(player.VehicleOwned.contains(vehicle_guid)) { //simplistic vehicle ownership management
player.VehicleOwned = None
}
} }
case VehicleResponse.SeatPermissions(vehicle_guid, seat_group, permission) => case VehicleResponse.SeatPermissions(vehicle_guid, seat_group, permission) =>
@ -524,6 +545,19 @@ class WorldSessionActor extends Actor with MDCContextAware {
sendResponse(PacketCoding.CreateGamePacket(0, ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Learn, false))) sendResponse(PacketCoding.CreateGamePacket(0, ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Learn, false)))
} }
case Terminal.BuyVehicle(vehicle, loadout) =>
continent.Map.TerminalToSpawnPad.get(msg.terminal_guid.guid) match {
case Some(pad_guid) =>
val pad = continent.GUID(pad_guid).get.asInstanceOf[VehicleSpawnPad]
vehicle.Faction = tplayer.Faction
vehicle.Position = pad.Position
vehicle.Orientation = pad.Orientation
taskResolver ! RegisterNewVehicle(vehicle, pad)
sendResponse(PacketCoding.CreateGamePacket(0, ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Learn, true)))
case None =>
log.error(s"$tplayer wanted to spawn a vehicle, but there was no spawn pad associated with terminal ${msg.terminal_guid} to accept it")
}
case Terminal.NoDeal() => case Terminal.NoDeal() =>
log.warn(s"$tplayer made a request but the terminal rejected the order $msg") log.warn(s"$tplayer made a request but the terminal rejected the order $msg")
sendResponse(PacketCoding.CreateGamePacket(0, ItemTransactionResultMessage(msg.terminal_guid, msg.transaction_type, false))) sendResponse(PacketCoding.CreateGamePacket(0, ItemTransactionResultMessage(msg.terminal_guid, msg.transaction_type, false)))
@ -533,9 +567,21 @@ class WorldSessionActor extends Actor with MDCContextAware {
reply match { reply match {
case Vehicle.CanSeatPlayer(vehicle, seat_num) => case Vehicle.CanSeatPlayer(vehicle, seat_num) =>
log.info(s"MountVehicleMsg: ${player.GUID} mounts ${vehicle.GUID} @ $seat_num") log.info(s"MountVehicleMsg: ${player.GUID} mounts ${vehicle.GUID} @ $seat_num")
vehicleService ! VehicleServiceMessage.UnscheduleDeconstruction(vehicle.GUID) //clear all deconstruction timers
val vehicle_guid : PlanetSideGUID = vehicle.GUID val vehicle_guid : PlanetSideGUID = vehicle.GUID
tplayer.VehicleSeated = Some(vehicle_guid) tplayer.VehicleSeated = Some(vehicle_guid)
if(seat_num == 0) { //simplistic vehicle ownership management if(seat_num == 0) { //simplistic vehicle ownership management
vehicle.Owner match {
case Some(owner_guid) =>
continent.GUID(owner_guid) match {
case Some(previous_owner : Player) =>
if(previous_owner.VehicleOwned.contains(vehicle_guid)) {
previous_owner.VehicleOwned = None //simplistic ownership management, player loses vehicle ownership
}
case _ => ;
}
case None => ;
}
player.VehicleOwned = Some(vehicle_guid) player.VehicleOwned = Some(vehicle_guid)
vehicle.Owner = Some(player.GUID) vehicle.Owner = Some(player.GUID)
} }
@ -558,12 +604,52 @@ class WorldSessionActor extends Actor with MDCContextAware {
vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.MountVehicle(player_guid, vehicle_guid, seat_num)) vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.MountVehicle(player_guid, vehicle_guid, seat_num))
case Vehicle.CannotSeatPlayer(vehicle, seat_num) => case Vehicle.CannotSeatPlayer(vehicle, seat_num) =>
val seat : Seat = vehicle.Seat(seat_num).get
log.warn(s"MountVehicleMsg: player $tplayer attempted to board vehicle ${vehicle.GUID}'s seat $seat_num, but was not allowed") log.warn(s"MountVehicleMsg: player $tplayer attempted to board vehicle ${vehicle.GUID}'s seat $seat_num, but was not allowed")
case _ => ; case _ => ;
} }
case VehicleSpawnPad.ConcealPlayer =>
sendResponse(PacketCoding.CreateGamePacket(0, GenericObjectActionMessage(player.GUID, 36)))
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ConcealPlayer(player.GUID))
case VehicleSpawnPad.LoadVehicle(vehicle, _/*pad*/) =>
val player_guid = player.GUID
val definition = vehicle.Definition
val objedtId = definition.ObjectId
val vehicle_guid = vehicle.GUID
val vdata = definition.Packet.ConstructorData(vehicle).get
sendResponse(PacketCoding.CreateGamePacket(0, ObjectCreateMessage(objedtId, vehicle_guid, vdata)))
continent.Transport ! Zone.SpawnVehicle(vehicle)
vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.LoadVehicle(player_guid, vehicle, objedtId, vehicle_guid, vdata))
sendResponse(PacketCoding.CreateGamePacket(0, PlanetsideAttributeMessage(vehicle_guid, 22, 1L))) //mount points off?
//sendResponse(PacketCoding.CreateGamePacket(0, PlanetsideAttributeMessage(vehicle_guid, 21, player_guid.guid))) //fte and ownership?
//sendResponse(PacketCoding.CreateGamePacket(0, ObjectAttachMessage(vehicle_guid, player_guid, 0)))
vehicleService ! VehicleServiceMessage.UnscheduleDeconstruction(vehicle_guid) //cancel queue timeout delay
vehicleService ! VehicleServiceMessage.DelayedVehicleDeconstruction(vehicle, continent, 21L) //temporary drive away from pad delay
vehicle.Actor ! Vehicle.TrySeatPlayer(0, player)
case VehicleSpawnPad.PlayerSeatedInVehicle(vehicle) =>
vehicleService ! VehicleServiceMessage.DelayedVehicleDeconstruction(vehicle, continent, 21L) //sitting in the vehicle clears the drive away delay
val vehicle_guid = vehicle.GUID
sendResponse(PacketCoding.CreateGamePacket(0, PlanetsideAttributeMessage(vehicle_guid, 22, 0L))) //mount points on?
//sendResponse(PacketCoding.CreateGamePacket(0, PlanetsideAttributeMessage(vehicle_guid, 0, vehicle.Definition.MaxHealth)))
sendResponse(PacketCoding.CreateGamePacket(0, PlanetsideAttributeMessage(vehicle_guid, 68, 0L))) //???
sendResponse(PacketCoding.CreateGamePacket(0, PlanetsideAttributeMessage(vehicle_guid, 113, 0L))) //???
ReloadVehicleAccessPermissions(vehicle)
case VehicleSpawnPad.SpawnPadBlockedWarning(vehicle, warning_count) =>
if(warning_count > 2) {
sendResponse(PacketCoding.CreateGamePacket(0, TriggerSoundMessage(TriggeredSound.Unknown14, vehicle.Position, 20, 1f)))
sendResponse(PacketCoding.CreateGamePacket(0,
ChatMsg(ChatMessageType.CMT_TELL, true, "", "\\#FYour vehicle is blocking the spawn pad, and will be deconstructed if not moved.", None))
)
}
case VehicleSpawnPad.SpawnPadUnblocked(vehicle_guid) =>
//vehicle has moved away from spawn pad after initial spawn
vehicleService ! VehicleServiceMessage.UnscheduleDeconstruction(vehicle_guid) //cancel temporary drive away from pad delay
case ListAccountCharacters => case ListAccountCharacters =>
import net.psforever.objects.definition.converter.CharacterSelectConverter import net.psforever.objects.definition.converter.CharacterSelectConverter
val gen : AtomicInteger = new AtomicInteger(1) val gen : AtomicInteger = new AtomicInteger(1)
@ -605,15 +691,8 @@ class WorldSessionActor extends Actor with MDCContextAware {
failWithError(s"$tplayer failed to load anywhere") failWithError(s"$tplayer failed to load anywhere")
} }
case VehicleLoaded(vehicle) => case VehicleLoaded(_/*vehicle*/) => ;
val definition = vehicle.Definition //currently being handled by VehicleSpawnPad.LoadVehicle during testing phase
val objedtId = definition.ObjectId
val vehicle_guid = vehicle.GUID
val vdata = definition.Packet.ConstructorData(vehicle).get
sendResponse(PacketCoding.CreateGamePacket(0, ObjectCreateMessage(objedtId, vehicle_guid, vdata)))
ReloadVehicleAccessPermissions(vehicle)
continent.Transport ! Zone.SpawnVehicle(vehicle)
vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.LoadVehicle(player.GUID, vehicle, objedtId, vehicle_guid, vdata))
case Zone.ClientInitialization(/*initList*/_) => case Zone.ClientInitialization(/*initList*/_) =>
//TODO iterate over initList; for now, just do this //TODO iterate over initList; for now, just do this
@ -795,7 +874,6 @@ class WorldSessionActor extends Actor with MDCContextAware {
} }
var player : Player = null var player : Player = null
var harasser : Vehicle = null //TODO used in testing
def handleGamePkt(pkt : PlanetSideGamePacket) = pkt match { def handleGamePkt(pkt : PlanetSideGamePacket) = pkt match {
case ConnectToWorldRequestMessage(server, token, majorVersion, minorVersion, revision, buildDate, unk) => case ConnectToWorldRequestMessage(server, token, majorVersion, minorVersion, revision, buildDate, unk) =>
@ -803,18 +881,9 @@ class WorldSessionActor extends Actor with MDCContextAware {
log.info(s"New world login to $server with Token:$token. $clientVersion") log.info(s"New world login to $server with Token:$token. $clientVersion")
//TODO begin temp player character auto-loading; remove later //TODO begin temp player character auto-loading; remove later
import net.psforever.objects.GlobalDefinitions._ import net.psforever.objects.GlobalDefinitions._
val
beamer1 = Tool(beamer)
beamer1.AmmoSlots.head.Box = AmmoBox(energy_cell, 16)
val
suppressor1 = Tool(suppressor)
suppressor1.AmmoSlots.head.Box = AmmoBox(bullet_9mm, 25)
val
forceblade1 = Tool(forceblade)
forceblade1.AmmoSlots.head.Box = AmmoBox(melee_ammo)
player = Player("TestCharacter"+sessionId.toString, PlanetSideEmpire.VS, CharacterGender.Female, 41, 1) player = Player("TestCharacter"+sessionId.toString, PlanetSideEmpire.VS, CharacterGender.Female, 41, 1)
player.Position = Vector3(3674.8438f, 2726.789f, 91.15625f) //player.Position = Vector3(3674.8438f, 2726.789f, 91.15625f)
player.Position = Vector3(3523.039f, 2855.5078f, 90.859375f)
player.Orientation = Vector3(0f, 0f, 90f) player.Orientation = Vector3(0f, 0f, 90f)
player.Certifications += CertificationType.StandardAssault player.Certifications += CertificationType.StandardAssault
player.Certifications += CertificationType.MediumAssault player.Certifications += CertificationType.MediumAssault
@ -823,9 +892,24 @@ class WorldSessionActor extends Actor with MDCContextAware {
player.Certifications += CertificationType.ReinforcedExoSuit player.Certifications += CertificationType.ReinforcedExoSuit
player.Certifications += CertificationType.ATV player.Certifications += CertificationType.ATV
player.Certifications += CertificationType.Harasser player.Certifications += CertificationType.Harasser
player.Slot(0).Equipment = beamer1 //
player.Slot(2).Equipment = suppressor1 player.Certifications += CertificationType.GroundSupport
player.Slot(4).Equipment = forceblade1 player.Certifications += CertificationType.GroundTransport
player.Certifications += CertificationType.Flail
player.Certifications += CertificationType.Switchblade
player.Certifications += CertificationType.AssaultBuggy
player.Certifications += CertificationType.ArmoredAssault1
player.Certifications += CertificationType.ArmoredAssault2
player.Certifications += CertificationType.AirCavalryScout
player.Certifications += CertificationType.AirCavalryAssault
player.Certifications += CertificationType.AirCavalryInterceptor
player.Certifications += CertificationType.AirSupport
player.Certifications += CertificationType.GalaxyGunship
player.Certifications += CertificationType.Phantasm
//player.ExoSuit = ExoSuitType.Infiltrator
player.Slot(0).Equipment = Tool(beamer)
player.Slot(2).Equipment = Tool(suppressor)
player.Slot(4).Equipment = Tool(forceblade)
player.Slot(6).Equipment = AmmoBox(bullet_9mm) player.Slot(6).Equipment = AmmoBox(bullet_9mm)
player.Slot(9).Equipment = AmmoBox(bullet_9mm) player.Slot(9).Equipment = AmmoBox(bullet_9mm)
player.Slot(12).Equipment = AmmoBox(bullet_9mm) player.Slot(12).Equipment = AmmoBox(bullet_9mm)
@ -917,21 +1001,6 @@ class WorldSessionActor extends Actor with MDCContextAware {
}) })
ReloadVehicleAccessPermissions(vehicle) ReloadVehicleAccessPermissions(vehicle)
}) })
//TODO begin temp vehicle auto-loading
import net.psforever.objects.GlobalDefinitions._
if(continent.Vehicles.isEmpty) {
harasser = Vehicle(two_man_assault_buggy)
harasser.Position = Vector3(3674.8438f, 2730.789f, 91.15625f)
harasser.Faction = PlanetSideEmpire.VS
harasser.Orientation = Vector3(0f, 0f, 90f)
harasser.Weapons(2).Equipment.get.asInstanceOf[Tool].AmmoSlots.head.Box = AmmoBox(bullet_12mm, 150)
harasser.Trunk += 30 -> AmmoBox(bullet_12mm, 100)
taskResolver ! RegisterNewVehicle(harasser)
}
else {
harasser = continent.Vehicles.head //subsequent players after first
}
//TODO end temp vehicle auto-loading
avatarService ! Service.Join(player.Continent) avatarService ! Service.Join(player.Continent)
localService ! Service.Join(player.Continent) localService ! Service.Join(player.Continent)
vehicleService ! Service.Join(player.Continent) vehicleService ! Service.Join(player.Continent)
@ -1129,6 +1198,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
continent.GUID(object_guid) match { continent.GUID(object_guid) match {
case Some(vehicle : Vehicle) => case Some(vehicle : Vehicle) =>
if(player.VehicleOwned.contains(object_guid) && vehicle.Owner.contains(player.GUID)) { if(player.VehicleOwned.contains(object_guid) && vehicle.Owner.contains(player.GUID)) {
vehicleService ! VehicleServiceMessage.UnscheduleDeconstruction(object_guid)
vehicleService ! VehicleServiceMessage.RequestDeleteVehicle(vehicle, continent) vehicleService ! VehicleServiceMessage.RequestDeleteVehicle(vehicle, continent)
log.info(s"RequestDestroy: vehicle $object_guid") log.info(s"RequestDestroy: vehicle $object_guid")
} }
@ -1157,14 +1227,14 @@ class WorldSessionActor extends Actor with MDCContextAware {
sendResponse(PacketCoding.CreateGamePacket(0, ObjectDeleteMessage(object_guid, 0))) sendResponse(PacketCoding.CreateGamePacket(0, ObjectDeleteMessage(object_guid, 0)))
log.info("ObjectDelete: " + msg) log.info("ObjectDelete: " + msg)
case msg @ MoveItemMessage(item_guid, avatar_guid_1, avatar_guid_2, dest, unk1) => case msg @ MoveItemMessage(item_guid, source_guid, destination_guid, dest, unk1) =>
player.Find(item_guid) match { player.Find(item_guid) match {
case Some(index) => case Some(index) =>
val indexSlot = player.Slot(index) val indexSlot = player.Slot(index)
var itemOpt = indexSlot.Equipment //use this to short circuit var itemOpt : Option[Equipment] = indexSlot.Equipment
//use this to short circuit
val item = itemOpt.get val item = itemOpt.get
val destSlot = player.Slot(dest) val destSlot = player.Slot(dest)
val destItem = if((-1 < dest && dest < 5) || dest == Player.FreeHandSlot) { val destItem = if((-1 < dest && dest < 5) || dest == Player.FreeHandSlot) {
destSlot.Equipment match { destSlot.Equipment match {
case Some(found) => case Some(found) =>
@ -1181,20 +1251,21 @@ class WorldSessionActor extends Actor with MDCContextAware {
case Success(_) | scala.util.Failure(_) => itemOpt = None; None //abort item move altogether case Success(_) | scala.util.Failure(_) => itemOpt = None; None //abort item move altogether
} }
} }
if(itemOpt.isDefined) { if(itemOpt.isDefined) {
log.info(s"MoveItem: $item_guid moved from $avatar_guid_1 @ $index to $avatar_guid_1 @ $dest") log.info(s"MoveItem: $item_guid moved from $source_guid @ $index to $source_guid @ $dest")
indexSlot.Equipment = None indexSlot.Equipment = None
destItem match { //do we have a swap item? destItem match {
//do we have a swap item?
case Some(entry) => //yes, swap case Some(entry) => //yes, swap
val item2 = entry.obj val item2 = entry.obj
player.Slot(entry.start).Equipment = None //remove item2 to make room for item player.Slot(entry.start).Equipment = None //remove item2 to make room for item
destSlot.Equipment = item //in case dest and index could block each other destSlot.Equipment = item //in case dest and index could block each other
(indexSlot.Equipment = entry.obj) match { (indexSlot.Equipment = entry.obj) match {
case Some(_) => //item and item2 swapped places successfully case Some(_) => //item and item2 swapped places successfully
log.info(s"MoveItem: ${item2.GUID} swapped to $avatar_guid_1 @ $index") log.info(s"MoveItem: ${item2.GUID} swapped to $source_guid @ $index")
//we must shuffle items around cleanly to avoid causing icons to "disappear" //we must shuffle items around cleanly to avoid causing icons to "disappear"
if(index == Player.FreeHandSlot) { //temporarily put in safe location, A -> C if(index == Player.FreeHandSlot) {
//temporarily put in safe location, A -> C
sendResponse(PacketCoding.CreateGamePacket(0, ObjectDetachMessage(player.GUID, item.GUID, Vector3(0f, 0f, 0f), 0f, 0f, 0f))) //ground sendResponse(PacketCoding.CreateGamePacket(0, ObjectDetachMessage(player.GUID, item.GUID, Vector3(0f, 0f, 0f), 0f, 0f, 0f))) //ground
} }
else { else {
@ -1217,13 +1288,13 @@ class WorldSessionActor extends Actor with MDCContextAware {
case None => //just move item over case None => //just move item over
destSlot.Equipment = item destSlot.Equipment = item
} }
sendResponse(PacketCoding.CreateGamePacket(0, ObjectAttachMessage(avatar_guid_1, item_guid, dest))) sendResponse(PacketCoding.CreateGamePacket(0, ObjectAttachMessage(source_guid, item_guid, dest)))
if(0 <= dest && dest < 5) { if(0 <= dest && dest < 5) {
avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.EquipmentInHand(player.GUID, dest, item)) avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.EquipmentInHand(player.GUID, dest, item))
} }
} }
case None => case None =>
log.info(s"MoveItem: $avatar_guid_1 wanted to move the item $item_guid but could not find it") log.info(s"MoveItem: $source_guid wanted to move the item $item_guid but could not find it")
} }
case msg @ ChangeAmmoMessage(item_guid, unk1) => case msg @ ChangeAmmoMessage(item_guid, unk1) =>
@ -1384,6 +1455,9 @@ class WorldSessionActor extends Actor with MDCContextAware {
player.VehicleSeated = None player.VehicleSeated = None
sendResponse(PacketCoding.CreateGamePacket(0, DismountVehicleMsg(player_guid, unk1, unk2))) //should be safe; replace with ObjectDetachMessage later sendResponse(PacketCoding.CreateGamePacket(0, DismountVehicleMsg(player_guid, unk1, unk2))) //should be safe; replace with ObjectDetachMessage later
vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.DismountVehicle(player_guid, unk1, unk2)) vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.DismountVehicle(player_guid, unk1, unk2))
if(obj.Seats.count(seat => seat.isOccupied) == 0) {
vehicleService ! VehicleServiceMessage.DelayedVehicleDeconstruction(obj, continent, 600L) //start vehicle decay (10m)
}
} }
case None => case None =>
log.warn(s"DismountVehicleMsg: can not find where player $player_guid is seated in vehicle $vehicle_guid") log.warn(s"DismountVehicleMsg: can not find where player $player_guid is seated in vehicle $vehicle_guid")
@ -1409,6 +1483,9 @@ class WorldSessionActor extends Actor with MDCContextAware {
seat.Occupant = None seat.Occupant = None
tplayer.VehicleSeated = None tplayer.VehicleSeated = None
vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.KickPassenger(player_guid, unk1, unk2)) vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.KickPassenger(player_guid, unk1, unk2))
if(obj.Seats.count(seat => seat.isOccupied) == 0) {
vehicleService ! VehicleServiceMessage.DelayedVehicleDeconstruction(obj, continent, 600L) //start vehicle decay (10m)
}
case None => case None =>
log.warn(s"DismountVehicleMsg: can not find where player $player_guid is seated in vehicle $vehicle_guid") log.warn(s"DismountVehicleMsg: can not find where player $player_guid is seated in vehicle $vehicle_guid")
} }
@ -1733,12 +1810,16 @@ class WorldSessionActor extends Actor with MDCContextAware {
* @see `RegisterVehicle` * @see `RegisterVehicle`
* @return a `TaskResolver.GiveTask` message * @return a `TaskResolver.GiveTask` message
*/ */
def RegisterNewVehicle(obj : Vehicle) : TaskResolver.GiveTask = { def RegisterNewVehicle(obj : Vehicle, pad : VehicleSpawnPad) : TaskResolver.GiveTask = {
TaskResolver.GiveTask( TaskResolver.GiveTask(
new Task() { new Task() {
private val localVehicle = obj private val localVehicle = obj
private val localAnnounce = vehicleService private val localAnnounce = vehicleService
private val localSession : String = sessionId.toString private val localSession : String = sessionId.toString
private val localPad = pad.Actor
private val localPlayer = player
private val localVehicleService = vehicleService
private val localZone = continent
override def isComplete : Task.Resolution.Value = { override def isComplete : Task.Resolution.Value = {
if(localVehicle.Actor != ActorRef.noSender) { if(localVehicle.Actor != ActorRef.noSender) {
@ -1751,6 +1832,8 @@ class WorldSessionActor extends Actor with MDCContextAware {
def Execute(resolver : ActorRef) : Unit = { def Execute(resolver : ActorRef) : Unit = {
localAnnounce ! VehicleServiceMessage.GiveActorControl(obj, localSession) localAnnounce ! VehicleServiceMessage.GiveActorControl(obj, localSession)
localPad ! VehicleSpawnPad.VehicleOrder(localPlayer, localVehicle)
localVehicleService ! VehicleServiceMessage.DelayedVehicleDeconstruction(localVehicle, localZone, 60L)
resolver ! scala.util.Success(this) resolver ! scala.util.Success(this)
} }
}, List(RegisterVehicle(obj))) }, List(RegisterVehicle(obj)))
@ -1945,35 +2028,4 @@ object WorldSessionActor {
delta : Float, delta : Float,
completeAction : () => Unit, completeAction : () => Unit,
tickAction : Option[() => Unit] = None) tickAction : Option[() => Unit] = None)
/**
* A placeholder `Cancellable` object.
*/
private final val DefaultCancellable = new Cancellable() {
def cancel : Boolean = true
def isCancelled() : Boolean = true
}
/**
* Calculate the actual distance between two points.
* @param pos1 the first point
* @param pos2 the second point
* @return the distance
*/
def Distance(pos1 : Vector3, pos2 : Vector3) : Float = {
math.sqrt(DistanceSquared(pos1, pos2)).toFloat
}
/**
* Calculate the squared distance between two points.
* Though some time is saved care must be taken that any comparative distance is also squared.
* @param pos1 the first point
* @param pos2 the second point
* @return the distance
*/
def DistanceSquared(pos1 : Vector3, pos2 : Vector3) : Float = {
val dx : Float = pos1.x - pos2.x
val dy : Float = pos1.y - pos2.y
val dz : Float = pos1.z - pos2.z
(dx * dx) + (dy * dy) + (dz * dz)
}
} }

View file

@ -10,6 +10,7 @@ object AvatarAction {
trait Action trait Action
final case class ArmorChanged(player_guid : PlanetSideGUID, suit : ExoSuitType.Value, subtype : Int) extends Action final case class ArmorChanged(player_guid : PlanetSideGUID, suit : ExoSuitType.Value, subtype : Int) extends Action
final case class ConcealPlayer(player_guid : PlanetSideGUID) extends Action
//final case class DropItem(pos : Vector3, orient : Vector3, item : PlanetSideGUID) extends Action //final case class DropItem(pos : Vector3, orient : Vector3, item : PlanetSideGUID) extends Action
final case class EquipmentInHand(player_guid : PlanetSideGUID, slot : Int, item : Equipment) extends Action final case class EquipmentInHand(player_guid : PlanetSideGUID, slot : Int, item : Equipment) extends Action
final case class EquipmentOnGround(player_guid : PlanetSideGUID, pos : Vector3, orient : Vector3, item : Equipment) extends Action final case class EquipmentOnGround(player_guid : PlanetSideGUID, pos : Vector3, orient : Vector3, item : Equipment) extends Action

View file

@ -10,6 +10,7 @@ object AvatarResponse {
trait Response trait Response
final case class ArmorChanged(suit : ExoSuitType.Value, subtype : Int) extends Response final case class ArmorChanged(suit : ExoSuitType.Value, subtype : Int) extends Response
final case class ConcealPlayer() extends Response
//final case class DropItem(pos : Vector3, orient : Vector3, item : PlanetSideGUID) extends Response //final case class DropItem(pos : Vector3, orient : Vector3, item : PlanetSideGUID) extends Response
final case class EquipmentInHand(slot : Int, item : Equipment) extends Response final case class EquipmentInHand(slot : Int, item : Equipment) extends Response
final case class EquipmentOnGround(pos : Vector3, orient : Vector3, item : Equipment) extends Response final case class EquipmentOnGround(pos : Vector3, orient : Vector3, item : Equipment) extends Response

View file

@ -33,6 +33,10 @@ class AvatarService extends Actor {
AvatarEvents.publish( AvatarEvents.publish(
AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.ArmorChanged(suit, subtype)) AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.ArmorChanged(suit, subtype))
) )
case AvatarAction.ConcealPlayer(player_guid) =>
AvatarEvents.publish(
AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.ConcealPlayer())
)
case AvatarAction.EquipmentInHand(player_guid, slot, obj) => case AvatarAction.EquipmentInHand(player_guid, slot, obj) =>
AvatarEvents.publish( AvatarEvents.publish(
AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.EquipmentInHand(slot, obj)) AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.EquipmentInHand(slot, obj))

View file

@ -2,6 +2,7 @@
package services.local.support package services.local.support
import akka.actor.{Actor, Cancellable} import akka.actor.{Actor, Cancellable}
import net.psforever.objects.DefaultCancellable
import net.psforever.objects.serverobject.doors.Door import net.psforever.objects.serverobject.doors.Door
import net.psforever.objects.zones.Zone import net.psforever.objects.zones.Zone
import net.psforever.packet.game.PlanetSideGUID import net.psforever.packet.game.PlanetSideGUID
@ -16,7 +17,7 @@ import scala.concurrent.duration._
*/ */
class DoorCloseActor() extends Actor { class DoorCloseActor() extends Actor {
/** The periodic `Executor` that checks for doors to be closed */ /** The periodic `Executor` that checks for doors to be closed */
private var doorCloserTrigger : Cancellable = DoorCloseActor.DefaultCloser private var doorCloserTrigger : Cancellable = DefaultCancellable.obj
/** A `List` of currently open doors */ /** A `List` of currently open doors */
private var openDoors : List[DoorCloseActor.DoorEntry] = Nil private var openDoors : List[DoorCloseActor.DoorEntry] = Nil
//private[this] val log = org.log4s.getLogger //private[this] val log = org.log4s.getLogger
@ -98,11 +99,6 @@ object DoorCloseActor {
/** The wait before an open door closes; as a `FiniteDuration` for `Executor` simplicity */ /** The wait before an open door closes; as a `FiniteDuration` for `Executor` simplicity */
private final val timeout : FiniteDuration = timeout_time nanoseconds private final val timeout : FiniteDuration = timeout_time nanoseconds
private final val DefaultCloser : Cancellable = new Cancellable() {
override def cancel : Boolean = true
override def isCancelled : Boolean = true
}
/** /**
* Message that carries information about a door that has been opened. * Message that carries information about a door that has been opened.
* @param door the door object * @param door the door object

View file

@ -2,6 +2,7 @@
package services.local.support package services.local.support
import akka.actor.{Actor, Cancellable} import akka.actor.{Actor, Cancellable}
import net.psforever.objects.DefaultCancellable
import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject} import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject}
import net.psforever.objects.zones.Zone import net.psforever.objects.zones.Zone
import net.psforever.packet.game.PlanetSideGUID import net.psforever.packet.game.PlanetSideGUID
@ -16,7 +17,7 @@ import scala.concurrent.duration._
*/ */
class HackClearActor() extends Actor { class HackClearActor() extends Actor {
/** The periodic `Executor` that checks for server objects to be unhacked */ /** The periodic `Executor` that checks for server objects to be unhacked */
private var clearTrigger : Cancellable = HackClearActor.DefaultClearer private var clearTrigger : Cancellable = DefaultCancellable.obj
/** A `List` of currently hacked server objects */ /** A `List` of currently hacked server objects */
private var hackedObjects : List[HackClearActor.HackEntry] = Nil private var hackedObjects : List[HackClearActor.HackEntry] = Nil
//private[this] val log = org.log4s.getLogger //private[this] val log = org.log4s.getLogger
@ -99,11 +100,6 @@ object HackClearActor {
/** The wait before a server object is to unhack; as a `FiniteDuration` for `Executor` simplicity */ /** The wait before a server object is to unhack; as a `FiniteDuration` for `Executor` simplicity */
private final val timeout : FiniteDuration = timeout_time nanoseconds private final val timeout : FiniteDuration = timeout_time nanoseconds
private final val DefaultClearer : Cancellable = new Cancellable() {
override def cancel : Boolean = true
override def isCancelled : Boolean = true
}
/** /**
* Message that carries information about a server object that has been hacked. * Message that carries information about a server object that has been hacked.
* @param target the server object * @param target the server object

View file

@ -2,12 +2,13 @@
package services.vehicle package services.vehicle
import akka.actor.{Actor, ActorRef, Props} import akka.actor.{Actor, ActorRef, Props}
import services.vehicle.support.{DeconstructionActor, VehicleContextActor} import services.vehicle.support.{DeconstructionActor, DelayedDeconstructionActor, VehicleContextActor}
import services.{GenericEventBus, Service} import services.{GenericEventBus, Service}
class VehicleService extends Actor { class VehicleService extends Actor {
private val vehicleContext : ActorRef = context.actorOf(Props[VehicleContextActor], "vehicle-context-root") private val vehicleContext : ActorRef = context.actorOf(Props[VehicleContextActor], "vehicle-context-root")
private val vehicleDecon : ActorRef = context.actorOf(Props[DeconstructionActor], "vehicle-decon-agent") private val vehicleDecon : ActorRef = context.actorOf(Props[DeconstructionActor], "vehicle-decon-agent")
private val vehicleDelayedDecon : ActorRef = context.actorOf(Props[DelayedDeconstructionActor], "vehicle-delayed-decon-agent")
vehicleDecon ! DeconstructionActor.RequestTaskResolver vehicleDecon ! DeconstructionActor.RequestTaskResolver
private [this] val log = org.log4s.getLogger private [this] val log = org.log4s.getLogger
@ -79,6 +80,14 @@ class VehicleService extends Actor {
case VehicleServiceMessage.RequestDeleteVehicle(vehicle, continent) => case VehicleServiceMessage.RequestDeleteVehicle(vehicle, continent) =>
vehicleDecon ! DeconstructionActor.RequestDeleteVehicle(vehicle, continent) vehicleDecon ! DeconstructionActor.RequestDeleteVehicle(vehicle, continent)
//message to DelayedDeconstructionActor
case VehicleServiceMessage.DelayedVehicleDeconstruction(vehicle, zone, timeAlive) =>
vehicleDelayedDecon ! DelayedDeconstructionActor.ScheduleDeconstruction(vehicle, zone, timeAlive)
//message to DelayedDeconstructionActor
case VehicleServiceMessage.UnscheduleDeconstruction(vehicle_guid) =>
vehicleDelayedDecon ! DelayedDeconstructionActor.UnscheduleDeconstruction(vehicle_guid)
//response from DeconstructionActor //response from DeconstructionActor
case DeconstructionActor.DeleteVehicle(vehicle_guid, zone_id) => case DeconstructionActor.DeleteVehicle(vehicle_guid, zone_id) =>
VehicleEvents.publish( VehicleEvents.publish(

View file

@ -3,11 +3,14 @@ package services.vehicle
import net.psforever.objects.Vehicle import net.psforever.objects.Vehicle
import net.psforever.objects.zones.Zone import net.psforever.objects.zones.Zone
import net.psforever.packet.game.PlanetSideGUID
final case class VehicleServiceMessage(forChannel : String, actionMessage : VehicleAction.Action) final case class VehicleServiceMessage(forChannel : String, actionMessage : VehicleAction.Action)
object VehicleServiceMessage { object VehicleServiceMessage {
final case class DelayedVehicleDeconstruction(vehicle : Vehicle, continent : Zone, timeAlive : Long)
final case class GiveActorControl(vehicle : Vehicle, actorName : String) final case class GiveActorControl(vehicle : Vehicle, actorName : String)
final case class RevokeActorControl(vehicle : Vehicle) final case class RevokeActorControl(vehicle : Vehicle)
final case class RequestDeleteVehicle(vehicle : Vehicle, continent : Zone) final case class RequestDeleteVehicle(vehicle : Vehicle, continent : Zone)
final case class UnscheduleDeconstruction(vehicle_guid : PlanetSideGUID)
} }

View file

@ -2,7 +2,7 @@
package services.vehicle.support package services.vehicle.support
import akka.actor.{Actor, ActorRef, Cancellable} import akka.actor.{Actor, ActorRef, Cancellable}
import net.psforever.objects.Vehicle import net.psforever.objects.{DefaultCancellable, Vehicle}
import net.psforever.objects.guid.TaskResolver import net.psforever.objects.guid.TaskResolver
import net.psforever.objects.vehicles.Seat import net.psforever.objects.vehicles.Seat
import net.psforever.objects.zones.Zone import net.psforever.objects.zones.Zone
@ -20,11 +20,13 @@ import scala.concurrent.duration._
* A reference to a vehicle should be passed to this object as soon as it is going to be cleaned-up from the game world. * A reference to a vehicle should be passed to this object as soon as it is going to be cleaned-up from the game world.
* Once accepted, only a few seconds will remain before the vehicle is deleted. * Once accepted, only a few seconds will remain before the vehicle is deleted.
* To ensure that no players are lost in the deletion, all occupants of the vehicle are kicked out. * To ensure that no players are lost in the deletion, all occupants of the vehicle are kicked out.
* Furthermore, the vehicle is rendered "dead" and inaccessible right up to the point where it is removed. * Furthermore, the vehicle is rendered "dead" and inaccessible right up to the point where it is removed.<br>
* <br>
* This `Actor` is intended to sit on top of the event system that handles broadcast messaging.
*/ */
class DeconstructionActor extends Actor { class DeconstructionActor extends Actor {
/** The periodic `Executor` that scraps the next vehicle on the list */ /** The periodic `Executor` that scraps the next vehicle on the list */
private var scrappingProcess : Cancellable = DeconstructionActor.DefaultProcess private var scrappingProcess : Cancellable = DefaultCancellable.obj
/** A `List` of currently doomed vehicles */ /** A `List` of currently doomed vehicles */
private var vehicles : List[DeconstructionActor.VehicleEntry] = Nil private var vehicles : List[DeconstructionActor.VehicleEntry] = Nil
/** The manager that helps unregister the vehicle from its current GUID scope */ /** The manager that helps unregister the vehicle from its current GUID scope */
@ -171,11 +173,6 @@ object DeconstructionActor {
/** The wait before completely deleting a vehicle; as a `FiniteDuration` for `Executor` simplicity */ /** The wait before completely deleting a vehicle; as a `FiniteDuration` for `Executor` simplicity */
private final val timeout : FiniteDuration = timeout_time nanoseconds private final val timeout : FiniteDuration = timeout_time nanoseconds
private final val DefaultProcess : Cancellable = new Cancellable() {
override def cancel : Boolean = true
override def isCancelled : Boolean = true
}
final case class RequestTaskResolver() final case class RequestTaskResolver()
/** /**

View file

@ -0,0 +1,104 @@
// Copyright (c) 2017 PSForever
package services.vehicle.support
import akka.actor.{Actor, Cancellable}
import net.psforever.objects.{DefaultCancellable, Vehicle}
import net.psforever.objects.zones.Zone
import net.psforever.packet.game.PlanetSideGUID
import services.vehicle.VehicleServiceMessage
import scala.concurrent.duration._
/**
* Maintain and curate a list of timed `vehicle` object deconstruction tasks.<br>
* <br>
* These tasks are queued or dismissed by player activity but they are executed independent of player activity.
* A common disconnected cause of deconstruction is neglect for an extended period of time.
* At that point, the original owner of the vehicle no longer matters.
* Deconstruction neglect, however, is averted by having someone become seated.
* A realized deconstruction is entirely based on a fixed interval after an unresolved request has been received.
* The actual process of deconstructing the vehicle and cleaning up its resources is performed by an external agent.<br>
* <br>
* This `Actor` is intended to sit on top of the event system that handles broadcast messaging.
*/
class DelayedDeconstructionActor extends Actor {
/** The periodic `Executor` that scraps the next vehicle on the list */
private var monitor : Cancellable = DefaultCancellable.obj
/** A `List` of currently doomed vehicles */
private var vehicles : List[DelayedDeconstructionActor.VehicleEntry] = Nil
private[this] val log = org.log4s.getLogger
private[this] def trace(msg : String) : Unit = log.trace(msg)
def receive : Receive = {
case DelayedDeconstructionActor.ScheduleDeconstruction(vehicle, zone, timeAlive) =>
trace(s"delayed deconstruction order for $vehicle in $timeAlive")
vehicles = vehicles :+ DelayedDeconstructionActor.VehicleEntry(vehicle, zone, timeAlive * 1000000000L)
if(vehicles.size == 1) { //we were the only entry so the event must be started from scratch
import scala.concurrent.ExecutionContext.Implicits.global
monitor = context.system.scheduler.scheduleOnce(DelayedDeconstructionActor.periodicTest, self, DelayedDeconstructionActor.PeriodicTaskCulling)
}
case DelayedDeconstructionActor.UnscheduleDeconstruction(vehicle_guid) =>
//all tasks for this vehicle are cleared from the queue
//clear any task that is no longer valid by determination of unregistered GUID
val before = vehicles.length
vehicles = vehicles.filter(entry => { !entry.vehicle.HasGUID || entry.vehicle.GUID != vehicle_guid })
trace(s"attempting to clear deconstruction order for vehicle $vehicle_guid, found ${before - vehicles.length}")
if(vehicles.isEmpty) {
monitor.cancel
}
case DelayedDeconstructionActor.PeriodicTaskCulling =>
//filter the list of deconstruction tasks for any that are need to be triggered
monitor.cancel
val now : Long = System.nanoTime
val (vehiclesToDecon, vehiclesRemain) = vehicles.partition(entry => { now - entry.logTime >= entry.survivalTime })
vehicles = vehiclesRemain
trace(s"vehicle culling - ${vehiclesToDecon.length} deconstruction tasks found")
vehiclesToDecon.foreach(entry => { context.parent ! VehicleServiceMessage.RequestDeleteVehicle(entry.vehicle, entry.zone) })
if(vehiclesRemain.nonEmpty) {
import scala.concurrent.ExecutionContext.Implicits.global
monitor = context.system.scheduler.scheduleOnce(DelayedDeconstructionActor.periodicTest, self, DelayedDeconstructionActor.PeriodicTaskCulling)
}
case _ => ;
}
}
object DelayedDeconstructionActor {
/**
* Timer for the repeating executor.
*/
private final val periodicTest : FiniteDuration = 5000000000L nanoseconds //5s
/**
* Queue a future vehicle deconstruction action.
* @param vehicle the `Vehicle` object
* @param zone the `Zone` that the vehicle currently occupies
* @param survivalTime how long until the vehicle will be deconstructed in seconds
*/
final case class ScheduleDeconstruction(vehicle : Vehicle, zone : Zone, survivalTime : Long)
/**
* Dequeue a vehicle from being deconstructed.
* @param vehicle_guid the vehicle
*/
final case class UnscheduleDeconstruction(vehicle_guid : PlanetSideGUID)
/**
* A message the `Actor` sends to itself.
* The trigger for the periodic deconstruction task.
*/
private final case class PeriodicTaskCulling()
/**
* An entry that stores vehicle deconstruction tasks.
* @param vehicle the `Vehicle` object
* @param zone the `Zone` that the vehicle currently occupies
* @param survivalTime how long until the vehicle will be deconstructed in nanoseconds
* @param logTime when this deconstruction request was initially created in nanoseconds;
* initialized by default to a "now"
*/
private final case class VehicleEntry(vehicle : Vehicle, zone : Zone, survivalTime : Long, logTime : Long = System.nanoTime())
}

View file

@ -1,7 +1,7 @@
// Copyright (c) 2017 PSForever // Copyright (c) 2017 PSForever
package services.vehicle.support package services.vehicle.support
import akka.actor.{Actor, Props} import akka.actor.{Actor, ActorRef, Props}
import net.psforever.objects.vehicles.VehicleControl import net.psforever.objects.vehicles.VehicleControl
import services.vehicle.VehicleServiceMessage import services.vehicle.VehicleServiceMessage
@ -15,15 +15,18 @@ import services.vehicle.VehicleServiceMessage
* <br> * <br>
* The only purpose of this `Actor` is to allow vehicles to borrow a context for the purpose of `Actor` creation. * The only purpose of this `Actor` is to allow vehicles to borrow a context for the purpose of `Actor` creation.
* It is also be allowed to be responsible for cleaning up that context. * It is also be allowed to be responsible for cleaning up that context.
* (In reality, it can be cleaned up anywhere a `PoisonPill` can be sent.) * (In reality, it can be cleaned up anywhere a `PoisonPill` can be sent.)<br>
* <br>
* This `Actor` is intended to sit on top of the event system that handles broadcast messaging.
*/ */
class VehicleContextActor() extends Actor { class VehicleContextActor() extends Actor {
def receive : Receive = { def receive : Receive = {
case VehicleServiceMessage.GiveActorControl(vehicle, actorName) => case VehicleServiceMessage.GiveActorControl(vehicle, actorName) =>
vehicle.Actor = context.actorOf(Props(classOf[VehicleControl], vehicle), s"${vehicle.Definition.Name}_$actorName") vehicle.Actor = context.actorOf(Props(classOf[VehicleControl], vehicle), s"${vehicle.Definition.Name}_$actorName.${System.nanoTime()}")
case VehicleServiceMessage.RevokeActorControl(vehicle) => case VehicleServiceMessage.RevokeActorControl(vehicle) =>
vehicle.Actor ! akka.actor.PoisonPill vehicle.Actor ! akka.actor.PoisonPill
vehicle.Actor = ActorRef.noSender
case _ => ; case _ => ;
} }