diff --git a/build.sbt b/build.sbt
index 04186d28..7e3106c7 100644
--- a/build.sbt
+++ b/build.sbt
@@ -48,8 +48,8 @@ lazy val commonSettings = Seq(
"org.scala-graph" %% "graph-core" % "1.12.5",
"io.kamon" %% "kamon-bundle" % "2.1.0",
"io.kamon" %% "kamon-apm-reporter" % "2.1.0",
- "org.json4s" %% "json4s-native" % "3.6.8"
- )
+ "org.json4s" %% "json4s-native" % "3.6.8",
+ ),
)
lazy val pscryptoSettings = Seq(
diff --git a/common/src/main/scala/net/psforever/objects/Avatar.scala b/common/src/main/scala/net/psforever/objects/Avatar.scala
index 451d85ff..02c83a26 100644
--- a/common/src/main/scala/net/psforever/objects/Avatar.scala
+++ b/common/src/main/scala/net/psforever/objects/Avatar.scala
@@ -57,33 +57,6 @@ class Avatar(private val char_id : Long, val name : String, val faction : Planet
private var lfs : Boolean = false
private var vehicleOwned : Option[PlanetSideGUID] = None
- /** key - object id
- * value - time last used (ms)
- * */
- private var lastUsedEquipmentTimes : mutable.LongMap[Long] = mutable.LongMap[Long]()
- /** exo-suit times are sorted by `Enumeration` order, which was determined by packet process
- * key - exo-suit id
- * value - time last used (ms)
- * */
- private val lastUsedExoSuitTimes : Array[Long] = Array.fill[Long](ExoSuitType.values.size)(0L)
- /** mechanized exo-suit times are sorted by subtype distinction, which was determined by packet process
- * key - subtype id
- * value - time last used (ms)
- * */
- private val lastUsedMaxExoSuitTimes : Array[Long] = Array.fill[Long](4)(0L) //invalid, ai, av, aa
- /** key - object id
- * value - time last acquired (from a terminal) (ms)
- * */
- private var lastPurchaseTimes : mutable.LongMap[Long] = mutable.LongMap[Long]()
- /**
- * To reload purchase and use timers, a string representing the item must be produced.
- * Point directly from the object id to the object definition and get the `Name` from that definition.
- * Allocate only when an item is purchased or used.
- * The keys match the keys for both `lastUsedEquipmentTimes` and `lastPurchaseTimes`.
- * key - object id
- * value - most basic object definition information
- */
- private val objectTypeNameReference : mutable.LongMap[String] = new mutable.LongMap[String]()
def CharId : Long = char_id
@@ -216,8 +189,7 @@ class Avatar(private val char_id : Long, val name : String, val faction : Planet
def FifthSlot : EquipmentSlot = {
new OffhandEquipmentSlot(EquipmentSize.Inventory) {
- val obj = new LockerEquipment(locker)
- Equipment = obj
+ Equipment = locker
}
}
@@ -248,70 +220,6 @@ class Avatar(private val char_id : Long, val name : String, val faction : Planet
VehicleOwned
}
- def GetLastUsedTime(code : Int) : Long = {
- lastUsedEquipmentTimes.get(code) match {
- case Some(time) => time
- case None => 0
- }
- }
-
- def GetLastUsedTime(code : ExoSuitType.Value) : Long = {
- lastUsedExoSuitTimes(code.id)
- }
-
- def GetLastUsedTime(code : ExoSuitType.Value, subtype : Int) : Long = {
- if(code == ExoSuitType.MAX) {
- lastUsedMaxExoSuitTimes(subtype)
- }
- else {
- GetLastUsedTime(code)
- }
- }
-
- def GetAllLastUsedTimes : Map[Long, Long] = lastUsedEquipmentTimes.toMap
-
- def SetLastUsedTime(code : Int, time : Long) : Unit = {
- lastUsedEquipmentTimes += code.toLong -> time
- }
-
- def SetLastUsedTime(code : ExoSuitType.Value) : Unit = SetLastUsedTime(code, System.currentTimeMillis())
-
- def SetLastUsedTime(code : ExoSuitType.Value, time : Long) : Unit = {
- lastUsedExoSuitTimes(code.id) = time
- }
-
- def SetLastUsedTime(code : ExoSuitType.Value, subtype : Int, time : Long) : Unit = {
- if(code == ExoSuitType.MAX) {
- lastUsedMaxExoSuitTimes(subtype) = time
- }
- SetLastUsedTime(code, time)
- }
-
- def GetLastPurchaseTime(code : Int) : Long = {
- lastPurchaseTimes.get(code) match {
- case Some(time) => time
- case None => 0
- }
- }
-
- def GetAllLastPurchaseTimes : Map[Long, Long] = lastPurchaseTimes.toMap
-
- def SetLastPurchaseTime(code : Int, time : Long) : Unit = {
- lastPurchaseTimes += code.toLong -> time
- }
-
- def ObjectTypeNameReference(id : Long) : String = {
- objectTypeNameReference.get(id) match {
- case Some(name) => name
- case None => ""
- }
- }
-
- def ObjectTypeNameReference(id : Long, name : String) : String = {
- objectTypeNameReference(id) = name
- name
- }
-
def Definition : AvatarDefinition = GlobalDefinitions.avatar
/*
diff --git a/common/src/main/scala/net/psforever/objects/Deployables.scala b/common/src/main/scala/net/psforever/objects/Deployables.scala
index 25b2a69f..a241e978 100644
--- a/common/src/main/scala/net/psforever/objects/Deployables.scala
+++ b/common/src/main/scala/net/psforever/objects/Deployables.scala
@@ -8,7 +8,7 @@ import net.psforever.objects.ce.{Deployable, DeployedItem}
import net.psforever.objects.vehicles.{Utility, UtilityType}
import net.psforever.objects.zones.Zone
import net.psforever.packet.game.{DeployableInfo, DeploymentAction}
-import net.psforever.types.{CertificationType, PlanetSideGUID}
+import net.psforever.types.PlanetSideGUID
import services.RemoverActor
import services.local.{LocalAction, LocalServiceMessage}
@@ -123,48 +123,4 @@ object Deployables {
case _ => ;
}
}
-
-
-
- /**
- * Initialize the deployables backend information.
- * @param avatar the player's core
- */
- def InitializeDeployableQuantities(avatar : Avatar) : Boolean = {
- log.info("Setting up combat engineering ...")
- avatar.Deployables.Initialize(avatar.Certifications.toSet)
- }
-
- /**
- * Initialize the UI elements for deployables.
- * @param avatar the player's core
- */
- def InitializeDeployableUIElements(avatar : Avatar) : List[(Int,Int,Int,Int)] = {
- log.info("Setting up combat engineering UI ...")
- avatar.Deployables.UpdateUI()
- }
-
- /**
- * The player learned a new certification.
- * Update the deployables user interface elements if it was an "Engineering" certification.
- * The certification "Advanced Hacking" also relates to an element.
- * @param certification the certification that was added
- * @param certificationSet all applicable certifications
- */
- def AddToDeployableQuantities(avatar : Avatar, certification : CertificationType.Value, certificationSet : Set[CertificationType.Value]) : List[(Int,Int,Int,Int)] = {
- avatar.Deployables.AddToDeployableQuantities(certification, certificationSet)
- avatar.Deployables.UpdateUI(certification)
- }
-
- /**
- * The player forgot a certification he previously knew.
- * Update the deployables user interface elements if it was an "Engineering" certification.
- * The certification "Advanced Hacking" also relates to an element.
- * @param certification the certification that was added
- * @param certificationSet all applicable certifications
- */
- def RemoveFromDeployableQuantities(avatar : Avatar, certification : CertificationType.Value, certificationSet : Set[CertificationType.Value]) : List[(Int,Int,Int,Int)] = {
- avatar.Deployables.RemoveFromDeployableQuantities(certification, certificationSet)
- avatar.Deployables.UpdateUI(certification)
- }
}
diff --git a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala
index 572b4803..c8541002 100644
--- a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala
+++ b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala
@@ -1703,11 +1703,11 @@ object GlobalDefinitions {
plasma_grenade_ammo.Name = "plasma_grenade_ammo"
plasma_grenade_ammo.Size = EquipmentSize.Blocked
- bullet_9mm.Name = "9mmbullet"
+ bullet_9mm.Name = "bullet_9mm"
bullet_9mm.Capacity = 50
bullet_9mm.Tile = InventoryTile.Tile33
- bullet_9mm_AP.Name="9mmbullet_AP"
+ bullet_9mm_AP.Name="bullet_9mm_AP"
bullet_9mm_AP.Capacity = 50
bullet_9mm_AP.Tile = InventoryTile.Tile33
@@ -1841,7 +1841,7 @@ object GlobalDefinitions {
trek_ammo.Name = "trek_ammo"
trek_ammo.Size = EquipmentSize.Blocked
- bullet_35mm.Name = "35mmbullet"
+ bullet_35mm.Name = "bullet_35mm"
bullet_35mm.Capacity = 100
bullet_35mm.Tile = InventoryTile.Tile44
@@ -1889,11 +1889,11 @@ object GlobalDefinitions {
liberator_bomb.Capacity = 20
liberator_bomb.Tile = InventoryTile.Tile44
- bullet_25mm.Name = "25mmbullet"
+ bullet_25mm.Name = "bullet_25mm"
bullet_25mm.Capacity = 150
bullet_25mm.Tile = InventoryTile.Tile44
- bullet_75mm.Name = "75mmbullet"
+ bullet_75mm.Name = "bullet_75mm"
bullet_75mm.Capacity = 100
bullet_75mm.Tile = InventoryTile.Tile44
@@ -1913,11 +1913,11 @@ object GlobalDefinitions {
reaver_rocket.Capacity = 12
reaver_rocket.Tile = InventoryTile.Tile44
- bullet_20mm.Name = "20mmbullet"
+ bullet_20mm.Name = "bullet_20mm"
bullet_20mm.Capacity = 200
bullet_20mm.Tile = InventoryTile.Tile44
- bullet_12mm.Name = "12mmbullet"
+ bullet_12mm.Name = "bullet_12mm"
bullet_12mm.Capacity = 300
bullet_12mm.Tile = InventoryTile.Tile44
@@ -1929,7 +1929,7 @@ object GlobalDefinitions {
wasp_gun_ammo.Capacity = 150
wasp_gun_ammo.Tile = InventoryTile.Tile44
- bullet_15mm.Name = "15mmbullet"
+ bullet_15mm.Name = "bullet_15mm"
bullet_15mm.Capacity = 360
bullet_15mm.Tile = InventoryTile.Tile44
@@ -1953,7 +1953,7 @@ object GlobalDefinitions {
colossus_tank_cannon_ammo.Capacity = 110
colossus_tank_cannon_ammo.Tile = InventoryTile.Tile44
- bullet_105mm.Name = "105mmbullet"
+ bullet_105mm.Name = "bullet_105mm"
bullet_105mm.Capacity = 100
bullet_105mm.Tile = InventoryTile.Tile44
@@ -1981,7 +1981,7 @@ object GlobalDefinitions {
peregrine_sparrow_ammo.Capacity = 150
peregrine_sparrow_ammo.Tile = InventoryTile.Tile44
- bullet_150mm.Name = "150mmbullet"
+ bullet_150mm.Name = "bullet_150mm"
bullet_150mm.Capacity = 50
bullet_150mm.Tile = InventoryTile.Tile44
diff --git a/common/src/main/scala/net/psforever/objects/LockerContainer.scala b/common/src/main/scala/net/psforever/objects/LockerContainer.scala
index 02ab4471..7793a6a5 100644
--- a/common/src/main/scala/net/psforever/objects/LockerContainer.scala
+++ b/common/src/main/scala/net/psforever/objects/LockerContainer.scala
@@ -1,17 +1,9 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects
-import akka.actor.Actor
import net.psforever.objects.definition.EquipmentDefinition
import net.psforever.objects.equipment.Equipment
import net.psforever.objects.inventory.{Container, GridInventory}
-import net.psforever.objects.serverobject.PlanetSideServerObject
-import net.psforever.objects.serverobject.containable.{Containable, ContainableBehavior}
-import net.psforever.packet.game.{ObjectAttachMessage, ObjectCreateDetailedMessage, ObjectDetachMessage}
-import net.psforever.packet.game.objectcreate.ObjectCreateMessageParent
-import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID, Vector3}
-import services.Service
-import services.avatar.{AvatarAction, AvatarServiceMessage}
/**
* The companion of a `Locker` that is carried with a player
@@ -19,18 +11,9 @@ import services.avatar.{AvatarAction, AvatarServiceMessage}
* The `Player` class refers to it as the "fifth slot" as its permanent slot number is encoded as `0x85`.
* The inventory of this object is accessed using a game world `Locker` object (`mb_locker`).
*/
-class LockerContainer extends PlanetSideServerObject
- with Container {
- private var faction : PlanetSideEmpire.Value = PlanetSideEmpire.NEUTRAL
+class LockerContainer extends Equipment with Container {
private val inventory = GridInventory(30, 20)
- def Faction : PlanetSideEmpire.Value = faction
-
- override def Faction_=(fact : PlanetSideEmpire.Value) : PlanetSideEmpire.Value = {
- faction = fact
- Faction
- }
-
def Inventory : GridInventory = inventory
def VisibleSlots : Set[Int] = Set.empty[Int]
@@ -43,79 +26,3 @@ object LockerContainer {
new LockerContainer()
}
}
-
-class LockerEquipment(locker : LockerContainer) extends Equipment
- with Container {
- private val obj = locker
-
- override def GUID : PlanetSideGUID = obj.GUID
-
- override def GUID_=(guid : PlanetSideGUID) : PlanetSideGUID = obj.GUID_=(guid)
-
- override def HasGUID : Boolean = obj.HasGUID
-
- override def Invalidate() : Unit = obj.Invalidate()
-
- override def Faction : PlanetSideEmpire.Value = obj.Faction
-
- def Inventory : GridInventory = obj.Inventory
-
- def VisibleSlots : Set[Int] = Set.empty[Int]
-
- def Definition : EquipmentDefinition = obj.Definition
-}
-
-class LockerContainerControl(locker : LockerContainer, toChannel : String) extends Actor
- with ContainableBehavior {
- def ContainerObject = locker
-
- def receive : Receive = containerBehavior
- .orElse {
- case _ => ;
- }
-
- def MessageDeferredCallback(msg : Any) : Unit = {
- msg match {
- case Containable.MoveItem(_, item, _) =>
- //momentarily put item back where it was originally
- val obj = ContainerObject
- obj.Find(item) match {
- case Some(slot) =>
- obj.Zone.AvatarEvents ! AvatarServiceMessage(
- toChannel,
- AvatarAction.SendResponse(Service.defaultPlayerGUID, ObjectAttachMessage(obj.GUID, item.GUID, slot))
- )
- case None => ;
- }
- case _ => ;
- }
- }
-
- def RemoveItemFromSlotCallback(item : Equipment, slot : Int) : Unit = {
- val zone = locker.Zone
- zone.AvatarEvents ! AvatarServiceMessage(toChannel, AvatarAction.ObjectDelete(Service.defaultPlayerGUID, item.GUID))
- }
-
- def PutItemInSlotCallback(item : Equipment, slot : Int) : Unit = {
- val zone = locker.Zone
- val definition = item.Definition
- item.Faction = PlanetSideEmpire.NEUTRAL
- zone.AvatarEvents ! AvatarServiceMessage(
- toChannel,
- AvatarAction.SendResponse(
- Service.defaultPlayerGUID,
- ObjectCreateDetailedMessage(
- definition.ObjectId,
- item.GUID,
- ObjectCreateMessageParent(locker.GUID, slot),
- definition.Packet.DetailedConstructorData(item).get
- )
- )
- )
- }
-
- def SwapItemCallback(item : Equipment) : Unit = {
- val zone = locker.Zone
- zone.AvatarEvents ! AvatarServiceMessage(toChannel, AvatarAction.SendResponse(Service.defaultPlayerGUID, ObjectDetachMessage(locker.GUID, item.GUID, Vector3.Zero, 0f)))
- }
-}
diff --git a/common/src/main/scala/net/psforever/objects/Player.scala b/common/src/main/scala/net/psforever/objects/Player.scala
index ff1b6e14..5ba47533 100644
--- a/common/src/main/scala/net/psforever/objects/Player.scala
+++ b/common/src/main/scala/net/psforever/objects/Player.scala
@@ -216,8 +216,6 @@ class Player(private val core : Avatar) extends PlanetSideServerObject
def Locker : LockerContainer = core.Locker
- def FifthSlot : EquipmentSlot = core.FifthSlot
-
override def Fit(obj : Equipment) : Option[Int] = {
recursiveHolsterFit(holsters.iterator, obj.Size) match {
case Some(index) =>
@@ -610,30 +608,6 @@ class Player(private val core : Avatar) extends PlanetSideServerObject
def VehicleOwned_=(guid : Option[PlanetSideGUID]) : Option[PlanetSideGUID] = core.VehicleOwned_=(guid)
- def GetLastUsedTime(code : Int) : Long = core.GetLastUsedTime(code)
-
- def GetLastUsedTime(code : ExoSuitType.Value) : Long = core.GetLastUsedTime(code)
-
- def GetLastUsedTime(code : ExoSuitType.Value, subtype : Int) : Long = core.GetLastUsedTime(code, subtype)
-
- def SetLastUsedTime(code : Int, time : Long) : Unit = core.SetLastUsedTime(code, time)
-
- def SetLastUsedTime(code : ExoSuitType.Value): Unit = core.SetLastUsedTime(code)
-
- def SetLastUsedTime(code : ExoSuitType.Value, time : Long) : Unit = core.SetLastUsedTime(code, time)
-
- def SetLastUsedTime(code : ExoSuitType.Value, subtype : Int): Unit = core.SetLastUsedTime(code, subtype)
-
- def SetLastUsedTime(code : ExoSuitType.Value, subtype : Int, time : Long) : Unit = core.SetLastUsedTime(code, subtype, time)
-
- def GetLastPurchaseTime(code : Int) : Long = core.GetLastPurchaseTime(code)
-
- def SetLastPurchaseTime(code : Int, time : Long) : Unit = core.SetLastPurchaseTime(code, time)
-
- def ObjectTypeNameReference(id : Long) : String = core.ObjectTypeNameReference(id)
-
- def ObjectTypeNameReference(id : Long, name : String) : String = core.ObjectTypeNameReference(id, name)
-
def DamageModel = exosuit.asInstanceOf[DamageResistanceModel]
def Definition : AvatarDefinition = core.Definition
diff --git a/common/src/main/scala/net/psforever/objects/Players.scala b/common/src/main/scala/net/psforever/objects/Players.scala
index 9260da45..486265c7 100644
--- a/common/src/main/scala/net/psforever/objects/Players.scala
+++ b/common/src/main/scala/net/psforever/objects/Players.scala
@@ -1,17 +1,10 @@
// Copyright (c) 2020 PSForever
package net.psforever.objects
-import net.psforever.objects.definition.ExoSuitDefinition
-import net.psforever.objects.equipment.EquipmentSlot
-import net.psforever.objects.inventory.InventoryItem
-import net.psforever.objects.loadouts.InfantryLoadout
import net.psforever.packet.game.{InventoryStateMessage, RepairMessage}
-import net.psforever.types.ExoSuitType
import services.Service
import services.avatar.{AvatarAction, AvatarServiceMessage}
-import scala.annotation.tailrec
-
object Players {
private val log = org.log4s.getLogger("Players")
@@ -53,71 +46,4 @@ object Players {
log.info(s"$medic had revived $name")
target.Zone.AvatarEvents ! AvatarServiceMessage(name, AvatarAction.Revive(target.GUID))
}
-
- /**
- * Iterate over a group of `EquipmentSlot`s, some of which may be occupied with an item.
- * Remove any encountered items and add them to an output `List`.
- * @param iter the `Iterator` of `EquipmentSlot`s
- * @param index a number that equals the "current" holster slot (`EquipmentSlot`)
- * @param list a persistent `List` of `Equipment` in the holster slots
- * @return a `List` of `Equipment` in the holster slots
- */
- @tailrec def clearHolsters(iter : Iterator[EquipmentSlot], index : Int = 0, list : List[InventoryItem] = Nil) : List[InventoryItem] = {
- if(!iter.hasNext) {
- list
- }
- else {
- val slot = iter.next
- slot.Equipment match {
- case Some(equipment) =>
- slot.Equipment = None
- clearHolsters(iter, index + 1, InventoryItem(equipment, index) +: list)
- case None =>
- clearHolsters(iter, index + 1, list)
- }
- }
- }
-
- /**
- * Iterate over a group of `EquipmentSlot`s, some of which may be occupied with an item.
- * For any slots that are not yet occupied by an item, search through the `List` and find an item that fits in that slot.
- * Add that item to the slot and remove it from the list.
- * @param iter the `Iterator` of `EquipmentSlot`s
- * @param list a `List` of all `Equipment` that is not yet assigned to a holster slot or an inventory slot
- * @return the `List` of all `Equipment` not yet assigned to a holster slot or an inventory slot
- */
- @tailrec def fillEmptyHolsters(iter : Iterator[EquipmentSlot], list : List[InventoryItem]) : List[InventoryItem] = {
- if(!iter.hasNext) {
- list
- }
- else {
- val slot = iter.next
- if(slot.Equipment.isEmpty) {
- list.find(item => item.obj.Size == slot.Size) match {
- case Some(obj) =>
- val index = list.indexOf(obj)
- slot.Equipment = obj.obj
- fillEmptyHolsters(iter, list.take(index) ++ list.drop(index + 1))
- case None =>
- fillEmptyHolsters(iter, list)
- }
- }
- else {
- fillEmptyHolsters(iter, list)
- }
- }
- }
-
- def CertificationToUseExoSuit(player : Player, exosuit : ExoSuitType.Value, subtype : Int) : Boolean = {
- ExoSuitDefinition.Select(exosuit, player.Faction).Permissions match {
- case Nil =>
- true
- case permissions if subtype != 0 =>
- val certs = player.Certifications
- certs.intersect(permissions.toSet).nonEmpty &&
- certs.intersect(InfantryLoadout.DetermineSubtypeC(subtype)).nonEmpty
- case permissions =>
- player.Certifications.intersect(permissions.toSet).nonEmpty
- }
- }
}
diff --git a/common/src/main/scala/net/psforever/objects/Vehicle.scala b/common/src/main/scala/net/psforever/objects/Vehicle.scala
index 8a872f6a..6db09377 100644
--- a/common/src/main/scala/net/psforever/objects/Vehicle.scala
+++ b/common/src/main/scala/net/psforever/objects/Vehicle.scala
@@ -4,7 +4,7 @@ package net.psforever.objects
import akka.actor.ActorRef
import net.psforever.objects.definition.VehicleDefinition
import net.psforever.objects.equipment.{Equipment, EquipmentSize, EquipmentSlot, JammableUnit}
-import net.psforever.objects.inventory.{Container, GridInventory, InventoryItem, InventoryTile}
+import net.psforever.objects.inventory.{Container, GridInventory, InventoryTile}
import net.psforever.objects.serverobject.mount.Mountable
import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.serverobject.affinity.FactionAffinity
@@ -17,7 +17,6 @@ import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID}
import scala.annotation.tailrec
import scala.concurrent.duration.FiniteDuration
-import scala.util.{Success, Try}
/**
* The server-side support object that represents a vehicle.
@@ -453,20 +452,6 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends AmenityOwner
}
}
- override def Collisions(dest : Int, width : Int, height : Int) : Try[List[InventoryItem]] = {
- weapons.get(dest) match {
- case Some(slot) =>
- slot.Equipment match {
- case Some(item) =>
- Success(List(InventoryItem(item, dest)))
- case None =>
- Success(List())
- }
- case None =>
- super.Collisions(dest, width, height)
- }
- }
-
/**
* A reference to the `Vehicle` `Trunk` space.
* @return this `Vehicle` `Trunk`
diff --git a/common/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala b/common/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala
index d283f24f..4588b083 100644
--- a/common/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala
+++ b/common/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala
@@ -1,28 +1,22 @@
// Copyright (c) 2020 PSForever
package net.psforever.objects.avatar
-import akka.actor.{Actor, ActorRef, Props}
-import net.psforever.objects.{Players, _}
-import net.psforever.objects.ballistics.{PlayerSource, ResolvedProjectile}
+import akka.actor.Actor
+import net.psforever.objects.{DefaultCancellable, GlobalDefinitions, ImplantSlot, Player, Players, Tool}
+import net.psforever.objects.ballistics.{PlayerSource, ResolvedProjectile, SourceEntry}
import net.psforever.objects.definition.ImplantDefinition
-import net.psforever.objects.equipment.{Ammo, Equipment, EquipmentSize, JammableBehavior, JammableUnit}
-import net.psforever.objects.inventory.{GridInventory, InventoryItem}
-import net.psforever.objects.loadouts.Loadout
-import net.psforever.objects.serverobject.containable.{Containable, ContainableBehavior}
+import net.psforever.objects.equipment.{Ammo, JammableBehavior, JammableUnit}
import net.psforever.objects.vital.{PlayerSuicide, Vitality}
-import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject}
+import net.psforever.objects.serverobject.CommonMessages
import net.psforever.objects.serverobject.damage.Damageable
import net.psforever.objects.serverobject.mount.Mountable
import net.psforever.objects.serverobject.repair.Repairable
-import net.psforever.objects.serverobject.terminals.Terminal
import net.psforever.objects.vital._
import net.psforever.objects.zones.Zone
import net.psforever.packet.game._
-import net.psforever.packet.game.objectcreate.ObjectCreateMessageParent
-import net.psforever.types.{ExoSuitType, PlanetSideEmpire, PlanetSideGUID, Vector3}
-import services.{RemoverActor, Service}
+import net.psforever.types.{ExoSuitType, ImplantType, PlanetSideGUID, Vector3}
+import services.Service
import services.avatar.{AvatarAction, AvatarServiceMessage}
-import services.local.{LocalAction, LocalServiceMessage}
import scala.concurrent.duration._
import scala.collection.mutable
@@ -31,119 +25,112 @@ import scala.concurrent.ExecutionContext.Implicits.global
class PlayerControl(player : Player) extends Actor
with JammableBehavior
- with Damageable
- with ContainableBehavior {
+ with Damageable {
def JammableObject = player
def DamageableObject = player
- def ContainerObject = player
- private[this] val log = org.log4s.getLogger(player.Name)
- private[this] val damageLog = org.log4s.getLogger(Damageable.LogChannel)
+ private [this] val log = org.log4s.getLogger(player.Name)
+ private [this] val damageLog = org.log4s.getLogger(Damageable.LogChannel)
+
// A collection of timers for each slot to trigger stamina drain on an interval
val implantSlotStaminaDrainTimers = mutable.HashMap(0 -> DefaultCancellable.obj, 1 -> DefaultCancellable.obj, 2 -> DefaultCancellable.obj)
- // control agency for the player's locker container (dedicated inventory slot #5)
- val lockerControlAgent : ActorRef = {
- val locker = player.Locker
- locker.Zone = player.Zone
- locker.Actor = context.actorOf(Props(classOf[LockerContainerControl], locker, player.Name), PlanetSideServerObject.UniqueActorName(locker))
- }
-
- override def postStop() : Unit = {
- context.stop(lockerControlAgent)
- player.Locker.Actor = ActorRef.noSender
- implantSlotStaminaDrainTimers.values.foreach { _.cancel }
- }
def receive : Receive = jammableBehavior
.orElse(takesDamage)
- .orElse(containerBehavior)
.orElse {
- case Player.ImplantActivation(slot : Int, status : Int) =>
- // todo: disable implants with stamina cost when changing armour type
- val implantSlot = player.ImplantSlot(slot)
+ case Player.ImplantActivation(slot: Int, status : Int) =>
+ // todo: disable implants with stamina cost when changing armour type
+ val implantSlot = player.ImplantSlot(slot)
- // Allow uninitialized implants to be deactivated in case they're stuck in a state where they are no longer active or initialized but still draining stamina (e.g. by EMP)
- if (status == 0 && (implantSlot.Active || !implantSlot.Initialized)) {
- // Cancel stamina drain timer
- implantSlotStaminaDrainTimers(slot).cancel()
- implantSlotStaminaDrainTimers(slot) = DefaultCancellable.obj
- implantSlot.Active = false
- player.Zone.AvatarEvents ! AvatarServiceMessage(player.Zone.Id, AvatarAction.PlanetsideAttribute(player.GUID, 28, player.Implant(slot).id * 2)) // Deactivation sound / effect
- player.Zone.AvatarEvents ! AvatarServiceMessage(player.Name, AvatarAction.DeactivateImplantSlot(player.GUID, slot))
+ // Allow uninitialized implants to be deactivated in case they're stuck in a state where they are no longer active or initialized but still draining stamina (e.g. by EMP)
+ if(status == 0 && (implantSlot.Active || !implantSlot.Initialized)) {
+ // Cancel stamina drain timer
+ implantSlotStaminaDrainTimers(slot).cancel()
+ implantSlotStaminaDrainTimers(slot) = DefaultCancellable.obj
+
+ implantSlot.Active = false
+ player.Zone.AvatarEvents ! AvatarServiceMessage(player.Zone.Id, AvatarAction.PlanetsideAttribute(player.GUID, 28, player.Implant(slot).id * 2)) // Deactivation sound / effect
+ player.Zone.AvatarEvents ! AvatarServiceMessage(player.Name, AvatarAction.DeactivateImplantSlot(player.GUID, slot))
+ } else if (status == 1 && implantSlot.Initialized && !player.Fatigued) {
+ implantSlot.Installed match {
+ case Some(implant: ImplantDefinition) =>
+ if(implantSlot.Active) {
+ // Some events such as zoning will reset the implant on the client side without sending a deactivation packet
+ // But the implant will remain in an active state server side. For now, allow reactivation of the implant.
+ // todo: Deactivate implants server side when actions like zoning happen. (Other actions?)
+ log.warn(s"Implant ${slot} is already active, but activating again")
+ implantSlotStaminaDrainTimers(slot).cancel()
+ implantSlotStaminaDrainTimers(slot) = DefaultCancellable.obj
+ }
+ implantSlot.Active = true
+
+ if (implant.ActivationStaminaCost >= 0) {
+ player.Stamina -= implant.ActivationStaminaCost // Activation stamina drain
+ }
+
+ if(implant.StaminaCost > 0 && implant.GetCostIntervalByExoSuit(player.ExoSuit) > 0) { // Ongoing stamina drain, if applicable
+ implantSlotStaminaDrainTimers(slot) = context.system.scheduler.schedule(0 seconds, implant.GetCostIntervalByExoSuit(player.ExoSuit) milliseconds, self, Player.DrainStamina(implant.StaminaCost))
+ }
+
+ player.Zone.AvatarEvents ! AvatarServiceMessage(player.Zone.Id, AvatarAction.PlanetsideAttribute(player.GUID, 28, player.Implant(slot).id * 2 + 1)) // Activation sound / effect
+ player.Zone.AvatarEvents ! AvatarServiceMessage(player.Name, AvatarAction.ActivateImplantSlot(player.GUID, slot))
+ case _ => ;
}
- else if (status == 1 && implantSlot.Initialized && !player.Fatigued) {
- implantSlot.Installed match {
- case Some(implant : ImplantDefinition) =>
- if (implantSlot.Active) {
- // Some events such as zoning will reset the implant on the client side without sending a deactivation packet
- // But the implant will remain in an active state server side. For now, allow reactivation of the implant.
- // todo: Deactivate implants server side when actions like zoning happen. (Other actions?)
- log.warn(s"Implant $slot is already active, but activating again")
- implantSlotStaminaDrainTimers(slot).cancel()
- implantSlotStaminaDrainTimers(slot) = DefaultCancellable.obj
- }
- implantSlot.Active = true
- if (implant.ActivationStaminaCost >= 0) {
- player.Stamina -= implant.ActivationStaminaCost // Activation stamina drain
- }
- if (implant.StaminaCost > 0 && implant.GetCostIntervalByExoSuit(player.ExoSuit) > 0) { // Ongoing stamina drain, if applicable
- implantSlotStaminaDrainTimers(slot) = context.system.scheduler.schedule(0 seconds, implant.GetCostIntervalByExoSuit(player.ExoSuit) milliseconds, self, Player.DrainStamina(implant.StaminaCost))
- }
- player.Zone.AvatarEvents ! AvatarServiceMessage(player.Zone.Id, AvatarAction.PlanetsideAttribute(player.GUID, 28, player.Implant(slot).id * 2 + 1)) // Activation sound / effect
- player.Zone.AvatarEvents ! AvatarServiceMessage(player.Name, AvatarAction.ActivateImplantSlot(player.GUID, slot))
- case _ => ;
- }
+ }
+ else {
+ log.warn(s"Can't handle ImplantActivation: Player GUID: ${player.GUID} Slot ${slot} Status: ${status} Initialized: ${implantSlot.Initialized} Active: ${implantSlot.Active} Fatigued: ${player.Fatigued}")
+ }
+
+ case Player.UninitializeImplant(slot: Int) => {
+ PlayerControl.UninitializeImplant(player, slot)
+ }
+
+ case Player.ImplantInitializationStart(slot: Int) =>
+ val implantSlot = player.ImplantSlot(slot)
+ if(implantSlot.Installed.isDefined) {
+ if(implantSlot.Initialized) {
+ PlayerControl.UninitializeImplant(player, slot)
}
- case Player.UninitializeImplant(slot : Int) =>
- PlayerControl.UninitializeImplant(player, slot)
+ // Start client side initialization timer
+ player.Zone.AvatarEvents ! AvatarServiceMessage(player.Name, AvatarAction.SendResponse(player.GUID, ActionProgressMessage(slot + 6, 0)))
- case Player.ImplantInitializationStart(slot : Int) =>
- val implantSlot = player.ImplantSlot(slot)
- if (implantSlot.Installed.isDefined) {
- if (implantSlot.Initialized) {
- PlayerControl.UninitializeImplant(player, slot)
- }
+ // Callback after initialization timer to complete initialization
+ implantSlot.InitializeTimer = context.system.scheduler.scheduleOnce(implantSlot.MaxTimer seconds, self, Player.ImplantInitializationComplete(slot))
+ }
- // Start client side initialization timer
- player.Zone.AvatarEvents ! AvatarServiceMessage(player.Name, AvatarAction.SendResponse(player.GUID, ActionProgressMessage(slot + 6, 0)))
-
- // Callback after initialization timer to complete initialization
- implantSlot.InitializeTimer = context.system.scheduler.scheduleOnce(implantSlot.MaxTimer seconds, self, Player.ImplantInitializationComplete(slot))
+ case Player.ImplantInitializationComplete(slot: Int) =>
+ val implantSlot = player.ImplantSlot(slot)
+ if(implantSlot.Installed.isDefined) {
+ player.Zone.AvatarEvents ! AvatarServiceMessage(player.Name, AvatarAction.SendResponse(player.GUID, AvatarImplantMessage(player.GUID, ImplantAction.Initialization, slot, 1)))
+ implantSlot.Initialized = true
+ if(implantSlot.InitializeTimer != DefaultCancellable.obj) {
+ implantSlot.InitializeTimer.cancel()
+ implantSlot.InitializeTimer = DefaultCancellable.obj
}
+ }
- case Player.ImplantInitializationComplete(slot : Int) =>
- val implantSlot = player.ImplantSlot(slot)
- if (implantSlot.Installed.isDefined) {
- player.Zone.AvatarEvents ! AvatarServiceMessage(player.Name, AvatarAction.SendResponse(player.GUID, AvatarImplantMessage(player.GUID, ImplantAction.Initialization, slot, 1)))
- implantSlot.Initialized = true
- if (implantSlot.InitializeTimer != DefaultCancellable.obj) {
- implantSlot.InitializeTimer.cancel()
- implantSlot.InitializeTimer = DefaultCancellable.obj
- }
- }
+ case Player.DrainStamina(amount : Int) =>
+ player.Stamina -= amount
- case Player.DrainStamina(amount : Int) =>
- player.Stamina -= amount
+ case Player.StaminaChanged(currentStamina : Int) =>
+ if(currentStamina == 0) {
+ player.Fatigued = true
+ player.skipStaminaRegenForTurns += 4
+ for(slot <- 0 to player.Implants.length - 1) { // Disable all implants
+ self ! Player.ImplantActivation(slot, 0)
+ player.Zone.AvatarEvents ! AvatarServiceMessage(player.Zone.Id, AvatarAction.SendResponseTargeted(player.GUID, AvatarImplantMessage(player.GUID, ImplantAction.OutOfStamina, slot, 1)))
+ }
+ } else if (player.Fatigued && currentStamina >= 20) {
+ player.Fatigued = false
+ for(slot <- 0 to player.Implants.length - 1) { // Re-enable all implants
+ player.Zone.AvatarEvents ! AvatarServiceMessage(player.Zone.Id, AvatarAction.SendResponseTargeted(player.GUID, AvatarImplantMessage(player.GUID, ImplantAction.OutOfStamina, slot, 0)))
+ }
+ }
- case Player.StaminaChanged(currentStamina : Int) =>
- if (currentStamina == 0) {
- player.Fatigued = true
- player.skipStaminaRegenForTurns += 4
- player.Implants.indices.foreach { slot => // Disable all implants
- self ! Player.ImplantActivation(slot, 0)
- player.Zone.AvatarEvents ! AvatarServiceMessage(player.Zone.Id, AvatarAction.SendResponseTargeted(player.GUID, AvatarImplantMessage(player.GUID, ImplantAction.OutOfStamina, slot, 1)))
- }
- }
- else if (player.Fatigued && currentStamina >= 20) {
- player.Fatigued = false
- player.Implants.indices.foreach { slot => // Re-enable all implants
- player.Zone.AvatarEvents ! AvatarServiceMessage(player.Zone.Id, AvatarAction.SendResponseTargeted(player.GUID, AvatarImplantMessage(player.GUID, ImplantAction.OutOfStamina, slot, 0)))
- }
- }
- player.Zone.AvatarEvents ! AvatarServiceMessage(player.Zone.Id, AvatarAction.PlanetsideAttributeSelf(player.GUID, 2, player.Stamina))
+ player.Zone.AvatarEvents ! AvatarServiceMessage(player.Zone.Id, AvatarAction.PlanetsideAttributeSelf(player.GUID, 2, player.Stamina))
case Player.Die() =>
- if (player.isAlive) {
+ if(player.isAlive) {
PlayerControl.DestructionAwareness(player, None)
}
@@ -151,7 +138,7 @@ class PlayerControl(player : Player) extends Actor
//heal
val originalHealth = player.Health
val definition = player.Definition
- if (player.MaxHealth > 0 && originalHealth < player.MaxHealth &&
+ if(player.MaxHealth > 0 && originalHealth < player.MaxHealth &&
user.Faction == player.Faction &&
item.Magazine > 0 &&
Vector3.Distance(user.Position, player.Position) < definition.RepairDistance) {
@@ -159,14 +146,14 @@ class PlayerControl(player : Player) extends Actor
val events = zone.AvatarEvents
val uname = user.Name
val guid = player.GUID
- if (!(player.isMoving || user.isMoving)) { //only allow stationary heals
+ if(!(player.isMoving || user.isMoving)) { //only allow stationary heals
val newHealth = player.Health = originalHealth + 10
val magazine = item.Discharge
events ! AvatarServiceMessage(uname, AvatarAction.SendResponse(Service.defaultPlayerGUID, InventoryStateMessage(item.AmmoSlot.Box.GUID, item.GUID, magazine.toLong)))
events ! AvatarServiceMessage(zone.Id, AvatarAction.PlanetsideAttributeToAll(guid, 0, newHealth))
player.History(HealFromEquipment(PlayerSource(player), PlayerSource(user), newHealth - originalHealth, GlobalDefinitions.medicalapplicator))
}
- if (player != user) {
+ if(player != user) {
//"Someone is trying to heal you"
events ! AvatarServiceMessage(player.Name, AvatarAction.PlanetsideAttributeToAll(guid, 55, 1))
//progress bar remains visible for all heal attempts
@@ -176,7 +163,7 @@ class PlayerControl(player : Player) extends Actor
case CommonMessages.Use(user, Some(item : Tool)) if item.Definition == GlobalDefinitions.medicalapplicator =>
//revive
- if (user != player &&
+ if(user != player &&
user.Faction == player.Faction &&
user.isAlive && !user.isMoving &&
!player.isAlive && !player.isBackpack &&
@@ -191,7 +178,7 @@ class PlayerControl(player : Player) extends Actor
case CommonMessages.Use(user, Some(item : Tool)) if item.Definition == GlobalDefinitions.bank =>
val originalArmor = player.Armor
val definition = player.Definition
- if (player.MaxArmor > 0 && originalArmor < player.MaxArmor &&
+ if(player.MaxArmor > 0 && originalArmor < player.MaxArmor &&
user.Faction == player.Faction &&
item.AmmoType == Ammo.armor_canister && item.Magazine > 0 &&
Vector3.Distance(user.Position, player.Position) < definition.RepairDistance) {
@@ -199,15 +186,15 @@ class PlayerControl(player : Player) extends Actor
val events = zone.AvatarEvents
val uname = user.Name
val guid = player.GUID
- if (!(player.isMoving || user.isMoving)) { //only allow stationary repairs
+ if(!(player.isMoving || user.isMoving)) { //only allow stationary repairs
val newArmor = player.Armor = originalArmor + Repairable.Quality + RepairValue(item) + definition.RepairMod
val magazine = item.Discharge
events ! AvatarServiceMessage(uname, AvatarAction.SendResponse(Service.defaultPlayerGUID, InventoryStateMessage(item.AmmoSlot.Box.GUID, item.GUID, magazine.toLong)))
events ! AvatarServiceMessage(zone.Id, AvatarAction.PlanetsideAttributeToAll(guid, 4, player.Armor))
player.History(RepairFromEquipment(PlayerSource(player), PlayerSource(user), newArmor - originalArmor, GlobalDefinitions.bank))
}
- if (player != user) {
- if (player.isAlive) {
+ if(player != user) {
+ if(player.isAlive) {
//"Someone is trying to repair you" gets strobed twice for visibility
val msg = AvatarServiceMessage(player.Name, AvatarAction.PlanetsideAttributeToAll(guid, 56, 1))
events ! msg
@@ -219,263 +206,6 @@ class PlayerControl(player : Player) extends Actor
}
}
- case Terminal.TerminalMessage(_, msg, order) =>
- order match {
- case Terminal.BuyExosuit(exosuit, subtype) =>
- val time = System.currentTimeMillis
- var toDelete : List[InventoryItem] = Nil
- val originalSuit = player.ExoSuit
- val originalSubtype = Loadout.DetermineSubtype(player)
- val requestToChangeArmor = originalSuit != exosuit || originalSubtype != subtype
- val allowedToChangeArmor = Players.CertificationToUseExoSuit(player, exosuit, subtype) &&
- (if (exosuit == ExoSuitType.MAX) {
- if (time - player.GetLastUsedTime(exosuit, subtype) < 30000L) {
- false
- }
- else {
- player.SetLastUsedTime(exosuit, subtype, time)
- true
- }
- }
- else {
- player.SetLastUsedTime(exosuit, subtype, time)
- true
- })
- val result = if (requestToChangeArmor && allowedToChangeArmor) {
- log.info(s"${player.Name} wants to change to a different exo-suit - $exosuit")
- player.SetLastUsedTime(exosuit, subtype, System.currentTimeMillis())
- val beforeHolsters = Players.clearHolsters(player.Holsters().iterator)
- val beforeInventory = player.Inventory.Clear()
- //change suit
- val originalArmor = player.Armor
- player.ExoSuit = exosuit //changes the value of MaxArmor to reflect the new exo-suit
- val toMaxArmor = player.MaxArmor
- if (originalSuit != exosuit || originalSubtype != subtype || originalArmor > toMaxArmor) {
- player.History(HealFromExoSuitChange(PlayerSource(player), exosuit))
- player.Armor = toMaxArmor
- }
- else {
- player.Armor = originalArmor
- }
- //ensure arm is down, even if it needs to go back up
- if (player.DrawnSlot != Player.HandsDownSlot) {
- player.DrawnSlot = Player.HandsDownSlot
- }
- val normalHolsters = if (originalSuit == ExoSuitType.MAX) {
- val (maxWeapons, normalWeapons) = beforeHolsters.partition(elem => elem.obj.Size == EquipmentSize.Max)
- toDelete ++= maxWeapons
- normalWeapons
- }
- else {
- beforeHolsters
- }
- //populate holsters
- val (afterHolsters, finalInventory) = if (exosuit == ExoSuitType.MAX) {
- (normalHolsters, Players.fillEmptyHolsters(List(player.Slot(4)).iterator, normalHolsters) ++ beforeInventory)
- }
- else if (originalSuit == exosuit) { //note - this will rarely be the situation
- (normalHolsters, Players.fillEmptyHolsters(player.Holsters().iterator, normalHolsters))
- }
- else {
- val (afterHolsters, toInventory) = normalHolsters.partition(elem => elem.obj.Size == player.Slot(elem.start).Size)
- afterHolsters.foreach({ elem => player.Slot(elem.start).Equipment = elem.obj })
- val remainder = Players.fillEmptyHolsters(player.Holsters().iterator, toInventory ++ beforeInventory)
- (
- player.Holsters()
- .zipWithIndex
- .map { case (slot, i) => (slot.Equipment, i) }
- .collect { case (Some(obj), index) => InventoryItem(obj, index) }
- .toList,
- remainder
- )
- }
- //put items back into inventory
- val (stow, drop) = if (originalSuit == exosuit) {
- (finalInventory, Nil)
- }
- else {
- val (a, b) = GridInventory.recoverInventory(finalInventory, player.Inventory)
- (a, b.map {
- InventoryItem(_, -1)
- })
- }
- stow.foreach { elem =>
- player.Inventory.InsertQuickly(elem.start, elem.obj)
- }
- player.Zone.AvatarEvents ! AvatarServiceMessage(player.Zone.Id,
- AvatarAction.ChangeExosuit(player.GUID, exosuit, subtype, player.LastDrawnSlot, exosuit == ExoSuitType.MAX && requestToChangeArmor,
- beforeHolsters.map { case InventoryItem(obj, _) => (obj, obj.GUID) }, afterHolsters,
- beforeInventory.map { case InventoryItem(obj, _) => (obj, obj.GUID) }, stow, drop,
- toDelete.map { case InventoryItem(obj, _) => (obj, obj.GUID) }
- )
- )
- true
- }
- else {
- false
- }
- player.Zone.AvatarEvents ! AvatarServiceMessage(player.Name, AvatarAction.TerminalOrderResult(msg.terminal_guid, msg.transaction_type, result))
-
- case Terminal.InfantryLoadout(exosuit, subtype, holsters, inventory) =>
- log.info(s"wants to change equipment loadout to their option #${msg.unk1 + 1}")
- val fallbackSubtype = 0
- val fallbackSuit = ExoSuitType.Standard
- val originalSuit = player.ExoSuit
- val originalSubtype = Loadout.DetermineSubtype(player)
- //sanitize exo-suit for change
- val dropPred = ContainableBehavior.DropPredicate(player)
- val oldHolsters = Players.clearHolsters(player.Holsters().iterator)
- val dropHolsters = oldHolsters.filter(dropPred)
- val oldInventory = player.Inventory.Clear()
- val dropInventory = oldInventory.filter(dropPred)
- val toDeleteOrDrop : List[InventoryItem] = (player.FreeHand.Equipment match {
- case Some(obj) =>
- val out = InventoryItem(obj, -1)
- player.FreeHand.Equipment = None
- if (dropPred(out)) {
- List(out)
- }
- else {
- Nil
- }
- case _ =>
- Nil
- }) ++ dropHolsters ++ dropInventory
- //a loadout with a prohibited exo-suit type will result in the fallback exo-suit type
- //imposed 5min delay on mechanized exo-suit switches
- val time = System.currentTimeMillis()
- val (nextSuit, nextSubtype) = if (Players.CertificationToUseExoSuit(player, exosuit, subtype) &&
- (if (exosuit == ExoSuitType.MAX) {
- if (time - player.GetLastUsedTime(exosuit, subtype) < 30000L) {
- false
- }
- else {
- player.SetLastUsedTime(exosuit, subtype, time)
- true
- }
- }
- else {
- player.SetLastUsedTime(exosuit, subtype, time)
- true
- })) {
- (exosuit, subtype)
- }
- else {
- log.warn(s"no longer has permission to wear the exo-suit type $exosuit; will wear $fallbackSuit instead")
- player.SetLastUsedTime(fallbackSuit, fallbackSubtype, time)
- (fallbackSuit, fallbackSubtype)
- }
- //sanitize (incoming) inventory
- //TODO equipment permissions; these loops may be expanded upon in future
- val curatedHolsters = for {
- item <- holsters
- //id = item.obj.Definition.ObjectId
- //lastTime = player.GetLastUsedTime(id)
- if true
- } yield item
- val curatedInventory = for {
- item <- inventory
- //id = item.obj.Definition.ObjectId
- //lastTime = player.GetLastUsedTime(id)
- if true
- } yield item
- //update suit internally
- val originalArmor = player.Armor
- player.ExoSuit = nextSuit
- val toMaxArmor = player.MaxArmor
- if (originalSuit != nextSuit || originalSubtype != nextSubtype || originalArmor > toMaxArmor) {
- player.History(HealFromExoSuitChange(PlayerSource(player), nextSuit))
- player.Armor = toMaxArmor
- }
- else {
- player.Armor = originalArmor
- }
- //ensure arm is down, even if it needs to go back up
- if (player.DrawnSlot != Player.HandsDownSlot) {
- player.DrawnSlot = Player.HandsDownSlot
- }
- //a change due to exo-suit permissions mismatch will result in (more) items being re-arranged and/or dropped
- //dropped items are not registered and can just be forgotten
- val (afterHolsters, afterInventory) = if (nextSuit == exosuit) {
- (
- //melee slot preservation for MAX
- if (nextSuit == ExoSuitType.MAX) {
- holsters.filter(_.start == 4)
- }
- else {
- curatedHolsters.filterNot(dropPred)
- },
- curatedInventory.filterNot(dropPred)
- )
- }
- else {
- //our exo-suit type was hijacked by changing permissions; we shouldn't even be able to use that loadout(!)
- //holsters
- val leftoversForInventory = Players.fillEmptyHolsters(
- player.Holsters().iterator,
- (curatedHolsters ++ curatedInventory).filterNot(dropPred)
- )
- val finalHolsters = player.Holsters()
- .zipWithIndex
- .collect { case (slot, index) if slot.Equipment.nonEmpty => InventoryItem(slot.Equipment.get, index) }
- .toList
- //inventory
- val (finalInventory, _) = GridInventory.recoverInventory(leftoversForInventory, player.Inventory)
- (finalHolsters, finalInventory)
- }
- (afterHolsters ++ afterInventory).foreach { entry => entry.obj.Faction = player.Faction }
- toDeleteOrDrop.foreach { entry => entry.obj.Faction = PlanetSideEmpire.NEUTRAL }
- player.Zone.AvatarEvents ! AvatarServiceMessage(player.Zone.Id,
- AvatarAction.ChangeLoadout(player.GUID, nextSuit, nextSubtype, player.LastDrawnSlot, exosuit == ExoSuitType.MAX,
- oldHolsters.map { case InventoryItem(obj, _) => (obj, obj.GUID) }, afterHolsters,
- oldInventory.map { case InventoryItem(obj, _) => (obj, obj.GUID) }, afterInventory, toDeleteOrDrop)
- )
- player.Zone.AvatarEvents ! AvatarServiceMessage(player.Name, AvatarAction.TerminalOrderResult(msg.terminal_guid, msg.transaction_type, true))
-
- case _ => ; //terminal messages not handled here
- }
-
- case Zone.Ground.ItemOnGround(item, _, _) => ;
- val name = player.Name
- val zone = player.Zone
- val avatarEvents = zone.AvatarEvents
- val localEvents = zone.LocalEvents
- item match {
- case trigger : BoomerTrigger =>
- //dropped the trigger, no longer own the boomer; make certain whole faction is aware of that
- (zone.GUID(trigger.Companion), zone.Players.find { _.name == name}) match {
- case (Some(boomer : BoomerDeployable), Some(avatar)) =>
- val guid = boomer.GUID
- val factionChannel = boomer.Faction.toString
- if(avatar.Deployables.Remove(boomer)) {
- boomer.Faction = PlanetSideEmpire.NEUTRAL
- boomer.AssignOwnership(None)
- avatar.Deployables.UpdateUIElement(boomer.Definition.Item).foreach { case (currElem, curr, maxElem, max) =>
- avatarEvents ! AvatarServiceMessage(name, AvatarAction.PlanetsideAttributeToAll(Service.defaultPlayerGUID, maxElem, max))
- avatarEvents ! AvatarServiceMessage(name, AvatarAction.PlanetsideAttributeToAll(Service.defaultPlayerGUID, currElem, curr))
- }
- localEvents ! LocalServiceMessage.Deployables(RemoverActor.AddTask(boomer, zone))
- localEvents ! LocalServiceMessage(factionChannel,
- LocalAction.DeployableMapIcon(Service.defaultPlayerGUID, DeploymentAction.Dismiss,
- DeployableInfo(guid, DeployableIcon.Boomer, boomer.Position, PlanetSideGUID(0))
- )
- )
- avatarEvents ! AvatarServiceMessage(factionChannel, AvatarAction.SetEmpire(Service.defaultPlayerGUID, guid, PlanetSideEmpire.NEUTRAL))
- }
- case _ => ; //pointless trigger? or a trigger being deleted?
- }
- case _ => ;
- }
-
-
- case Zone.Ground.CanNotDropItem(_, item, reason) =>
- log.warn(s"${player.Name} tried to drop a ${item.Definition.Name} on the ground, but it $reason")
-
- case Zone.Ground.ItemInHand(_) => ;
-
- case Zone.Ground.CanNotPickupItem(_, item_guid, reason) =>
- log.warn(s"${player.Name} failed to pick up an item ($item_guid) from the ground because $reason")
-
case _ => ;
}
@@ -524,22 +254,27 @@ class PlayerControl(player : Player) extends Actor
* @param target an object that can be affected by the jammered status
* @param dur the duration of the timer, in milliseconds
*/
- override def StartJammeredStatus(target : Any, dur : Int) : Unit = {
- //TODO these features
- val zone = player.Zone
+ override def StartJammeredStatus(target : Any, dur : Int) : Unit = target match {
+ case obj : Player =>
+ //TODO these features
+ val guid = obj.GUID
+ val zone = obj.Zone
+ val zoneId = zone.Id
+ val events = zone.AvatarEvents
- player.Implants.indices.foreach { slot => // Deactivate & uninitialize all implants
- zone.AvatarEvents ! AvatarServiceMessage(zone.Id, AvatarAction.PlanetsideAttribute(player.GUID, 28, player.Implant(slot).id * 2)) // Deactivation sound / effect
- self ! Player.ImplantActivation(slot, 0)
- PlayerControl.UninitializeImplant(player, slot)
- }
+ for(slot <- 0 to player.Implants.length - 1) { // Deactivate & uninitialize all implants
+ player.Zone.AvatarEvents ! AvatarServiceMessage(player.Zone.Id, AvatarAction.PlanetsideAttribute(player.GUID, 28, player.Implant(slot).id * 2)) // Deactivation sound / effect
+ self ! Player.ImplantActivation(slot, 0)
+ PlayerControl.UninitializeImplant(player, slot)
+ }
- player.skipStaminaRegenForTurns = math.max(player.skipStaminaRegenForTurns, 10)
- super.StartJammeredStatus(target, dur)
+ obj.skipStaminaRegenForTurns = math.max(obj.skipStaminaRegenForTurns, 10)
+ super.StartJammeredStatus(target, dur)
+ case _ => ;
}
override def CancelJammeredStatus(target: Any): Unit = {
- player.Implants.indices.foreach { slot => // Start reinitializing all implants
+ for(slot <- 0 to player.Implants.length - 1) { // Start reinitializing all implants
self ! Player.ImplantInitializationStart(slot)
}
@@ -564,101 +299,6 @@ class PlayerControl(player : Player) extends Actor
else {
item.FireMode.Modifiers.Damage3
}
-
- def MessageDeferredCallback(msg : Any) : Unit = {
- msg match {
- case Containable.MoveItem(_, item, _) =>
- //momentarily put item back where it was originally
- val obj = ContainerObject
- obj.Find(item) match {
- case Some(slot) =>
- obj.Zone.AvatarEvents ! AvatarServiceMessage(
- player.Name,
- AvatarAction.SendResponse(Service.defaultPlayerGUID, ObjectAttachMessage(obj.GUID, item.GUID, slot))
- )
- case None => ;
- }
- case _ => ;
- }
- }
-
- def RemoveItemFromSlotCallback(item : Equipment, slot : Int) : Unit = {
- val obj = ContainerObject
- val zone = obj.Zone
- val name = player.Name
- val toChannel = if(obj.VisibleSlots.contains(slot) || obj.isBackpack) zone.Id else name
- val events = zone.AvatarEvents
- item.Faction = PlanetSideEmpire.NEUTRAL
- if(slot == obj.DrawnSlot) {
- obj.DrawnSlot = Player.HandsDownSlot
- }
- events ! AvatarServiceMessage(toChannel, AvatarAction.ObjectDelete(Service.defaultPlayerGUID, item.GUID))
- }
-
- def PutItemInSlotCallback(item : Equipment, slot : Int) : Unit = {
- val obj = ContainerObject
- val guid = obj.GUID
- val zone = obj.Zone
- val events = zone.AvatarEvents
- val name = player.Name
- val definition = item.Definition
- val msg = AvatarAction.SendResponse(
- Service.defaultPlayerGUID,
- ObjectCreateDetailedMessage(
- definition.ObjectId,
- item.GUID,
- ObjectCreateMessageParent(guid, slot),
- definition.Packet.DetailedConstructorData(item).get
- )
- )
- if(obj.isBackpack) {
- item.Faction = PlanetSideEmpire.NEUTRAL
- events ! AvatarServiceMessage(zone.Id, msg)
- }
- else {
- val faction = obj.Faction
- item.Faction = faction
- events ! AvatarServiceMessage(name, msg)
- if(obj.VisibleSlots.contains(slot)) {
- events ! AvatarServiceMessage(zone.Id, AvatarAction.EquipmentInHand(guid, guid, slot, item))
- }
- //handle specific types of items
- item match {
- case trigger : BoomerTrigger =>
- //pick up the trigger, own the boomer; make certain whole faction is aware of that
- (zone.GUID(trigger.Companion), zone.Players.find { _.name == name }) match {
- case (Some(boomer : BoomerDeployable), Some(avatar))
- if !boomer.OwnerName.contains(name) || boomer.Faction != faction =>
- val bguid = boomer.GUID
- val faction = player.Faction
- val factionChannel = faction.toString
- if(avatar.Deployables.Add(boomer)) {
- boomer.Faction = faction
- boomer.AssignOwnership(player)
- avatar.Deployables.UpdateUIElement(boomer.Definition.Item).foreach { case (currElem, curr, maxElem, max) =>
- events ! AvatarServiceMessage(name, AvatarAction.PlanetsideAttributeToAll(Service.defaultPlayerGUID, maxElem, max))
- events ! AvatarServiceMessage(name, AvatarAction.PlanetsideAttributeToAll(Service.defaultPlayerGUID, currElem, curr))
- }
- zone.LocalEvents ! LocalServiceMessage.Deployables(RemoverActor.ClearSpecific(List(boomer), zone))
- events ! AvatarServiceMessage(factionChannel, AvatarAction.SetEmpire(Service.defaultPlayerGUID, bguid, faction))
- zone.LocalEvents ! LocalServiceMessage(factionChannel,
- LocalAction.DeployableMapIcon(Service.defaultPlayerGUID, DeploymentAction.Build,
- DeployableInfo(bguid, DeployableIcon.Boomer, boomer.Position, boomer.Owner.getOrElse(PlanetSideGUID(0)))
- )
- )
- }
- case _ => ; //pointless trigger?
- }
- case _ => ;
- }
- }
- }
-
- def SwapItemCallback(item : Equipment) : Unit = {
- val obj = ContainerObject
- val zone = obj.Zone
- zone.AvatarEvents ! AvatarServiceMessage(player.Name, AvatarAction.SendResponse(Service.defaultPlayerGUID, ObjectDetachMessage(obj.GUID, item.GUID, Vector3.Zero, 0f)))
- }
}
object PlayerControl {
diff --git a/common/src/main/scala/net/psforever/objects/definition/converter/LockerContainerConverter.scala b/common/src/main/scala/net/psforever/objects/definition/converter/LockerContainerConverter.scala
index ba24b1d8..38fe6984 100644
--- a/common/src/main/scala/net/psforever/objects/definition/converter/LockerContainerConverter.scala
+++ b/common/src/main/scala/net/psforever/objects/definition/converter/LockerContainerConverter.scala
@@ -1,7 +1,7 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.definition.converter
-import net.psforever.objects.LockerEquipment
+import net.psforever.objects.LockerContainer
import net.psforever.objects.equipment.Equipment
import net.psforever.objects.inventory.GridInventory
import net.psforever.packet.game.objectcreate._
@@ -9,8 +9,8 @@ import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID}
import scala.util.{Success, Try}
-class LockerContainerConverter extends ObjectCreateConverter[LockerEquipment]() {
- override def ConstructorData(obj : LockerEquipment) : Try[LockerContainerData] = {
+class LockerContainerConverter extends ObjectCreateConverter[LockerContainer]() {
+ override def ConstructorData(obj : LockerContainer) : Try[LockerContainerData] = {
MakeInventory(obj.Inventory) match {
case Nil =>
Success(LockerContainerData(None))
@@ -19,7 +19,7 @@ class LockerContainerConverter extends ObjectCreateConverter[LockerEquipment]()
}
}
- override def DetailedConstructorData(obj : LockerEquipment) : Try[DetailedLockerContainerData] = {
+ override def DetailedConstructorData(obj : LockerContainer) : Try[DetailedLockerContainerData] = {
if(obj.Inventory.Size > 0) {
Success(DetailedLockerContainerData(
CommonFieldData(PlanetSideEmpire.NEUTRAL, false, false, true, None, false, None, None, PlanetSideGUID(0)),
diff --git a/common/src/main/scala/net/psforever/objects/definition/converter/VehicleConverter.scala b/common/src/main/scala/net/psforever/objects/definition/converter/VehicleConverter.scala
index 9f9b5c2e..f62d5ec3 100644
--- a/common/src/main/scala/net/psforever/objects/definition/converter/VehicleConverter.scala
+++ b/common/src/main/scala/net/psforever/objects/definition/converter/VehicleConverter.scala
@@ -86,11 +86,12 @@ class VehicleConverter extends ObjectCreateConverter[Vehicle]() {
}
private def MakeMountings(obj : Vehicle) : List[InventoryItemData.InventoryItem] = {
- obj.Weapons.collect { case (index, slot) if slot.Equipment.nonEmpty =>
- val equip : Equipment = slot.Equipment.get
- val equipDef = equip.Definition
- InventoryItemData(equipDef.ObjectId, equip.GUID, index, equipDef.Packet.ConstructorData(equip).get)
- }.toList
+ obj.Weapons.map({
+ case(index, slot) =>
+ val equip : Equipment = slot.Equipment.get
+ val equipDef = equip.Definition
+ InventoryItemData(equipDef.ObjectId, equip.GUID, index, equipDef.Packet.ConstructorData(equip).get)
+ }).toList
}
protected def MakeUtilities(obj : Vehicle) : List[InventoryItemData.InventoryItem] = {
diff --git a/common/src/main/scala/net/psforever/objects/guid/GUIDTask.scala b/common/src/main/scala/net/psforever/objects/guid/GUIDTask.scala
index 9107d2a9..3443ad96 100644
--- a/common/src/main/scala/net/psforever/objects/guid/GUIDTask.scala
+++ b/common/src/main/scala/net/psforever/objects/guid/GUIDTask.scala
@@ -43,8 +43,6 @@ object GUIDTask {
private val localObject = obj
private val localAccessor = guid
- override def Description : String = s"register $localObject"
-
override def isComplete : Task.Resolution.Value = if(localObject.HasGUID) {
Task.Resolution.Success
}
@@ -92,9 +90,6 @@ object GUIDTask {
def RegisterLocker(obj : LockerContainer)(implicit guid : ActorRef) : TaskResolver.GiveTask = {
TaskResolver.GiveTask(RegisterObjectTask(obj).task, RegisterInventory(obj))
}
- def RegisterLocker(obj : LockerEquipment)(implicit guid : ActorRef) : TaskResolver.GiveTask = {
- TaskResolver.GiveTask(RegisterObjectTask(obj).task, RegisterInventory(obj))
- }
/**
* Construct tasking that registers the objects that are within the given container's inventory
@@ -129,6 +124,8 @@ object GUIDTask {
obj match {
case tool : Tool =>
RegisterTool(tool)
+ case locker : LockerContainer =>
+ RegisterLocker(locker)
case _ =>
RegisterObjectTask(obj)
}
@@ -218,8 +215,6 @@ object GUIDTask {
private val localObject = obj
private val localAccessor = guid
- override def Description : String = s"unregister $localObject"
-
override def isComplete : Task.Resolution.Value = if(!localObject.HasGUID) {
Task.Resolution.Success
}
@@ -259,9 +254,6 @@ object GUIDTask {
def UnregisterLocker(obj : LockerContainer)(implicit guid : ActorRef) : TaskResolver.GiveTask = {
TaskResolver.GiveTask(UnregisterObjectTask(obj).task, UnregisterInventory(obj))
}
- def UnregisterLocker(obj : LockerEquipment)(implicit guid : ActorRef) : TaskResolver.GiveTask = {
- TaskResolver.GiveTask(RegisterObjectTask(obj).task, RegisterInventory(obj))
- }
/**
* Construct tasking that unregisters the objects that are within the given container's inventory
@@ -290,6 +282,8 @@ object GUIDTask {
obj match {
case tool : Tool =>
UnregisterTool(tool)
+ case locker : LockerContainer =>
+ UnregisterLocker(locker)
case _ =>
UnregisterObjectTask(obj)
}
diff --git a/common/src/main/scala/net/psforever/objects/guid/Task.scala b/common/src/main/scala/net/psforever/objects/guid/Task.scala
index 9b4e83e3..625adc77 100644
--- a/common/src/main/scala/net/psforever/objects/guid/Task.scala
+++ b/common/src/main/scala/net/psforever/objects/guid/Task.scala
@@ -4,7 +4,6 @@ package net.psforever.objects.guid
import akka.actor.ActorRef
trait Task {
- def Description : String = "write_descriptive_task_message"
def Execute(resolver : ActorRef) : Unit
def isComplete : Task.Resolution.Value = Task.Resolution.Incomplete
def Timeout : Long = 200L //milliseconds
diff --git a/common/src/main/scala/net/psforever/objects/guid/TaskResolver.scala b/common/src/main/scala/net/psforever/objects/guid/TaskResolver.scala
index 9b41834f..037aacf7 100644
--- a/common/src/main/scala/net/psforever/objects/guid/TaskResolver.scala
+++ b/common/src/main/scala/net/psforever/objects/guid/TaskResolver.scala
@@ -82,7 +82,7 @@ class TaskResolver() extends Actor {
private def GiveTask(aTask : Task) : Unit = {
val entry : TaskResolver.TaskEntry = TaskResolver.TaskEntry(aTask)
tasks += entry
- trace(s"enqueue and start task ${aTask.Description}")
+ trace(s"enqueue and start task $aTask")
entry.Execute(self)
StartTimeoutCheck()
}
@@ -112,13 +112,12 @@ class TaskResolver() extends Actor {
private def QueueSubtasks(task : Task, subtasks : List[TaskResolver.GiveTask], resolver : ActorRef = Actor.noSender) : Unit = {
val entry : TaskResolver.TaskEntry = TaskResolver.TaskEntry(task, subtasks.map(task => task.task), resolver)
tasks += entry
- trace(s"enqueue task ${task.Description}")
+ trace(s"enqueue task $task")
if(subtasks.isEmpty) { //a leaf in terms of task dependency; so, not dependent on any other work
- trace(s"start task ${task.Description}")
+ trace(s"start task $task")
entry.Execute(self)
}
else {
- trace(s"enqueuing ${subtasks.length} substask(s) belonging to ${task.Description}")
subtasks.foreach({subtask =>
context.parent ! TaskResolver.GiveSubtask(subtask.task, subtask.subs, self) //route back to submit subtask to pool
})
@@ -161,7 +160,7 @@ class TaskResolver() extends Actor {
private def GeneralOnSuccess(index : Int) : Unit = {
val entry = tasks(index)
entry.task.onSuccess()
- trace(s"success with task ${entry.task.Description}")
+ trace(s"success with this task ${entry.task}")
if(entry.supertaskRef != ActorRef.noSender) {
entry.supertaskRef ! TaskResolver.CompletedSubtask(entry.task) //alert our dependent task's resolver that we have completed
}
@@ -178,7 +177,7 @@ class TaskResolver() extends Actor {
case Some(index) =>
val entry = tasks(index)
if(TaskResolver.filterCompletionMatch(entry.subtasks.iterator, Task.Resolution.Success)) {
- trace(s"start new task ${entry.task.Description}")
+ trace(s"start new task ${entry.task}")
entry.Execute(self)
StartTimeoutCheck()
}
@@ -225,7 +224,7 @@ class TaskResolver() extends Actor {
private def GeneralOnFailure(index : Int, ex : Throwable) : Unit = {
val entry = tasks(index)
val task = entry.task
- trace(s"failure with task ${task.Description}")
+ trace(s"failure with this task $task")
task.onAbort(ex)
task.onFailure(ex)
if(entry.supertaskRef != ActorRef.noSender) {
@@ -268,7 +267,7 @@ class TaskResolver() extends Actor {
private def PropagateAbort(index : Int, ex : Throwable) : Unit = {
tasks(index).subtasks.foreach({subtask =>
if(subtask.isComplete == Task.Resolution.Success) {
- trace(s"aborting task ${subtask.Description}")
+ trace(s"aborting task $subtask")
subtask.onAbort(ex)
}
context.parent ! Broadcast(TaskResolver.AbortTask(subtask, ex))
diff --git a/common/src/main/scala/net/psforever/objects/inventory/GridInventory.scala b/common/src/main/scala/net/psforever/objects/inventory/GridInventory.scala
index 2038d5aa..bc08949a 100644
--- a/common/src/main/scala/net/psforever/objects/inventory/GridInventory.scala
+++ b/common/src/main/scala/net/psforever/objects/inventory/GridInventory.scala
@@ -110,7 +110,7 @@ class GridInventory extends Container {
* Test whether a given piece of `Equipment` would collide with any stowed content in the inventory.
*
* A "collision" is considered a situation where the stowed placards of two items would overlap in some way.
- * The grid keeps track of the location of items by storing the primitive of their GUID in one or more cells.
+ * The gridkeeps track of the location of items by storing the primitive of their GUID in one or more cells.
* Two primitives can not be stored in the same cell.
* If placing two items into the same inventory leads to a situation where two primitive values might be in the same cell,
* that is a collision.
@@ -188,19 +188,17 @@ class GridInventory extends Container {
}
else {
val collisions : mutable.Set[InventoryItem] = mutable.Set[InventoryItem]()
- items
- .map { case (_, item : InventoryItem) => item }
- .foreach { item : InventoryItem =>
- val actualItemStart : Int = item.start - offset
- val itemx : Int = actualItemStart % width
- val itemy : Int = actualItemStart / width
- val tile = item.obj.Tile
- 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 }
- if(clipsOnX && clipsOnY) {
- collisions += item
- }
+ items.values.foreach({ item : InventoryItem =>
+ val actualItemStart : Int = item.start - offset
+ val itemx : Int = actualItemStart % width
+ val itemy : Int = actualItemStart / width
+ val tile = item.obj.Tile
+ 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 }
+ if(clipsOnX && clipsOnY) {
+ collisions += item
}
+ })
Success(collisions.toList)
}
}
@@ -580,7 +578,7 @@ class GridInventory extends Container {
def Clear() : List[InventoryItem] = {
val list = items.values.toList
items.clear
- entryIndex.set(0)
+ //entryIndex.set(0)
grid = SetCellsOnlyNoOffset(0, width, height)
list
}
@@ -781,17 +779,4 @@ object GridInventory {
node.down.get(node.x, node.y + height, node.width, node.height - height)
node.right.get(node.x + width, node.y, node.width - width, height)
}
-
- def toPrintedList(inv : GridInventory) : String = {
- val list = new StringBuilder
- list.append("\n")
- inv.Items.zipWithIndex.foreach { case (InventoryItem(obj, start), index) =>
- list.append(s"${index+1}: ${obj.Definition.Name}@${obj.GUID} -> $start\n")
- }
- list.toString
- }
-
- def toPrintedGrid(inv : GridInventory) : String = {
- new StringBuilder().append("\n").append(inv.grid.toSeq.grouped(inv.width).mkString("\n")).toString
- }
}
diff --git a/common/src/main/scala/net/psforever/objects/loadouts/Loadout.scala b/common/src/main/scala/net/psforever/objects/loadouts/Loadout.scala
index 0f2ad13f..64668c4e 100644
--- a/common/src/main/scala/net/psforever/objects/loadouts/Loadout.scala
+++ b/common/src/main/scala/net/psforever/objects/loadouts/Loadout.scala
@@ -53,9 +53,7 @@ object Loadout {
def Create(vehicle : Vehicle, label : String) : Loadout = {
VehicleLoadout(
label,
- packageSimplifications(vehicle.Weapons.collect { case (index, slot) if slot.Equipment.nonEmpty =>
- InventoryItem(slot.Equipment.get, index) }.toList
- ),
+ packageSimplifications(vehicle.Weapons.map({ case (index, weapon) => InventoryItem(weapon.Equipment.get, index) }).toList),
packageSimplifications(vehicle.Trunk.Items),
vehicle.Definition
)
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/containable/ContainableBehavior.scala b/common/src/main/scala/net/psforever/objects/serverobject/containable/ContainableBehavior.scala
deleted file mode 100644
index 1d3bada4..00000000
--- a/common/src/main/scala/net/psforever/objects/serverobject/containable/ContainableBehavior.scala
+++ /dev/null
@@ -1,631 +0,0 @@
-// Copyright (c) 2020 PSForever
-package net.psforever.objects.serverobject.containable
-
-import akka.actor.{Actor, ActorRef}
-import akka.pattern.{AskTimeoutException, ask}
-import akka.util.Timeout
-import net.psforever.objects.equipment.Equipment
-import net.psforever.objects.inventory.{Container, InventoryItem}
-import net.psforever.objects.serverobject.PlanetSideServerObject
-import net.psforever.objects.zones.Zone
-import net.psforever.objects.{BoomerTrigger, GlobalDefinitions, Player}
-import net.psforever.types.{PlanetSideEmpire, Vector3}
-
-import scala.concurrent.ExecutionContext.Implicits.global
-import scala.concurrent.duration._
-import scala.util.Success
-
-/** Parent of all standard (input) messages handled by a `ContainableBehavior` object for the purposes of item transfer */
-sealed trait ContainableMsg
-/** `ContainableBehavior` messages that are allowed to be temporarily blocked in event of a complicated item transfer */
-sealed trait DeferrableMsg extends ContainableMsg
-
-/**
- * A mixin for handling synchronized movement of `Equipment` items into or out from `Container` entities.
- * The most important feature of this synchronization is the movmement of equipment
- * out from one container into another container
- * without causing representation overlap, overwriting, or unintended stacking of other equipment
- * including equipment that has nort yet been inserted.
- */
-trait ContainableBehavior {
- _ : Actor =>
- def ContainerObject : PlanetSideServerObject with Container
-
- /**
- * A flag for handling deferred messages during an attempt at complicated item movement (`MoveItem`) procedures.
- * Complicated item movement procedures generally occur when the source and the destination are not the same container.
- * The flag is set to `1` on the destination and is designed to block interference other normal insertion messages
- * by taking those messages and pushing them back into the mailbox after a short delay.
- * If two attempts on the same destination occur due to extremely coincidental item movement messages,
- * the flag is set to `2` and most all messages involving item movement and item insertion are deferred.
- * The destination is set back to normal - flag to `0` - when both of the attempts short-circuit due to timeout.
- */
- private var waitOnMoveItemOps : Int = 0
-
- final val containerBehavior : Receive = {
- /* messages that modify delivery order */
- case ContainableBehavior.Wait() => Wait()
-
- case ContainableBehavior.Resume() => Resume()
-
- case repeatMsg @ ContainableBehavior.Defer(msg, sentBy) =>
- //received a previously blocked message; is it still blocked?
- msg match {
- case _ : ContainableMsg if waitOnMoveItemOps == 2 => RepeatMessageLater(repeatMsg)
- case _ : DeferrableMsg if waitOnMoveItemOps == 1 => RepeatMessageLater(repeatMsg)
- case _ => self.tell(msg, sentBy)
- }
-
- case msg : ContainableMsg if waitOnMoveItemOps == 2 =>
- //all standard messages are blocked
- RepeatMessageLater(ContainableBehavior.Defer(msg, sender))
- MessageDeferredCallback(msg)
-
- case msg : DeferrableMsg if waitOnMoveItemOps == 1 =>
- //insertion messages not related to an item move attempt are blocked
- RepeatMessageLater(ContainableBehavior.Defer(msg, sender))
- MessageDeferredCallback(msg)
-
- /* normal messages */
- case Containable.RemoveItemFromSlot(None, Some(slot)) =>
- sender ! LocalRemoveItemFromSlot(slot)
-
- case Containable.RemoveItemFromSlot(Some(item), _) =>
- sender ! LocalRemoveItemFromSlot(item)
-
- case Containable.PutItemInSlot(item, dest) => /* can be deferred */
- sender ! LocalPutItemInSlot(item, dest)
-
- case Containable.PutItemInSlotOnly(item, dest) => /* can be deferred */
- sender ! LocalPutItemInSlotOnly(item, dest)
-
- case Containable.PutItemAway(item) => /* can be deferred */
- sender ! LocalPutItemAway(item)
-
- case Containable.PutItemInSlotOrAway(item, dest) => /* can be deferred */
- sender ! LocalPutItemInSlotOrAway(item, dest)
-
- case msg @ Containable.MoveItem(destination, equipment, destSlot) => /* can be deferred */
- if(ContainableBehavior.TestPutItemInSlot(destination, equipment, destSlot).nonEmpty) { //test early, before we try to move the item
- val source = ContainerObject
- val item = equipment
- val dest = destSlot
- LocalRemoveItemFromSlot(item) match {
- case Containable.ItemFromSlot(_, Some(_), slot @ Some(originalSlot)) =>
- if(source eq destination) {
- //when source and destination are the same, moving the item can be performed in one pass
- LocalPutItemInSlot(item, dest) match {
- case Containable.ItemPutInSlot(_, _, _, None) => ; //success
- case Containable.ItemPutInSlot(_, _, _, Some(swapItem)) => //success, but with swap item
- LocalPutItemInSlotOnlyOrAway(swapItem, slot) match {
- case Containable.ItemPutInSlot(_, _, _, None) => ;
- case _ =>
- source.Zone.Ground.tell(Zone.Ground.DropItem(swapItem, source.Position, Vector3.z(source.Orientation.z)), source.Actor) //drop it
- }
- case _ : Containable.CanNotPutItemInSlot => //failure case ; try restore original item placement
- LocalPutItemInSlot(item, originalSlot)
- }
- }
- else {
- //destination sync
- destination.Actor ! ContainableBehavior.Wait()
- implicit val timeout = new Timeout(1000 milliseconds)
- val moveItemOver = ask(destination.Actor, ContainableBehavior.MoveItemPutItemInSlot(item, dest))
- moveItemOver.onSuccess {
- case Containable.ItemPutInSlot(_, _, _, None) => ; //successful
-
- case Containable.ItemPutInSlot(_, _, _, Some(swapItem)) => //successful, but with swap item
- PutItBackOrDropIt(source, swapItem, slot, destination.Actor)
-
- case _ : Containable.CanNotPutItemInSlot => //failure case ; try restore original item placement
- PutItBackOrDropIt(source, item, slot, source.Actor)
- }
- moveItemOver.onFailure {
- case _ => //failure case ; try restore original item placement
- PutItBackOrDropIt(source, item, slot, source.Actor)
- }
- //always do this
- moveItemOver
- .recover { case _ : AskTimeoutException => destination.Actor ! ContainableBehavior.Resume() }
- .onComplete { _ => destination.Actor ! ContainableBehavior.Resume() }
- }
- case _ => ;
- //we could not find the item to be moved in the source location; trying to act on old data?
- }
- }
- else {
- MessageDeferredCallback(msg)
- }
-
- case ContainableBehavior.MoveItemPutItemInSlot(item, dest) =>
- sender ! LocalPutItemInSlot(item, dest)
-
- case ContainableBehavior.MoveItemPutItemInSlotOrAway(item, dest) =>
- sender ! LocalPutItemInSlotOrAway(item, dest)
- }
-
- /* Functions (message control) */
-
- /**
- * Defer a message until later.
- * @see `ContainableBehavior.Defer`
- * @see `DeferrableMsg`
- * @param msg the message to defer
- */
- def RepeatMessageLater(msg : Any) : Unit = {
- import scala.concurrent.ExecutionContext.Implicits.global
- context.system.scheduler.scheduleOnce(100 milliseconds, self, msg)
- }
-
- /**
- * Increment the flag for blocking messages.
- */
- def Wait() : Unit = {
- waitOnMoveItemOps = math.min(waitOnMoveItemOps + 1, 2)
- }
-
- /**
- * Decrement the flag for blocking messages.
- */
- def Resume() : Unit = {
- waitOnMoveItemOps = math.max(0, waitOnMoveItemOps - 1)
- }
-
- /**
- * Stop blocking messages.
- */
- def Reset() : Unit = {
- waitOnMoveItemOps = 0
- }
-
- /* Functions (item transfer) */
-
- private def LocalRemoveItemFromSlot(slot : Int) : Any = {
- val source = ContainerObject
- val (outSlot, item) = ContainableBehavior.TryRemoveItemFromSlot(source, slot)
- item match {
- case Some(thing) => RemoveItemFromSlotCallback(thing, outSlot.get)
- case None => ;
- }
- Containable.ItemFromSlot(source, item, outSlot)
- }
-
- private def LocalRemoveItemFromSlot(item : Equipment) : Any = {
- val source = ContainerObject
- val(slot, retItem) = ContainableBehavior.TryRemoveItemFromSlot(source, item)
- retItem match {
- case Some(thing) => RemoveItemFromSlotCallback(thing, slot.get)
- case None => ;
- }
- Containable.ItemFromSlot(source, Some(item), slot)
- }
-
- private def LocalPutItemInSlot(item : Equipment, dest : Int) : Any = {
- val destination = ContainerObject
- ContainableBehavior.TryPutItemInSlot(destination, item, dest) match {
- case (true, swapItem) =>
- swapItem match {
- case Some(thing) => SwapItemCallback(thing)
- case None => ;
- }
- PutItemInSlotCallback(item, dest)
- Containable.ItemPutInSlot(destination, item, dest, swapItem)
- case (false, _) =>
- Containable.CanNotPutItemInSlot(destination, item, dest)
- }
- }
-
- private def LocalPutItemInSlotOnly(item : Equipment, dest : Int) : Any = {
- val destination = ContainerObject
- if(ContainableBehavior.TryPutItemInSlotOnly(destination, item, dest)) {
- PutItemInSlotCallback(item, dest)
- Containable.ItemPutInSlot(destination, item, dest, None)
- }
- else {
- Containable.CanNotPutItemInSlot(destination, item, dest)
- }
- }
-
- private def LocalPutItemAway(item : Equipment) : Any = {
- val destination = ContainerObject
- ContainableBehavior.TryPutItemAway(destination, item) match {
- case Some(dest) =>
- PutItemInSlotCallback(item, dest)
- Containable.ItemPutInSlot(destination, item, dest, None)
- case _ =>
- Containable.CanNotPutItemInSlot(destination, item, -1)
- }
- }
-
- private def LocalPutItemInSlotOrAway(item : Equipment, dest : Option[Int]) : Any = {
- val destination = ContainerObject
- ContainableBehavior.TryPutItemInSlotOrAway(destination, item, dest) match {
- case (Some(slot), swapItem) =>
- swapItem match {
- case Some(thing) => SwapItemCallback(thing)
- case None => ;
- }
- PutItemInSlotCallback(item, slot)
- Containable.ItemPutInSlot(destination, item, slot, swapItem)
- case (None, _) =>
- Containable.CanNotPutItemInSlot(destination, item, dest.getOrElse(-1))
- }
- }
-
- private def LocalPutItemInSlotOnlyOrAway(item : Equipment, dest : Option[Int]) : Any = {
- val destination = ContainerObject
- ContainableBehavior.TryPutItemInSlotOnlyOrAway(destination, item, dest) match {
- case (Some(slot), None) =>
- PutItemInSlotCallback(item, slot)
- Containable.ItemPutInSlot(destination, item, slot, None)
- case _ =>
- Containable.CanNotPutItemInSlot(destination, item, dest.getOrElse(-1))
- }
- }
-
- /**
- * A controlled response where, in certain situations,
- * it is appropriate to attempt to place an item into a specific container,
- * first testing a specific slot,
- * and attempting anywhere available in the container if not that slot,
- * and, if nowhere is available, then it gets dropped on the ground.
- * The inserted item is not permitted to swap places with another item in this case.
- * @param container the container
- * @param item the item to be inserted
- * @param slot in which slot the insertion is prioritized (upper left corner of item)
- * @param to a recipient to redirect the response message
- * @param timeout how long the request has to complete before expiring
- */
- private def PutItBackOrDropIt(container : PlanetSideServerObject with Container, item : Equipment, slot : Option[Int], to : ActorRef)(implicit timeout : Timeout) : Unit = {
- val restore = ask(container.Actor, ContainableBehavior.MoveItemPutItemInSlotOrAway(item, slot))
- restore.onSuccess {
- case _ : Containable.CanNotPutItemInSlot =>
- container.Zone.Ground.tell(Zone.Ground.DropItem(item, container.Position, Vector3.z(container.Orientation.z)), to)
- case _ =>
- }
- restore.onFailure {
- case _ =>
- container.Zone.Ground.tell(Zone.Ground.DropItem(item, container.Position, Vector3.z(container.Orientation.z)), to)
- }
- }
-
- /**
- * Reaction to the initial deferrence of a message that should handle the visual aspects of not immediately addressing the message.
- * To be implemented.
- * @param msg the deferred message
- */
- def MessageDeferredCallback(msg : Any) : Unit
-
- /**
- * Reaction to an item being removed a container.
- * To be implemented.
- * @param item the item that was removed
- * @param slot the slot from which is was removed
- */
- def RemoveItemFromSlotCallback(item : Equipment, slot : Int) : Unit
-
- /**
- * Reaction to an item being placed into a container.
- * To be implemented.
- * @param item the item that was removed
- * @param slot the slot from which is was removed
- */
- def PutItemInSlotCallback(item : Equipment, slot : Int) : Unit
-
- /**
- * Reaction to the existence of a swap item being produced from a container into the environment.
- * To be implemented.
- * @param item the item that was removed
- */
- def SwapItemCallback(item : Equipment) : Unit
-}
-
-object ContainableBehavior {
- /** Control message for temporarily blocking some messages to maintain integrity of underlying `Container` object */
- private case class Wait()
- /** Control message for unblocking all messages */
- private case class Resume()
- /** Internal message for the purpose of refreshing a blocked message in the mailbox */
- private case class Defer(msg : Any, from : ActorRef)
-
- /* The same as `PutItemInSlot`, but is not a `DeferrableMsg` for the purposes of completing a `MoveItem` */
- private case class MoveItemPutItemInSlot(item : Equipment, slot : Int) extends ContainableMsg
- /* The same as `PutItemInSlotOrAway`, but is not a `DeferrableMsg` for the purposes of completing a `MoveItem` */
- private case class MoveItemPutItemInSlotOrAway(item : Equipment, slot : Option[Int]) extends ContainableMsg
-
- /* Functions */
-
- /**
- * If the target item can be found in a container, remove the item from the container.
- * This process can fail if the item can not be found or if it can not be removed for some reason.
- * @see `Container.Find`
- * @see `EquipmentSlot.Equipment`
- * @param source the container in which the `item` is currently located
- * @param item the item to be removed
- * @return a `Tuple` of two optional values;
- * the first is from what index in the container the `item` was removed, if it was removed;
- * the second is the item again, if it has been removed;
- * will use `(None, None)` to report failure
- */
- def TryRemoveItemFromSlot(source : PlanetSideServerObject with Container, item : Equipment) : (Option[Int], Option[Equipment]) = {
- source.Find(item) match {
- case slot @ Some(index) =>
- source.Slot(index).Equipment = None
- if(source.Slot(index).Equipment.isEmpty) {
- (slot, Some(item))
- }
- else {
- (None, None)
- }
- case None =>
- (None, None)
- }
- }
-
- /**
- * If the target slot of a container contains an item, remove that item from the container
- * fromthe upper left corner position of the item as found in the container.
- * This process can fail if no item can be found or if it can not be removed for some reason.
- * @see `Container.Find`
- * @see `EquipmentSlot.Equipment`
- * @param source the container in which the `slot` is to be searched
- * @param slot where the container will be searched
- * @return a `Tuple` of two values;
- * the first is from what `slot` in the container an `item` was removed, if any item removed;
- * the second is the item, if it has been removed;
- * will use `(None, None)` to report failure
- */
- def TryRemoveItemFromSlot(source : PlanetSideServerObject with Container, slot : Int) : (Option[Int], Option[Equipment]) = {
- val (item, outSlot) = source.Slot(slot).Equipment match {
- case Some(thing) => (Some(thing), source.Find(thing))
- case None => (None, None)
- }
- source.Slot(slot).Equipment = None
- item match {
- case Some(_) if item.nonEmpty && source.Slot(slot).Equipment.isEmpty =>
- (outSlot, item)
- case None =>
- (None, None)
- }
- }
-
- /**
- * Are the conditions for an item insertion acceptable?
- * If another item occupies the expected region of insertion (collision of bounding regions),
- * the insertion can still be permitted with the assumption that
- * the displaced item ("swap item") will have to be put somewhere else.
- * @see `ContainableBehavior.PermitEquipmentStow`
- * @see `Container.Collisions`
- * @see `InventoryTile`
- * @param destination the container
- * @param item the item to be tested for insertion
- * @param dest the upper left corner of the insertion position
- * @return the results of the insertion test, if an insertion can be permitted;
- * `None`, otherwise, and the insertion is not permitted
- */
- def TestPutItemInSlot(destination : PlanetSideServerObject with Container, item : Equipment, dest : Int) : Option[List[InventoryItem]] = {
- if(ContainableBehavior.PermitEquipmentStow(destination, item)) {
- val tile = item.Definition.Tile
- val destinationCollisionTest = destination.Collisions(dest, tile.Width, tile.Height)
- destinationCollisionTest match {
- case Success(Nil) => Some(Nil) //no item to swap
- case Success(out @ List(_)) => Some(out) //one item to swap
- case _ => None //abort when too many items at destination or other failure case
- }
- }
- else {
- None //blocked insertion (object type not permitted in container)
- }
- }
-
- /**
- * Put an item in a container at the given position.
- * The inserted item may swap places with another item.
- * If the new item can not be inserted, the swap item is kept in its original position.
- * @param destination the container
- * @param item the item to be inserted
- * @param dest in which slot the insertion is expected to occur (upper left corner of item)
- * @return a `Tuple` of two values;
- * the first is `true` if the insertion occurred; and, `false`, otherwise
- * the second is an optional item that was removed from a coincidental position in the container ("swap item")
- */
- def TryPutItemInSlot(destination : PlanetSideServerObject with Container, item : Equipment, dest : Int) : (Boolean, Option[Equipment]) = {
- ContainableBehavior.TestPutItemInSlot(destination, item, dest) match {
- case Some(results) =>
- //insert and swap, if applicable
- val (swapItem, swapSlot) = results match {
- case List(InventoryItem(obj, start)) => (Some(obj), start)
- case _ => (None, dest)
- }
- destination.Slot(swapSlot).Equipment = None
- if((destination.Slot(dest).Equipment = item).contains(item)) {
- (true, swapItem)
- }
- else {
- //put the swapItem back
- destination.Slot(swapSlot).Equipment = swapItem
- (false, None)
- }
- case None =>
- (false, None)
- }
- }
-
- /**
- * Put an item in a container at the given position.
- * The inserted item is not permitted to swap places with another item in this case.
- * @param destination the container
- * @param item the item to be inserted
- * @param dest in which slot the insertion is expected to occur (upper left corner of item)
- * @return `true` if the insertion occurred;
- * `false`, otherwise
- */
- def TryPutItemInSlotOnly(destination : PlanetSideServerObject with Container, item : Equipment, dest : Int) : Boolean = {
- ContainableBehavior.TestPutItemInSlot(destination, item, dest).contains(Nil) && (destination.Slot(dest).Equipment = item).contains(item)
- }
-
- /**
- * Put an item in a container in the whatever position it cleanly fits.
- * The inserted item will not swap places with another item in this case.
- * @param destination the container
- * @param item the item to be inserted
- * @return the slot index of the insertion point;
- * `None`, if a clean insertion is not possible
- */
- def TryPutItemAway(destination : PlanetSideServerObject with Container, item : Equipment) : Option[Int] = {
- destination.Fit(item) match {
- case out @ Some(dest)
- if ContainableBehavior.PermitEquipmentStow(destination, item) && (destination.Slot(dest).Equipment = item).contains(item) =>
- out
- case _ =>
- None
- }
- }
-
- /**
- * Attempt to put an item in a container at the given position.
- * The inserted item may swap places with another item at this time.
- * If the targeted insertion at this position fails,
- * attempt to put the item in the container in the whatever position it cleanly fits.
- * @param destination the container
- * @param item the item to be inserted
- * @param dest in which specific slot the insertion is first tested (upper left corner of item)
- * @return na
- */
- def TryPutItemInSlotOrAway(destination : PlanetSideServerObject with Container, item : Equipment, dest : Option[Int]) : (Option[Int], Option[Equipment]) = {
- (dest match {
- case Some(slot) => ContainableBehavior.TryPutItemInSlot(destination, item, slot)
- case None => (false, None)
- }) match {
- case (true, swapItem) =>
- (dest, swapItem)
- case _ =>
- ContainableBehavior.TryPutItemAway(destination, item) match {
- case out @ Some(_) => (out, None)
- case None => (None, None)
- }
- }
- }
-
- /**
- * Attempt to put an item in a container at the given position.
- * The inserted item may not swap places with another item at this time.
- * If the targeted insertion at this position fails,
- * attempt to put the item in the container in the whatever position it cleanly fits.
- * @param destination the container
- * @param item the item to be inserted
- * @param dest in which specific slot the insertion is first tested (upper left corner of item)
- * @return na
- */
- def TryPutItemInSlotOnlyOrAway(destination : PlanetSideServerObject with Container, item : Equipment, dest : Option[Int]) : (Option[Int], Option[Equipment]) = {
- (dest match {
- case Some(slot) if ContainableBehavior.TestPutItemInSlot(destination, item, slot).contains(Nil) => ContainableBehavior.TryPutItemInSlot(destination, item, slot)
- case None => (false, None)
- }) match {
- case (true, swapItem) =>
- (dest, swapItem)
- case _ =>
- ContainableBehavior.TryPutItemAway(destination, item) match {
- case out @ Some(_) => (out, None)
- case None => (None, None)
- }
- }
- }
-
- /**
- * Apply incontestable, arbitrary limitations
- * whereby certain items are denied insertion into certain containers
- * for vaguely documented but assuredly fantastic excuses on the part of the developer.
- * @param destination the container
- * @param equipment the item to be inserted
- * @return `true`, if the object is allowed to contain the type of equipment object;
- * `false`, otherwise
- */
- def PermitEquipmentStow(destination : PlanetSideServerObject with Container, equipment : Equipment) : Boolean = {
- import net.psforever.objects.{BoomerTrigger, Player}
- equipment match {
- case _ : BoomerTrigger =>
- //a BoomerTrigger can only be stowed in a player's holsters or inventory
- //this is only a requirement until they, and their Boomer explosive complement, are cleaned-up properly
- destination.isInstanceOf[Player]
- case _ =>
- true
- }
- }
-
- /**
- * A predicate used to determine if an `InventoryItem` object contains `Equipment` that should be dropped.
- * Used to filter through lists of object data before it is placed into a player's inventory.
- * Drop the item if:
- * - the item is cavern equipment
- * - the item is a `BoomerTrigger` type object
- * - the item is a `router_telepad` type object
- * - the item is another faction's exclusive equipment
- * @param tplayer the player
- * @return true if the item is to be dropped; false, otherwise
- */
- def DropPredicate(tplayer : Player) : InventoryItem => Boolean = entry => {
- val objDef = entry.obj.Definition
- val faction = GlobalDefinitions.isFactionEquipment(objDef)
- GlobalDefinitions.isCavernEquipment(objDef) ||
- objDef == GlobalDefinitions.router_telepad ||
- entry.obj.isInstanceOf[BoomerTrigger] ||
- (faction != tplayer.Faction && faction != PlanetSideEmpire.NEUTRAL)
- }
-}
-
-object Containable {
- final case class RemoveItemFromSlot(item : Option[Equipment], slot : Option[Int]) extends ContainableMsg
-
- object RemoveItemFromSlot {
- def apply(slot : Int) : RemoveItemFromSlot = RemoveItemFromSlot(None, Some(slot))
-
- def apply(item : Equipment) : RemoveItemFromSlot = RemoveItemFromSlot(Some(item), None)
- }
-
- /**
- * A response for the `RemoveItemFromSlot` message.
- * It serves the dual purpose of reporting a missing item (by not reporting any slot information)
- * and reporting no item ata given position (by not reporting any item information).
- * @param obj the container
- * @param item the equipment that was removed
- * @param slot the index position from which any item was removed
- */
- final case class ItemFromSlot(obj : PlanetSideServerObject with Container, item : Option[Equipment], slot : Option[Int])
-
- final case class PutItemInSlot(item : Equipment, slot : Int) extends DeferrableMsg
-
- final case class PutItemInSlotOnly(item : Equipment, slot : Int) extends DeferrableMsg
-
- final case class PutItemAway(item : Equipment) extends DeferrableMsg
-
- final case class PutItemInSlotOrAway(item : Equipment, slot : Option[Int]) extends DeferrableMsg
-
- /**
- * A "successful insertion" response for the variety message of messages that attempt to insert an item into a container.
- * @param obj the container
- * @param item the equipment that was inserted
- * @param slot the slot position into which the item was inserted
- * @param swapped_item any other item, previously in the container, that was displaced to make room for this insertion
- */
- final case class ItemPutInSlot(obj : PlanetSideServerObject with Container, item : Equipment, slot : Int, swapped_item : Option[Equipment])
-
- /**
- * A "failed insertion" response for the variety message of messages that attempt to insert an item into a container.
- * @param obj the container
- * @param item the equipment that was not inserted
- * @param slot the slot position into which the item should have been inserted;
- * `-1` if no insertion slot was reported in the original message or discovered in the process of inserting
- */
- final case class CanNotPutItemInSlot(obj : PlanetSideServerObject with Container, item : Equipment, slot : Int)
-
- /**
- * The item should already be contained by us.
- * The item is being removed from our containment and placed into a fixed slot position in another container.
- * `MoveItem` is a process that may be complicated and is one reason why `DeferrableMsg`s are employed.
- * @param destination the container into which the item is being placed
- * @param item the item
- * @param destination_slot where in the destination container the item is being placed
- */
- final case class MoveItem(destination : PlanetSideServerObject with Container, item : Equipment, destination_slot : Int) extends DeferrableMsg
-}
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/OrderTerminalDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/OrderTerminalDefinition.scala
index aca3ffe6..5ab86e44 100644
--- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/OrderTerminalDefinition.scala
+++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/OrderTerminalDefinition.scala
@@ -1,7 +1,7 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.terminals
-import akka.actor.{ActorContext, ActorRef}
+import akka.actor.ActorContext
import net.psforever.objects.definition.ImplantDefinition
import net.psforever.objects.{Player, Vehicle}
import net.psforever.objects.equipment.Equipment
@@ -92,14 +92,6 @@ class OrderTerminalDefinition(objId : Int) extends TerminalDefinition(objId) {
}
}
}
-
- override def Dispatch(sender : ActorRef, terminal : Terminal, msg : Terminal.TerminalMessage) : Unit = {
- tabs.get(msg.msg.item_page) match {
- case Some(page) =>
- page.Dispatch(sender, terminal, msg)
- case _ => ;
- }
- }
}
object OrderTerminalDefinition {
@@ -108,9 +100,8 @@ object OrderTerminalDefinition {
* @see `ItemTransactionMessage`
*/
sealed trait Tab {
- def Buy(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange
+ def Buy(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = Terminal.NoDeal()
def Sell(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = Terminal.NoDeal()
- def Dispatch(sender : ActorRef, terminal : Terminal, msg : Terminal.TerminalMessage) : Unit
}
/**
@@ -128,10 +119,6 @@ object OrderTerminalDefinition {
Terminal.NoDeal()
}
}
-
- def Dispatch(sender : ActorRef, terminal : Terminal, msg : Terminal.TerminalMessage) : Unit = {
- msg.player.Actor ! msg
- }
}
/**
@@ -157,13 +144,6 @@ object OrderTerminalDefinition {
}
}
}
-
- def Dispatch(sender : ActorRef, terminal : Terminal, msg : Terminal.TerminalMessage) : Unit = {
- msg.response match {
- case _ : Terminal.BuyExosuit => msg.player.Actor ! msg
- case _ => sender ! msg
- }
- }
}
/**
@@ -191,10 +171,6 @@ object OrderTerminalDefinition {
Terminal.NoDeal()
}
}
-
- def Dispatch(sender : ActorRef, terminal : Terminal, msg : Terminal.TerminalMessage) : Unit = {
- sender ! msg
- }
}
/**
@@ -211,10 +187,6 @@ object OrderTerminalDefinition {
Terminal.NoDeal()
}
}
-
- def Dispatch(sender : ActorRef, terminal : Terminal, msg : Terminal.TerminalMessage) : Unit = {
- sender ! msg
- }
}
/**
@@ -243,10 +215,6 @@ object OrderTerminalDefinition {
Terminal.NoDeal()
}
}
-
- def Dispatch(sender : ActorRef, terminal : Terminal, msg : Terminal.TerminalMessage) : Unit = {
- sender ! msg
- }
}
/**
@@ -304,10 +272,6 @@ object OrderTerminalDefinition {
Terminal.NoDeal()
}
}
-
- def Dispatch(sender : ActorRef, terminal : Terminal, msg : Terminal.TerminalMessage) : Unit = {
- msg.player.Actor ! msg
- }
}
/**
@@ -336,14 +300,6 @@ object OrderTerminalDefinition {
Terminal.NoDeal()
}
}
-
- def Dispatch(sender : ActorRef, terminal : Terminal, msg : Terminal.TerminalMessage) : Unit = {
- val player = msg.player
- player.Zone.GUID(player.VehicleOwned) match {
- case Some(vehicle : Vehicle) => vehicle.Actor ! msg
- case _ => sender ! Terminal.TerminalMessage(player, msg.msg, Terminal.NoDeal())
- }
- }
}
/**
@@ -375,10 +331,6 @@ object OrderTerminalDefinition {
Terminal.NoDeal()
}
}
-
- def Dispatch(sender : ActorRef, terminal : Terminal, msg : Terminal.TerminalMessage) : Unit = {
- sender ! msg
- }
}
/**
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/TerminalControl.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/TerminalControl.scala
index 525ed0ae..b493477a 100644
--- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/TerminalControl.scala
+++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/TerminalControl.scala
@@ -1,7 +1,7 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.terminals
-import akka.actor.{Actor, ActorRef}
+import akka.actor.Actor
import net.psforever.objects.{GlobalDefinitions, SimpleItem}
import net.psforever.objects.serverobject.CommonMessages
import net.psforever.objects.serverobject.affinity.FactionAffinityBehavior
@@ -30,10 +30,7 @@ class TerminalControl(term : Terminal) extends Actor
.orElse(canBeRepairedByNanoDispenser)
.orElse {
case Terminal.Request(player, msg) =>
- TerminalControl.Dispatch(
- sender,
- term,
- Terminal.TerminalMessage(player, msg, term.Request(player, msg)))
+ sender ! Terminal.TerminalMessage(player, msg, term.Request(player, msg))
case CommonMessages.Use(player, Some(item : SimpleItem)) if item.Definition == GlobalDefinitions.remote_electronics_kit =>
//TODO setup certifications check
@@ -49,16 +46,5 @@ class TerminalControl(term : Terminal) extends Actor
case _ => ;
}
-
override def toString : String = term.Definition.Name
}
-
-object TerminalControl {
- def Dispatch(sender : ActorRef, terminal : Terminal, msg : Terminal.TerminalMessage) : Unit = {
- msg.response match {
- case Terminal.NoDeal() => sender ! msg
- case _ =>
- terminal.Definition.Dispatch(sender, terminal, msg)
- }
- }
-}
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/TerminalDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/TerminalDefinition.scala
index cb2a2885..10d01760 100644
--- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/TerminalDefinition.scala
+++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/TerminalDefinition.scala
@@ -1,7 +1,6 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.terminals
-import akka.actor.ActorRef
import net.psforever.objects.Player
import net.psforever.objects.definition.converter.TerminalConverter
import net.psforever.objects.serverobject.structures.AmenityDefinition
@@ -23,6 +22,4 @@ abstract class TerminalDefinition(objectId : Int) extends AmenityDefinition(obje
* @return a message that resolves the transaction
*/
def Request(player : Player, msg : Any) : Terminal.Exchange
-
- def Dispatch(sender : ActorRef, terminal : Terminal, msg : Terminal.TerminalMessage) : Unit = { }
}
diff --git a/common/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala b/common/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala
index ef6de382..6a62b822 100644
--- a/common/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala
+++ b/common/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala
@@ -1,28 +1,21 @@
-// Copyright (c) 2017-2020 PSForever
+// Copyright (c) 2017 PSForever
package net.psforever.objects.vehicles
import akka.actor.{Actor, ActorRef, Cancellable}
import net.psforever.objects._
import net.psforever.objects.ballistics.{ResolvedProjectile, VehicleSource}
-import net.psforever.objects.equipment.{Equipment, JammableMountedWeapons}
-import net.psforever.objects.inventory.{GridInventory, InventoryItem}
+import net.psforever.objects.equipment.JammableMountedWeapons
import net.psforever.objects.serverobject.CommonMessages
import net.psforever.objects.serverobject.mount.{Mountable, MountableBehavior}
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
-import net.psforever.objects.serverobject.containable.{Containable, ContainableBehavior}
import net.psforever.objects.serverobject.damage.DamageableVehicle
import net.psforever.objects.serverobject.deploy.DeploymentBehavior
import net.psforever.objects.serverobject.hackable.GenericHackables
import net.psforever.objects.serverobject.repair.RepairableVehicle
-import net.psforever.objects.serverobject.terminals.Terminal
import net.psforever.objects.vital.VehicleShieldCharge
import net.psforever.objects.zones.Zone
-import net.psforever.types._
-import services.RemoverActor
-import net.psforever.packet.game._
-import net.psforever.packet.game.objectcreate.ObjectCreateMessageParent
-import services.Service
-import services.avatar.{AvatarAction, AvatarServiceMessage}
+import net.psforever.types.{DriveState, ExoSuitType, PlanetSideGUID, Vector3}
+import services.{RemoverActor, Service}
import services.vehicle.{VehicleAction, VehicleServiceMessage}
import scala.concurrent.ExecutionContext.Implicits.global
@@ -43,8 +36,7 @@ class VehicleControl(vehicle : Vehicle) extends Actor
with CargoBehavior
with DamageableVehicle
with RepairableVehicle
- with JammableMountedWeapons
- with ContainableBehavior {
+ with JammableMountedWeapons {
//make control actors belonging to utilities when making control actor belonging to vehicle
vehicle.Utilities.foreach({case (_, util) => util.Setup })
@@ -56,7 +48,6 @@ class VehicleControl(vehicle : Vehicle) extends Actor
def DeploymentObject = vehicle
def DamageableObject = vehicle
def RepairableObject = vehicle
- def ContainerObject = vehicle
/** cheap flag for whether the vehicle is decaying */
var decaying : Boolean = false
@@ -81,7 +72,6 @@ class VehicleControl(vehicle : Vehicle) extends Actor
.orElse(jammableBehavior)
.orElse(takesDamage)
.orElse(canBeRepairedByNanoDispenser)
- .orElse(containerBehavior)
.orElse {
case Vehicle.Ownership(None) =>
LoseOwnership()
@@ -153,52 +143,6 @@ class VehicleControl(vehicle : Vehicle) extends Actor
)
}
- case Terminal.TerminalMessage(player, msg, reply) =>
- reply match {
- case Terminal.VehicleLoadout(definition, weapons, inventory) =>
- org.log4s.getLogger(vehicle.Definition.Name).info(s"changing vehicle equipment loadout to ${player.Name}'s option #${msg.unk1 + 1}")
- //remove old inventory
- val oldInventory = vehicle.Inventory.Clear().map { case InventoryItem(obj, _) => (obj, obj.GUID) }
- //"dropped" items are lost; if it doesn't go in the trunk, it vanishes into the nanite cloud
- val (_, afterInventory) = inventory.partition(ContainableBehavior.DropPredicate(player))
- val (oldWeapons, newWeapons, finalInventory) = if(vehicle.Definition == definition) {
- //vehicles are the same type
- //TODO want to completely swap weapons, but holster icon vanishes temporarily after swap
- //TODO BFR arms must be swapped properly
-// //remove old weapons
-// val oldWeapons = vehicle.Weapons.values.collect { case slot if slot.Equipment.nonEmpty =>
-// val obj = slot.Equipment.get
-// slot.Equipment = None
-// (obj, obj.GUID)
-// }.toList
-// (oldWeapons, weapons, afterInventory)
- //TODO for now, just refill ammo; assume weapons stay the same
- vehicle.Weapons
- .collect { case (_, slot) if slot.Equipment.nonEmpty => slot.Equipment.get }
- .collect { case weapon : Tool =>
- weapon.AmmoSlots.foreach { ammo => ammo.Box.Capacity = ammo.Box.Definition.Capacity }
- }
- (Nil, Nil, afterInventory)
- }
- else {
- //vehicle loadout is not for this vehicle
- //do not transfer over weapon ammo
- if(vehicle.Definition.TrunkSize == definition.TrunkSize && vehicle.Definition.TrunkOffset == definition.TrunkOffset) {
- (Nil, Nil, afterInventory) //trunk is the same dimensions, however
- }
- else {
- //accommodate as much of inventory as possible
- val (stow, _) = GridInventory.recoverInventory(afterInventory, vehicle.Inventory)
- (Nil, Nil, stow)
- }
- }
- finalInventory.foreach { _.obj.Faction = vehicle.Faction }
- player.Zone.VehicleEvents ! VehicleServiceMessage(player.Zone.Id, VehicleAction.ChangeLoadout(vehicle.GUID, oldWeapons, newWeapons, oldInventory, finalInventory))
- player.Zone.AvatarEvents ! AvatarServiceMessage(player.Name, AvatarAction.TerminalOrderResult(msg.terminal_guid, msg.transaction_type, true))
-
- case _ => ;
- }
-
case Vehicle.Deconstruct(time) =>
time match {
case Some(delay) =>
@@ -300,73 +244,6 @@ class VehicleControl(vehicle : Vehicle) extends Actor
}
}
- def MessageDeferredCallback(msg : Any) : Unit = {
- msg match {
- case Containable.MoveItem(_, item, _) =>
- //momentarily put item back where it was originally
- val obj = ContainerObject
- obj.Find(item) match {
- case Some(slot) =>
- obj.Zone.AvatarEvents ! AvatarServiceMessage(
- self.toString,
- AvatarAction.SendResponse(Service.defaultPlayerGUID, ObjectAttachMessage(obj.GUID, item.GUID, slot))
- )
- case None => ;
- }
- case _ => ;
- }
- }
-
- def RemoveItemFromSlotCallback(item : Equipment, slot : Int) : Unit = {
- val zone = ContainerObject.Zone
- zone.VehicleEvents ! VehicleServiceMessage(self.toString, VehicleAction.UnstowEquipment(Service.defaultPlayerGUID, item.GUID))
- }
-
- def PutItemInSlotCallback(item : Equipment, slot : Int) : Unit = {
- val obj = ContainerObject
- val oguid = obj.GUID
- val zone = obj.Zone
- val channel = self.toString
- val events = zone.VehicleEvents
- val iguid = item.GUID
- val definition = item.Definition
- item.Faction = obj.Faction
- events ! VehicleServiceMessage(
- //TODO when a new weapon, the equipment slot ui goes blank, but the weapon functions; remount vehicle to correct it
- if(obj.VisibleSlots.contains(slot)) zone.Id else channel,
- VehicleAction.SendResponse(
- Service.defaultPlayerGUID,
- ObjectCreateMessage(
- definition.ObjectId,
- iguid,
- ObjectCreateMessageParent(oguid, slot),
- definition.Packet.ConstructorData(item).get
- )
- )
- )
- item match {
- case box : AmmoBox =>
- events ! VehicleServiceMessage(
- channel,
- VehicleAction.InventoryState2(Service.defaultPlayerGUID, iguid, oguid, box.Capacity)
- )
- case weapon : Tool =>
- weapon.AmmoSlots.map { slot => slot.Box }.foreach { box =>
- events ! VehicleServiceMessage(
- channel,
- VehicleAction.InventoryState2(Service.defaultPlayerGUID, iguid, weapon.GUID, box.Capacity)
- )
- }
- case _ => ;
- }
- }
-
- def SwapItemCallback(item : Equipment) : Unit = {
- val obj = ContainerObject
- val zone = obj.Zone
- zone.VehicleEvents ! VehicleServiceMessage(self.toString, VehicleAction.SendResponse(Service.defaultPlayerGUID, ObjectDetachMessage(obj.GUID, item.GUID, Vector3.Zero, 0f)))
- }
-
def LoseOwnership() : Unit = {
val obj = MountableObject
Vehicles.Disown(obj.GUID, obj)
diff --git a/common/src/main/scala/net/psforever/objects/zones/ZoneGroundActor.scala b/common/src/main/scala/net/psforever/objects/zones/ZoneGroundActor.scala
index 41d375c9..7d41516d 100644
--- a/common/src/main/scala/net/psforever/objects/zones/ZoneGroundActor.scala
+++ b/common/src/main/scala/net/psforever/objects/zones/ZoneGroundActor.scala
@@ -4,8 +4,6 @@ package net.psforever.objects.zones
import akka.actor.Actor
import net.psforever.objects.equipment.Equipment
import net.psforever.types.PlanetSideGUID
-import services.Service
-import services.avatar.{AvatarAction, AvatarServiceMessage}
import scala.annotation.tailrec
import scala.collection.mutable.ListBuffer
@@ -30,28 +28,19 @@ class ZoneGroundActor(zone : Zone, equipmentOnGround : ListBuffer[Equipment]) ex
}
else {
equipmentOnGround += item
- item.Position = pos
- item.Orientation = orient
- zone.AvatarEvents ! AvatarServiceMessage(zone.Id, AvatarAction.DropItem(Service.defaultPlayerGUID, item))
Zone.Ground.ItemOnGround(item, pos, orient)
})
case Zone.Ground.PickupItem(item_guid) =>
sender ! (FindItemOnGround(item_guid) match {
case Some(item) =>
- zone.AvatarEvents ! AvatarServiceMessage(zone.Id, AvatarAction.PickupItem(Service.defaultPlayerGUID, item, 0))
Zone.Ground.ItemInHand(item)
case None =>
Zone.Ground.CanNotPickupItem(zone, item_guid, "can not find")
})
case Zone.Ground.RemoveItem(item_guid) =>
- //intentionally no callback
- FindItemOnGround(item_guid) match {
- case Some(item) =>
- zone.AvatarEvents ! AvatarServiceMessage(zone.Id, AvatarAction.PickupItem(Service.defaultPlayerGUID, item, 0))
- case None => ;
- }
+ FindItemOnGround(item_guid) //intentionally no callback
case _ => ;
}
diff --git a/common/src/main/scala/net/psforever/objects/zones/ZonePopulationActor.scala b/common/src/main/scala/net/psforever/objects/zones/ZonePopulationActor.scala
index 889d9a21..1f7bdf0a 100644
--- a/common/src/main/scala/net/psforever/objects/zones/ZonePopulationActor.scala
+++ b/common/src/main/scala/net/psforever/objects/zones/ZonePopulationActor.scala
@@ -63,26 +63,13 @@ class ZonePopulationActor(zone : Zone, playerMap : TrieMap[Avatar, Option[Player
}
case Zone.Corpse.Add(player) =>
- val (playerIsCorpse, playerInZone) = CorpseAdd(player, playerMap, corpseList)
- if(playerIsCorpse) {
- if(!playerInZone && player.Actor == ActorRef.noSender) {
- player.Zone = zone
- player.Actor = context.actorOf(Props(classOf[PlayerControl], player), s"corpse_of_${player.CharId}_${player.GUID.guid}_${System.currentTimeMillis}")
- }
- }
+ CorpseAdd(player, corpseList)
case Zone.Corpse.Remove(player) =>
- if(CorpseRemove(player, corpseList)) {
- PlayerLeave(player)
- }
+ CorpseRemove(player, corpseList)
case _ => ;
}
-
- def PlayerLeave(player : Player) : Unit = {
- context.stop(player.Actor)
- player.Actor = ActorRef.noSender
- }
}
object ZonePopulationActor {
@@ -161,33 +148,18 @@ object ZonePopulationActor {
/**
* If the given `player` passes a condition check, add it to the list.
- * Also, ensure that "this player" is not currently counted among the living.
* @param player a `Player` object
- * @param playerMap the mapping of `Avatar` objects to `Player` objects
* @param corpseList a list of `Player` objects
- * @return a `Tuple` of two flags;
- * the first is whether the player was turned into a corpse or not;
- * the second is whether the player was found in the zone before being turned into a corpse
+ * @return true, if the `player` was added to the list;
+ * false, otherwise
*/
- def CorpseAdd(player : Player, playerMap : TrieMap[Avatar, Option[Player]], corpseList : ListBuffer[Player]) : (Boolean, Boolean) = {
+ def CorpseAdd(player : Player, corpseList : ListBuffer[Player]) : Boolean = {
if(player.isBackpack) {
- val playerFoundInZone = playerMap.find {
- case (_, Some(p)) => p.CharId == player.CharId
- case (_, None) => false
- } match {
- case Some((a, _)) => PopulationRelease(a, playerMap).nonEmpty
- case _ => false
- }
- corpseList.find { _ eq player } match {
- case None => ;
- corpseList += player
- (true, playerFoundInZone)
- case _ =>
- (false, false)
- }
+ corpseList += player
+ true
}
else {
- (false, false)
+ false
}
}
@@ -195,19 +167,20 @@ object ZonePopulationActor {
* Remove the given `player` from the list.
* @param player a `Player` object
* @param corpseList a list of `Player` objects
- * @return `true`, if the corpse was found and removed;
- * `false`, otherwise
*/
- def CorpseRemove(player : Player, corpseList : ListBuffer[Player]) : Boolean = {
+ def CorpseRemove(player : Player, corpseList : ListBuffer[Player]) : Unit = {
recursiveFindCorpse(corpseList.iterator, player) match {
- case None =>
- false
+ case None => ;
case Some(index) =>
corpseList.remove(index)
- true
}
}
+ def PlayerLeave(player : Player) : Unit = {
+ player.Actor ! akka.actor.PoisonPill
+ player.Actor = ActorRef.noSender
+ }
+
/**
* A recursive function that finds and removes a specific player from a list of players.
* @param iter an `Iterator` of `Player` objects
diff --git a/common/src/main/scala/services/avatar/AvatarService.scala b/common/src/main/scala/services/avatar/AvatarService.scala
index eae49fb7..93faada0 100644
--- a/common/src/main/scala/services/avatar/AvatarService.scala
+++ b/common/src/main/scala/services/avatar/AvatarService.scala
@@ -94,7 +94,7 @@ class AvatarService(zone : Zone) extends Actor {
AvatarEvents.publish(
AvatarServiceResponse(s"/$forChannel/Avatar", Service.defaultPlayerGUID, AvatarResponse.DestroyDisplay(killer, victim, method, unk))
)
- case AvatarAction.DropItem(player_guid, item) =>
+ case AvatarAction.DropItem(player_guid, item, _) =>
val definition = item.Definition
val objectData = DroppedItemData(
PlacementData(item.Position, item.Orientation),
@@ -179,10 +179,21 @@ class AvatarService(zone : Zone) extends Actor {
AvatarEvents.publish(
AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.ProjectileState(projectile_guid, shot_pos, shot_vel, shot_orient, sequence, end, target))
)
- case AvatarAction.PickupItem(player_guid, item, unk) =>
+ case AvatarAction.PickupItem(player_guid, _, target, slot, item, unk) =>
janitor forward RemoverActor.ClearSpecific(List(item), zone)
AvatarEvents.publish(
- AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.ObjectDelete(item.GUID, unk))
+ AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, {
+ val itemGUID = item.GUID
+ if(target.VisibleSlots.contains(slot)) {
+ val definition = item.Definition
+ val containerData = ObjectCreateMessageParent(target.GUID, slot)
+ val objectData = definition.Packet.ConstructorData(item).get
+ AvatarResponse.EquipmentInHand(ObjectCreateMessage(definition.ObjectId, itemGUID, containerData, objectData))
+ }
+ else {
+ AvatarResponse.ObjectDelete(itemGUID, unk)
+ }
+ })
)
case AvatarAction.PutDownFDU(player_guid) =>
AvatarEvents.publish(
@@ -227,19 +238,6 @@ class AvatarService(zone : Zone) extends Actor {
AvatarServiceResponse(s"/$forChannel/Avatar", Service.defaultPlayerGUID, AvatarResponse.TeardownConnection())
)
- case AvatarAction.TerminalOrderResult(terminal, term_action, result) =>
- AvatarEvents.publish(
- AvatarServiceResponse(s"/$forChannel/Avatar", Service.defaultPlayerGUID, AvatarResponse.TerminalOrderResult(terminal, term_action, result))
- )
- case AvatarAction.ChangeExosuit(target, exosuit, subtype, slot, maxhand, old_holsters, holsters, old_inventory, inventory, drop, delete) =>
- AvatarEvents.publish(
- AvatarServiceResponse(s"/$forChannel/Avatar", Service.defaultPlayerGUID, AvatarResponse.ChangeExosuit(target, exosuit, subtype, slot, maxhand, old_holsters, holsters, old_inventory, inventory, drop, delete))
- )
- case AvatarAction.ChangeLoadout(target, exosuit, subtype, slot, maxhand, old_holsters, holsters, old_inventory, inventory, drop) =>
- AvatarEvents.publish(
- AvatarServiceResponse(s"/$forChannel/Avatar", Service.defaultPlayerGUID, AvatarResponse.ChangeLoadout(target, exosuit, subtype, slot, maxhand, old_holsters, holsters, old_inventory, inventory, drop))
- )
-
case _ => ;
}
@@ -261,7 +259,6 @@ class AvatarService(zone : Zone) extends Actor {
))
}
*/
-
case msg =>
log.warn(s"Unhandled message $msg from $sender")
}
diff --git a/common/src/main/scala/services/avatar/AvatarServiceMessage.scala b/common/src/main/scala/services/avatar/AvatarServiceMessage.scala
index 234dffef..e7fc9dab 100644
--- a/common/src/main/scala/services/avatar/AvatarServiceMessage.scala
+++ b/common/src/main/scala/services/avatar/AvatarServiceMessage.scala
@@ -5,11 +5,12 @@ import net.psforever.objects.{PlanetSideGameObject, Player}
import net.psforever.objects.ballistics.{Projectile, SourceEntry}
import net.psforever.objects.ce.Deployable
import net.psforever.objects.equipment.Equipment
-import net.psforever.objects.inventory.{Container, InventoryItem}
+import net.psforever.objects.inventory.Container
import net.psforever.objects.zones.Zone
import net.psforever.packet.PlanetSideGamePacket
+import net.psforever.packet.game.ImplantAction
import net.psforever.packet.game.objectcreate.{ConstructorData, ObjectCreateMessageParent}
-import net.psforever.types.{ExoSuitType, PlanetSideEmpire, PlanetSideGUID, TransactionType, Vector3}
+import net.psforever.types.{ExoSuitType, PlanetSideEmpire, PlanetSideGUID, Vector3}
import scala.concurrent.duration.FiniteDuration
@@ -35,7 +36,7 @@ object AvatarAction {
final case class ActivateImplantSlot(player_guid : PlanetSideGUID, slot : Int) extends Action
final case class Destroy(victim : PlanetSideGUID, killer : PlanetSideGUID, weapon : PlanetSideGUID, pos : Vector3) extends Action
final case class DestroyDisplay(killer : SourceEntry, victim : SourceEntry, method : Int, unk : Int = 121) extends Action
- final case class DropItem(player_guid : PlanetSideGUID, item : Equipment) extends Action
+ final case class DropItem(player_guid : PlanetSideGUID, item : Equipment, zone : Zone) extends Action
final case class EquipmentInHand(player_guid : PlanetSideGUID, target_guid : PlanetSideGUID, slot : Int, item : Equipment) extends Action
final case class GenericObjectAction(player_guid : PlanetSideGUID, object_guid : PlanetSideGUID, action_code : Int) extends Action
final case class HitHint(source_guid : PlanetSideGUID, player_guid : PlanetSideGUID) extends Action
@@ -48,7 +49,7 @@ object AvatarAction {
final case class PlanetsideAttributeToAll(player_guid : PlanetSideGUID, attribute_type : Int, attribute_value : Long) extends Action
final case class PlanetsideAttributeSelf(player_guid : PlanetSideGUID, attribute_type : Int, attribute_value : Long) extends Action
final case class PlayerState(player_guid : PlanetSideGUID, pos : Vector3, vel : Option[Vector3], facingYaw : Float, facingPitch : Float, facingYawUpper : Float, timestamp : Int, is_crouching : Boolean, is_jumping : Boolean, jump_thrust : Boolean, is_cloaked : Boolean, spectator : Boolean, weaponInHand : Boolean) extends Action
- final case class PickupItem(player_guid : PlanetSideGUID, item : Equipment, unk : Int = 0) extends Action
+ final case class PickupItem(player_guid : PlanetSideGUID, zone : Zone, target : PlanetSideGameObject with Container, slot : Int, item : Equipment, unk : Int = 0) extends Action
final case class ProjectileAutoLockAwareness(mode : Int) extends Action
final case class ProjectileExplodes(player_guid : PlanetSideGUID, projectile_guid : PlanetSideGUID, projectile : Projectile) extends Action
final case class ProjectileState(player_guid : PlanetSideGUID, projectile_guid : PlanetSideGUID, shot_pos : Vector3, shot_vel : Vector3, shot_orient : Vector3, sequence : Int, end : Boolean, hit_target : PlanetSideGUID) extends Action
@@ -63,10 +64,6 @@ object AvatarAction {
final case class SendResponse(player_guid: PlanetSideGUID, msg: PlanetSideGamePacket) extends Action
final case class SendResponseTargeted(target_guid: PlanetSideGUID, msg: PlanetSideGamePacket) extends Action
- final case class TerminalOrderResult(terminal_guid : PlanetSideGUID, action : TransactionType.Value, result : Boolean) extends Action
- final case class ChangeExosuit(target_guid : PlanetSideGUID, exosuit : ExoSuitType.Value, subtype : Int, last_drawn_slot : Int, new_max_hand : Boolean, old_holsters : List[(Equipment, PlanetSideGUID)], holsters : List[InventoryItem], old_inventory : List[(Equipment, PlanetSideGUID)], inventory : List[InventoryItem], drop : List[InventoryItem], delete : List[(Equipment, PlanetSideGUID)]) extends Action
- final case class ChangeLoadout(target_guid : PlanetSideGUID, exosuit : ExoSuitType.Value, subtype : Int, last_drawn_slot : Int, new_max_hand : Boolean, old_holsters : List[(Equipment, PlanetSideGUID)], holsters : List[InventoryItem], old_inventory : List[(Equipment, PlanetSideGUID)], inventory : List[InventoryItem], drop : List[InventoryItem]) extends Action
-
final case class TeardownConnection() extends Action
// final case class PlayerStateShift(killer : PlanetSideGUID, victim : PlanetSideGUID) extends Action
// final case class DestroyDisplay(killer : PlanetSideGUID, victim : PlanetSideGUID) extends Action
diff --git a/common/src/main/scala/services/avatar/AvatarServiceResponse.scala b/common/src/main/scala/services/avatar/AvatarServiceResponse.scala
index da811964..7920703f 100644
--- a/common/src/main/scala/services/avatar/AvatarServiceResponse.scala
+++ b/common/src/main/scala/services/avatar/AvatarServiceResponse.scala
@@ -4,11 +4,10 @@ package services.avatar
import net.psforever.objects.Player
import net.psforever.objects.ballistics.{Projectile, SourceEntry}
import net.psforever.objects.equipment.Equipment
-import net.psforever.objects.inventory.InventoryItem
import net.psforever.packet.PlanetSideGamePacket
import net.psforever.packet.game.objectcreate.ConstructorData
-import net.psforever.packet.game.ObjectCreateMessage
-import net.psforever.types.{ExoSuitType, PlanetSideEmpire, PlanetSideGUID, TransactionType, Vector3}
+import net.psforever.packet.game.{ImplantAction, ObjectCreateMessage}
+import net.psforever.types.{ExoSuitType, PlanetSideEmpire, PlanetSideGUID, Vector3}
import services.GenericEventBusMsg
final case class AvatarServiceResponse(toChannel : String,
@@ -57,10 +56,6 @@ object AvatarResponse {
final case class SendResponse(msg: PlanetSideGamePacket) extends Response
final case class SendResponseTargeted(target_guid : PlanetSideGUID, msg: PlanetSideGamePacket) extends Response
- final case class TerminalOrderResult(terminal_guid : PlanetSideGUID, action : TransactionType.Value, result : Boolean) extends Response
- final case class ChangeExosuit(target_guid : PlanetSideGUID, exosuit : ExoSuitType.Value, subtype : Int, last_drawn_slot : Int, new_max_hand : Boolean, old_holsters : List[(Equipment, PlanetSideGUID)], holsters : List[InventoryItem], old_inventory : List[(Equipment, PlanetSideGUID)], inventory : List[InventoryItem], drop : List[InventoryItem], delete : List[(Equipment, PlanetSideGUID)]) extends Response
- final case class ChangeLoadout(target_guid : PlanetSideGUID, exosuit : ExoSuitType.Value, subtype : Int, last_drawn_slot : Int, new_max_hand : Boolean, old_holsters : List[(Equipment, PlanetSideGUID)], holsters : List[InventoryItem], old_inventory : List[(Equipment, PlanetSideGUID)], inventory : List[InventoryItem], drop : List[InventoryItem]) extends Response
-
final case class TeardownConnection() extends Response
// final case class PlayerStateShift(itemID : PlanetSideGUID) extends Response
}
diff --git a/common/src/main/scala/services/vehicle/VehicleService.scala b/common/src/main/scala/services/vehicle/VehicleService.scala
index ff664e18..d535056e 100644
--- a/common/src/main/scala/services/vehicle/VehicleService.scala
+++ b/common/src/main/scala/services/vehicle/VehicleService.scala
@@ -141,11 +141,6 @@ class VehicleService(zone : Zone) extends Actor {
VehicleEvents.publish(
VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.KickCargo(cargo, speed, delay))
)
-
- case VehicleAction.ChangeLoadout(target_guid, removed_weapons, new_weapons, old_inventory, new_inventory) =>
- VehicleEvents.publish(
- VehicleServiceResponse(s"/$forChannel/Vehicle", Service.defaultPlayerGUID, VehicleResponse.ChangeLoadout(target_guid, removed_weapons, new_weapons, old_inventory, new_inventory))
- )
case _ => ;
}
diff --git a/common/src/main/scala/services/vehicle/VehicleServiceMessage.scala b/common/src/main/scala/services/vehicle/VehicleServiceMessage.scala
index 5fa5d672..8d101911 100644
--- a/common/src/main/scala/services/vehicle/VehicleServiceMessage.scala
+++ b/common/src/main/scala/services/vehicle/VehicleServiceMessage.scala
@@ -3,7 +3,6 @@ package services.vehicle
import net.psforever.objects.{PlanetSideGameObject, Vehicle}
import net.psforever.objects.equipment.Equipment
-import net.psforever.objects.inventory.InventoryItem
import net.psforever.objects.zones.Zone
import net.psforever.packet.PlanetSideGamePacket
import net.psforever.packet.game.objectcreate.ConstructorData
@@ -48,6 +47,4 @@ object VehicleAction {
final case class TransferPassengerChannel(player_guid : PlanetSideGUID, temp_channel : String, new_channel : String, vehicle : Vehicle, vehicle_to_delete : PlanetSideGUID) extends Action
final case class KickCargo(player_guid : PlanetSideGUID, cargo : Vehicle, speed : Int, delay : Long) extends Action
-
- final case class ChangeLoadout(target_guid : PlanetSideGUID, removed_weapons : List[(Equipment, PlanetSideGUID)], new_weapons : List[InventoryItem], old_inventory : List[(Equipment, PlanetSideGUID)], new_inventory : List[InventoryItem]) extends Action
}
diff --git a/common/src/main/scala/services/vehicle/VehicleServiceResponse.scala b/common/src/main/scala/services/vehicle/VehicleServiceResponse.scala
index 93c16f5a..68834cc4 100644
--- a/common/src/main/scala/services/vehicle/VehicleServiceResponse.scala
+++ b/common/src/main/scala/services/vehicle/VehicleServiceResponse.scala
@@ -1,8 +1,6 @@
// Copyright (c) 2017 PSForever
package services.vehicle
-import net.psforever.objects.equipment.Equipment
-import net.psforever.objects.inventory.InventoryItem
import net.psforever.objects.serverobject.pad.VehicleSpawnPad
import net.psforever.objects.serverobject.pad.VehicleSpawnPad.Reminders
import net.psforever.objects.{PlanetSideGameObject, Vehicle}
@@ -55,6 +53,4 @@ object VehicleResponse {
final case class TransferPassengerChannel(old_channel : String, temp_channel : String, vehicle : Vehicle, vehicle_to_delete : PlanetSideGUID) extends Response
final case class KickCargo(cargo : Vehicle, speed : Int, delay : Long) extends Response
-
- final case class ChangeLoadout(target_guid : PlanetSideGUID, removed_weapons : List[(Equipment, PlanetSideGUID)], new_weapons : List[InventoryItem], old_inventory : List[(Equipment, PlanetSideGUID)], new_inventory : List[InventoryItem]) extends Response
}
diff --git a/common/src/test/scala/objects/AvatarTest.scala b/common/src/test/scala/objects/AvatarTest.scala
index 6c0ff545..6e1502b1 100644
--- a/common/src/test/scala/objects/AvatarTest.scala
+++ b/common/src/test/scala/objects/AvatarTest.scala
@@ -389,10 +389,7 @@ class AvatarTest extends Specification {
"the fifth slot is the locker wrapped in an EquipmentSlot" in {
val (_, avatar) = CreatePlayer()
- avatar.FifthSlot.Equipment match {
- case Some(slot : LockerEquipment) => slot.Inventory mustEqual avatar.Locker.Inventory
- case _ => ko
- }
+ avatar.FifthSlot.Equipment.contains(avatar.Locker)
}
"toString" in {
diff --git a/common/src/test/scala/objects/ConverterTest.scala b/common/src/test/scala/objects/ConverterTest.scala
index bd1eaaa5..04a58b5f 100644
--- a/common/src/test/scala/objects/ConverterTest.scala
+++ b/common/src/test/scala/objects/ConverterTest.scala
@@ -631,7 +631,7 @@ class ConverterTest extends Specification {
"LockerContainer" should {
"convert to packet (empty)" in {
- val obj = new LockerEquipment(LockerContainer())
+ val obj = LockerContainer()
obj.Definition.Packet.DetailedConstructorData(obj) match {
case Success(pkt) =>
pkt mustEqual DetailedLockerContainerData(CommonFieldData(PlanetSideEmpire.NEUTRAL, false, false, true, None, false, None, None, PlanetSideGUID(0)), None)
@@ -648,7 +648,7 @@ class ConverterTest extends Specification {
"convert to packet (occupied)" in {
import GlobalDefinitions._
- val obj = new LockerEquipment(LockerContainer())
+ val obj = LockerContainer()
val rek = SimpleItem(remote_electronics_kit)
rek.GUID = PlanetSideGUID(1)
obj.Inventory += 0 -> rek
diff --git a/common/src/test/scala/objects/InventoryTest.scala b/common/src/test/scala/objects/InventoryTest.scala
index b5b1b4d6..828f5a78 100644
--- a/common/src/test/scala/objects/InventoryTest.scala
+++ b/common/src/test/scala/objects/InventoryTest.scala
@@ -525,136 +525,4 @@ class InventoryTest extends Specification {
ok
}
}
-
- "InventoryEquiupmentSlot" should {
- "insert, collide, insert" in {
- val obj : GridInventory = GridInventory(7, 7)
- obj.Slot(16).Equipment = bullet9mmBox1
- //confirm all squares
- obj.Slot( 8).Equipment.nonEmpty mustEqual false
- obj.Slot( 9).Equipment.nonEmpty mustEqual false
- obj.Slot( 10).Equipment.nonEmpty mustEqual false
- obj.Slot( 11).Equipment.nonEmpty mustEqual false
- obj.Slot( 12).Equipment.nonEmpty mustEqual false
- //
- obj.Slot(15).Equipment.nonEmpty mustEqual false
- obj.Slot(16).Equipment.nonEmpty mustEqual true
- obj.Slot(17).Equipment.nonEmpty mustEqual true
- obj.Slot(18).Equipment.nonEmpty mustEqual true
- obj.Slot(19).Equipment.nonEmpty mustEqual false
- //
- obj.Slot(22).Equipment.nonEmpty mustEqual false
- obj.Slot(23).Equipment.nonEmpty mustEqual true
- obj.Slot(24).Equipment.nonEmpty mustEqual true
- obj.Slot(25).Equipment.nonEmpty mustEqual true
- obj.Slot(26).Equipment.nonEmpty mustEqual false
- //
- obj.Slot(29).Equipment.nonEmpty mustEqual false
- obj.Slot(30).Equipment.nonEmpty mustEqual true
- obj.Slot(31).Equipment.nonEmpty mustEqual true
- obj.Slot(32).Equipment.nonEmpty mustEqual true
- obj.Slot(33).Equipment.nonEmpty mustEqual false
- //
- obj.Slot(36).Equipment.nonEmpty mustEqual false
- obj.Slot(37).Equipment.nonEmpty mustEqual false
- obj.Slot(38).Equipment.nonEmpty mustEqual false
- obj.Slot(39).Equipment.nonEmpty mustEqual false
- obj.Slot(40).Equipment.nonEmpty mustEqual false
- //
- //remove
- obj.Slot(16).Equipment = None
- obj.Slot( 8).Equipment.nonEmpty mustEqual false
- obj.Slot( 9).Equipment.nonEmpty mustEqual false
- obj.Slot( 10).Equipment.nonEmpty mustEqual false
- obj.Slot( 11).Equipment.nonEmpty mustEqual false
- obj.Slot( 12).Equipment.nonEmpty mustEqual false
- //
- obj.Slot(15).Equipment.nonEmpty mustEqual false
- obj.Slot(16).Equipment.nonEmpty mustEqual false
- obj.Slot(17).Equipment.nonEmpty mustEqual false
- obj.Slot(18).Equipment.nonEmpty mustEqual false
- obj.Slot(19).Equipment.nonEmpty mustEqual false
- //
- obj.Slot(22).Equipment.nonEmpty mustEqual false
- obj.Slot(23).Equipment.nonEmpty mustEqual false
- obj.Slot(24).Equipment.nonEmpty mustEqual false
- obj.Slot(25).Equipment.nonEmpty mustEqual false
- obj.Slot(26).Equipment.nonEmpty mustEqual false
- //
- obj.Slot(29).Equipment.nonEmpty mustEqual false
- obj.Slot(30).Equipment.nonEmpty mustEqual false
- obj.Slot(31).Equipment.nonEmpty mustEqual false
- obj.Slot(32).Equipment.nonEmpty mustEqual false
- obj.Slot(33).Equipment.nonEmpty mustEqual false
- //
- obj.Slot(36).Equipment.nonEmpty mustEqual false
- obj.Slot(37).Equipment.nonEmpty mustEqual false
- obj.Slot(38).Equipment.nonEmpty mustEqual false
- obj.Slot(39).Equipment.nonEmpty mustEqual false
- obj.Slot(40).Equipment.nonEmpty mustEqual false
- //insert again
- obj.Slot(16).Equipment = bullet9mmBox2
- obj.Slot( 8).Equipment.nonEmpty mustEqual false
- obj.Slot( 9).Equipment.nonEmpty mustEqual false
- obj.Slot( 10).Equipment.nonEmpty mustEqual false
- obj.Slot( 11).Equipment.nonEmpty mustEqual false
- obj.Slot( 12).Equipment.nonEmpty mustEqual false
- //
- obj.Slot(15).Equipment.nonEmpty mustEqual false
- obj.Slot(16).Equipment.nonEmpty mustEqual true
- obj.Slot(17).Equipment.nonEmpty mustEqual true
- obj.Slot(18).Equipment.nonEmpty mustEqual true
- obj.Slot(19).Equipment.nonEmpty mustEqual false
- //
- obj.Slot(22).Equipment.nonEmpty mustEqual false
- obj.Slot(23).Equipment.nonEmpty mustEqual true
- obj.Slot(24).Equipment.nonEmpty mustEqual true
- obj.Slot(25).Equipment.nonEmpty mustEqual true
- obj.Slot(26).Equipment.nonEmpty mustEqual false
- //
- obj.Slot(29).Equipment.nonEmpty mustEqual false
- obj.Slot(30).Equipment.nonEmpty mustEqual true
- obj.Slot(31).Equipment.nonEmpty mustEqual true
- obj.Slot(32).Equipment.nonEmpty mustEqual true
- obj.Slot(33).Equipment.nonEmpty mustEqual false
- //
- obj.Slot(36).Equipment.nonEmpty mustEqual false
- obj.Slot(37).Equipment.nonEmpty mustEqual false
- obj.Slot(38).Equipment.nonEmpty mustEqual false
- obj.Slot(39).Equipment.nonEmpty mustEqual false
- obj.Slot(40).Equipment.nonEmpty mustEqual false
- //
- //remove
- obj.Slot(16).Equipment = None
- obj.Slot( 8).Equipment.nonEmpty mustEqual false
- obj.Slot( 9).Equipment.nonEmpty mustEqual false
- obj.Slot( 10).Equipment.nonEmpty mustEqual false
- obj.Slot( 11).Equipment.nonEmpty mustEqual false
- obj.Slot( 12).Equipment.nonEmpty mustEqual false
- //
- obj.Slot(15).Equipment.nonEmpty mustEqual false
- obj.Slot(16).Equipment.nonEmpty mustEqual false
- obj.Slot(17).Equipment.nonEmpty mustEqual false
- obj.Slot(18).Equipment.nonEmpty mustEqual false
- obj.Slot(19).Equipment.nonEmpty mustEqual false
- //
- obj.Slot(22).Equipment.nonEmpty mustEqual false
- obj.Slot(23).Equipment.nonEmpty mustEqual false
- obj.Slot(24).Equipment.nonEmpty mustEqual false
- obj.Slot(25).Equipment.nonEmpty mustEqual false
- obj.Slot(26).Equipment.nonEmpty mustEqual false
- //
- obj.Slot(29).Equipment.nonEmpty mustEqual false
- obj.Slot(30).Equipment.nonEmpty mustEqual false
- obj.Slot(31).Equipment.nonEmpty mustEqual false
- obj.Slot(32).Equipment.nonEmpty mustEqual false
- obj.Slot(33).Equipment.nonEmpty mustEqual false
- //
- obj.Slot(36).Equipment.nonEmpty mustEqual false
- obj.Slot(37).Equipment.nonEmpty mustEqual false
- obj.Slot(38).Equipment.nonEmpty mustEqual false
- obj.Slot(39).Equipment.nonEmpty mustEqual false
- obj.Slot(40).Equipment.nonEmpty mustEqual false
- }
- }
}
diff --git a/common/src/test/scala/objects/PlayerControlTest.scala b/common/src/test/scala/objects/PlayerControlTest.scala
index a1d81b60..2fa7419a 100644
--- a/common/src/test/scala/objects/PlayerControlTest.scala
+++ b/common/src/test/scala/objects/PlayerControlTest.scala
@@ -32,12 +32,10 @@ class PlayerControlHealTest extends ActorTest {
player1.Zone = zone
player1.Spawn
player1.Position = Vector3(2, 0, 0)
- guid.register(player1.Locker, 5)
player1.Actor = system.actorOf(Props(classOf[PlayerControl], player1), "player1-control")
val player2 = Player(Avatar("TestCharacter2", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)) //guid=2
player2.Zone = zone
player2.Spawn
- guid.register(player2.Locker, 6)
player2.Actor = system.actorOf(Props(classOf[PlayerControl], player2), "player2-control")
val tool = Tool(GlobalDefinitions.medicalapplicator) //guid=3 & 4
@@ -104,7 +102,6 @@ class PlayerControlHealSelfTest extends ActorTest {
player1.Zone = zone
player1.Spawn
player1.Position = Vector3(2, 0, 0)
- guid.register(player1.Locker, 5)
player1.Actor = system.actorOf(Props(classOf[PlayerControl], player1), "player1-control")
val tool = Tool(GlobalDefinitions.medicalapplicator) //guid=3 & 4
@@ -170,12 +167,10 @@ class PlayerControlRepairTest extends ActorTest {
player1.Zone = zone
player1.Spawn
player1.Position = Vector3(2, 0, 0)
- guid.register(player1.Locker, 5)
player1.Actor = system.actorOf(Props(classOf[PlayerControl], player1), "player1-control")
val player2 = Player(Avatar("TestCharacter2", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)) //guid=2
player2.Zone = zone
player2.Spawn
- guid.register(player2.Locker, 6)
player2.Actor = system.actorOf(Props(classOf[PlayerControl], player2), "player2-control")
val tool = Tool(GlobalDefinitions.bank) //guid=3 & 4
@@ -248,7 +243,6 @@ class PlayerControlRepairSelfTest extends ActorTest {
player1.Zone = zone
player1.Spawn
player1.Position = Vector3(2, 0, 0)
- guid.register(player1.Locker, 5)
player1.Actor = system.actorOf(Props(classOf[PlayerControl], player1), "player1-control")
val tool = Tool(GlobalDefinitions.bank) //guid=3 & 4
@@ -315,12 +309,10 @@ class PlayerControlDamageTest extends ActorTest {
player1.Zone = zone
player1.Spawn
player1.Position = Vector3(2, 0, 0)
- guid.register(player1.Locker, 5)
player1.Actor = system.actorOf(Props(classOf[PlayerControl], player1), "player1-control")
val player2 = Player(Avatar("TestCharacter2", PlanetSideEmpire.NC, CharacterGender.Male, 0, CharacterVoice.Mute)) //guid=2
player2.Zone = zone
player2.Spawn
- guid.register(player2.Locker, 6)
player2.Actor = system.actorOf(Props(classOf[PlayerControl], player2), "player2-control")
val tool = Tool(GlobalDefinitions.suppressor) //guid 3 & 4
val projectile = tool.Projectile
@@ -393,12 +385,10 @@ class PlayerControlDeathStandingTest extends ActorTest {
player1.Zone = zone
player1.Spawn
player1.Position = Vector3(2,0,0)
- guid.register(player1.Locker, 5)
player1.Actor = system.actorOf(Props(classOf[PlayerControl], player1), "player1-control")
val player2 = Player(Avatar("TestCharacter2", PlanetSideEmpire.NC, CharacterGender.Male, 0, CharacterVoice.Mute)) //guid=2
player2.Zone = zone
player2.Spawn
- guid.register(player2.Locker, 6)
player2.Actor = system.actorOf(Props(classOf[PlayerControl], player2), "player2-control")
val tool = Tool(GlobalDefinitions.suppressor) //guid 3 & 4
@@ -503,12 +493,10 @@ class PlayerControlDeathSeatedTest extends ActorTest {
player1.Zone = zone
player1.Spawn
player1.Position = Vector3(2,0,0)
- guid.register(player1.Locker, 6)
player1.Actor = system.actorOf(Props(classOf[PlayerControl], player1), "player1-control")
val player2 = Player(Avatar("TestCharacter2", PlanetSideEmpire.NC, CharacterGender.Male, 0, CharacterVoice.Mute)) //guid=2
player2.Zone = zone
player2.Spawn
- guid.register(player2.Locker, 7)
player2.Actor = system.actorOf(Props(classOf[PlayerControl], player2), "player2-control")
val vehicle = Vehicle(GlobalDefinitions.quadstealth) //guid=5
@@ -607,4 +595,5 @@ class PlayerControlDeathSeatedTest extends ActorTest {
}
}
+
object PlayerControlTest { }
diff --git a/common/src/test/scala/objects/PlayerTest.scala b/common/src/test/scala/objects/PlayerTest.scala
index ee9e43c6..69507fec 100644
--- a/common/src/test/scala/objects/PlayerTest.scala
+++ b/common/src/test/scala/objects/PlayerTest.scala
@@ -279,7 +279,7 @@ class PlayerTest extends Specification {
"can access the player's locker-space" in {
val obj = TestPlayer("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
- obj.Slot(5).Equipment.get.isInstanceOf[LockerEquipment] mustEqual true
+ obj.Slot(5).Equipment.get.isInstanceOf[LockerContainer] mustEqual true
}
"can find equipment" in {
diff --git a/common/src/test/scala/objects/guidtask/GUIDTaskRegisterAvatarTest.scala b/common/src/test/scala/objects/guidtask/GUIDTaskRegisterAvatarTest.scala
index 632b827e..4fdd378f 100644
--- a/common/src/test/scala/objects/guidtask/GUIDTaskRegisterAvatarTest.scala
+++ b/common/src/test/scala/objects/guidtask/GUIDTaskRegisterAvatarTest.scala
@@ -18,7 +18,7 @@ class GUIDTaskRegisterAvatarTest extends ActorTest {
obj.Slot(6).Equipment = obj_inv_ammo
val obj_locker = obj.Slot(5).Equipment.get
val obj_locker_ammo = AmmoBox(GlobalDefinitions.energy_cell)
- obj_locker.asInstanceOf[LockerEquipment].Inventory += 0 -> obj_locker_ammo
+ obj_locker.asInstanceOf[LockerContainer].Inventory += 0 -> obj_locker_ammo
assert(!obj.HasGUID)
assert(!obj_wep.HasGUID)
diff --git a/common/src/test/scala/objects/guidtask/GUIDTaskRegisterPlayerTest.scala b/common/src/test/scala/objects/guidtask/GUIDTaskRegisterPlayerTest.scala
index e60349b3..0171d8d6 100644
--- a/common/src/test/scala/objects/guidtask/GUIDTaskRegisterPlayerTest.scala
+++ b/common/src/test/scala/objects/guidtask/GUIDTaskRegisterPlayerTest.scala
@@ -18,7 +18,7 @@ class GUIDTaskRegisterPlayerTest extends ActorTest {
obj.Slot(6).Equipment = obj_inv_ammo
val obj_locker = obj.Slot(5).Equipment.get
val obj_locker_ammo = AmmoBox(GlobalDefinitions.energy_cell)
- obj_locker.asInstanceOf[LockerEquipment].Inventory += 0 -> obj_locker_ammo
+ obj_locker.asInstanceOf[LockerContainer].Inventory += 0 -> obj_locker_ammo
assert(!obj.HasGUID)
assert(!obj_wep.HasGUID)
diff --git a/common/src/test/scala/objects/guidtask/GUIDTaskUnregisterAvatarTest.scala b/common/src/test/scala/objects/guidtask/GUIDTaskUnregisterAvatarTest.scala
index 09d8f8e3..6bddc5be 100644
--- a/common/src/test/scala/objects/guidtask/GUIDTaskUnregisterAvatarTest.scala
+++ b/common/src/test/scala/objects/guidtask/GUIDTaskUnregisterAvatarTest.scala
@@ -18,7 +18,7 @@ class GUIDTaskUnregisterAvatarTest extends ActorTest {
obj.Slot(6).Equipment = obj_inv_ammo
val obj_locker = obj.Slot(5).Equipment.get
val obj_locker_ammo = AmmoBox(GlobalDefinitions.energy_cell)
- obj_locker.asInstanceOf[LockerEquipment].Inventory += 0 -> obj_locker_ammo
+ obj_locker.asInstanceOf[LockerContainer].Inventory += 0 -> obj_locker_ammo
guid.register(obj, "dynamic")
guid.register(obj_wep, "dynamic")
guid.register(obj_wep_ammo, "dynamic")
diff --git a/common/src/test/scala/objects/guidtask/GUIDTaskUnregisterPlayerTest.scala b/common/src/test/scala/objects/guidtask/GUIDTaskUnregisterPlayerTest.scala
index 03ef7b34..3a4bf21a 100644
--- a/common/src/test/scala/objects/guidtask/GUIDTaskUnregisterPlayerTest.scala
+++ b/common/src/test/scala/objects/guidtask/GUIDTaskUnregisterPlayerTest.scala
@@ -18,7 +18,7 @@ class GUIDTaskUnregisterPlayerTest extends ActorTest {
obj.Slot(6).Equipment = obj_inv_ammo
val obj_locker = obj.Slot(5).Equipment.get
val obj_locker_ammo = AmmoBox(GlobalDefinitions.energy_cell)
- obj_locker.asInstanceOf[LockerEquipment].Inventory += 0 -> obj_locker_ammo
+ obj_locker.asInstanceOf[LockerContainer].Inventory += 0 -> obj_locker_ammo
guid.register(obj, "dynamic")
guid.register(obj_wep, "dynamic")
guid.register(obj_wep_ammo, "dynamic")
diff --git a/pslogin/src/main/scala/WorldSession.scala b/pslogin/src/main/scala/WorldSession.scala
deleted file mode 100644
index a16f0c33..00000000
--- a/pslogin/src/main/scala/WorldSession.scala
+++ /dev/null
@@ -1,603 +0,0 @@
-// Copyright (c) 2020 PSForever
-import akka.actor.ActorRef
-import akka.pattern.{AskTimeoutException, ask}
-import akka.util.Timeout
-import net.psforever.objects.{AmmoBox, GlobalDefinitions, Player, Tool}
-import net.psforever.objects.equipment.{Ammo, Equipment}
-import net.psforever.objects.guid.{GUIDTask, Task, TaskResolver}
-import net.psforever.objects.inventory.{Container, InventoryItem}
-import net.psforever.objects.serverobject.PlanetSideServerObject
-import net.psforever.objects.serverobject.containable.Containable
-import net.psforever.objects.zones.Zone
-import net.psforever.packet.game.ObjectHeldMessage
-import net.psforever.types.{PlanetSideGUID, TransactionType, Vector3}
-import services.Service
-import services.avatar.{AvatarAction, AvatarServiceMessage}
-
-import scala.concurrent.ExecutionContext.Implicits.global
-import scala.concurrent.Future
-import scala.concurrent.duration._
-import scala.language.implicitConversions
-
-object WorldSession {
- /**
- * Convert a boolean value into an integer value.
- * Use: `true:Int` or `false:Int`
- * @param b `true` or `false` (or `null`)
- * @return 1 for `true`; 0 for `false`
- */
- implicit def boolToInt(b : Boolean) : Int = if(b) 1 else 0
- private implicit val timeout = new Timeout(5000 milliseconds)
-
- /**
- * Use this for placing equipment that has yet to be registered into a container,
- * such as in support of changing ammunition types in `Tool` objects (weapons).
- * If the object can not be placed into the container, it will be dropped onto the ground.
- * It will also be dropped if it takes too long to be placed.
- * Item swapping during the placement is not allowed.
- * @see `ask`
- * @see `ChangeAmmoMessage`
- * @see `Containable.CanNotPutItemInSlot`
- * @see `Containable.PutItemAway`
- * @see `Future.onComplete`
- * @see `Future.recover`
- * @see `tell`
- * @see `Zone.Ground.DropItem`
- * @param obj the container
- * @param item the item being manipulated
- * @return a `Future` that anticipates the resolution to this manipulation
- */
- def PutEquipmentInInventoryOrDrop(obj : PlanetSideServerObject with Container)(item : Equipment) : Future[Any] = {
- val localContainer = obj
- val localItem = item
- val result = ask(localContainer.Actor, Containable.PutItemAway(localItem))
- result.onComplete {
- case scala.util.Failure(_) | scala.util.Success(_ : Containable.CanNotPutItemInSlot) =>
- localContainer.Zone.Ground.tell(Zone.Ground.DropItem(localItem, localContainer.Position, Vector3.z(localContainer.Orientation.z)), localContainer.Actor)
- case _ => ;
- }
- result
- }
-
- /**
- * Use this for placing equipment that has yet to be registered into a container,
- * such as in support of changing ammunition types in `Tool` objects (weapons).
- * Equipment will go wherever it fits in containing object, or be dropped if it fits nowhere.
- * Item swapping during the placement is not allowed.
- * @see `ChangeAmmoMessage`
- * @see `GUIDTask.RegisterEquipment`
- * @see `PutEquipmentInInventoryOrDrop`
- * @see `Task`
- * @see `TaskResolver.GiveTask`
- * @param obj the container
- * @param item the item being manipulated
- * @return a `TaskResolver` object
- */
- def PutNewEquipmentInInventoryOrDrop(obj : PlanetSideServerObject with Container)(item : Equipment) : TaskResolver.GiveTask = {
- val localZone = obj.Zone
- TaskResolver.GiveTask(
- new Task() {
- private val localContainer = obj
- private val localItem = item
-
- override def isComplete : Task.Resolution.Value = Task.Resolution.Success
-
- def Execute(resolver : ActorRef) : Unit = {
- PutEquipmentInInventoryOrDrop(localContainer)(localItem)
- resolver ! scala.util.Success(this)
- }
- },
- List(GUIDTask.RegisterEquipment(item)(localZone.GUID))
- )
- }
-
- /**
- * Use this for obtaining new equipment from a loadout specification.
- * The loadout specification contains a specific slot position for placing the item.
- * This request will (probably) be coincidental with a number of other such requests based on that loadout
- * so items must be rigidly placed else cascade into a chaostic order.
- * Item swapping during the placement is not allowed.
- * @see `ask`
- * @see `AvatarAction.ObjectDelete`
- * @see `ChangeAmmoMessage`
- * @see `Containable.CanNotPutItemInSlot`
- * @see `Containable.PutItemAway`
- * @see `Future.onComplete`
- * @see `Future.recover`
- * @see `GUIDTask.UnregisterEquipment`
- * @see `tell`
- * @see `Zone.AvatarEvents`
- * @param obj the container
- * @param taskResolver na
- * @param item the item being manipulated
- * @param slot na
- * @return a `Future` that anticipates the resolution to this manipulation
- */
- def PutEquipmentInInventorySlot(obj : PlanetSideServerObject with Container, taskResolver : ActorRef)(item : Equipment, slot : Int) : Future[Any] = {
- val localContainer = obj
- val localItem = item
- val localResolver = taskResolver
- val result = ask(localContainer.Actor, Containable.PutItemInSlotOnly(localItem, slot))
- result.onComplete {
- case scala.util.Failure(_) | scala.util.Success(_ : Containable.CanNotPutItemInSlot) =>
- localResolver ! GUIDTask.UnregisterEquipment(localItem)(localContainer.Zone.GUID)
- case _ => ;
- }
- result
- }
-
- /**
- * Use this for obtaining new equipment from a loadout specification.
- * The loadout specification contains a specific slot position for placing the item.
- * This request will (probably) be coincidental with a number of other such requests based on that loadout
- * so items must be rigidly placed else cascade into a chaostic order.
- * Item swapping during the placement is not allowed.
- * @see `GUIDTask.RegisterEquipment`
- * @see `PutEquipmentInInventorySlot`
- * @see `Task`
- * @see `TaskResolver.GiveTask`
- * @param obj the container
- * @param taskResolver na
- * @param item the item being manipulated
- * @param slot where the item will be placed in the container
- * @return a `TaskResolver` object
- */
- def PutLoadoutEquipmentInInventory(obj : PlanetSideServerObject with Container, taskResolver : ActorRef)(item : Equipment, slot : Int) : TaskResolver.GiveTask = {
- val localZone = obj.Zone
- TaskResolver.GiveTask(
- new Task() {
- private val localContainer = obj
- private val localItem = item
- private val localSlot = slot
- private val localFunc : (Equipment,Int)=>Future[Any] = PutEquipmentInInventorySlot(obj, taskResolver)
-
- override def Timeout : Long = 1000
-
- override def isComplete : Task.Resolution.Value = {
- if(localItem.HasGUID && localContainer.Find(localItem).nonEmpty)
- Task.Resolution.Success
- else
- Task.Resolution.Incomplete
- }
-
- override def Description : String = s"PutEquipmentInInventorySlot - ${localItem.Definition.Name}"
-
- def Execute(resolver : ActorRef) : Unit = {
- localFunc(localItem, localSlot)
- resolver ! scala.util.Success(this)
- }
- },
- List(GUIDTask.RegisterEquipment(item)(localZone.GUID))
- )
- }
-
- /**
- * Used for purchasing new equipment from a terminal and placing it somewhere in a player's loadout.
- * Two levels of query are performed here based on the behavior expected of the item.
- * First, an attempt is made to place the item anywhere in the target container as long as it does not cause swap items to be generated.
- * Second, if it fails admission to the target container, an attempt is made to place it into the target player's free hand.
- * If the container and the suggested player are the same, it will skip the second attempt.
- * As a terminal operation, the player must receive a report regarding whether the transaction was successful.
- * @see `ask`
- * @see `Containable.CanNotPutItemInSlot`
- * @see `Containable.PutItemInSlotOnly`
- * @see `GUIDTask.RegisterEquipment`
- * @see `GUIDTask.UnregisterEquipment`
- * @see `Future.onComplete`
- * @see `PutEquipmentInInventorySlot`
- * @see `TerminalMessageOnTimeout`
- * @param obj the container
- * @param taskResolver na
- * @param player na
- * @param term na
- * @param item the item being manipulated
- * @return a `TaskResolver` object
- */
- def BuyNewEquipmentPutInInventory(obj : PlanetSideServerObject with Container, taskResolver : ActorRef, player : Player, term : PlanetSideGUID)(item : Equipment) : TaskResolver.GiveTask = {
- val localZone = obj.Zone
- TaskResolver.GiveTask(
- new Task() {
- private val localContainer = obj
- private val localItem = item
- private val localPlayer = player
- private val localResolver = taskResolver
- private val localTermMsg : Boolean=>Unit = TerminalResult(term, localPlayer, TransactionType.Buy)
-
- override def Timeout : Long = 1000
-
- override def isComplete : Task.Resolution.Value = {
- if(localItem.HasGUID && localContainer.Find(localItem).nonEmpty)
- Task.Resolution.Success
- else
- Task.Resolution.Incomplete
- }
-
- def Execute(resolver : ActorRef) : Unit = {
- TerminalMessageOnTimeout(
- ask(localContainer.Actor, Containable.PutItemAway(localItem)),
- localTermMsg
- )
- .onComplete {
- case scala.util.Failure(_) | scala.util.Success(_ : Containable.CanNotPutItemInSlot) =>
- if(localContainer != localPlayer) {
- TerminalMessageOnTimeout(
- PutEquipmentInInventorySlot(localPlayer, localResolver)(localItem, Player.FreeHandSlot),
- localTermMsg
- )
- .onComplete {
- case scala.util.Failure(_) | scala.util.Success(_ : Containable.CanNotPutItemInSlot) =>
- localTermMsg(false)
- case _ =>
- localTermMsg(true)
- }
- }
- else {
- localResolver ! GUIDTask.UnregisterEquipment(localItem)(localContainer.Zone.GUID)
- localTermMsg(false)
- }
- case _ =>
- localTermMsg(true)
- }
- resolver ! scala.util.Success(this)
- }
- },
- List(GUIDTask.RegisterEquipment(item)(localZone.GUID))
- )
- }
-
- /**
- * The primary use is to register new mechanized assault exo-suit armaments,
- * place the newly registered weapon in hand,
- * and then raise that hand (draw that slot) so that the weapon is active.
- * (Players in MAX suits can not manipulate their drawn slot manually.)
- * In general, this can be used for any equipment that is to be equipped to a player's hand then immediately drawn.
- * Do not allow the item to be (mis)placed in any available slot.
- * Item swapping during the placement is not allowed and the possibility should be proactively avoided.
- * @throws `RuntimeException` if slot is not a player visible slot (holsters)
- * @see `ask`
- * @see `AvatarAction.ObjectDelete`
- * @see `AvatarAction.SendResponse`
- * @see `Containable.CanNotPutItemInSlot`
- * @see `Containable.PutItemInSlotOnly`
- * @see `GUIDTask.RegisterEquipment`
- * @see `GUIDTask.UnregisterEquipment`
- * @see `Future.onComplete`
- * @see `ObjectHeldMessage`
- * @see `Player.DrawnSlot`
- * @see `Player.LastDrawnSlot`
- * @see `Service.defaultPlayerGUID`
- * @see `TaskResolver.GiveTask`
- * @see `Zone.AvatarEvents`
- * @param player the player whose visible slot will be equipped and drawn
- * @param taskResolver na
- * @param item the item to equip
- * @param slot the slot in which the item will be equipped
- * @return a `TaskResolver` object
- */
- def HoldNewEquipmentUp(player : Player, taskResolver : ActorRef)(item : Equipment, slot : Int) : TaskResolver.GiveTask = {
- if(player.VisibleSlots.contains(slot)) {
- val localZone = player.Zone
- TaskResolver.GiveTask(
- new Task() {
- private val localPlayer = player
- private val localGUID = player.GUID
- private val localItem = item
- private val localSlot = slot
- private val localResolver = taskResolver
-
- override def Timeout : Long = 1000
-
- override def isComplete : Task.Resolution.Value = {
- if(localPlayer.DrawnSlot == localSlot)
- Task.Resolution.Success
- else
- Task.Resolution.Incomplete
- }
-
- def Execute(resolver : ActorRef) : Unit = {
- ask(localPlayer.Actor, Containable.PutItemInSlotOnly(localItem, localSlot))
- .onComplete {
- case scala.util.Failure(_) | scala.util.Success(_ : Containable.CanNotPutItemInSlot) =>
- localResolver ! GUIDTask.UnregisterEquipment(localItem)(localZone.GUID)
- case _ =>
- if(localPlayer.DrawnSlot != Player.HandsDownSlot) {
- localPlayer.DrawnSlot = Player.HandsDownSlot
- localZone.AvatarEvents ! AvatarServiceMessage(localPlayer.Name,
- AvatarAction.SendResponse(Service.defaultPlayerGUID, ObjectHeldMessage(localGUID, Player.HandsDownSlot, false))
- )
- localZone.AvatarEvents ! AvatarServiceMessage(localZone.Id, AvatarAction.ObjectHeld(localGUID, localPlayer.LastDrawnSlot))
- }
- localPlayer.DrawnSlot = localSlot
- localZone.AvatarEvents ! AvatarServiceMessage(localZone.Id,
- AvatarAction.SendResponse(Service.defaultPlayerGUID, ObjectHeldMessage(localGUID, localSlot, false))
- )
- }
- resolver ! scala.util.Success(this)
- }
- },
- List(GUIDTask.RegisterEquipment(item)(localZone.GUID))
- )
- }
- else {
- //TODO log.error
- throw new RuntimeException(s"provided slot $slot is not a player visible slot (holsters)")
- }
- }
-
- /**
- * Get an item from the ground and put it into the given container.
- * The zone in which the item is found is expected to be the same in which the container object is located.
- * If the object can not be placed into the container, it is put back on the ground.
- * The item that was collected off the ground, if it is placed back on the ground,
- * will be positioned with respect to the container object rather than its original location.
- * @see `ask`
- * @see `AvatarAction.ObjectDelete`
- * @see `Future.onComplete`
- * @see `Zone.AvatarEvents`
- * @see `Zone.Ground.CanNotPickUpItem`
- * @see `Zone.Ground.ItemInHand`
- * @see `Zone.Ground.PickUpItem`
- * @see `PutEquipmentInInventoryOrDrop`
- * @param obj the container into which the item will be placed
- * @param item the item being collected from off the ground of the container's zone
- * @return a `Future` that anticipates the resolution to this manipulation
- */
- def PickUpEquipmentFromGround(obj : PlanetSideServerObject with Container)(item : Equipment) : Future[Any] = {
- val localZone = obj.Zone
- val localContainer = obj
- val localItem = item
- val future = ask(localZone.Ground, Zone.Ground.PickupItem(item.GUID))
- future.onComplete {
- case scala.util.Success(Zone.Ground.ItemInHand(_)) =>
- PutEquipmentInInventoryOrDrop(localContainer)(localItem)
- case scala.util.Success(Zone.Ground.CanNotPickupItem(_, item_guid, _)) =>
- localZone.GUID(item_guid) match {
- case Some(_) => ;
- case None => //acting on old data?
- localZone.AvatarEvents ! AvatarServiceMessage(localZone.Id, AvatarAction.ObjectDelete(Service.defaultPlayerGUID, item_guid))
- }
- case _ => ;
- }
- future
- }
-
- /**
- * Remove an item from a container and drop it on the ground.
- * @see `ask`
- * @see `AvatarAction.ObjectDelete`
- * @see `Containable.ItemFromSlot`
- * @see `Containable.RemoveItemFromSlot`
- * @see `Future.onComplete`
- * @see `Future.recover`
- * @see `tell`
- * @see `Zone.AvatarEvents`
- * @see `Zone.Ground.DropItem`
- * @param obj the container to search
- * @param item the item to find and remove from the container
- * @param pos an optional position where to drop the item on the ground;
- * expected override from original container's position
- * @return a `Future` that anticipates the resolution to this manipulation
- */
- def DropEquipmentFromInventory(obj : PlanetSideServerObject with Container)(item : Equipment, pos : Option[Vector3] = None) : Future[Any] = {
- val localContainer = obj
- val localItem = item
- val localPos = pos
- val result = ask(localContainer.Actor, Containable.RemoveItemFromSlot(localItem))
- result.onComplete {
- case scala.util.Success(Containable.ItemFromSlot(_, Some(_), Some(_))) =>
- localContainer.Zone.Ground.tell(Zone.Ground.DropItem(localItem, localPos.getOrElse(localContainer.Position), Vector3.z(localContainer.Orientation.z)), localContainer.Actor)
- case _ => ;
- }
- result
- }
-
- /**
- * Remove an item from a container and delete it.
- * @see `ask`
- * @see `AvatarAction.ObjectDelete`
- * @see `Containable.ItemFromSlot`
- * @see `Containable.RemoveItemFromSlot`
- * @see `Future.onComplete`
- * @see `Future.recover`
- * @see `GUIDTask.UnregisterEquipment`
- * @see `Zone.AvatarEvents`
- * @param obj the container to search
- * @param taskResolver na
- * @param item the item to find and remove from the container
- * @return a `Future` that anticipates the resolution to this manipulation
- */
- def RemoveOldEquipmentFromInventory(obj : PlanetSideServerObject with Container, taskResolver : ActorRef)(item : Equipment) : Future[Any] = {
- val localContainer = obj
- val localItem = item
- val localResolver = taskResolver
- val result = ask(localContainer.Actor, Containable.RemoveItemFromSlot(localItem))
- result.onComplete {
- case scala.util.Success(Containable.ItemFromSlot(_, Some(_), Some(_))) =>
- localResolver ! GUIDTask.UnregisterEquipment(localItem)(localContainer.Zone.GUID)
- case _ =>
- }
- result
- }
-
- /**
- * Primarily, remove an item from a container and delete it.
- * As a terminal operation, the player must receive a report regarding whether the transaction was successful.
- * At the end of a successful transaction, and only a successful transaction,
- * the item that was removed is no longer considered a valid game object.
- * Contrasting `RemoveOldEquipmentFromInventory` which identifies the actual item to be eliminated,
- * this function uses the slot where the item is (should be) located.
- * @see `ask`
- * @see `Containable.ItemFromSlot`
- * @see `Containable.RemoveItemFromSlot`
- * @see `Future.onComplete`
- * @see `Future.recover`
- * @see `GUIDTask.UnregisterEquipment`
- * @see `RemoveOldEquipmentFromInventory`
- * @see `TerminalMessageOnTimeout`
- * @see `TerminalResult`
- * @param obj the container to search
- * @param taskResolver na
- * @param player the player who used the terminal
- * @param term the unique identifier number of the terminal
- * @param slot from which slot the equipment is to be removed
- * @return a `Future` that anticipates the resolution to this manipulation
- */
- def SellEquipmentFromInventory(obj : PlanetSideServerObject with Container, taskResolver : ActorRef, player : Player, term : PlanetSideGUID)(slot : Int) : Future[Any] = {
- val localContainer = obj
- val localPlayer = player
- val localSlot = slot
- val localResolver = taskResolver
- val localTermMsg : Boolean=>Unit = TerminalResult(term, localPlayer, TransactionType.Sell)
- val result = TerminalMessageOnTimeout(
- ask(localContainer.Actor, Containable.RemoveItemFromSlot(localSlot)),
- localTermMsg
- )
- result.onComplete {
- case scala.util.Success(Containable.ItemFromSlot(_, Some(item), Some(_))) =>
- localResolver ! GUIDTask.UnregisterEquipment(item)(localContainer.Zone.GUID)
- localTermMsg(true)
- case _ =>
- localTermMsg(false)
- }
- result
- }
-
- /**
- * If a timeout occurs on the manipulation, declare a terminal transaction failure.
- * @see `AskTimeoutException`
- * @see `recover`
- * @param future the item manipulation's `Future` object
- * @param terminalMessage how to call the terminal message
- * @return a `Future` that anticipates the resolution to this manipulation
- */
- def TerminalMessageOnTimeout(future : Future[Any], terminalMessage : Boolean=>Unit) : Future[Any] = {
- future.recover {
- case _ : AskTimeoutException =>
- terminalMessage(false)
- }
- }
-
- /**
- * Announced the result of this player's terminal use, to the player that used the terminal.
- * This is a necessary step for regaining terminal use which is naturally blocked by the client after a transaction request.
- * @see `AvatarAction.TerminalOrderResult`
- * @see `ItemTransactionResultMessage`
- * @see `TransactionType`
- * @param guid the terminal's unique identifier
- * @param player the player who used the terminal
- * @param transaction what kind of transaction was involved in terminal use
- * @param result the result of that transaction
- */
- def TerminalResult(guid : PlanetSideGUID, player : Player, transaction : TransactionType.Value)(result : Boolean) : Unit = {
- player.Zone.AvatarEvents ! AvatarServiceMessage(player.Name, AvatarAction.TerminalOrderResult(guid, transaction, result))
- }
-
- /**
- * Drop some items on the ground is a given location.
- * The location corresponds to the previous container for those items.
- * @see `Zone.Ground.DropItem`
- * @param container the original object that contained the items
- * @param drops the items to be dropped on the ground
- */
- def DropLeftovers(container : PlanetSideServerObject with Container)(drops : List[InventoryItem]) : Unit = {
- //drop or retire
- val zone = container.Zone
- val pos = container.Position
- val orient = Vector3.z(container.Orientation.z)
- //TODO make a sound when dropping stuff?
- drops.foreach { entry => zone.Ground.tell(Zone.Ground.DropItem(entry.obj, pos, orient), container.Actor) }
- }
-
- /**
- * Within a specified `Container`, find the smallest number of `Equipment` objects of a certain qualifying type
- * whose sum count is greater than, or equal to, a `desiredAmount` based on an accumulator method.
- *
- * In an occupied `List` of returned `Inventory` entries, all but the last entry is typically considered "emptied."
- * For objects with contained quantities, the last entry may require having that quantity be set to a non-zero number.
- * @param obj the `Container` to search
- * @param filterTest test used to determine inclusivity of `Equipment` collection
- * @param desiredAmount how much is requested
- * @param counting test used to determine value of found `Equipment`;
- * defaults to one per entry
- * @return a `List` of all discovered entries totaling approximately the amount requested
- */
- def FindEquipmentStock(obj : Container,
- filterTest : Equipment=>Boolean,
- desiredAmount : Int,
- counting : Equipment=>Int = DefaultCount) : List[InventoryItem] = {
- var currentAmount : Int = 0
- obj.Inventory.Items
- .filter(item => filterTest(item.obj))
- .sortBy(_.start)
- .takeWhile(entry => {
- val previousAmount = currentAmount
- currentAmount += counting(entry.obj)
- previousAmount < desiredAmount
- })
- }
-
-
- /**
- * The default counting function for an item.
- * Counts the number of item(s).
- * @param e the `Equipment` object
- * @return the quantity;
- * always one
- */
- def DefaultCount(e : Equipment) : Int = 1
-
- /**
- * The counting function for an item of `AmmoBox`.
- * Counts the `Capacity` of the ammunition.
- * @param e the `Equipment` object
- * @return the quantity
- */
- def CountAmmunition(e : Equipment) : Int = {
- e match {
- case a : AmmoBox => a.Capacity
- case _ => 0
- }
- }
-
- /**
- * The counting function for an item of `Tool` where the item is also a grenade.
- * Counts the number of grenades.
- * @see `GlobalDefinitions.isGrenade`
- * @param e the `Equipment` object
- * @return the quantity
- */
- def CountGrenades(e : Equipment) : Int = {
- e match {
- case t : Tool => (GlobalDefinitions.isGrenade(t.Definition):Int) * t.Magazine
- case _ => 0
- }
- }
-
- /**
- * Flag an `AmmoBox` object that matches for the given ammunition type.
- * @param ammo the type of `Ammo` to check
- * @param e the `Equipment` object
- * @return `true`, if the object is an `AmmoBox` of the correct ammunition type; `false`, otherwise
- */
- def FindAmmoBoxThatUses(ammo : Ammo.Value)(e : Equipment) : Boolean = {
- e match {
- case t : AmmoBox => t.AmmoType == ammo
- case _ => false
- }
- }
-
- /**
- * Flag a `Tool` object that matches for loading the given ammunition type.
- * @param ammo the type of `Ammo` to check
- * @param e the `Equipment` object
- * @return `true`, if the object is a `Tool` that loads the correct ammunition type; `false`, otherwise
- */
- def FindToolThatUses(ammo : Ammo.Value)(e : Equipment) : Boolean = {
- e match {
- case t : Tool =>
- t.Definition.AmmoTypes.map { _.AmmoType }.contains(ammo)
- case _ =>
- false
- }
- }
-}
diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala
index b999ce2e..00700123 100644
--- a/pslogin/src/main/scala/WorldSessionActor.scala
+++ b/pslogin/src/main/scala/WorldSessionActor.scala
@@ -1,8 +1,6 @@
// Copyright (c) 2017-2020 PSForever
//language imports
import akka.actor.{Actor, ActorRef, Cancellable, MDCContextAware}
-import akka.pattern.ask
-import akka.util.Timeout
import com.github.mauricio.async.db.general.ArrayRowData
import com.github.mauricio.async.db.{Connection, QueryResult}
import java.util.concurrent.TimeUnit
@@ -33,7 +31,6 @@ import net.psforever.objects.loadouts.{InfantryLoadout, Loadout, SquadLoadout, V
import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject}
import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.serverobject.damage.Damageable
-import net.psforever.objects.serverobject.containable.{Containable, ContainableBehavior}
import net.psforever.objects.serverobject.deploy.Deployment
import net.psforever.objects.serverobject.doors.Door
import net.psforever.objects.serverobject.generator.Generator
@@ -78,7 +75,6 @@ class WorldSessionActor extends Actor
with MDCContextAware {
import WorldSessionActor._
- import WorldSession._
private[this] val log = org.log4s.getLogger
private[this] val damageLog = org.log4s.getLogger(Damageable.LogChannel)
@@ -112,7 +108,17 @@ class WorldSessionActor extends Actor
//keep track of avatar's ServerVehicleOverride state
var traveler : Traveler = null
var deadState : DeadState.Value = DeadState.Dead
+ var whenUsedLastAAMAX : Long = 0
+ var whenUsedLastAIMAX : Long = 0
+ var whenUsedLastAVMAX : Long = 0
+ var whenUsedLastMAX : Array[Long] = Array.fill[Long](4)(0L)
var whenUsedLastMAXName : Array[String] = Array.fill[String](4)("")
+ var whenUsedLastItem : Array[Long] = Array.fill[Long](1020)(0L)
+ var whenUsedLastItemName : Array[String] = Array.fill[String](1020)("")
+ var whenUsedLastKit : Long = 0
+ var whenUsedLastSMKit : Long = 0
+ var whenUsedLastSAKit : Long = 0
+ var whenUsedLastSSKit : Long = 0
val projectiles : Array[Option[Projectile]] = Array.fill[Option[Projectile]](Projectile.RangeUID - Projectile.BaseUID)(None)
val projectilesToCleanUp : Array[Boolean] = Array.fill[Boolean](Projectile.RangeUID - Projectile.BaseUID)(false)
var drawDeloyableIcon : PlanetSideGameObject with Deployable => Unit = RedrawDeployableIcons
@@ -143,7 +149,7 @@ class WorldSessionActor extends Actor
var squad_supplement_id : Int = 0
/**
* When joining or creating a squad, the original state of the avatar's internal LFS variable is blanked.
- * This `WorldSessionActor`-local variable is then used to indicate the ongoing state of the LFS UI component,
+ * This `WSA`-local variable is then used to indicate the ongoing state of the LFS UI component,
* now called "Looking for Squad Member."
* Only the squad leader may toggle the LFSM marquee.
* Upon leaving or disbanding a squad, this value is made false.
@@ -176,6 +182,17 @@ class WorldSessionActor extends Actor
var antDischargingTick : Cancellable = DefaultCancellable.obj
var zoningTimer : Cancellable = DefaultCancellable.obj
var zoningReset : Cancellable = DefaultCancellable.obj
+ /**
+ * Convert a boolean value into an integer value.
+ * Use: `true:Int` or `false:Int`
+ * @param b `true` or `false` (or `null`)
+ * @return 1 for `true`; 0 for `false`
+ */
+
+ import scala.language.implicitConversions
+
+ implicit def boolToInt(b : Boolean) : Int = if(b) 1
+ else 0
override def postStop() : Unit = {
//normally, the player avatar persists a minute or so after disconnect; we are subject to the SessionReaper
@@ -244,7 +261,7 @@ class WorldSessionActor extends Actor
case None if id.nonEmpty && id.get != PlanetSideGUID(0) =>
//delete stale entity reference from client
log.warn(s"Player ${player.Name} has an invalid reference to GUID ${id.get} in zone ${continent.Id}.")
- sendResponse(ObjectDeleteMessage(id.get, 0))
+ //sendResponse(ObjectDeleteMessage(id.get, 0))
None
case _ =>
None
@@ -861,6 +878,82 @@ class WorldSessionActor extends Actor
case msg@Zone.Vehicle.CanNotDespawn(zone, vehicle, reason) =>
log.warn(s"$msg")
+ case Zone.Ground.ItemOnGround(item : BoomerTrigger, pos, orient) =>
+ //dropped the trigger, no longer own the boomer; make certain whole faction is aware of that
+ val playerGUID = player.GUID
+ continent.GUID(item.Companion) match {
+ case Some(obj : BoomerDeployable) =>
+ val guid = obj.GUID
+ val factionChannel = s"${player.Faction}"
+ obj.AssignOwnership(None)
+ avatar.Deployables.Remove(obj)
+ UpdateDeployableUIElements(avatar.Deployables.UpdateUIElement(obj.Definition.Item))
+ continent.LocalEvents ! LocalServiceMessage.Deployables(RemoverActor.AddTask(obj, continent))
+ obj.Faction = PlanetSideEmpire.NEUTRAL
+ sendResponse(SetEmpireMessage(guid, PlanetSideEmpire.NEUTRAL))
+ continent.AvatarEvents ! AvatarServiceMessage(factionChannel, AvatarAction.SetEmpire(playerGUID, guid, PlanetSideEmpire.NEUTRAL))
+ val info = DeployableInfo(guid, DeployableIcon.Boomer, obj.Position, PlanetSideGUID(0))
+ sendResponse(DeployableObjectsInfoMessage(DeploymentAction.Dismiss, info))
+ continent.LocalEvents ! LocalServiceMessage(factionChannel, LocalAction.DeployableMapIcon(playerGUID, DeploymentAction.Dismiss, info))
+ PutItemOnGround(item, pos, orient)
+ case Some(_) | None =>
+ //pointless trigger
+ val guid = item.GUID
+ continent.Ground ! Zone.Ground.RemoveItem(guid) //undo; no callback
+ continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.ObjectDelete(PlanetSideGUID(0), guid))
+ taskResolver ! GUIDTask.UnregisterObjectTask(item)(continent.GUID)
+ }
+
+ case Zone.Ground.ItemOnGround(item : ConstructionItem, pos, orient) =>
+ //defensively, reset CItem configuration
+ item.FireModeIndex = 0
+ item.AmmoTypeIndex = 0
+ PutItemOnGround(item, pos, orient)
+
+ case Zone.Ground.ItemOnGround(item : PlanetSideGameObject, pos, orient) =>
+ PutItemOnGround(item, pos, orient)
+
+ case Zone.Ground.CanNotDropItem(zone, item, reason) =>
+ log.warn(s"DropItem: $player tried to drop a $item on the ground, but $reason")
+ if(!item.HasGUID) {
+ log.warn(s"DropItem: zone ${continent.Id} contents may be in disarray")
+ }
+
+ case Zone.Ground.ItemInHand(item : BoomerTrigger) =>
+ if(PutItemInHand(item)) {
+ //pick up the trigger, own the boomer; make certain whole faction is aware of that
+ continent.GUID(item.Companion) match {
+ case Some(obj : BoomerDeployable) =>
+ val guid = obj.GUID
+ val playerGUID = player.GUID
+ val faction = player.Faction
+ val factionChannel = s"$faction"
+ obj.AssignOwnership(player)
+ obj.Faction = faction
+ avatar.Deployables.Add(obj)
+ UpdateDeployableUIElements(avatar.Deployables.UpdateUIElement(obj.Definition.Item))
+ continent.LocalEvents ! LocalServiceMessage.Deployables(RemoverActor.ClearSpecific(List(obj), continent))
+ sendResponse(SetEmpireMessage(guid, faction))
+ continent.AvatarEvents ! AvatarServiceMessage(factionChannel, AvatarAction.SetEmpire(playerGUID, guid, faction))
+ val info = DeployableInfo(obj.GUID, DeployableIcon.Boomer, obj.Position, obj.Owner.getOrElse(PlanetSideGUID(0)))
+ sendResponse(DeployableObjectsInfoMessage(DeploymentAction.Build, info))
+ continent.LocalEvents ! LocalServiceMessage(factionChannel, LocalAction.DeployableMapIcon(playerGUID, DeploymentAction.Build, info))
+ case Some(_) | None => ; //pointless trigger; see Zone.Ground.ItemOnGround(BoomerTrigger, ...)
+ }
+ }
+
+ case Zone.Ground.ItemInHand(item : Equipment) =>
+ PutItemInHand(item)
+
+ case Zone.Ground.CanNotPickupItem(zone, item_guid, _) =>
+ zone.GUID(item_guid) match {
+ case Some(item) =>
+ log.warn(s"DropItem: finding a $item on the ground was suggested, but $player can not reach it")
+ case None =>
+ log.warn(s"DropItem: finding an item ($item_guid) on the ground was suggested, but $player can not see it")
+ sendResponse(ObjectDeleteMessage(item_guid, 0))
+ }
+
case Zone.Deployable.DeployableIsBuilt(obj, tool) =>
val index = player.Find(tool) match {
case Some(x) =>
@@ -887,7 +980,7 @@ class WorldSessionActor extends Actor
)
}
else {
- TryDropFDU(tool, index, obj.Position)
+ TryDropConstructionTool(tool, index, obj.Position)
sendResponse(ObjectDeployedMessage.Failure(obj.Definition.Name))
obj.Position = Vector3.Zero
obj.AssignOwnership(None)
@@ -917,11 +1010,11 @@ class WorldSessionActor extends Actor
val holster = player.Slot(index)
if(holster.Equipment.contains(tool)) {
holster.Equipment = None
- taskResolver ! HoldNewEquipmentUp(player, taskResolver)(trigger, index)
+ taskResolver ! DelayedObjectHeld(player, index, List(PutEquipmentInSlot(player, trigger, index)))
}
else {
//don't know where boomer trigger should go; drop it on the ground
- taskResolver ! NewItemDrop(player, continent)(trigger)
+ taskResolver ! NewItemDrop(player, continent, continent.AvatarEvents)(trigger)
}
StopBundlingPackets()
@@ -960,7 +1053,7 @@ class WorldSessionActor extends Actor
log.info(s"FinalizeDeployable: setup for telepad #${guid.guid} in zone ${continent.Id}")
obj.Router = routerGUID //necessary; forwards link to the router
DeployableBuildActivity(obj)
- RemoveOldEquipmentFromInventory(player, taskResolver)(tool)
+ CommonDestroyConstructionItem(tool, index)
StopBundlingPackets()
//it takes 60s for the telepad to become properly active
continent.LocalEvents ! LocalServiceMessage.Telepads(RouterTelepadActivation.AddTask(obj, continent))
@@ -982,16 +1075,16 @@ class WorldSessionActor extends Actor
sendResponse(ObjectDeployedMessage.Failure(definition.Name))
log.warn(s"FinalizeDeployable: deployable ${definition.asInstanceOf[BaseDeployableDefinition].Item}@$guid not handled by specific case")
log.warn(s"FinalizeDeployable: deployable will be cleaned up, but may not get unregistered properly")
- TryDropFDU(tool, index, obj.Position)
+ TryDropConstructionTool(tool, index, obj.Position)
obj.Position = Vector3.Zero
continent.Deployables ! Zone.Deployable.Dismiss(obj)
StopBundlingPackets()
- //!!only dispatch Zone.Deployable.Dismiss from WorldSessionActor as cleanup if the target deployable was never fully introduced
+ //!!only dispatch Zone.Deployable.Dismiss from WSA as cleanup if the target deployable was never fully introduced
case Zone.Deployable.DeployableIsDismissed(obj : TurretDeployable) =>
taskResolver ! GUIDTask.UnregisterDeployableTurret(obj)(continent.GUID)
- //!!only dispatch Zone.Deployable.Dismiss from WorldSessionActor as cleanup if the target deployable was never fully introduced
+ //!!only dispatch Zone.Deployable.Dismiss from WSA as cleanup if the target deployable was never fully introduced
case Zone.Deployable.DeployableIsDismissed(obj) =>
taskResolver ! GUIDTask.UnregisterObjectTask(obj)(continent.GUID)
@@ -1554,7 +1647,7 @@ class WorldSessionActor extends Actor
// avatar.Certifications += BattleFrameRobotics
// avatar.Certifications += BFRAntiInfantry
// avatar.Certifications += BFRAntiAircraft
- Deployables.InitializeDeployableQuantities(avatar) //set deployables ui elements
+ InitializeDeployableQuantities(avatar) //set deployables ui elements
AwardBattleExperiencePoints(avatar, 20000000L)
avatar.CEP = 600000
avatar.Implants(0).Unlocked = true
@@ -1581,7 +1674,7 @@ class WorldSessionActor extends Actor
case PlayerToken.LoginInfo(playerName, inZone, pos) =>
log.info(s"LoginInfo: player $playerName is already logged in zone ${inZone.Id}; rejoining that character")
persist = UpdatePersistence(sender)
- //tell the old WorldSessionActor to kill itself by using its own subscriptions against itself
+ //tell the old WSA to kill itself by using its own subscriptions against itself
inZone.AvatarEvents ! AvatarServiceMessage(playerName, AvatarAction.TeardownConnection())
//find and reload previous player
(inZone.Players.find(p => p.name.equals(playerName)), inZone.LivePlayers.find(p => p.Name.equals(playerName))) match {
@@ -1601,11 +1694,12 @@ class WorldSessionActor extends Actor
player.Zone = inZone
setupAvatarFunc = AvatarDeploymentPassOver
beginZoningSetCurrentAvatarFunc = SetCurrentAvatarUponDeployment
+ p.Release
+ inZone.Population ! Zone.Population.Release(avatar)
if(p.VehicleSeated.isEmpty) {
PrepareToTurnPlayerIntoCorpse(p, inZone)
}
else {
- inZone.Population ! Zone.Population.Release(avatar)
inZone.GUID(p.VehicleSeated) match {
case Some(v : Vehicle) if v.Destroyed =>
v.Actor ! Vehicle.Deconstruct(
@@ -1644,12 +1738,6 @@ class WorldSessionActor extends Actor
self ! PlayerToken.LoginInfo(playerName, Zone.Nowhere, pos)
}
- case msg @ Containable.ItemPutInSlot(_ : PlanetSideServerObject with Container, _ : Equipment, _ : Int, _ : Option[Equipment]) =>
- log.info(s"$msg")
-
- case msg @ Containable.CanNotPutItemInSlot(_ : PlanetSideServerObject with Container, _ : Equipment, _ : Int) =>
- log.info(s"$msg")
-
case default =>
log.warn(s"Invalid packet class received: $default from $sender")
}
@@ -2186,155 +2274,10 @@ class WorldSessionActor extends Actor
sendResponse(WeaponDryFireMessage(weapon_guid))
}
- case AvatarResponse.TerminalOrderResult(terminal_guid, action, result) =>
- sendResponse(ItemTransactionResultMessage(terminal_guid, action, result))
- lastTerminalOrderFulfillment = true
-
- case AvatarResponse.ChangeExosuit(target, exosuit, subtype, slot, maxhand, old_holsters, holsters, old_inventory, inventory, drop, delete) =>
- StartBundlingPackets()
- sendResponse(ArmorChangedMessage(target, exosuit, subtype))
- sendResponse(PlanetsideAttributeMessage(target, 4, player.Armor))
- if(tplayer_guid == target) {
- //happening to this player
- if(exosuit == ExoSuitType.MAX) {
- sendResponse(AvatarVehicleTimerMessage(player.GUID, whenUsedLastMAXName(subtype), 300, true))
- }
- //cleanup
- sendResponse(ObjectHeldMessage(target, Player.HandsDownSlot, false))
- (old_holsters ++ old_inventory ++ delete).foreach { case (_, guid) => sendResponse(ObjectDeleteMessage(guid, 0)) }
- //functionally delete
- delete.foreach { case (obj, _) => taskResolver ! GUIDTask.UnregisterEquipment(obj)(continent.GUID) }
- //redraw
- if(maxhand) {
- taskResolver ! HoldNewEquipmentUp(player, taskResolver)(Tool(GlobalDefinitions.MAXArms(subtype, player.Faction)), 0)
- }
- //draw free hand
- player.FreeHand.Equipment match {
- case Some(obj) =>
- val definition = obj.Definition
- sendResponse(
- ObjectCreateDetailedMessage(
- definition.ObjectId,
- obj.GUID,
- ObjectCreateMessageParent(target, Player.FreeHandSlot),
- definition.Packet.DetailedConstructorData(obj).get
- )
- )
- case None => ;
- }
- //draw holsters and inventory
- (holsters ++ inventory).foreach { case InventoryItem(obj, index) =>
- val definition = obj.Definition
- sendResponse(
- ObjectCreateDetailedMessage(
- definition.ObjectId,
- obj.GUID,
- ObjectCreateMessageParent(target, index),
- definition.Packet.DetailedConstructorData(obj).get
- )
- )
- }
- DropLeftovers(player)(drop)
- }
- else {
- //happening to some other player
- sendResponse(ObjectHeldMessage(target, slot, false))
- //cleanup
- (old_holsters ++ delete).foreach { case (_, guid) => sendResponse(ObjectDeleteMessage(guid, 0)) }
- //draw holsters
- holsters.foreach { case InventoryItem(obj, index) =>
- val definition = obj.Definition
- sendResponse(
- ObjectCreateMessage(
- definition.ObjectId,
- obj.GUID,
- ObjectCreateMessageParent(target, index),
- definition.Packet.ConstructorData(obj).get
- )
- )
- }
- }
- StopBundlingPackets()
-
- case AvatarResponse.ChangeLoadout(target, exosuit, subtype, slot, maxhand, old_holsters, holsters, old_inventory, inventory, drops) =>
- StartBundlingPackets()
- sendResponse(ArmorChangedMessage(target, exosuit, subtype))
- sendResponse(PlanetsideAttributeMessage(target, 4, player.Armor))
- if(tplayer_guid == target) {
- //happening to this player
- if(exosuit == ExoSuitType.MAX) {
- sendResponse(AvatarVehicleTimerMessage(player.GUID, whenUsedLastMAXName(subtype), 300, true))
- }
- sendResponse(ObjectHeldMessage(target, Player.HandsDownSlot, false))
- //cleanup
- (old_holsters ++ old_inventory).foreach { case (obj, guid) =>
- sendResponse(ObjectDeleteMessage(guid, 0))
- taskResolver ! GUIDTask.UnregisterEquipment(obj)(continent.GUID)
- }
- //redraw
- if(maxhand) {
- taskResolver ! HoldNewEquipmentUp(player, taskResolver)(Tool(GlobalDefinitions.MAXArms(subtype, player.Faction)), 0)
- }
- ApplyPurchaseTimersBeforePackingLoadout(player, player, holsters ++ inventory)
- DropLeftovers(player)(drops)
- }
- else {
- //happening to some other player
- sendResponse(ObjectHeldMessage(target, slot, false))
- //cleanup
- old_holsters.foreach { case (_, guid) => sendResponse(ObjectDeleteMessage(guid, 0)) }
- //redraw handled by callback
- }
- StopBundlingPackets()
-
case _ => ;
}
}
- /**
- * Enforce constraints on bulk purchases as determined by a given player's previous purchase times and hard acquisition delays.
- * Intended to assist in sanitizing loadout information from the perspectvie of the player, or target owner.
- * The equipment is expected to be unregistered and already fitted to their ultimate slot in the target container.
- * @see `AvatarVehicleTimerMessage`
- * @see `Container`
- * @see `delayedPurchaseEntries`
- * @see `InventoryItem`
- * @see `Player.GetLastUsedTime`
- * @see `Player.SetLastUsedTime`
- * @see `TaskResolver.GiveTask`
- * @see `WorldSession.PutLoadoutEquipmentInInventory`
- * @param player the player whose purchasing constraints are to be tested
- * @param target the location in which the equipment will be stowed
- * @param slots the equipment, in the standard object-slot format container
- */
- def ApplyPurchaseTimersBeforePackingLoadout(player : Player, target : PlanetSideServerObject with Container, slots : List[InventoryItem]) : Unit = {
- //depiction of packed equipment is handled through callbacks
- val loadoutEquipmentFunc : (Equipment, Int)=>TaskResolver.GiveTask = PutLoadoutEquipmentInInventory(target, taskResolver)
- val time = System.currentTimeMillis
- slots.collect { case _obj@InventoryItem(obj, slot)
- if {
- val id = obj.Definition.ObjectId
- delayedPurchaseEntries.get(id) match {
- case Some(delay) =>
- val lastUse = player.GetLastPurchaseTime(id)
- time - lastUse > delay
- case None =>
- true
- }
- } =>
- val definition = obj.Definition
- val id = definition.ObjectId
- player.SetLastPurchaseTime(id, time)
- player.ObjectTypeNameReference(id.toLong, definition.Name)
- delayedPurchaseEntries.get(id) match {
- case Some(delay) =>
- sendResponse(AvatarVehicleTimerMessage(player.GUID, definition.Name, delay / 1000, true))
- case _ => ;
- }
- taskResolver ! loadoutEquipmentFunc(obj, slot)
- }
- }
-
/**
* na
* @param tplayer na
@@ -2733,35 +2676,388 @@ class WorldSessionActor extends Actor
*/
def HandleTerminalMessage(tplayer : Player, msg : ItemTransactionMessage, order : Terminal.Exchange) : Unit = {
order match {
- case Terminal.BuyEquipment(item) =>
- val definition = item.Definition
- val itemid = definition.ObjectId
- val time = System.currentTimeMillis
- if(delayedPurchaseEntries.get(itemid) match {
- case Some(delay) if time - tplayer.GetLastPurchaseTime(itemid) > delay =>
- player.SetLastPurchaseTime(itemid, time)
- player.ObjectTypeNameReference(itemid.toLong, definition.Name)
- sendResponse(AvatarVehicleTimerMessage(tplayer.GUID, definition.Name, delay / 1000, true))
- true
- case Some(_) =>
- false
- case _ => ;
- true
- }) {
- taskResolver ! BuyNewEquipmentPutInInventory(
- continent.GUID(tplayer.VehicleSeated) match { case Some(v : Vehicle) => v; case _ => player },
- taskResolver,
- tplayer,
- msg.terminal_guid
- )(item)
+ case Terminal.BuyExosuit(exosuit, subtype) =>
+ //TODO check exo-suit permissions
+ val originalSuit = tplayer.ExoSuit
+ val originalSubtype = Loadout.DetermineSubtype(tplayer)
+ val lTime = System.currentTimeMillis
+ var changeArmor : Boolean = true
+ if(lTime - whenUsedLastMAX(subtype) < 300000) {
+ changeArmor = false
+ }
+ if(changeArmor && exosuit.id == 2) {
+ for(i <- 1 to 3) {
+ sendResponse(AvatarVehicleTimerMessage(tplayer.GUID, whenUsedLastMAXName(i), 300, true))
+ whenUsedLastMAX(i) = lTime
+ }
+ }
+ if(originalSuit != exosuit || originalSubtype != subtype && changeArmor) {
+ sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, true))
+ //prepare lists of valid objects
+ val beforeInventory = tplayer.Inventory.Clear()
+ val beforeHolsters = clearHolsters(tplayer.Holsters().iterator)
+ //change suit (clear inventory and change holster sizes; holsters must be empty before this point)
+ val originalArmor = tplayer.Armor
+ tplayer.ExoSuit = exosuit //changes the value of MaxArmor to reflect the new exo-suit
+ val toMaxArmor = tplayer.MaxArmor
+ if(originalSuit != exosuit || originalSubtype != subtype || originalArmor > toMaxArmor) {
+ tplayer.History(HealFromExoSuitChange(PlayerSource(tplayer), exosuit))
+ tplayer.Armor = toMaxArmor
+ sendResponse(PlanetsideAttributeMessage(tplayer.GUID, 4, toMaxArmor))
+ continent.AvatarEvents ! AvatarServiceMessage(player.Continent, AvatarAction.PlanetsideAttribute(tplayer.GUID, 4, toMaxArmor))
+ }
+ else {
+ tplayer.Armor = originalArmor
+ }
+ //ensure arm is down, even if it needs to go back up
+ if(tplayer.DrawnSlot != Player.HandsDownSlot) {
+ tplayer.DrawnSlot = Player.HandsDownSlot
+ sendResponse(ObjectHeldMessage(tplayer.GUID, Player.HandsDownSlot, true))
+ continent.AvatarEvents ! AvatarServiceMessage(tplayer.Continent, AvatarAction.ObjectHeld(tplayer.GUID, tplayer.LastDrawnSlot))
+ }
+ //delete everything not dropped
+ (beforeHolsters ++ beforeInventory).foreach({ elem =>
+ sendResponse(ObjectDeleteMessage(elem.obj.GUID, 0))
+ })
+ beforeHolsters.foreach({ elem =>
+ continent.AvatarEvents ! AvatarServiceMessage(tplayer.Continent, AvatarAction.ObjectDelete(tplayer.GUID, elem.obj.GUID))
+ })
+ //report change
+ sendResponse(ArmorChangedMessage(tplayer.GUID, exosuit, subtype))
+ continent.AvatarEvents ! AvatarServiceMessage(player.Continent, AvatarAction.ArmorChanged(tplayer.GUID, exosuit, subtype))
+ //sterilize holsters
+ val normalHolsters = if(originalSuit == ExoSuitType.MAX) {
+ val (maxWeapons, normalWeapons) = beforeHolsters.partition(elem => elem.obj.Size == EquipmentSize.Max)
+ maxWeapons.foreach(entry => {
+ taskResolver ! GUIDTask.UnregisterEquipment(entry.obj)(continent.GUID)
+ })
+ normalWeapons
+ }
+ else {
+ beforeHolsters
+ }
+ //populate holsters
+ val finalInventory = if(exosuit == ExoSuitType.MAX) {
+ taskResolver ! DelayedObjectHeld(tplayer, 0, List(PutEquipmentInSlot(tplayer, Tool(GlobalDefinitions.MAXArms(subtype, tplayer.Faction)), 0)))
+ fillEmptyHolsters(List(tplayer.Slot(4)).iterator, normalHolsters) ++ beforeInventory
+ }
+ else if(originalSuit == exosuit) { //note - this will rarely be the situation
+ fillEmptyHolsters(tplayer.Holsters().iterator, normalHolsters)
+ }
+ else {
+ val (afterHolsters, toInventory) = normalHolsters.partition(elem => elem.obj.Size == tplayer.Slot(elem.start).Size)
+ afterHolsters.foreach({ elem => tplayer.Slot(elem.start).Equipment = elem.obj })
+ fillEmptyHolsters(tplayer.Holsters().iterator, toInventory ++ beforeInventory)
+ }
+ //draw holsters
+ tplayer.VisibleSlots.foreach({ index =>
+ tplayer.Slot(index).Equipment match {
+ case Some(obj) =>
+ val definition = obj.Definition
+ sendResponse(
+ ObjectCreateDetailedMessage(
+ definition.ObjectId,
+ obj.GUID,
+ ObjectCreateMessageParent(tplayer.GUID, index),
+ definition.Packet.DetailedConstructorData(obj).get
+ )
+ )
+ continent.AvatarEvents ! AvatarServiceMessage(player.Continent, AvatarAction.EquipmentInHand(player.GUID, player.GUID, index, obj))
+ case None => ;
+ }
+ })
+ //re-draw equipment held in free hand
+ tplayer.FreeHand.Equipment match {
+ case Some(item) =>
+ val definition = item.Definition
+ sendResponse(
+ ObjectCreateDetailedMessage(
+ definition.ObjectId,
+ item.GUID,
+ ObjectCreateMessageParent(tplayer.GUID, Player.FreeHandSlot),
+ definition.Packet.DetailedConstructorData(item).get
+ )
+ )
+ case None => ;
+ }
+ //put items back into inventory
+ val (stow, drop) = if(originalSuit == exosuit) {
+ (finalInventory, Nil)
+ }
+ else {
+ GridInventory.recoverInventory(finalInventory, tplayer.Inventory)
+ }
+ stow.foreach(elem => {
+ tplayer.Inventory.Insert(elem.start, elem.obj)
+ val obj = elem.obj
+ val definition = obj.Definition
+ sendResponse(
+ ObjectCreateDetailedMessage(
+ definition.ObjectId,
+ obj.GUID,
+ ObjectCreateMessageParent(tplayer.GUID, elem.start),
+ definition.Packet.DetailedConstructorData(obj).get
+ )
+ )
+ })
+ val (finalDroppedItems, retiredItems) = drop.map(item => InventoryItem(item, -1)).partition(DropPredicate(tplayer))
+ //drop special items on ground
+ val pos = tplayer.Position
+ val orient = Vector3.z(tplayer.Orientation.z)
+ finalDroppedItems.foreach(entry => {
+ //TODO make a sound when dropping stuff
+ continent.Ground ! Zone.Ground.DropItem(entry.obj, pos, orient)
+ })
+ //deconstruct normal items
+ retiredItems.foreach({ entry =>
+ taskResolver ! GUIDTask.UnregisterEquipment(entry.obj)(continent.GUID)
+ })
}
else {
- lastTerminalOrderFulfillment = true
sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, false))
}
+ lastTerminalOrderFulfillment = true
+
+ case Terminal.BuyEquipment(item) =>
+ continent.GUID(tplayer.VehicleSeated) match {
+ //vehicle trunk
+ case Some(vehicle : Vehicle) =>
+ vehicle.Fit(item) match {
+ case Some(index) =>
+ item.Faction = tplayer.Faction
+ sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, true))
+ taskResolver ! StowNewEquipmentInVehicle(vehicle)(index, item)
+ case None => //player free hand?
+ tplayer.FreeHand.Equipment match {
+ case None =>
+ item.Faction = tplayer.Faction
+ sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, true))
+ taskResolver ! PutEquipmentInSlot(tplayer, item, Player.FreeHandSlot)
+ case Some(_) =>
+ sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, false))
+ }
+ }
+ //player backpack or free hand
+ case _ =>
+ tplayer.Fit(item) match {
+ case Some(index) =>
+ item.Faction = tplayer.Faction
+ sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, true))
+ taskResolver ! PutEquipmentInSlot(tplayer, item, index)
+ case None =>
+ sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, false))
+ }
+ }
+ lastTerminalOrderFulfillment = true
case Terminal.SellEquipment() =>
- SellEquipmentFromInventory(tplayer, taskResolver, tplayer, msg.terminal_guid)(Player.FreeHandSlot)
+ tplayer.FreeHand.Equipment match {
+ case Some(item) =>
+ if(item.GUID == msg.item_guid) {
+ sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Sell, true))
+ taskResolver ! RemoveEquipmentFromSlot(tplayer, item, Player.FreeHandSlot)
+ }
+ case None =>
+ sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Sell, false))
+ }
+ lastTerminalOrderFulfillment = true
+
+ case Terminal.InfantryLoadout(exosuit, subtype, holsters, inventory) =>
+ log.info(s"$tplayer wants to change equipment loadout to their option #${msg.unk1 + 1}")
+ sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Loadout, true))
+ //sanitize exo-suit for change
+ val originalSuit = player.ExoSuit
+ val originalSubtype = Loadout.DetermineSubtype(tplayer)
+ //prepare lists of valid objects
+ val beforeFreeHand = tplayer.FreeHand.Equipment
+ val dropPred = DropPredicate(tplayer)
+ val (dropHolsters, beforeHolsters) = clearHolsters(tplayer.Holsters().iterator).partition(dropPred)
+ val (dropInventory, beforeInventory) = tplayer.Inventory.Clear().partition(dropPred)
+ tplayer.FreeHand.Equipment = None //terminal and inventory will close, so prematurely dropping should be fine
+ val fallbackSuit = ExoSuitType.Standard
+ val fallbackSubtype = 0
+ //a loadout with a prohibited exo-suit type will result in a fallback exo-suit type
+ val (nextSuit : ExoSuitType.Value, nextSubtype : Int) =
+ if(ExoSuitDefinition.Select(exosuit, player.Faction).Permissions match {
+ case Nil =>
+ true
+ case permissions if subtype != 0 =>
+ val certs = tplayer.Certifications
+ certs.intersect(permissions.toSet).nonEmpty &&
+ certs.intersect(InfantryLoadout.DetermineSubtypeC(subtype)).nonEmpty
+ case permissions =>
+ tplayer.Certifications.intersect(permissions.toSet).nonEmpty
+ }) {
+ val lTime = System.currentTimeMillis
+ if(lTime - whenUsedLastMAX(subtype) < 300000) { // PTS v3 hack
+ (originalSuit, subtype)
+ }
+ else {
+ if(lTime - whenUsedLastMAX(subtype) > 300000 && subtype != 0) {
+ for(i <- 1 to 3) {
+ sendResponse(AvatarVehicleTimerMessage(tplayer.GUID, whenUsedLastMAXName(i), 300, true))
+ whenUsedLastMAX(i) = lTime
+ }
+ }
+ (exosuit, subtype)
+ }
+ }
+ else {
+ log.warn(s"$tplayer no longer has permission to wear the exo-suit type $exosuit; will wear $fallbackSuit instead")
+ (fallbackSuit, fallbackSubtype)
+ }
+ //update suit interally (holsters must be empty before this point)
+ val originalArmor = player.Armor
+ tplayer.ExoSuit = nextSuit
+ val toMaxArmor = tplayer.MaxArmor
+ if(originalSuit != nextSuit || originalSubtype != nextSubtype || originalArmor > toMaxArmor) {
+ tplayer.History(HealFromExoSuitChange(PlayerSource(tplayer), nextSuit))
+ tplayer.Armor = toMaxArmor
+ sendResponse(PlanetsideAttributeMessage(tplayer.GUID, 4, toMaxArmor))
+ continent.AvatarEvents ! AvatarServiceMessage(player.Continent, AvatarAction.PlanetsideAttribute(tplayer.GUID, 4, toMaxArmor))
+ }
+ else {
+ tplayer.Armor = originalArmor
+ }
+ //ensure arm is down, even if it needs to go back up
+ if(tplayer.DrawnSlot != Player.HandsDownSlot) {
+ tplayer.DrawnSlot = Player.HandsDownSlot
+ sendResponse(ObjectHeldMessage(tplayer.GUID, Player.HandsDownSlot, true))
+ continent.AvatarEvents ! AvatarServiceMessage(tplayer.Continent, AvatarAction.ObjectHeld(tplayer.GUID, tplayer.LastDrawnSlot))
+ }
+ //a change due to exo-suit permissions mismatch will result in (more) items being re-arranged and/or dropped
+ //dropped items can be forgotten safely
+ val (afterHolsters, afterInventory) = if(nextSuit == exosuit) {
+ (
+ holsters.filterNot(dropPred),
+ inventory.filterNot(dropPred)
+ )
+ }
+ else {
+ val newSuitDef = ExoSuitDefinition.Select(nextSuit, player.Faction)
+ val (afterInventory, extra) = GridInventory.recoverInventory(
+ inventory.filterNot(dropPred),
+ tplayer.Inventory
+ )
+ val afterHolsters = {
+ val preservedHolsters = if(exosuit == ExoSuitType.MAX) {
+ holsters.filter(_.start == 4) //melee slot perservation
+ }
+ else {
+ holsters
+ .filterNot(dropPred)
+ .collect {
+ case item@InventoryItem(obj, index) if newSuitDef.Holster(index) == obj.Size => item
+ }
+ }
+ val size = newSuitDef.Holsters.size
+ val indexMap = preservedHolsters.map { entry => entry.start }
+ preservedHolsters ++ (extra.map { obj =>
+ tplayer.Fit(obj) match {
+ case Some(index : Int) if index < size && !indexMap.contains(index) =>
+ InventoryItem(obj, index)
+ case _ =>
+ InventoryItem(obj, -1)
+ }
+ }).filterNot(entry => entry.start == -1)
+ }
+ (afterHolsters, afterInventory)
+ }
+ //delete everything (not dropped)
+ beforeHolsters.foreach({ elem =>
+ continent.AvatarEvents ! AvatarServiceMessage(tplayer.Continent, AvatarAction.ObjectDelete(tplayer.GUID, elem.obj.GUID))
+ })
+ (beforeHolsters ++ beforeInventory).foreach({ elem =>
+ sendResponse(ObjectDeleteMessage(elem.obj.GUID, 0))
+ taskResolver ! GUIDTask.UnregisterEquipment(elem.obj)(continent.GUID)
+ })
+ //report change
+ sendResponse(ArmorChangedMessage(tplayer.GUID, nextSuit, nextSubtype))
+ continent.AvatarEvents ! AvatarServiceMessage(tplayer.Continent, AvatarAction.ArmorChanged(tplayer.GUID, nextSuit, nextSubtype))
+ if(nextSuit == ExoSuitType.MAX) {
+ val (maxWeapons, otherWeapons) = afterHolsters.partition(entry => {
+ entry.obj.Size == EquipmentSize.Max
+ })
+ val weapon = maxWeapons.headOption match {
+ case Some(mweapon) =>
+ mweapon.obj
+ case None =>
+ Tool(GlobalDefinitions.MAXArms(nextSubtype, tplayer.Faction))
+ }
+ taskResolver ! DelayedObjectHeld(tplayer, 0, List(PutEquipmentInSlot(tplayer, weapon, 0)))
+ otherWeapons
+ }
+ else {
+ afterHolsters
+ }.foreach(entry => {
+ entry.obj.Faction = tplayer.Faction
+ taskResolver ! PutEquipmentInSlot(tplayer, entry.obj, entry.start)
+ })
+ //put items into inventory
+ afterInventory.foreach(entry => {
+ entry.obj.Faction = tplayer.Faction
+ taskResolver ! PutEquipmentInSlot(tplayer, entry.obj, entry.start)
+ })
+ //drop stuff on ground
+ val pos = tplayer.Position
+ val orient = Vector3.z(tplayer.Orientation.z)
+ ((beforeFreeHand match {
+ case Some(item) => List(InventoryItem(item, -1)) //add the item previously in free hand, if any
+ case None => Nil
+ }) ++ dropHolsters ++ dropInventory).foreach(entry => {
+ entry.obj.Faction = PlanetSideEmpire.NEUTRAL
+ continent.Ground ! Zone.Ground.DropItem(entry.obj, pos, orient)
+ })
+ lastTerminalOrderFulfillment = true
+
+ case Terminal.VehicleLoadout(definition, weapons, inventory) =>
+ log.info(s"$tplayer wants to change their vehicle equipment loadout to their option #${msg.unk1 + 1}")
+ FindLocalVehicle match {
+ case Some(vehicle) =>
+ sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Loadout, true))
+ val (_, afterInventory) = inventory.partition(DropPredicate(tplayer))
+ //dropped items are lost
+ //remove old inventory
+ val deleteEquipment : (Int, Equipment) => Unit = DeleteEquipmentFromVehicle(vehicle)
+ vehicle.Inventory.Clear().foreach({ case InventoryItem(obj, index) => deleteEquipment(index, obj) })
+ val stowEquipment : (Int, Equipment) => TaskResolver.GiveTask = StowNewEquipmentInVehicle(vehicle)
+ (if(vehicle.Definition == definition) {
+ //vehicles are the same type; transfer over weapon ammo
+ //TODO ammo switching? no vehicle weapon does that currently but ...
+ //TODO want to completely swap weapons, but holster icon vanishes temporarily after swap
+ //TODO BFR arms must be swapped properly
+ val channel = s"${vehicle.Actor}"
+ weapons.foreach({ case InventoryItem(obj, index) =>
+ val savedWeapon = obj.asInstanceOf[Tool]
+ val existingWeapon = vehicle.Weapons(index).Equipment.get.asInstanceOf[Tool]
+ (0 until existingWeapon.MaxAmmoSlot).foreach({ index =>
+ val existingBox = existingWeapon.AmmoSlots(index).Box
+ existingBox.Capacity = savedWeapon.AmmoSlots(index).Box.Capacity
+ //use VehicleAction.InventoryState2; VehicleAction.InventoryState temporarily glitches ammo count in ui
+ continent.VehicleEvents ! VehicleServiceMessage(channel, VehicleAction.InventoryState2(PlanetSideGUID(0), existingBox.GUID, existingWeapon.GUID, existingBox.Capacity))
+ })
+ })
+ afterInventory
+ }
+ else {
+ //do not transfer over weapon ammo
+ if(vehicle.Definition.TrunkSize == definition.TrunkSize && vehicle.Definition.TrunkOffset == definition.TrunkOffset) {
+ afterInventory
+ }
+ else {
+ //accommodate as much of inventory as possible
+ val (stow, _) = GridInventory.recoverInventory(afterInventory, vehicle.Inventory) //dropped items can be forgotten
+ stow
+ }
+ }).foreach({ case InventoryItem(obj, index) =>
+ obj.Faction = tplayer.Faction
+ taskResolver ! stowEquipment(index, obj)
+ })
+ case None =>
+ log.error(s"can not apply the loadout - can not find a vehicle")
+ sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Loadout, false))
+ }
+ lastTerminalOrderFulfillment = true
case Terminal.LearnCertification(cert) =>
val name = tplayer.Name
@@ -2770,7 +3066,7 @@ class WorldSessionActor extends Actor
log.info(s"$name is learning the $cert certification for ${Certification.Cost.Of(cert)} points")
avatar.Certifications += cert
StartBundlingPackets()
- UpdateDeployableUIElements(Deployables.AddToDeployableQuantities(avatar, cert, player.Certifications))
+ AddToDeployableQuantities(cert, player.Certifications)
sendResponse(PlanetsideAttributeMessage(guid, 24, cert.id))
tplayer.Certifications.intersect(Certification.Dependencies.Like(cert)).foreach(entry => {
log.info(s"$cert replaces the learned certification $entry that cost ${Certification.Cost.Of(entry)} points")
@@ -2793,12 +3089,12 @@ class WorldSessionActor extends Actor
log.info(s"$name is forgetting the $cert certification for ${Certification.Cost.Of(cert)} points")
avatar.Certifications -= cert
StartBundlingPackets()
- UpdateDeployableUIElements(Deployables.RemoveFromDeployableQuantities(avatar, cert, player.Certifications))
+ RemoveFromDeployablesQuantities(cert, player.Certifications)
sendResponse(PlanetsideAttributeMessage(guid, 25, cert.id))
tplayer.Certifications.intersect(Certification.Dependencies.FromAll(cert)).foreach(entry => {
log.info(s"$name is also forgetting the ${Certification.Cost.Of(entry)}-point $entry certification which depends on $cert")
avatar.Certifications -= entry
- UpdateDeployableUIElements(Deployables.RemoveFromDeployableQuantities(avatar, entry, player.Certifications))
+ RemoveFromDeployablesQuantities(entry, player.Certifications)
sendResponse(PlanetsideAttributeMessage(guid, 25, entry.id))
})
StopBundlingPackets()
@@ -2893,20 +3189,11 @@ class WorldSessionActor extends Actor
case Terminal.BuyVehicle(vehicle, weapons, trunk) =>
continent.Map.TerminalToSpawnPad.get(msg.terminal_guid.guid) match {
case Some(pad_guid) =>
- val definition = vehicle.Definition
- val vid = definition.ObjectId
- val time = System.currentTimeMillis
- if(delayedPurchaseEntries.get(vid) match {
- case Some(delay) if time - tplayer.GetLastPurchaseTime(vid) > delay =>
- tplayer.SetLastPurchaseTime(vid, time)
- tplayer.ObjectTypeNameReference(vid.toLong, definition.Name)
- sendResponse(AvatarVehicleTimerMessage(tplayer.GUID, definition.Name, delay / 1000, true))
- true
- case Some(_) =>
- false
- case None => ;
- true
- }) {
+ val lTime = System.currentTimeMillis
+ if(lTime - whenUsedLastItem(vehicle.Definition.ObjectId) > 300000) {
+ whenUsedLastItem(vehicle.Definition.ObjectId) = lTime
+ whenUsedLastItemName(vehicle.Definition.ObjectId) = msg.item_name
+ sendResponse(AvatarVehicleTimerMessage(tplayer.GUID, msg.item_name, 300, true))
val toFaction = tplayer.Faction
val pad = continent.GUID(pad_guid).get.asInstanceOf[VehicleSpawnPad]
vehicle.Faction = toFaction
@@ -2930,7 +3217,7 @@ class WorldSessionActor extends Actor
vTrunk.Clear()
trunk.foreach(entry => {
entry.obj.Faction = toFaction
- vTrunk.InsertQuickly(entry.start, entry.obj)
+ vTrunk += entry.start -> entry.obj
})
taskResolver ! RegisterVehicleFromSpawnPad(vehicle, pad)
sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, true))
@@ -2944,7 +3231,7 @@ class WorldSessionActor extends Actor
}
lastTerminalOrderFulfillment = true
- case Terminal.NoDeal() =>
+ case _ =>
val order : String = if(msg == null) {
s"order $msg"
}
@@ -2954,8 +3241,6 @@ class WorldSessionActor extends Actor
log.warn(s"${tplayer.Name} made a request but the terminal rejected the $order")
sendResponse(ItemTransactionResultMessage(msg.terminal_guid, msg.transaction_type, false))
lastTerminalOrderFulfillment = true
-
- case _ => ;
}
}
@@ -3167,36 +3452,6 @@ class WorldSessionActor extends Actor
})
sendResponse(ChatMsg(ChatMessageType.CMT_OPEN, true, "", msg, None))
- case VehicleResponse.ChangeLoadout(target, old_weapons, added_weapons, old_inventory, new_inventory) =>
- //TODO when vehicle weapons can be changed without visual glitches, rewrite this
- continent.GUID(target) match {
- case Some(vehicle : Vehicle) =>
- StartBundlingPackets()
- if(player.VehicleOwned.contains(target)) {
- //owner: must unregister old equipment, and register and install new equipment
- (old_weapons ++ old_inventory).foreach { case (obj, guid) =>
- sendResponse(ObjectDeleteMessage(guid, 0))
- taskResolver ! GUIDTask.UnregisterEquipment(obj)(continent.GUID)
- }
- ApplyPurchaseTimersBeforePackingLoadout(player, vehicle, added_weapons ++ new_inventory)
- }
- else if(accessedContainer.contains(target)) {
- //external participant: observe changes to equipment
- (old_weapons ++ old_inventory).foreach { case (_, guid) => sendResponse(ObjectDeleteMessage(guid, 0)) }
- }
- vehicle.PassengerInSeat(player) match {
- case Some(seatNum) =>
- //participant: observe changes to equipment
- (old_weapons ++ old_inventory).foreach { case (_, guid) => sendResponse(ObjectDeleteMessage(guid, 0)) }
- UpdateWeaponAtSeatPosition(vehicle, seatNum)
- case None =>
- //observer: observe changes to external equipment
- old_weapons.foreach { case (_, guid) => sendResponse(ObjectDeleteMessage(guid, 0)) }
- }
- StopBundlingPackets()
- case _ => ;
- }
-
case _ => ;
}
}
@@ -3385,7 +3640,7 @@ class WorldSessionActor extends Actor
player = tplayer
val guid = tplayer.GUID
StartBundlingPackets()
- UpdateDeployableUIElements(Deployables.InitializeDeployableUIElements(avatar))
+ InitializeDeployableUIElements(avatar)
sendResponse(PlanetsideAttributeMessage(PlanetSideGUID(0), 75, 0))
sendResponse(SetCurrentAvatarMessage(guid, 0, 0))
sendResponse(ChatMsg(ChatMessageType.CMT_EXPANSIONS, true, "", "1 on", None)) //CC on //TODO once per respawn?
@@ -3770,10 +4025,9 @@ class WorldSessionActor extends Actor
log.info(s"CharacterRequest/Select: character $lName found in records")
avatar = new Avatar(charId, lName, lFaction, lGender, lHead, lVoice)
var faction : String = lFaction.toString.toLowerCase
- whenUsedLastMAXName(0) = faction + "hev"
- whenUsedLastMAXName(1) = faction + "hev_antipersonnel"
- whenUsedLastMAXName(2) = faction + "hev_antivehicular"
- whenUsedLastMAXName(3) = faction + "hev_antiaircraft"
+ whenUsedLastMAXName(2) = faction + "hev_antipersonnel"
+ whenUsedLastMAXName(3) = faction + "hev_antivehicular"
+ whenUsedLastMAXName(1) = faction + "hev_antiaircraft"
avatar.FirstTimeEvents = ftes
accountPersistence ! AccountPersistenceService.Login(lName)
case _ =>
@@ -4261,13 +4515,13 @@ class WorldSessionActor extends Actor
log.info(s"ReleaseAvatarRequest: ${player.GUID} on ${continent.Id} has released")
reviveTimer.cancel
GoToDeploymentMap()
+ continent.Population ! Zone.Population.Release(avatar)
player.VehicleSeated match {
case None =>
PrepareToTurnPlayerIntoCorpse(player, continent)
case Some(_) =>
val player_guid = player.GUID
- continent.Population ! Zone.Population.Release(avatar)
sendResponse(ObjectDeleteMessage(player_guid, 0))
GetMountableAndSeat(None, player) match {
case (Some(obj), Some(seatNum)) =>
@@ -4940,13 +5194,7 @@ class WorldSessionActor extends Actor
player.FreeHand.Equipment match {
case Some(item) =>
if(item.GUID == item_guid) {
- CancelZoningProcessWithDescriptiveReason("cancel_use")
- continent.GUID(player.VehicleSeated) match {
- case Some(_) =>
- RemoveOldEquipmentFromInventory(player, taskResolver)(item)
- case None =>
- DropEquipmentFromInventory(player)(item)
- }
+ continent.Ground ! Zone.Ground.DropItem(item, player.Position, player.Orientation)
}
case None =>
log.warn(s"DropItem: $player wanted to drop a $anItem, but it wasn't at hand")
@@ -4964,8 +5212,7 @@ class WorldSessionActor extends Actor
case Some(item : Equipment) =>
player.Fit(item) match {
case Some(_) =>
- CancelZoningProcessWithDescriptiveReason("cancel_use")
- PickUpEquipmentFromGround(player)(item)
+ continent.Ground ! Zone.Ground.PickupItem(item_guid)
case None => //skip
sendResponse(ActionResultMessage.Fail(16)) //error code?
}
@@ -4986,25 +5233,25 @@ class WorldSessionActor extends Actor
case Nil =>
log.warn(s"ReloadMessage: no ammunition could be found for $item_guid")
case x :: xs =>
- val (deleteFunc, modifyFunc) : (Equipment=>Future[Any], (AmmoBox, Int) => Unit) = obj match {
+ val (deleteFunc, modifyFunc) : ((Int, AmmoBox) => Unit, (AmmoBox, Int) => Unit) = obj match {
case (veh : Vehicle) =>
- (RemoveOldEquipmentFromInventory(veh, taskResolver), ModifyAmmunitionInVehicle(veh))
- case o : PlanetSideServerObject with Container =>
- (RemoveOldEquipmentFromInventory(o, taskResolver), ModifyAmmunition(o))
+ (DeleteEquipmentFromVehicle(veh), ModifyAmmunitionInVehicle(veh))
case _ =>
- throw new Exception("ReloadMessage: should be a server object, not a regular game object")
+ (DeleteEquipment(obj), ModifyAmmunition(obj))
}
- xs.foreach { item => deleteFunc(item.obj) }
+ xs.foreach(item => {
+ deleteFunc(item.start, item.obj.asInstanceOf[AmmoBox])
+ })
val box = x.obj.asInstanceOf[AmmoBox]
val tailReloadValue : Int = if(xs.isEmpty) {
0
}
else {
- xs.map(_.obj.asInstanceOf[AmmoBox].Capacity).sum
+ xs.map(_.obj.asInstanceOf[AmmoBox].Capacity).reduceLeft(_ + _)
}
val sumReloadValue : Int = box.Capacity + tailReloadValue
val actualReloadValue = (if(sumReloadValue <= reloadValue) {
- deleteFunc(box)
+ deleteFunc(x.start, box)
sumReloadValue
}
else {
@@ -5196,9 +5443,50 @@ class WorldSessionActor extends Actor
case msg@MoveItemMessage(item_guid, source_guid, destination_guid, dest, _) =>
log.info(s"MoveItem: $msg")
- (continent.GUID(source_guid), continent.GUID(destination_guid), ValidObject(item_guid)) match {
- case (Some(source : PlanetSideServerObject with Container), Some(destination : PlanetSideServerObject with Container), Some(item : Equipment)) =>
- source.Actor ! Containable.MoveItem(destination, item, dest)
+ (continent.GUID(source_guid), continent.GUID(destination_guid), continent.GUID(item_guid)) match {
+ case (Some(source : Container), Some(destination : Container), Some(item : Equipment)) =>
+ source.Find(item_guid) match {
+ case Some(index) =>
+ val indexSlot = source.Slot(index)
+ val tile = item.Definition.Tile
+ val destinationCollisionTest = destination.Collisions(dest, tile.Width, tile.Height)
+ val destItemEntry = destinationCollisionTest match {
+ case Success(entry :: Nil) =>
+ Some(entry)
+ case _ =>
+ None
+ }
+ if( {
+ destinationCollisionTest match {
+ case Success(Nil) | Success(_ :: Nil) =>
+ true //no item or one item to swap
+ case _ =>
+ false //abort when too many items at destination or other failure case
+ }
+ } && indexSlot.Equipment.contains(item)) {
+ if(PermitEquipmentStow(item, destination)) {
+ StartBundlingPackets()
+ PerformMoveItem(item, source, index, destination, dest, destItemEntry)
+ StopBundlingPackets()
+ }
+ else {
+ log.error(s"MoveItem: $item disallowed storage in $destination")
+ }
+ }
+ else if(!indexSlot.Equipment.contains(item)) {
+ log.error(s"MoveItem: wanted to move $item_guid, but found unexpected ${indexSlot.Equipment} at source location")
+ }
+ else {
+ destinationCollisionTest match {
+ case Success(_) =>
+ log.error(s"MoveItem: wanted to move $item_guid, but multiple unexpected items at destination blocked progress")
+ case scala.util.Failure(err) =>
+ log.error(s"MoveItem: wanted to move $item_guid, but $err")
+ }
+ }
+ case _ =>
+ log.error(s"MoveItem: wanted to move $item_guid, but could not find it")
+ }
case (None, _, _) =>
log.error(s"MoveItem: wanted to move $item_guid from $source_guid, but could not find source object")
case (_, None, _) =>
@@ -5211,27 +5499,34 @@ class WorldSessionActor extends Actor
case msg@LootItemMessage(item_guid, target_guid) =>
log.info(s"LootItem: $msg")
- (ValidObject(item_guid), continent.GUID(target_guid)) match {
- case (Some(item : Equipment), Some(destination : PlanetSideServerObject with Container)) =>
+ (ValidObject(item_guid), ValidObject(target_guid)) match {
+ case (Some(item : Equipment), Some(target : Container)) =>
//figure out the source
( {
- val findFunc : PlanetSideServerObject with Container => Option[(PlanetSideServerObject with Container, Option[Int])] = FindInLocalContainer(item_guid)
+ val findFunc : PlanetSideGameObject with Container => Option[(PlanetSideGameObject with Container, Option[Int])] = FindInLocalContainer(item_guid)
findFunc(player.Locker)
.orElse(findFunc(player))
.orElse(accessedContainer match {
- case Some(parent : PlanetSideServerObject) =>
+ case Some(parent) =>
findFunc(parent)
- case _ =>
+ case None =>
None
}
)
- }, destination.Fit(item)) match {
- case (Some((source, Some(_))), Some(dest)) =>
- source.Actor ! Containable.MoveItem(destination, item, dest)
+ }, target.Fit(item)) match {
+ case (Some((source, Some(index))), Some(dest)) =>
+ if(PermitEquipmentStow(item, target)) {
+ StartBundlingPackets()
+ PerformMoveItem(item, source, index, target, dest, None)
+ StopBundlingPackets()
+ }
+ else {
+ log.error(s"LootItem: $item disallowed storage in $target")
+ }
case (None, _) =>
log.error(s"LootItem: can not find where $item is put currently")
case (_, None) =>
- log.error(s"LootItem: can not find somwhere to put $item in $destination")
+ log.error(s"LootItem: can not find somwhere to put $item in $target")
case _ =>
log.error(s"LootItem: wanted to move $item_guid to $target_guid, but multiple problems were encountered")
}
@@ -5306,86 +5601,88 @@ class WorldSessionActor extends Actor
else if(!unk3 && player.isAlive) { //potential kit use
ValidObject(item_used_guid) match {
case Some(kit : Kit) =>
- val kid = kit.Definition.ObjectId
- val time = System.currentTimeMillis
- val lastUse = player.GetLastUsedTime(kid)
- val delay = delayedGratificationEntries.getOrElse(kid, 0L)
- if((time - lastUse) < delay) {
- sendResponse(ChatMsg(ChatMessageType.UNK_225, false, "", s"@TimeUntilNextUse^${((delay / 1000) - math.ceil((time - lastUse) / 1000))}~", None))
- }
- else {
- val indexOpt = player.Find(kit)
- val kitIsUsed = indexOpt match {
- case Some(index) =>
- if(kit.Definition == GlobalDefinitions.medkit) {
- if(player.Health == player.MaxHealth) {
- sendResponse(ChatMsg(ChatMessageType.UNK_225, false, "", "@HealComplete", None))
- false
- }
- else {
- player.History(HealFromKit(PlayerSource(player), 25, kit.Definition))
- player.Health = player.Health + 25
- sendResponse(PlanetsideAttributeMessage(avatar_guid, 0, player.Health))
- continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(avatar_guid, 0, player.Health))
- true
- }
+ player.Find(kit) match {
+ case Some(index) =>
+ if(kit.Definition == GlobalDefinitions.medkit) {
+ if(player.Health == player.MaxHealth) {
+ sendResponse(ChatMsg(ChatMessageType.UNK_225, false, "", "@HealComplete", None))
}
- else if(kit.Definition == GlobalDefinitions.super_medkit) {
- if(player.Health == player.MaxHealth) {
- sendResponse(ChatMsg(ChatMessageType.UNK_225, false, "", "@HealComplete", None))
- false
- }
- else {
- player.History(HealFromKit(PlayerSource(player), 100, kit.Definition))
- player.Health = player.Health + 100
- sendResponse(PlanetsideAttributeMessage(avatar_guid, 0, player.Health))
- continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(avatar_guid, 0, player.Health))
- true
- }
- }
- else if(kit.Definition == GlobalDefinitions.super_armorkit) {
- if(player.Armor == player.MaxArmor) {
- sendResponse(ChatMsg(ChatMessageType.UNK_225, false, "", "Armor at maximum - No repairing required.", None))
- false
- }
- else {
- player.History(RepairFromKit(PlayerSource(player), 200, kit.Definition))
- player.Armor = player.Armor + 200
- sendResponse(PlanetsideAttributeMessage(avatar_guid, 4, player.Armor))
- continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(avatar_guid, 4, player.Armor))
- true
- }
- }
- else if(kit.Definition == GlobalDefinitions.super_staminakit) {
- if(player.Stamina == player.MaxStamina) {
- sendResponse(ChatMsg(ChatMessageType.UNK_225, false, "", "Stamina at maximum - No recharge required.", None))
- false
- }
- else {
- player.Stamina = player.Stamina + 100
- sendResponse(PlanetsideAttributeMessage(avatar_guid, 2, player.Stamina))
- true
- }
+ else if(System.currentTimeMillis - whenUsedLastKit < 5000) {
+ sendResponse(ChatMsg(ChatMessageType.UNK_225, false, "", s"@TimeUntilNextUse^${5 - (System.currentTimeMillis - whenUsedLastKit) / 1000}~", None))
}
else {
- log.warn(s"UseItem: $kit behavior not supported")
- false
+ whenUsedLastKit = System.currentTimeMillis
+ player.Slot(index).Equipment = None //remove from slot immediately; must exist on client for next packet
+ sendResponse(UseItemMessage(avatar_guid, item_used_guid, object_guid, 0, unk3, unk4, unk5, unk6, unk7, unk8, itemType))
+ sendResponse(ObjectDeleteMessage(kit.GUID, 0))
+ taskResolver ! GUIDTask.UnregisterEquipment(kit)(continent.GUID)
+ player.History(HealFromKit(PlayerSource(player), 25, kit.Definition))
+ player.Health = player.Health + 25
+ sendResponse(PlanetsideAttributeMessage(avatar_guid, 0, player.Health))
+ continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(avatar_guid, 0, player.Health))
}
+ }
+ else if(kit.Definition == GlobalDefinitions.super_medkit) {
+ if(player.Health == player.MaxHealth) {
+ sendResponse(ChatMsg(ChatMessageType.UNK_225, false, "", "@HealComplete", None))
+ }
+ else if(System.currentTimeMillis - whenUsedLastSMKit < 1200000) {
+ sendResponse(ChatMsg(ChatMessageType.UNK_225, false, "", s"@TimeUntilNextUse^${1200 - (System.currentTimeMillis - whenUsedLastSMKit) / 1000}~", None))
+ }
+ else {
+ whenUsedLastSMKit = System.currentTimeMillis
+ player.Slot(index).Equipment = None //remove from slot immediately; must exist on client for next packet
+ sendResponse(UseItemMessage(avatar_guid, item_used_guid, object_guid, 0, unk3, unk4, unk5, unk6, unk7, unk8, itemType))
+ sendResponse(ObjectDeleteMessage(kit.GUID, 0))
+ taskResolver ! GUIDTask.UnregisterEquipment(kit)(continent.GUID)
+ player.History(HealFromKit(PlayerSource(player), 100, kit.Definition))
+ player.Health = player.Health + 100
+ sendResponse(PlanetsideAttributeMessage(avatar_guid, 0, player.Health))
+ continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(avatar_guid, 0, player.Health))
+ }
+ }
+ else if(kit.Definition == GlobalDefinitions.super_armorkit) {
+ if(player.Armor == player.MaxArmor) {
+ sendResponse(ChatMsg(ChatMessageType.UNK_225, false, "", "Armor at maximum - No repairing required.", None))
+ }
+ else if(System.currentTimeMillis - whenUsedLastSAKit < 1200000) {
+ sendResponse(ChatMsg(ChatMessageType.UNK_225, false, "", s"@TimeUntilNextUse^${1200 - (System.currentTimeMillis - whenUsedLastSAKit) / 1000}~", None))
+ }
+ else {
+ whenUsedLastSAKit = System.currentTimeMillis
+ player.Slot(index).Equipment = None //remove from slot immediately; must exist on client for next packet
+ sendResponse(UseItemMessage(avatar_guid, item_used_guid, object_guid, 0, unk3, unk4, unk5, unk6, unk7, unk8, itemType))
+ sendResponse(ObjectDeleteMessage(kit.GUID, 0))
+ taskResolver ! GUIDTask.UnregisterEquipment(kit)(continent.GUID)
+ player.History(RepairFromKit(PlayerSource(player), 200, kit.Definition))
+ player.Armor = player.Armor + 200
+ sendResponse(PlanetsideAttributeMessage(avatar_guid, 4, player.Armor))
+ continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(avatar_guid, 4, player.Armor))
+ }
+ }
+ else if(kit.Definition == GlobalDefinitions.super_staminakit) {
+ if(player.Stamina == player.MaxStamina) {
+ sendResponse(ChatMsg(ChatMessageType.UNK_225, false, "", "Stamina at maximum - No recharge required.", None))
+ }
+ else if(System.currentTimeMillis - whenUsedLastSSKit < 1200000) {
+ sendResponse(ChatMsg(ChatMessageType.UNK_225, false, "", s"@TimeUntilNextUse^${300 - (System.currentTimeMillis - whenUsedLastSSKit) / 1200}~", None))
+ }
+ else {
+ whenUsedLastSSKit = System.currentTimeMillis
+ player.Slot(index).Equipment = None //remove from slot immediately; must exist on client for next packet
+ sendResponse(UseItemMessage(avatar_guid, item_used_guid, object_guid, 0, unk3, unk4, unk5, unk6, unk7, unk8, itemType))
+ sendResponse(ObjectDeleteMessage(kit.GUID, 0))
+ taskResolver ! GUIDTask.UnregisterEquipment(kit)(continent.GUID)
+ player.Stamina = player.Stamina + 100
+ }
+ }
+ else {
+ log.warn(s"UseItem: $kit behavior not supported")
+ }
- case None =>
- log.error(s"UseItem: anticipated a $kit, but can't find it")
- false
- }
- if(kitIsUsed) {
- //kit was found belonging to player and was used
- player.SetLastUsedTime(kid, time)
- player.Slot(indexOpt.get).Equipment = None //remove from slot immediately; must exist on client for next packet
- sendResponse(UseItemMessage(avatar_guid, item_used_guid, object_guid, 0, unk3, unk4, unk5, unk6, unk7, unk8, itemType))
- sendResponse(ObjectDeleteMessage(kit.GUID, 0))
- taskResolver ! GUIDTask.UnregisterEquipment(kit)(continent.GUID)
- }
+ case None =>
+ log.error(s"UseItem: anticipated a $kit, but can't find it")
}
-
case Some(item) =>
log.warn(s"UseItem: looking for Kit to use, but found $item instead")
case None =>
@@ -5519,6 +5816,7 @@ class WorldSessionActor extends Actor
CancelZoningProcessWithDescriptiveReason("cancel_use")
PlayerActionsToCancel()
CancelAllProximityUnits()
+ continent.Population ! Zone.Population.Release(avatar)
GoToDeploymentMap()
case _ => ;
}
@@ -6258,6 +6556,151 @@ class WorldSessionActor extends Actor
case default => log.error(s"Unhandled GamePacket $pkt")
}
+ /**
+ * Iterate over a group of `EquipmentSlot`s, some of which may be occupied with an item.
+ * Remove any encountered items and add them to an output `List`.
+ * @param iter the `Iterator` of `EquipmentSlot`s
+ * @param index a number that equals the "current" holster slot (`EquipmentSlot`)
+ * @param list a persistent `List` of `Equipment` in the holster slots
+ * @return a `List` of `Equipment` in the holster slots
+ */
+ @tailrec private def clearHolsters(iter : Iterator[EquipmentSlot], index : Int = 0, list : List[InventoryItem] = Nil) : List[InventoryItem] = {
+ if(!iter.hasNext) {
+ list
+ }
+ else {
+ val slot = iter.next
+ slot.Equipment match {
+ case Some(equipment) =>
+ slot.Equipment = None
+ clearHolsters(iter, index + 1, InventoryItem(equipment, index) +: list)
+ case None =>
+ clearHolsters(iter, index + 1, list)
+ }
+ }
+ }
+
+ /**
+ * Iterate over a group of `EquipmentSlot`s, some of which may be occupied with an item.
+ * For any slots that are not yet occupied by an item, search through the `List` and find an item that fits in that slot.
+ * Add that item to the slot and remove it from the list.
+ * @param iter the `Iterator` of `EquipmentSlot`s
+ * @param list a `List` of all `Equipment` that is not yet assigned to a holster slot or an inventory slot
+ * @return the `List` of all `Equipment` not yet assigned to a holster slot or an inventory slot
+ */
+ @tailrec private def fillEmptyHolsters(iter : Iterator[EquipmentSlot], list : List[InventoryItem]) : List[InventoryItem] = {
+ if(!iter.hasNext) {
+ list
+ }
+ else {
+ val slot = iter.next
+ if(slot.Equipment.isEmpty) {
+ list.find(item => item.obj.Size == slot.Size) match {
+ case Some(obj) =>
+ val index = list.indexOf(obj)
+ slot.Equipment = obj.obj
+ fillEmptyHolsters(iter, list.take(index) ++ list.drop(index + 1))
+ case None =>
+ fillEmptyHolsters(iter, list)
+ }
+ }
+ else {
+ fillEmptyHolsters(iter, list)
+ }
+ }
+ }
+
+ /**
+ * Construct tasking that coordinates the following:
+ * 1) Accept a new piece of `Equipment` and register it with a globally unique identifier.
+ * 2) Once it is registered, give the `Equipment` to `target`.
+ * @param target what object will accept the new `Equipment`
+ * @param obj the new `Equipment`
+ * @param index the slot where the new `Equipment` will be placed
+ * @see `GUIDTask.RegisterEquipment`
+ * @see `PutInSlot`
+ * @return a `TaskResolver.GiveTask` message
+ */
+ private def PutEquipmentInSlot(target : PlanetSideGameObject with Container, obj : Equipment, index : Int) : TaskResolver.GiveTask = {
+ val regTask = GUIDTask.RegisterEquipment(obj)(continent.GUID)
+ obj match {
+ case tool : Tool =>
+ val linearToolTask = TaskResolver.GiveTask(regTask.task) +: regTask.subs
+ TaskResolver.GiveTask(PutInSlot(target, tool, index).task, linearToolTask)
+ case _ =>
+ TaskResolver.GiveTask(PutInSlot(target, obj, index).task, List(regTask))
+ }
+ }
+
+ /**
+ * Construct tasking that coordinates the following:
+ * 1) Remove a new piece of `Equipment` from where it is currently stored.
+ * 2) Once it is removed, un-register the `Equipment`'s globally unique identifier.
+ * @param target the object that currently possesses the `Equipment`
+ * @param obj the `Equipment`
+ * @param index the slot from where the `Equipment` will be removed
+ * @see `GUIDTask.UnregisterEquipment`
+ * @see `RemoveFromSlot`
+ * @return a `TaskResolver.GiveTask` message
+ */
+ private def RemoveEquipmentFromSlot(target : PlanetSideGameObject with Container, obj : Equipment, index : Int) : TaskResolver.GiveTask = {
+ val regTask = GUIDTask.UnregisterEquipment(obj)(continent.GUID)
+ //to avoid an error from a GUID-less object from being searchable, it is removed from the inventory first
+ obj match {
+ case _ : Tool =>
+ TaskResolver.GiveTask(regTask.task, RemoveFromSlot(target, obj, index) +: regTask.subs)
+ case _ =>
+ TaskResolver.GiveTask(regTask.task, List(RemoveFromSlot(target, obj, index)))
+ }
+ }
+
+ /**
+ * Construct tasking that gives the `Equipment` to `target`.
+ * @param target what object will accept the new `Equipment`
+ * @param obj the new `Equipment`
+ * @param index the slot where the new `Equipment` will be placed
+ * @return a `TaskResolver.GiveTask` message
+ */
+ private def PutInSlot(target : PlanetSideGameObject with Container, obj : Equipment, index : Int) : TaskResolver.GiveTask = {
+ TaskResolver.GiveTask(
+ new Task() {
+ private val localTarget = target
+ private val localIndex = index
+ private val localObject = obj
+ private val localAnnounce = self
+ private val localService = continent.AvatarEvents
+
+ override def isComplete : Task.Resolution.Value = {
+ if(localTarget.Slot(localIndex).Equipment.contains(localObject)) {
+ Task.Resolution.Success
+ }
+ else {
+ Task.Resolution.Incomplete
+ }
+ }
+
+ def Execute(resolver : ActorRef) : Unit = {
+ localTarget.Slot(localIndex).Equipment = localObject
+ resolver ! scala.util.Success(this)
+ }
+
+ override def onSuccess() : Unit = {
+ val definition = localObject.Definition
+ localAnnounce ! ResponseToSelf(
+ ObjectCreateDetailedMessage(
+ definition.ObjectId,
+ localObject.GUID,
+ ObjectCreateMessageParent(localTarget.GUID, localIndex),
+ definition.Packet.DetailedConstructorData(localObject).get
+ )
+ )
+ if(localTarget.VisibleSlots.contains(localIndex)) {
+ localService ! AvatarServiceMessage(continent.Id, AvatarAction.EquipmentInHand(localTarget.GUID, localTarget.GUID, localIndex, localObject))
+ }
+ }
+ })
+ }
+
/**
* Construct tasking that registers all aspects of a `Player` avatar.
* `Players` are complex objects that contain a variety of other register-able objects and each of these objects much be handled.
@@ -6270,8 +6713,6 @@ class WorldSessionActor extends Actor
private val localPlayer = tplayer
private val localAnnounce = self
- override def Description : String = s"register new player avatar ${localPlayer.Name}"
-
override def isComplete : Task.Resolution.Value = {
if(localPlayer.HasGUID) {
Task.Resolution.Success
@@ -6284,11 +6725,11 @@ class WorldSessionActor extends Actor
def Execute(resolver : ActorRef) : Unit = {
log.info(s"Player $localPlayer is registered")
resolver ! scala.util.Success(this)
- localAnnounce ! NewPlayerLoaded(localPlayer) //alerts WorldSessionActor
+ localAnnounce ! NewPlayerLoaded(localPlayer) //alerts WSA
}
override def onFailure(ex : Throwable) : Unit = {
- localAnnounce ! PlayerFailedToLoad(localPlayer) //alerts WorldSessionActor
+ localAnnounce ! PlayerFailedToLoad(localPlayer) //alerts WSA
}
}, List(GUIDTask.RegisterAvatar(tplayer)(continent.GUID))
)
@@ -6306,8 +6747,6 @@ class WorldSessionActor extends Actor
private val localPlayer = tplayer
private val localAnnounce = self
- override def Description : String = s"register player avatar ${localPlayer.Name}"
-
override def isComplete : Task.Resolution.Value = {
if(localPlayer.HasGUID) {
Task.Resolution.Success
@@ -6320,11 +6759,11 @@ class WorldSessionActor extends Actor
def Execute(resolver : ActorRef) : Unit = {
log.info(s"Player $localPlayer is registered")
resolver ! scala.util.Success(this)
- localAnnounce ! PlayerLoaded(localPlayer) //alerts WorldSessionActor
+ localAnnounce ! PlayerLoaded(localPlayer) //alerts WSA
}
override def onFailure(ex : Throwable) : Unit = {
- localAnnounce ! PlayerFailedToLoad(localPlayer) //alerts WorldSessionActor
+ localAnnounce ! PlayerFailedToLoad(localPlayer) //alerts WSA
}
}, List(GUIDTask.RegisterPlayer(tplayer)(continent.GUID))
)
@@ -6342,8 +6781,6 @@ class WorldSessionActor extends Actor
new Task() {
private val localVehicle = vehicle
- override def Description : String = s"register a ${localVehicle.Definition.Name}"
-
override def isComplete : Task.Resolution.Value = {
if(localVehicle.HasGUID) {
Task.Resolution.Success
@@ -6394,8 +6831,6 @@ class WorldSessionActor extends Actor
private val localVehicle = vehicle
private val localAnnounce = self
- override def Description : String = s"register a ${localVehicle.Definition.Name} manned by ${localDriver.Name}"
-
override def isComplete : Task.Resolution.Value = {
if(localVehicle.HasGUID) {
Task.Resolution.Success
@@ -6436,8 +6871,6 @@ class WorldSessionActor extends Actor
private val localVehicleService = continent.VehicleEvents
private val localZone = continent
- override def Description : String = s"register a ${localVehicle.Definition.Name} for spawn pad"
-
override def isComplete : Task.Resolution.Value = {
if(localVehicle.HasGUID) {
Task.Resolution.Success
@@ -6461,8 +6894,6 @@ class WorldSessionActor extends Actor
private val localDriver = driver
private val localAnnounce = self
- override def Description : String = s"register a ${localVehicle.Definition.Name} driven by ${localDriver.Name}"
-
override def isComplete : Task.Resolution.Value = {
if(localVehicle.HasGUID && localDriver.HasGUID) {
Task.Resolution.Success
@@ -6475,12 +6906,12 @@ class WorldSessionActor extends Actor
def Execute(resolver : ActorRef) : Unit = {
localDriver.VehicleSeated = localVehicle.GUID
Vehicles.Own(localVehicle, localDriver)
- localAnnounce ! NewPlayerLoaded(localDriver) //alerts WorldSessionActor
+ localAnnounce ! NewPlayerLoaded(localDriver) //alerts WSA
resolver ! scala.util.Success(this)
}
override def onFailure(ex : Throwable) : Unit = {
- localAnnounce ! PlayerFailedToLoad(localDriver) //alerts WorldSessionActor
+ localAnnounce ! PlayerFailedToLoad(localDriver) //alerts WSA
}
}, List(GUIDTask.RegisterAvatar(driver)(continent.GUID), GUIDTask.RegisterVehicle(obj)(continent.GUID)))
}
@@ -6499,8 +6930,6 @@ class WorldSessionActor extends Actor
private val globalProjectile = obj
private val localAnnounce = self
- override def Description : String = s"register a ${globalProjectile.profile.Name}"
-
override def isComplete : Task.Resolution.Value = {
if(globalProjectile.HasGUID) {
Task.Resolution.Success
@@ -6524,8 +6953,6 @@ class WorldSessionActor extends Actor
private val localVehicle = obj
private val localDriver = driver
- override def Description : String = s"unregister a ${localVehicle.Definition.Name} driven by ${localDriver.Name}"
-
override def isComplete : Task.Resolution.Value = {
if(!localVehicle.HasGUID && !localDriver.HasGUID) {
Task.Resolution.Success
@@ -6555,8 +6982,6 @@ class WorldSessionActor extends Actor
private val localAnnounce = continent.AvatarEvents
private val localMsg = AvatarServiceMessage(continent.Id, AvatarAction.ObjectDelete(player.GUID, obj.GUID, 2))
- override def Description : String = s"unregister a ${globalProjectile.profile.Name}"
-
override def isComplete : Task.Resolution.Value = {
if(!globalProjectile.HasGUID) {
Task.Resolution.Success
@@ -6574,6 +6999,48 @@ class WorldSessionActor extends Actor
)
}
+ /**
+ * Construct tasking that removes the `Equipment` to `target`.
+ * @param target what object that contains the `Equipment`
+ * @param obj the `Equipment`
+ * @param index the slot where the `Equipment` is stored
+ * @return a `TaskResolver.GiveTask` message
+ */
+ private def RemoveFromSlot(target : PlanetSideGameObject with Container, obj : Equipment, index : Int) : TaskResolver.GiveTask = {
+ TaskResolver.GiveTask(
+ new Task() {
+ private val localTarget = target
+ private val localIndex = index
+ private val localObject = obj
+ private val localObjectGUID = obj.GUID
+ private val localAnnounce = self //self may not be the same when it executes
+ private val localService = continent.AvatarEvents
+ private val localContinent = continent.Id
+
+ override def isComplete : Task.Resolution.Value = {
+ if(localTarget.Slot(localIndex).Equipment.contains(localObject)) {
+ Task.Resolution.Incomplete
+ }
+ else {
+ Task.Resolution.Success
+ }
+ }
+
+ def Execute(resolver : ActorRef) : Unit = {
+ localTarget.Slot(localIndex).Equipment = None
+ resolver ! scala.util.Success(this)
+ }
+
+ override def onSuccess() : Unit = {
+ localAnnounce ! ResponseToSelf( ObjectDeleteMessage(localObjectGUID, 0))
+ if(localTarget.VisibleSlots.contains(localIndex)) {
+ localService ! AvatarServiceMessage(localContinent, AvatarAction.ObjectDelete(localTarget.GUID, localObjectGUID))
+ }
+ }
+ }
+ )
+ }
+
/**
* If the projectile object is unregistered, register it.
* If the projectile object is already registered, unregister it and then register it again.
@@ -6598,6 +7065,45 @@ class WorldSessionActor extends Actor
}
}
+ /**
+ * After some subtasking is completed, draw a particular slot, as if an `ObjectHeldMessage` packet was sent/received.
+ *
+ * The resulting `Task` is most useful for sequencing MAX weaponry when combined with the proper subtasks.
+ * @param player the player
+ * @param index the slot to be drawn
+ * @param priorTasking subtasks that needs to be accomplished first
+ * @return a `TaskResolver.GiveTask` message
+ */
+ private def DelayedObjectHeld(player : Player, index : Int, priorTasking : List[TaskResolver.GiveTask]) : TaskResolver.GiveTask = {
+ TaskResolver.GiveTask(
+ new Task() {
+ private val localPlayer = player
+ private val localSlot = index
+ private val localAnnounce = self
+ private val localService = continent.AvatarEvents
+
+ override def isComplete : Task.Resolution.Value = {
+ if(localPlayer.DrawnSlot == localSlot) {
+ Task.Resolution.Success
+ }
+ else {
+ Task.Resolution.Incomplete
+ }
+ }
+
+ def Execute(resolver : ActorRef) : Unit = {
+ localPlayer.DrawnSlot = localSlot
+ resolver ! scala.util.Success(this)
+ }
+
+ override def onSuccess() : Unit = {
+ localAnnounce ! ResponseToSelf( ObjectHeldMessage(localPlayer.GUID, localSlot, true))
+ localService ! AvatarServiceMessage(localPlayer.Continent, AvatarAction.ObjectHeld(localPlayer.GUID, localSlot))
+ }
+ }, priorTasking
+ )
+ }
+
/**
* Before calling `Interstellar.GetWorld` to change zones, perform the following task (which can be a nesting of subtasks).
* @param priorTask the tasks to perform
@@ -6608,13 +7114,10 @@ class WorldSessionActor extends Actor
TaskResolver.GiveTask(
new Task() {
private val localZone = continent
- private val localNewZone = zoneId
private val localAvatarMsg = Zone.Population.Leave(avatar)
private val localService = cluster
private val localServiceMsg = InterstellarCluster.GetWorld(zoneId)
- override def Description : String = s"additional tasking in zone ${localZone.Id} before switching to zone $localNewZone"
-
override def isComplete : Task.Resolution.Value = priorTask.task.isComplete
def Execute(resolver : ActorRef) : Unit = {
@@ -6629,12 +7132,9 @@ class WorldSessionActor extends Actor
def CallBackForTask(task : TaskResolver.GiveTask, sendTo : ActorRef, pass : Any) : TaskResolver.GiveTask = {
TaskResolver.GiveTask(
new Task() {
- private val localDesc = task.task.Description
private val destination = sendTo
private val passMsg = pass
- override def Description : String = s"callback for tasking $localDesc"
-
def Execute(resolver : ActorRef) : Unit = {
destination ! passMsg
resolver ! scala.util.Success(this)
@@ -6868,6 +7368,105 @@ class WorldSessionActor extends Actor
*/
def FindWeapon : Option[Tool] = FindContainedWeapon._2
+ /**
+ * Within a specified `Container`, find the smallest number of `Equipment` objects of a certain qualifying type
+ * whose sum count is greater than, or equal to, a `desiredAmount` based on an accumulator method.
+ *
+ * In an occupied `List` of returned `Inventory` entries, all but the last entry is typically considered "emptied."
+ * For objects with contained quantities, the last entry may require having that quantity be set to a non-zero number.
+ * @param obj the `Container` to search
+ * @param filterTest test used to determine inclusivity of `Equipment` collection
+ * @param desiredAmount how much is requested
+ * @param counting test used to determine value of found `Equipment`;
+ * defaults to one per entry
+ * @return a `List` of all discovered entries totaling approximately the amount requested
+ */
+ def FindEquipmentStock(obj : Container,
+ filterTest : (Equipment)=>Boolean,
+ desiredAmount : Int,
+ counting : (Equipment)=>Int = DefaultCount) : List[InventoryItem] = {
+ var currentAmount : Int = 0
+ obj.Inventory.Items
+ .filter(item => filterTest(item.obj))
+ .toList
+ .sortBy(_.start)
+ .takeWhile(entry => {
+ val previousAmount = currentAmount
+ currentAmount += counting(entry.obj)
+ previousAmount < desiredAmount
+ })
+ }
+
+ /**
+ * The default counting function for an item.
+ * Counts the number of item(s).
+ * @param e the `Equipment` object
+ * @return the quantity;
+ * always one
+ */
+ def DefaultCount(e : Equipment) : Int = 1
+
+ /**
+ * The counting function for an item of `AmmoBox`.
+ * Counts the `Capacity` of the ammunition.
+ * @param e the `Equipment` object
+ * @return the quantity
+ */
+ def CountAmmunition(e : Equipment) : Int = {
+ e match {
+ case a : AmmoBox =>
+ a.Capacity
+ case _ =>
+ 0
+ }
+ }
+
+ /**
+ * The counting function for an item of `Tool` where the item is also a grenade.
+ * Counts the number of grenades.
+ * @see `GlobalDefinitions.isGrenade`
+ * @param e the `Equipment` object
+ * @return the quantity
+ */
+ def CountGrenades(e : Equipment) : Int = {
+ e match {
+ case t : Tool =>
+ (GlobalDefinitions.isGrenade(t.Definition):Int) * t.Magazine
+ case _ =>
+ 0
+ }
+ }
+
+ /**
+ * Flag an `AmmoBox` object that matches for the given ammunition type.
+ * @param ammo the type of `Ammo` to check
+ * @param e the `Equipment` object
+ * @return `true`, if the object is an `AmmoBox` of the correct ammunition type; `false`, otherwise
+ */
+ def FindAmmoBoxThatUses(ammo : Ammo.Value)(e : Equipment) : Boolean = {
+ e match {
+ case t : AmmoBox =>
+ t.AmmoType == ammo
+ case _ =>
+ false
+ }
+ }
+
+ /**
+ * Flag a `Tool` object that matches for loading the given ammunition type.
+ * @param ammo the type of `Ammo` to check
+ * @param e the `Equipment` object
+ * @return `true`, if the object is a `Tool` that loads the correct ammunition type; `false`, otherwise
+ */
+ def FindToolThatUses(ammo : Ammo.Value)(e : Equipment) : Boolean = {
+ e match {
+ case t : Tool =>
+ t.Definition.AmmoTypes.map { _.AmmoType }.contains(ammo)
+ case _ =>
+ false
+ }
+ }
+
/**
* Get the current `Vehicle` object that the player is riding/driving.
* The vehicle must be found solely through use of `player.VehicleSeated`.
@@ -6887,6 +7486,37 @@ class WorldSessionActor extends Actor
}
}
+ /**
+ * Given an object that contains an item (`Equipment`) in its `Inventory` at a certain location,
+ * remove it permanently.
+ * @param obj the `Container`
+ * @param start where the item can be found
+ * @param item an object to unregister;
+ * not explicitly checked
+ */
+ private def DeleteEquipment(obj : PlanetSideGameObject with Container)(start : Int, item : Equipment) : Unit = {
+ val item_guid = item.GUID
+ obj.Slot(start).Equipment = None
+ //obj.Inventory -= start
+ taskResolver ! GUIDTask.UnregisterEquipment(item)(continent.GUID)
+ sendResponse(ObjectDeleteMessage(item_guid, 0))
+ }
+
+ /**
+ * Given a vehicle that contains an item (`Equipment`) in its `Trunk` at a certain location,
+ * remove it permanently.
+ * @see `DeleteEquipment`
+ * @param obj the `Vehicle`
+ * @param start where the item can be found
+ * @param item an object to unregister;
+ * not explicitly checked
+ */
+ private def DeleteEquipmentFromVehicle(obj : Vehicle)(start : Int, item : Equipment) : Unit = {
+ val item_guid = item.GUID
+ DeleteEquipment(obj)(start, item)
+ continent.VehicleEvents ! VehicleServiceMessage(s"${obj.Actor}", VehicleAction.UnstowEquipment(player.GUID, item_guid))
+ }
+
/**
* Given an object that contains a box of amunition in its `Inventory` at a certain location,
* change the amount of ammunition within that box.
@@ -6918,6 +7548,251 @@ class WorldSessionActor extends Actor
}
}
+ /**
+ * Announce that an already-registered `AmmoBox` object exists in a given position in some `Container` object's inventory.
+ * @see `StowEquipmentInVehicles`
+ * @see `ChangeAmmoMessage`
+ * @param obj the `Container` object
+ * @param index an index in `obj`'s inventory
+ * @param item an `AmmoBox`
+ */
+ def StowEquipment(obj : PlanetSideGameObject with Container)(index : Int, item : AmmoBox) : Unit = {
+ obj.Inventory += index -> item
+ sendResponse(ObjectAttachMessage(obj.GUID, item.GUID, index))
+ }
+
+ /**
+ * Announce that an already-registered `AmmoBox` object exists in a given position in some vehicle's inventory.
+ * @see `StowEquipment`
+ * @see `ChangeAmmoMessage`
+ * @param obj the `Vehicle` object
+ * @param index an index in `obj`'s inventory
+ * @param item an `AmmoBox`
+ */
+ def StowEquipmentInVehicles(obj : Vehicle)(index : Int, item : AmmoBox) : Unit = {
+ StowEquipment(obj)(index, item)
+ continent.VehicleEvents ! VehicleServiceMessage(s"${obj.Actor}", VehicleAction.StowEquipment(player.GUID, obj.GUID, index, item))
+ }
+
+ /**
+ * Prepare tasking that registers an `AmmoBox` object
+ * and announces that it exists in a given position in some `Container` object's inventory.
+ * `PutEquipmentInSlot` is the fastest way to achieve these goals.
+ * @see `StowNewEquipmentInVehicle`
+ * @see `ChangeAmmoMessage`
+ * @param obj the `Container` object
+ * @param index an index in `obj`'s inventory
+ * @param item the `Equipment` item
+ * @return a `TaskResolver.GiveTask` chain that executes the action
+ */
+ def StowNewEquipment(obj : PlanetSideGameObject with Container)(index : Int, item : Equipment) : TaskResolver.GiveTask = {
+ PutEquipmentInSlot(obj, item, index)
+ }
+
+ /**
+ * Prepare tasking that registers an `AmmoBox` object
+ * and announces that it exists in a given position in some vehicle's inventory.
+ * `PutEquipmentInSlot` is the fastest way to achieve these goals.
+ * @see `StowNewEquipment`
+ * @see `ChangeAmmoMessage`
+ * @param obj the `Container` object
+ * @param index an index in `obj`'s inventory
+ * @param item the `Equipment` item
+ * @return a `TaskResolver.GiveTask` chain that executes the action
+ */
+ def StowNewEquipmentInVehicle(obj : Vehicle)(index : Int, item : Equipment) : TaskResolver.GiveTask = {
+ TaskResolver.GiveTask(
+ new Task() {
+ private val localService = continent.VehicleEvents
+ private val localPlayer = player
+ private val localVehicle = obj
+ private val localIndex = index
+ private val localItem = item
+
+ override def isComplete : Task.Resolution.Value = Task.Resolution.Success
+
+ def Execute(resolver : ActorRef) : Unit = {
+ localService ! VehicleServiceMessage(
+ s"${localVehicle.Actor}",
+ VehicleAction.StowEquipment(localPlayer.GUID, localVehicle.GUID, localIndex, localItem)
+ )
+ resolver ! scala.util.Success(this)
+ }
+ },
+ List(StowNewEquipment(obj)(index, item))
+ )
+ }
+
+ /**
+ * Given an item, and two places, one where the item currently is and one where the item will be moved,
+ * perform a controlled transfer of the item.
+ * If something exists at the `destination` side of the transfer in the position that `item` will occupy,
+ * resolve its location as well by swapping it with where `item` originally was positioned.
+ *
+ * Parameter checks will not be performed.
+ * Do perform checks before sending data to this function.
+ * Do not call with incorrect or unverified data, e.g., `item` not actually being at `source` @ `index`.
+ * @param item the item being moved
+ * @param source the container in which `item` is currently located
+ * @param index the index position in `source` where `item` is currently located
+ * @param destination the container where `item` is being moved
+ * @param dest the index position in `destination` where `item` is being moved
+ * @param destinationCollisionEntry information about the contents in an area of `destination` starting at index `dest`
+ */
+ private def PerformMoveItem(item : Equipment,
+ source : PlanetSideGameObject with Container,
+ index : Int,
+ destination : PlanetSideGameObject with Container,
+ dest : Int,
+ destinationCollisionEntry : Option[InventoryItem]) : Unit = {
+ val item_guid = item.GUID
+ val source_guid = source.GUID
+ val destination_guid = destination.GUID
+ val player_guid = player.GUID
+ val indexSlot = source.Slot(index)
+ val sourceIsNotDestination : Boolean = source != destination //if source is destination, explicit OCDM is not required
+ if(sourceIsNotDestination) {
+ log.info(s"MoveItem: $item moved from $source @ $index to $destination @ $dest")
+ }
+ else {
+ log.info(s"MoveItem: $item moved from $index to $dest in $source")
+ }
+ //remove item from source
+ indexSlot.Equipment = None
+ source match {
+ case obj : Vehicle =>
+ continent.VehicleEvents ! VehicleServiceMessage(s"${obj.Actor}", VehicleAction.UnstowEquipment(player_guid, item_guid))
+ case obj : Player =>
+ if(obj.isBackpack || source.VisibleSlots.contains(index)) { //corpse being looted, or item was in hands
+ continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.ObjectDelete(player_guid, item_guid))
+ }
+ case _ => ;
+ }
+
+ destinationCollisionEntry match { //do we have a swap item in the destination slot?
+ case Some(InventoryItem(item2, destIndex)) => //yes, swap
+ //cleanly shuffle items around to avoid losing icons
+ //the next ObjectDetachMessage is necessary to avoid icons being lost, but only as part of this swap
+ sendResponse(ObjectDetachMessage(source_guid, item_guid, Vector3.Zero, 0f))
+ val item2_guid = item2.GUID
+ destination.Slot(destIndex).Equipment = None //remove the swap item from destination
+ (indexSlot.Equipment = item2) match {
+ case Some(_) => //item and item2 swapped places successfully
+ log.info(s"MoveItem: $item2 swapped to $source @ $index")
+ //remove item2 from destination
+ sendResponse(ObjectDetachMessage(destination_guid, item2_guid, Vector3.Zero, 0f))
+ destination match {
+ case obj : Vehicle =>
+ continent.VehicleEvents ! VehicleServiceMessage(s"${obj.Actor}", VehicleAction.UnstowEquipment(player_guid, item2_guid))
+ case obj : Player =>
+ if(obj.isBackpack || destination.VisibleSlots.contains(dest)) { //corpse being looted, or item was accessible
+ continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.ObjectDelete(player_guid, item2_guid))
+ //put hand down locally
+ if(dest == player.DrawnSlot) {
+ player.DrawnSlot = Player.HandsDownSlot
+ }
+ }
+ case _ => ;
+ }
+ //display item2 in source
+ if(sourceIsNotDestination && player == source) {
+ val objDef = item2.Definition
+ sendResponse(
+ ObjectCreateDetailedMessage(
+ objDef.ObjectId,
+ item2_guid,
+ ObjectCreateMessageParent(source_guid, index),
+ objDef.Packet.DetailedConstructorData(item2).get
+ )
+ )
+ }
+ else {
+ sendResponse(ObjectAttachMessage(source_guid, item2_guid, index))
+ }
+ source match {
+ case obj : Vehicle =>
+ item2.Faction = PlanetSideEmpire.NEUTRAL
+ continent.VehicleEvents ! VehicleServiceMessage(s"${obj.Actor}", VehicleAction.StowEquipment(player_guid, source_guid, index, item2))
+ case obj : Player =>
+ item2.Faction = obj.Faction
+ if(source.VisibleSlots.contains(index)) { //item is put in hands
+ continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.EquipmentInHand(player_guid, source_guid, index, item2))
+ }
+ else if(obj.isBackpack) { //corpse being given item
+ continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.StowEquipment(player_guid, source_guid, index, item2))
+ }
+ case _ =>
+ item2.Faction = PlanetSideEmpire.NEUTRAL
+ }
+
+ case None => //item2 does not fit; drop on ground
+ log.info(s"MoveItem: $item2 can not fit in swap location; dropping on ground @ ${source.Position}")
+ val pos = source.Position
+ val sourceOrientZ = source.Orientation.z
+ val orient : Vector3 = Vector3(0f, 0f, sourceOrientZ)
+ continent.Ground ! Zone.Ground.DropItem(item2, pos, orient)
+ sendResponse(ObjectDetachMessage(destination_guid, item2_guid, pos, sourceOrientZ)) //ground
+ val objDef = item2.Definition
+ destination match {
+ case obj : Vehicle =>
+ continent.VehicleEvents ! VehicleServiceMessage(s"${obj.Actor}", VehicleAction.UnstowEquipment(player_guid, item2_guid))
+ case _ => ;
+ //Player does not require special case; the act of dropping forces the item and icon to change
+ }
+ continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.DropItem(player_guid, item2, continent))
+ }
+
+ case None => ;
+ }
+ //move item into destination slot
+ destination.Slot(dest).Equipment = item
+ if(sourceIsNotDestination && player == destination) {
+ val objDef = item.Definition
+ sendResponse(
+ ObjectCreateDetailedMessage(
+ objDef.ObjectId,
+ item_guid,
+ ObjectCreateMessageParent(destination_guid, dest),
+ objDef.Packet.DetailedConstructorData(item).get
+ )
+ )
+ }
+ else {
+ sendResponse(ObjectAttachMessage(destination_guid, item_guid, dest))
+ }
+ destination match {
+ case obj : Vehicle =>
+ item.Faction = PlanetSideEmpire.NEUTRAL
+ continent.VehicleEvents ! VehicleServiceMessage(s"${obj.Actor}", VehicleAction.StowEquipment(player_guid, destination_guid, dest, item))
+ case obj : Player =>
+ if(destination.VisibleSlots.contains(dest)) { //item is put in hands
+ item.Faction = obj.Faction
+ continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.EquipmentInHand(player_guid, destination_guid, dest, item))
+ }
+ else if(obj.isBackpack) { //corpse being given item
+ item.Faction = PlanetSideEmpire.NEUTRAL
+ continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.StowEquipment(player_guid, destination_guid, dest, item))
+ }
+ case _ =>
+ item.Faction = PlanetSideEmpire.NEUTRAL
+ }
+ }
+
+ /**
+ * na
+ * @param equipment na
+ * @param obj na
+ * @return `true`, if the object is allowed to contain the type of equipment object
+ */
+ def PermitEquipmentStow(equipment : Equipment, obj : PlanetSideGameObject with Container) : Boolean = {
+ equipment match {
+ case _ : BoomerTrigger =>
+ obj.isInstanceOf[Player] //a BoomerTrigger can only be stowed in a player's holsters or inventory
+ case _ =>
+ true
+ }
+ }
+
/**
* na
* @param tool na
@@ -6932,23 +7807,21 @@ class WorldSessionActor extends Actor
FindEquipmentStock(obj, FindAmmoBoxThatUses(requestedAmmoType), fullMagazine, CountAmmunition).reverse match {
case Nil => ;
case x :: xs =>
- val (deleteFunc, modifyFunc) : (Equipment=>Future[Any], (AmmoBox, Int) => Unit) = obj match {
+ val (deleteFunc, modifyFunc) : ((Int, AmmoBox)=>Unit, (AmmoBox, Int)=>Unit) = obj match {
case (veh : Vehicle) =>
- (RemoveOldEquipmentFromInventory(veh, taskResolver), ModifyAmmunitionInVehicle(veh))
- case o : PlanetSideServerObject with Container =>
- (RemoveOldEquipmentFromInventory(o, taskResolver), ModifyAmmunition(o))
+ (DeleteEquipmentFromVehicle(veh), ModifyAmmunitionInVehicle(veh))
case _ =>
- throw new Exception("PerformToolAmmoChange: (remove/modify) should be a server object, not a regular game object")
+ (DeleteEquipment(obj), ModifyAmmunition(obj))
}
- val (stowNewFunc, stowFunc) : (Equipment=>TaskResolver.GiveTask, Equipment=>Future[Any]) = obj match {
- case o : PlanetSideServerObject with Container =>
- (PutNewEquipmentInInventoryOrDrop(o), PutEquipmentInInventoryOrDrop(o))
+ val (stowFuncTask, stowFunc) : ((Int, AmmoBox)=>TaskResolver.GiveTask, (Int, AmmoBox)=>Unit) = obj match {
+ case (veh : Vehicle) =>
+ (StowNewEquipmentInVehicle(veh), StowEquipmentInVehicles(veh))
case _ =>
- throw new Exception("PerformToolAmmoChange: (new/put) should be a server object, not a regular game object")
+ (StowNewEquipment(obj), StowEquipment(obj))
}
xs.foreach(item => {
obj.Inventory -= x.start
- deleteFunc(item.obj)
+ deleteFunc(item.start, item.obj.asInstanceOf[AmmoBox])
})
//box will be the replacement ammo; give it the discovered magazine and load it into the weapon @ 0
@@ -6980,7 +7853,8 @@ class WorldSessionActor extends Actor
val splitReloadAmmo : Int = sumReloadValue - fullMagazine
log.info(s"ChangeAmmo: taking ${originalBoxCapacity - splitReloadAmmo} from a box of ${originalBoxCapacity} $requestedAmmoType")
val boxForInventory = AmmoBox(box.Definition, splitReloadAmmo)
- taskResolver ! stowNewFunc(boxForInventory)
+ obj.Inventory += x.start -> boxForInventory //block early; assumption warning: swappable ammo types have the same icon size
+ taskResolver ! stowFuncTask(x.start, boxForInventory)
fullMagazine
})
sendResponse(InventoryStateMessage(box.GUID, tool.GUID, box.Capacity)) //should work for both players and vehicles
@@ -7014,16 +7888,25 @@ class WorldSessionActor extends Actor
if(previousBox.Capacity > 0) {
//split previousBox into AmmoBox objects of appropriate max capacity, e.g., 100 9mm -> 2 x 50 9mm
obj.Inventory.Fit(previousBox) match {
- case Some(_) =>
- stowFunc(previousBox)
+ case Some(index) =>
+ stowFunc(index, previousBox)
case None =>
- NormalItemDrop(player, continent)(previousBox)
+ NormalItemDrop(player, continent, continent.AvatarEvents)(previousBox)
}
+ val dropFunc : (Equipment)=>TaskResolver.GiveTask = NewItemDrop(player, continent, continent.AvatarEvents)
AmmoBox.Split(previousBox) match {
- case Nil | List(_) => ; //done (the former case is technically not possible)
+ case Nil | _ :: Nil => ; //done (the former case is technically not possible)
case _ :: xs =>
modifyFunc(previousBox, 0) //update to changed capacity value
- xs.foreach(box => { taskResolver ! stowNewFunc(box) })
+ xs.foreach(box => {
+ obj.Inventory.Fit(box) match {
+ case Some(index) =>
+ obj.Inventory += index -> box //block early, for purposes of Fit
+ taskResolver ! stowFuncTask(index, box)
+ case None =>
+ taskResolver ! dropFunc(box)
+ }
+ })
}
}
else {
@@ -7044,10 +7927,13 @@ class WorldSessionActor extends Actor
* curried for callback
* @param zone the continent in which the item is being dropped;
* curried for callback
+ * @param service a reference to the event system that announces that the item has been dropped on the ground;
+ * "AvatarService";
+ * curried for callback
* @param item the item
*/
- def NormalItemDrop(obj : PlanetSideServerObject with Container, zone : Zone)(item : Equipment) : Unit = {
- zone.Ground.tell(Zone.Ground.DropItem(item, obj.Position, Vector3.z(obj.Orientation.z)), obj.Actor)
+ def NormalItemDrop(obj : PlanetSideGameObject with Container, zone : Zone, service : ActorRef)(item : Equipment) : Unit = {
+ continent.Ground ! Zone.Ground.DropItem(item, obj.Position, Vector3.z(obj.Orientation.z))
}
/**
@@ -7057,15 +7943,16 @@ class WorldSessionActor extends Actor
* curried for callback
* @param zone the continent in which the item is being dropped;
* curried for callback
+ * @param service a reference to the event system that announces that the item has been dropped on the ground;
+ * "AvatarService";
+ * curried for callback
* @param item the item
*/
- def NewItemDrop(obj : PlanetSideServerObject with Container, zone : Zone)(item : Equipment) : TaskResolver.GiveTask = {
+ def NewItemDrop(obj : PlanetSideGameObject with Container, zone : Zone, service : ActorRef)(item : Equipment) : TaskResolver.GiveTask = {
TaskResolver.GiveTask(
new Task() {
private val localItem = item
- private val localFunc : (Equipment)=>Unit = NormalItemDrop(obj, zone)
-
- override def Description : String = s"dropping a new ${localItem.Definition.Name} on the ground"
+ private val localFunc : (Equipment)=>Unit = NormalItemDrop(obj, zone, service)
def Execute(resolver : ActorRef) : Unit = {
localFunc(localItem)
@@ -7080,21 +7967,21 @@ class WorldSessionActor extends Actor
* @param tool a weapon
*/
def FireCycleCleanup(tool : Tool) : Unit = {
- //TODO replaced by more appropriate functionality in the future
+ //TODO this is temporary and will be replaced by more appropriate functionality in the future.
val tdef = tool.Definition
if(GlobalDefinitions.isGrenade(tdef)) {
val ammoType = tool.AmmoType
FindEquipmentStock(player, FindToolThatUses(ammoType), 3, CountGrenades).reverse match { //do not search sidearm holsters
case Nil =>
log.info(s"no more $ammoType grenades")
- RemoveOldEquipmentFromInventory(player, taskResolver)(tool)
+ taskResolver ! RemoveEquipmentFromSlot(player, tool, player.Find(tool).get)
case x :: xs => //this is similar to ReloadMessage
val box = x.obj.asInstanceOf[Tool]
val tailReloadValue : Int = if(xs.isEmpty) { 0 } else { xs.map(_.obj.asInstanceOf[Tool].Magazine).reduce(_ + _) }
val sumReloadValue : Int = box.Magazine + tailReloadValue
val actualReloadValue = (if(sumReloadValue <= 3) {
- RemoveOldEquipmentFromInventory(player, taskResolver)(x.obj)
+ taskResolver ! RemoveEquipmentFromSlot(player, x.obj, x.start)
sumReloadValue
}
else {
@@ -7103,14 +7990,36 @@ class WorldSessionActor extends Actor
})
log.info(s"found $actualReloadValue more $ammoType grenades to throw")
ModifyAmmunition(player)(tool.AmmoSlot.Box, -actualReloadValue) //grenade item already in holster (negative because empty)
- xs.foreach(item => { RemoveOldEquipmentFromInventory(player, taskResolver)(item.obj) })
+ xs.foreach(item => {
+ taskResolver ! RemoveEquipmentFromSlot(player, item.obj, item.start)
+ })
}
}
else if(tdef == GlobalDefinitions.phoenix) {
- RemoveOldEquipmentFromInventory(player, taskResolver)(tool)
+ taskResolver ! RemoveEquipmentFromSlot(player, tool, player.Find(tool).get)
}
}
+ /**
+ * A predicate used to determine if an `InventoryItem` object contains `Equipment` that should be dropped.
+ * Used to filter through lists of object data before it is placed into a player's inventory.
+ * Drop the item if:
+ * - the item is cavern equipment
+ * - the item is a `BoomerTrigger` type object
+ * - the item is a `router_telepad` type object
+ * - the item is another faction's exclusive equipment
+ * @param tplayer the player
+ * @return true if the item is to be dropped; false, otherwise
+ */
+ def DropPredicate(tplayer : Player) : (InventoryItem => Boolean) = entry => {
+ val objDef = entry.obj.Definition
+ val faction = GlobalDefinitions.isFactionEquipment(objDef)
+ GlobalDefinitions.isCavernEquipment(objDef) ||
+ objDef == GlobalDefinitions.router_telepad ||
+ entry.obj.isInstanceOf[BoomerTrigger] ||
+ (faction != tplayer.Faction && faction != PlanetSideEmpire.NEUTRAL)
+ }
+
/**
* Given an object globally unique identifier, search in a given location for it.
* @param object_guid the object
@@ -7119,7 +8028,7 @@ class WorldSessionActor extends Actor
* the first value is the container that matched correctly with the object's GUID;
* the second value is the slot position of the object
*/
- def FindInLocalContainer(object_guid : PlanetSideGUID)(parent : PlanetSideServerObject with Container) : Option[(PlanetSideServerObject with Container, Option[Int])] = {
+ def FindInLocalContainer(object_guid : PlanetSideGUID)(parent : PlanetSideGameObject with Container) : Option[(PlanetSideGameObject with Container, Option[Int])] = {
val slot : Option[Int] = parent.Find(object_guid)
slot match {
case place @ Some(_) =>
@@ -7163,14 +8072,14 @@ class WorldSessionActor extends Actor
else if(vehicle.Definition == GlobalDefinitions.ant) {
state match {
case DriveState.Deployed =>
- // We only want this WorldSessionActor (not other player's WorldSessionActor) to manage timers
+ // We only want this WSA (not other player's WSA) to manage timers
if(vehicle.Seats(0).Occupant.contains(player)){
// Start ntu regeneration
// If vehicle sends UseItemMessage with silo as target NTU regeneration will be disabled and orb particles will be disabled
antChargingTick = context.system.scheduler.scheduleOnce(1000 milliseconds, self, NtuCharging(player, vehicle))
}
case DriveState.Undeploying =>
- // We only want this WorldSessionActor (not other player's WorldSessionActor) to manage timers
+ // We only want this WSA (not other player's WSA) to manage timers
if(vehicle.Seats(0).Occupant.contains(player)){
antChargingTick.cancel() // Stop charging NTU if charging
}
@@ -7560,7 +8469,7 @@ class WorldSessionActor extends Actor
/**
* A part of the process of spawning the player into the game world.
* The function should work regardless of whether the player is alive or dead - it will make them alive.
- * It adds the `WorldSessionActor`-current `Player` to the current zone and sends out the expected packets.
+ * It adds the `WSA`-current `Player` to the current zone and sends out the expected packets.
*
* If that player is in a vehicle, it will construct that vehicle.
* If the player is the driver of the vehicle,
@@ -7575,7 +8484,7 @@ class WorldSessionActor extends Actor
* @see `GetKnownVehicleAndSeat`
* @see `LoadZoneTransferPassengerMessages`
* @see `Player.Spawn`
- * @see `ReloadItemCoolDownTimes`
+ * @see `ReloadUsedLastCoolDownTimes`
* @see `Vehicles.Own`
* @see `Vehicles.ReloadAccessPermissions`
*/
@@ -7662,7 +8571,19 @@ class WorldSessionActor extends Actor
continent.Population ! Zone.Population.Spawn(avatar, player)
//cautious redundancy
deadState = DeadState.Alive
- ReloadItemCoolDownTimes()
+ ReloadUsedLastCoolDownTimes()
+
+ val lTime = System.currentTimeMillis // PTS v3
+ for (i <- 0 to whenUsedLastItem.length-1) {
+ if (lTime - whenUsedLastItem(i) < 300000) {
+ sendResponse(AvatarVehicleTimerMessage(player.GUID, whenUsedLastItemName(i), 300 - ((lTime - whenUsedLastItem(i)) / 1000 toInt), true))
+ }
+ }
+ for (i <- 1 to 3) {
+ if (lTime - whenUsedLastMAX(i) < 300000) {
+ sendResponse(AvatarVehicleTimerMessage(player.GUID, whenUsedLastMAXName(i), 300 - ((lTime - whenUsedLastMAX(i)) / 1000 toInt), true))
+ }
+ }
}
/**
@@ -7795,7 +8716,7 @@ class WorldSessionActor extends Actor
* @see `ObjectAttachMessage`
* @see `ObjectCreateMessage`
* @see `PlayerInfo.LoginInfo`
- * @see `ReloadItemCoolDownTimes`
+ * @see `ReloadUsedLastCoolDownTimes`
* @see `UpdateWeaponAtSeatPosition`
* @see `Vehicles.ReloadAccessPermissions`
*/
@@ -7842,7 +8763,7 @@ class WorldSessionActor extends Actor
}
//cautious redundancy
deadState = DeadState.Alive
- ReloadItemCoolDownTimes()
+ ReloadUsedLastCoolDownTimes()
setupAvatarFunc = AvatarCreate
}
@@ -7854,10 +8775,10 @@ class WorldSessionActor extends Actor
* and this routine's normal operation when it revisits the same code.
* @see `avatarSetupFunc`
* @see `AvatarCreate`
- * @see `ReloadItemCoolDownTimes`
+ * @see `ReloadUsedLastCoolDownTimes`
*/
def AvatarDeploymentPassOver() : Unit = {
- ReloadItemCoolDownTimes()
+ ReloadUsedLastCoolDownTimes()
setupAvatarFunc = AvatarCreate
}
@@ -7866,31 +8787,16 @@ class WorldSessionActor extends Actor
* This is called "skill".
* @see `AvatarVehicleTimerMessage`
*/
- def ReloadItemCoolDownTimes() : Unit = {
- val time = System.currentTimeMillis
- //purchases
- val lastPurchases = avatar.GetAllLastPurchaseTimes
- delayedPurchaseEntries.collect { case (id, delay) if lastPurchases.contains(id) =>
- val lastTime = lastPurchases.getOrElse(id, 0L)
- val delay = delayedPurchaseEntries(id.toInt)
- if (time - lastTime < delay) {
- sendResponse(AvatarVehicleTimerMessage(player.GUID, player.ObjectTypeNameReference(id), ((delay - (time - lastTime)) / 1000) toInt, true))
+ def ReloadUsedLastCoolDownTimes() : Unit = {
+ val lTime = System.currentTimeMillis
+ for (i <- 0 to whenUsedLastItem.length-1) {
+ if (lTime - whenUsedLastItem(i) < 300000) {
+ sendResponse(AvatarVehicleTimerMessage(player.GUID, whenUsedLastItemName(i), 300 - ((lTime - whenUsedLastItem(i)) / 1000 toInt), true))
}
}
- //uses
- val lastUses = avatar.GetAllLastUsedTimes
- delayedGratificationEntries.collect { case (id, delay) if lastUses.contains(id) =>
- val lastTime = lastUses.getOrElse(id, 0L)
- val delay = delayedGratificationEntries(id.toInt)
- if (time - lastTime < delay) {
- sendResponse(AvatarVehicleTimerMessage(player.GUID, player.ObjectTypeNameReference(id), ((delay - (time - lastTime)) / 1000) toInt, true))
- }
- }
- //max exo-suits (specifically)
- (1 to 3).foreach { subtype =>
- val maxTime = player.GetLastUsedTime(ExoSuitType.MAX, subtype)
- if (maxTime > 0 && time - maxTime < 300000) { //5min
- sendResponse(AvatarVehicleTimerMessage(player.GUID, whenUsedLastMAXName(subtype), 300 - ((time - maxTime) / 1000 toInt), true))
+ for (i <- 1 to 3) {
+ if (lTime - whenUsedLastMAX(i) < 300000) {
+ sendResponse(AvatarVehicleTimerMessage(player.GUID, whenUsedLastMAXName(i), 300 - ((lTime - whenUsedLastMAX(i)) / 1000 toInt), true))
}
}
}
@@ -7917,16 +8823,18 @@ class WorldSessionActor extends Actor
* @param obj the player to be turned into a corpse
*/
def FriskDeadBody(obj : Player) : Unit = {
- if(!obj.isAlive) {
+ if(obj.isBackpack) {
obj.Slot(4).Equipment match {
case None => ;
case Some(knife) =>
- RemoveOldEquipmentFromInventory(obj, taskResolver)(knife)
+ obj.Slot(4).Equipment = None
+ taskResolver ! RemoveEquipmentFromSlot(obj, knife, 4)
}
obj.Slot(0).Equipment match {
case Some(arms : Tool) =>
if(GlobalDefinitions.isMaxArms(arms.Definition)) {
- RemoveOldEquipmentFromInventory(obj, taskResolver)(arms)
+ obj.Slot(0).Equipment = None
+ taskResolver ! RemoveEquipmentFromSlot(obj, arms, 0)
}
case _ => ;
}
@@ -7941,7 +8849,7 @@ class WorldSessionActor extends Actor
}
})
val triggers = RemoveBoomerTriggersFromInventory()
- triggers.foreach(trigger => { NormalItemDrop(obj, continent)(trigger) })
+ triggers.foreach(trigger => { NormalItemDrop(obj, continent, continent.AvatarEvents)(trigger) })
}
}
@@ -7963,15 +8871,13 @@ class WorldSessionActor extends Actor
def PrepareToTurnPlayerIntoCorpse(tplayer : Player, zone : Zone) : Unit = {
FriskDeadBody(tplayer)
if(!WellLootedDeadBody(tplayer)) {
- tplayer.Release
- zone.Population ! Zone.Corpse.Add(tplayer)
TurnPlayerIntoCorpse(tplayer)
+ zone.Population ! Zone.Corpse.Add(tplayer)
zone.AvatarEvents ! AvatarServiceMessage(zone.Id, AvatarAction.Release(tplayer, zone))
}
else {
//no items in inventory; leave no corpse
val pguid = tplayer.GUID
- zone.Population ! Zone.Population.Release(avatar)
sendResponse(ObjectDeleteMessage(pguid, 0))
zone.AvatarEvents ! AvatarServiceMessage(zone.Id, AvatarAction.ObjectDelete(pguid, pguid, 0))
taskResolver ! GUIDTask.UnregisterPlayer(tplayer)(zone.GUID)
@@ -7998,7 +8904,7 @@ class WorldSessionActor extends Actor
* `false`, otherwise
*/
def WellLootedDeadBody(obj : Player) : Boolean = {
- !obj.isAlive && obj.Holsters().count(_.Equipment.nonEmpty) == 0 && obj.Inventory.Size == 0
+ obj.isBackpack && obj.Holsters().count(_.Equipment.nonEmpty) == 0 && obj.Inventory.Size == 0
}
/**
@@ -8008,7 +8914,7 @@ class WorldSessionActor extends Actor
* `false`, otherwise
*/
def TryDisposeOfLootedCorpse(obj : Player) : Boolean = {
- if(obj.isBackpack && WellLootedDeadBody(obj)) {
+ if(WellLootedDeadBody(obj)) {
continent.AvatarEvents ! AvatarServiceMessage.Corpse(RemoverActor.HurrySpecific(List(obj), continent))
true
}
@@ -8430,7 +9336,7 @@ class WorldSessionActor extends Actor
case obj : ComplexDeployable if obj.CanDamage => obj.Actor ! Vitality.Damage(func)
case obj : SimpleDeployable if obj.CanDamage =>
- //damage is synchronized on `LSA` (results returned to and distributed from this `WorldSessionActor`)
+ //damage is synchronized on `LSA` (results returned to and distributed from this `WSA`)
continent.LocalEvents ! Vitality.DamageOn(obj, func)
case _ => ;
}
@@ -8463,11 +9369,53 @@ class WorldSessionActor extends Actor
)
}
+ /**
+ * Initialize the deployables backend information.
+ * @param avatar the player's core
+ */
+ def InitializeDeployableQuantities(avatar : Avatar) : Unit = {
+ log.info("Setting up combat engineering ...")
+ avatar.Deployables.Initialize(avatar.Certifications.toSet)
+ }
+
+ /**
+ * Initialize the UI elements for deployables.
+ * @param avatar the player's core
+ */
+ def InitializeDeployableUIElements(avatar : Avatar) : Unit = {
+ log.info("Setting up combat engineering UI ...")
+ UpdateDeployableUIElements(avatar.Deployables.UpdateUI())
+ }
+
+ /**
+ * The player learned a new certification.
+ * Update the deployables user interface elements if it was an "Engineering" certification.
+ * The certification "Advanced Hacking" also relates to an element.
+ * @param certification the certification that was added
+ * @param certificationSet all applicable certifications
+ */
+ def AddToDeployableQuantities(certification : CertificationType.Value, certificationSet : Set[CertificationType.Value]) : Unit = {
+ avatar.Deployables.AddToDeployableQuantities(certification, certificationSet)
+ UpdateDeployableUIElements(avatar.Deployables.UpdateUI(certification))
+ }
+
+ /**
+ * The player forgot a certification he previously knew.
+ * Update the deployables user interface elements if it was an "Engineering" certification.
+ * The certification "Advanced Hacking" also relates to an element.
+ * @param certification the certification that was added
+ * @param certificationSet all applicable certifications
+ */
+ def RemoveFromDeployablesQuantities(certification : CertificationType.Value, certificationSet : Set[CertificationType.Value]) : Unit = {
+ avatar.Deployables.RemoveFromDeployableQuantities(certification, certificationSet)
+ UpdateDeployableUIElements(avatar.Deployables.UpdateUI(certification))
+ }
+
/**
* Initialize the deployables user interface elements.
*
* All element initializations require both the maximum deployable amount and the current deployables active counts.
- * Until initialized, all elements will be RED 0/0 as if the corresponding certification were not `learn`ed.
+ * Until initialized, all elements will be RED 0/0 as if the cooresponding certification were not `learn`ed.
* The respective element will become a pair of numbers, the second always being non-zero, when properly initialized.
* The numbers will appear GREEN when more deployables of that type can be placed.
* The numbers will appear RED if the player can not place any more of that type of deployable.
@@ -8695,9 +9643,10 @@ class WorldSessionActor extends Actor
* @param index the slot index
* @param pos where to drop the object in the game world
*/
- def TryDropFDU(tool : ConstructionItem, index : Int, pos : Vector3) : Unit = {
- if(tool.Definition == GlobalDefinitions.advanced_ace) {
- DropEquipmentFromInventory(player)(tool, Some(pos))
+ def TryDropConstructionTool(tool : ConstructionItem, index : Int, pos : Vector3) : Unit = {
+ if(tool.Definition == GlobalDefinitions.advanced_ace &&
+ SafelyRemoveConstructionItemFromSlot(tool, index, "TryDropConstructionTool")) {
+ continent.Ground ! Zone.Ground.DropItem(tool, pos, Vector3.Zero)
}
}
@@ -8825,27 +9774,27 @@ class WorldSessionActor extends Actor
* `false`, otherwise
*/
def FindEquipmentToDelete(object_guid : PlanetSideGUID, obj : Equipment) : Boolean = {
- val findFunc : PlanetSideServerObject with Container => Option[(PlanetSideServerObject with Container, Option[Int])] =
+ val findFunc : PlanetSideGameObject with Container => Option[(PlanetSideGameObject with Container, Option[Int])] =
FindInLocalContainer(object_guid)
findFunc(player.Locker)
.orElse(findFunc(player))
.orElse(accessedContainer match {
- case Some(parent : PlanetSideServerObject) =>
+ case Some(parent) =>
findFunc(parent)
- case _ =>
+ case None =>
None
})
.orElse(FindLocalVehicle match {
- case Some(parent : PlanetSideServerObject) =>
+ case Some(parent) =>
findFunc(parent)
- case _ =>
+ case None =>
None
})
match {
case Some((parent, Some(slot))) =>
obj.Position = Vector3.Zero
- RemoveOldEquipmentFromInventory(parent, taskResolver)(obj)
+ taskResolver ! RemoveEquipmentFromSlot(parent, obj, slot)
log.info(s"RequestDestroy: equipment $obj")
true
@@ -8854,6 +9803,7 @@ class WorldSessionActor extends Actor
obj.Position = Vector3.Zero
continent.Ground ! Zone.Ground.RemoveItem(object_guid)
continent.AvatarEvents ! AvatarServiceMessage.Ground(RemoverActor.ClearSpecific(List(obj), continent))
+ continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.ObjectDelete(PlanetSideGUID(0), object_guid))
log.info(s"RequestDestroy: equipment $obj on ground")
true
}
@@ -8989,7 +9939,7 @@ class WorldSessionActor extends Actor
case Some(vehicle : Vehicle) => //driver or passenger in vehicle using a warp gate, or a droppod
LoadZoneInVehicle(vehicle, pos, ori, zone_id)
- case _ if player.HasGUID => //player is deconstructing self or instant action
+ case _ if player.HasGUID => //player is deconstructing self
val player_guid = player.GUID
sendResponse(ObjectDeleteMessage(player_guid, 4))
continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.ObjectDelete(player_guid, player_guid, 4))
@@ -9263,6 +10213,72 @@ class WorldSessionActor extends Actor
squadSetup = ZoneChangeSquadSetup
}
+ /**
+ * Primary functionality for tranferring a piece of equipment from a player's hands or his inventory to the ground.
+ * Items are always dropped at player's feet because for simplicity's sake
+ * because, by virtue of already standing there, the stability of the surface has been proven.
+ * The only exception to this is dropping items while falling.
+ * @see `Player.Find`
+ * `ObjectDetachMessage`
+ * @param item the `Equipment` object in the player's hand
+ * @param pos the game world coordinates where the object will be dropped
+ * @param orient a suggested orientation in which the object will settle when on the ground;
+ * as indicated, the simulation is only concerned with certain angles
+ */
+ def PutItemOnGround(item : Equipment, pos : Vector3, orient : Vector3) : Unit = {
+ CancelZoningProcessWithDescriptiveReason("cancel_use")
+ //TODO delay or reverse dropping item when player is falling down
+ item.Position = pos
+ item.Orientation = Vector3.z(orient.z)
+ item.Faction = PlanetSideEmpire.NEUTRAL
+ //dropped items rotate towards the user's standing direction
+ val exclusionId = player.Find(item) match {
+ //if the item is in our hands ...
+ case Some(slotNum) =>
+ player.Slot(slotNum).Equipment = None
+ sendResponse(ObjectDetachMessage(player.GUID, item.GUID, pos, orient.z))
+ sendResponse(ActionResultMessage.Pass)
+ player.GUID //we're dropping the item; don't need to see it dropped again
+ case None =>
+ PlanetSideGUID(0) //item is being introduced into the world upon drop
+ }
+ continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.DropItem(exclusionId, item, continent))
+ }
+
+ /**
+ * Primary functionality for tranferring a piece of equipment from the ground in a player's hands or his inventory.
+ * The final destination of the item in terms of slot position is not determined until the attempt is made.
+ * If it can not be placed in a slot correctly, the item will be returned to the ground in the same place.
+ * @see `Player.Fit`
+ * @param item the `Equipment` object on the ground
+ * @return `true`, if the object was properly picked up;
+ * `false` if it was returned to the ground
+ */
+ def PutItemInHand(item : Equipment) : Boolean = {
+ player.Fit(item) match {
+ case Some(slotNum) =>
+ CancelZoningProcessWithDescriptiveReason("cancel_use")
+ item.Faction = player.Faction
+ val item_guid = item.GUID
+ val player_guid = player.GUID
+ player.Slot(slotNum).Equipment = item
+ val definition = item.Definition
+ sendResponse(
+ ObjectCreateDetailedMessage(
+ definition.ObjectId,
+ item_guid,
+ ObjectCreateMessageParent(player_guid, slotNum),
+ definition.Packet.DetailedConstructorData(item).get
+ )
+ )
+ continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.PickupItem(player_guid, continent, player, slotNum, item))
+ true
+ case None =>
+ continent.Ground ! Zone.Ground.DropItem(item, item.Position, item.Orientation) //restore previous state
+ false
+ }
+ }
+
/**
* Attempt to link the router teleport system using the provided terminal information.
* Although additional states are necessary to properly use the teleportation system,
@@ -10102,7 +11118,8 @@ class WorldSessionActor extends Actor
* @see `Player.Release`
*/
def GoToDeploymentMap() : Unit = {
- deadState = DeadState.Release //we may be alive or dead, may or may not be a corpse
+ player.Release
+ deadState = DeadState.Release
sendResponse(AvatarDeadStateMessage(DeadState.Release, 0, 0, player.Position, player.Faction, true))
DrawCurrentAmsSpawnPoint()
}
@@ -10497,57 +11514,6 @@ class WorldSessionActor extends Actor
}
object WorldSessionActor {
- /** Object purchasing cooldowns.
- * key - object id
- * value - time last used (ms)
- * */
- val delayedPurchaseEntries : Map[Int, Long] = Map(
- GlobalDefinitions.ams.ObjectId -> 300000, //5min
- GlobalDefinitions.ant.ObjectId -> 300000, //5min
- GlobalDefinitions.apc_nc.ObjectId -> 300000, //5min
- GlobalDefinitions.apc_tr.ObjectId -> 300000, //5min
- GlobalDefinitions.apc_vs.ObjectId -> 300000, //5min
- GlobalDefinitions.aurora.ObjectId -> 300000, //5min
- GlobalDefinitions.battlewagon.ObjectId -> 300000, //5min
- GlobalDefinitions.dropship.ObjectId -> 300000, //5min
- GlobalDefinitions.flail.ObjectId -> 300000, //5min
- GlobalDefinitions.fury.ObjectId -> 300000, //5min
- GlobalDefinitions.galaxy_gunship.ObjectId -> 600000, //10min
- GlobalDefinitions.lodestar.ObjectId -> 300000, //5min
- GlobalDefinitions.liberator.ObjectId -> 300000, //5min
- GlobalDefinitions.lightgunship.ObjectId -> 300000, //5min
- GlobalDefinitions.lightning.ObjectId -> 300000, //5min
- GlobalDefinitions.magrider.ObjectId -> 300000, //5min
- GlobalDefinitions.mediumtransport.ObjectId -> 300000, //5min
- GlobalDefinitions.mosquito.ObjectId -> 300000, //5min
- GlobalDefinitions.phantasm.ObjectId -> 300000, //5min
- GlobalDefinitions.prowler.ObjectId -> 300000, //5min
- GlobalDefinitions.quadassault.ObjectId -> 300000, //5min
- GlobalDefinitions.quadstealth.ObjectId -> 300000, //5min
- GlobalDefinitions.router.ObjectId -> 300000, //5min
- GlobalDefinitions.switchblade.ObjectId -> 300000, //5min
- GlobalDefinitions.skyguard.ObjectId -> 300000, //5min
- GlobalDefinitions.threemanheavybuggy.ObjectId -> 300000, //5m
- GlobalDefinitions.thunderer.ObjectId -> 300000, //5min
- GlobalDefinitions.two_man_assault_buggy.ObjectId -> 300000, //5min
- GlobalDefinitions.twomanhoverbuggy.ObjectId -> 300000, //5min
- GlobalDefinitions.twomanheavybuggy.ObjectId -> 300000, //5min
- GlobalDefinitions.vanguard.ObjectId -> 300000, //5min
- GlobalDefinitions.vulture.ObjectId -> 300000, //5min
- GlobalDefinitions.wasp.ObjectId -> 300000, //5min
- GlobalDefinitions.flamethrower.ObjectId -> 180000 //3min
- )
- /** Object use cooldowns.
- * key - object id
- * value - time last used (ms)
- * */
- val delayedGratificationEntries : Map[Int, Long] = Map(
- GlobalDefinitions.medkit.ObjectId -> 5000, //5s
- GlobalDefinitions.super_armorkit.ObjectId -> 1200000, //20min
- GlobalDefinitions.super_medkit.ObjectId -> 1200000, //20min
- GlobalDefinitions.super_staminakit.ObjectId -> 1200000 //20min
- )
-
final case class ResponseToSelf(pkt : PlanetSideGamePacket)
private final case class PokeClient()
diff --git a/pslogin/src/test/scala/actor/service/AvatarServiceTest.scala b/pslogin/src/test/scala/actor/service/AvatarServiceTest.scala
index 01c6c0fd..42f5ae6d 100644
--- a/pslogin/src/test/scala/actor/service/AvatarServiceTest.scala
+++ b/pslogin/src/test/scala/actor/service/AvatarServiceTest.scala
@@ -163,7 +163,7 @@ class DroptItemTest extends ActorTest {
"AvatarService" should {
"pass DropItem" in {
service ! Service.Join("test")
- service ! AvatarServiceMessage("test", AvatarAction.DropItem(PlanetSideGUID(10), tool))
+ service ! AvatarServiceMessage("test", AvatarAction.DropItem(PlanetSideGUID(10), tool, Zone.Nowhere))
expectMsg(AvatarServiceResponse("/test/Avatar", PlanetSideGUID(10), AvatarResponse.DropItem(pkt)))
}
}
@@ -264,16 +264,41 @@ class PlayerStateTest extends ActorTest {
}
}
-class PickupItemTest extends ActorTest {
+class PickupItemATest extends ActorTest {
+ val obj = Player(Avatar("TestCharacter", PlanetSideEmpire.VS, CharacterGender.Female, 1, CharacterVoice.Voice1))
+ obj.GUID = PlanetSideGUID(10)
+ obj.Slot(5).Equipment.get.GUID = PlanetSideGUID(11)
+
+ val toolDef = GlobalDefinitions.beamer
+ val tool = Tool(toolDef)
+ tool.GUID = PlanetSideGUID(40)
+ tool.AmmoSlots.head.Box.GUID = PlanetSideGUID(41)
+ val pkt = ObjectCreateMessage(
+ toolDef.ObjectId,
+ tool.GUID,
+ ObjectCreateMessageParent(PlanetSideGUID(10), 0),
+ toolDef.Packet.ConstructorData(tool).get
+ )
+
+ "pass PickUpItem as EquipmentInHand (visible pistol slot)" in {
+ ServiceManager.boot(system)
+ val service = system.actorOf(Props(classOf[AvatarService], Zone.Nowhere), AvatarServiceTest.TestName)
+ service ! Service.Join("test")
+ service ! AvatarServiceMessage("test", AvatarAction.PickupItem(PlanetSideGUID(10), Zone.Nowhere, obj, 0, tool))
+ expectMsg(AvatarServiceResponse("/test/Avatar", PlanetSideGUID(10), AvatarResponse.EquipmentInHand(pkt)))
+ }
+}
+
+class PickupItemBTest extends ActorTest {
val obj = Player(Avatar("TestCharacter", PlanetSideEmpire.VS, CharacterGender.Female, 1, CharacterVoice.Voice1))
val tool = Tool(GlobalDefinitions.beamer)
tool.GUID = PlanetSideGUID(40)
- "pass PickUpItem" in {
+ "pass PickUpItem as ObjectDelete (not visible inventory space)" in {
ServiceManager.boot(system)
val service = system.actorOf(Props(classOf[AvatarService], Zone.Nowhere), AvatarServiceTest.TestName)
service ! Service.Join("test")
- service ! AvatarServiceMessage("test", AvatarAction.PickupItem(PlanetSideGUID(10), tool))
+ service ! AvatarServiceMessage("test", AvatarAction.PickupItem(PlanetSideGUID(10), Zone.Nowhere, obj, 6, tool))
expectMsg(AvatarServiceResponse("/test/Avatar", PlanetSideGUID(10), AvatarResponse.ObjectDelete(tool.GUID, 0)))
}
}