mirror of
https://github.com/2revoemag/PSF-BotServer.git
synced 2026-07-11 14:34:40 +00:00
Lightweight Locker (#578)
* object registration adjustments for players and avatars and lockers (especially lockers) * refactored locker container * modifications to fields and method names for guid-related files; SpecificNumberSource created * locker item display; proper item insertion into locker-space and searchability of that locker-space * proper item removal from locker-space, including swap-items on other insertion tasks * comments and tests; adjusted avatar/player registrations; allowed for restriction in the SpecificNumberSource; renamed CataloguedInventory to LocallyRegisteredInventory, and made internal object registration work * accommodations for RequestDestroy, to allow the locker's Clear button to work; modification of expectation for resolving projectiles through ValidObject
This commit is contained in:
parent
8245d3ff1e
commit
7626c8e6c9
55 changed files with 1743 additions and 948 deletions
|
|
@ -8,8 +8,11 @@ import akka.actor.{Actor, ActorRef, Cancellable, MDCContextAware}
|
|||
import akka.pattern.ask
|
||||
import akka.util.Timeout
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
import MDCContextAware.Implicits._
|
||||
import net.psforever.objects.locker.LockerContainer
|
||||
import org.log4s.MDC
|
||||
|
||||
import scala.collection.mutable
|
||||
import scala.concurrent.{Await, Future}
|
||||
import scala.concurrent.duration._
|
||||
|
|
@ -359,13 +362,29 @@ class SessionActor extends Actor with MDCContextAware {
|
|||
|
||||
def ValidObject(id: Option[PlanetSideGUID]): Option[PlanetSideGameObject] =
|
||||
continent.GUID(id) match {
|
||||
case Some(obj: LocalProjectile) =>
|
||||
FindProjectileEntry(id.get)
|
||||
|
||||
case Some(_: LocalLockerItem) =>
|
||||
player.avatar.locker.Inventory.hasItem(id.get) match {
|
||||
case out @ Some(_) =>
|
||||
out
|
||||
case None =>
|
||||
//delete stale entity reference from client
|
||||
log.warn(s"${player.Name} has an invalid reference to GUID ${id.get} in zone ${continent.id}")
|
||||
sendResponse(ObjectDeleteMessage(id.get, 0))
|
||||
None
|
||||
}
|
||||
|
||||
case out @ Some(obj) if obj.HasGUID =>
|
||||
out
|
||||
|
||||
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}.")
|
||||
log.warn(s"${player.Name} has an invalid reference to GUID ${id.get} in zone ${continent.id}")
|
||||
sendResponse(ObjectDeleteMessage(id.get, 0))
|
||||
None
|
||||
|
||||
case _ =>
|
||||
None
|
||||
}
|
||||
|
|
@ -1893,7 +1912,7 @@ class SessionActor extends Actor with MDCContextAware {
|
|||
continent.GUID(mount) match {
|
||||
case Some(obj: Vehicle) =>
|
||||
TotalDriverVehicleControl(obj)
|
||||
UnAccessContents(obj)
|
||||
UnaccessContainer(obj)
|
||||
case _ => ;
|
||||
}
|
||||
PlayerActionsToCancel()
|
||||
|
|
@ -2480,7 +2499,7 @@ class SessionActor extends Actor with MDCContextAware {
|
|||
// the player will receive no messages consistently except the KeepAliveMessage echo
|
||||
keepAliveFunc = KeepAlivePersistence
|
||||
}
|
||||
AccessContents(obj)
|
||||
AccessContainer(obj)
|
||||
UpdateWeaponAtSeatPosition(obj, seat_num)
|
||||
MountingAction(tplayer, obj, seat_num)
|
||||
|
||||
|
|
@ -2516,7 +2535,7 @@ class SessionActor extends Actor with MDCContextAware {
|
|||
DismountAction(tplayer, obj, seat_num)
|
||||
|
||||
case Mountable.CanDismount(obj: Vehicle, seat_num) if obj.Definition == GlobalDefinitions.droppod =>
|
||||
UnAccessContents(obj)
|
||||
UnaccessContainer(obj)
|
||||
DismountAction(tplayer, obj, seat_num)
|
||||
|
||||
case Mountable.CanDismount(obj: Vehicle, seat_num) =>
|
||||
|
|
@ -2524,7 +2543,7 @@ class SessionActor extends Actor with MDCContextAware {
|
|||
if (player_guid == player.GUID) {
|
||||
//disembarking self
|
||||
TotalDriverVehicleControl(obj)
|
||||
UnAccessContents(obj)
|
||||
UnaccessContainer(obj)
|
||||
DismountAction(tplayer, obj, seat_num)
|
||||
} else {
|
||||
continent.VehicleEvents ! VehicleServiceMessage(
|
||||
|
|
@ -2735,7 +2754,7 @@ class SessionActor extends Actor with MDCContextAware {
|
|||
if (tplayer_guid == guid) {
|
||||
continent.GUID(vehicle_guid) match {
|
||||
case Some(obj: Vehicle) =>
|
||||
UnAccessContents(obj)
|
||||
UnaccessContainer(obj)
|
||||
case _ => ;
|
||||
}
|
||||
}
|
||||
|
|
@ -3779,9 +3798,7 @@ class SessionActor extends Actor with MDCContextAware {
|
|||
val guid = player.GUID
|
||||
sendResponse(UnuseItemMessage(guid, veh.GUID))
|
||||
sendResponse(UnuseItemMessage(guid, guid))
|
||||
veh.AccessingTrunk = None
|
||||
UnAccessContents(veh)
|
||||
accessedContainer = None
|
||||
UnaccessContainer(veh)
|
||||
}
|
||||
case Some(container) => //just in case
|
||||
if (isMovingPlus) {
|
||||
|
|
@ -3791,7 +3808,7 @@ class SessionActor extends Actor with MDCContextAware {
|
|||
sendResponse(UnuseItemMessage(guid, container.GUID))
|
||||
}
|
||||
sendResponse(UnuseItemMessage(guid, guid))
|
||||
accessedContainer = None
|
||||
UnaccessContainer(container)
|
||||
}
|
||||
case None => ;
|
||||
}
|
||||
|
|
@ -4384,25 +4401,20 @@ class SessionActor extends Actor with MDCContextAware {
|
|||
case Some(obj: Equipment) =>
|
||||
FindEquipmentToDelete(object_guid, obj)
|
||||
|
||||
case Some(_: LocalProjectile) =>
|
||||
FindProjectileEntry(object_guid) match {
|
||||
case Some(projectile) =>
|
||||
if (projectile.isResolved) {
|
||||
log.warn(
|
||||
s"RequestDestroy: tried to clean up projectile ${object_guid.guid} but it was already resolved"
|
||||
)
|
||||
} else {
|
||||
projectile.Miss()
|
||||
if (projectile.profile.ExistsOnRemoteClients && projectile.HasGUID) {
|
||||
continent.AvatarEvents ! AvatarServiceMessage(
|
||||
continent.id,
|
||||
AvatarAction.ProjectileExplodes(player.GUID, projectile.GUID, projectile)
|
||||
)
|
||||
taskResolver ! UnregisterProjectile(projectile)
|
||||
}
|
||||
}
|
||||
case None =>
|
||||
log.warn(s"RequestDestroy: projectile ${object_guid.guid} has never been fired")
|
||||
case Some(obj: Projectile) =>
|
||||
if (obj.isResolved) {
|
||||
log.warn(
|
||||
s"RequestDestroy: tried to clean up projectile ${object_guid.guid} but it was already resolved"
|
||||
)
|
||||
} else {
|
||||
obj.Miss()
|
||||
if (obj.profile.ExistsOnRemoteClients && obj.HasGUID) {
|
||||
continent.AvatarEvents ! AvatarServiceMessage(
|
||||
continent.id,
|
||||
AvatarAction.ProjectileExplodes(player.GUID, obj.GUID, obj)
|
||||
)
|
||||
taskResolver ! UnregisterProjectile(obj)
|
||||
}
|
||||
}
|
||||
|
||||
case Some(obj: BoomerDeployable) =>
|
||||
|
|
@ -4463,7 +4475,7 @@ class SessionActor extends Actor with MDCContextAware {
|
|||
Some(destination: PlanetSideServerObject with Container),
|
||||
Some(item: Equipment)
|
||||
) =>
|
||||
source.Actor ! Containable.MoveItem(destination, item, dest)
|
||||
ContainableMoveItem(taskResolver, player.Name, source, destination, item, dest)
|
||||
case (None, _, _) =>
|
||||
log.error(s"MoveItem: wanted to move $item_guid from $source_guid, but could not find source object")
|
||||
case (_, None, _) =>
|
||||
|
|
@ -4500,7 +4512,7 @@ class SessionActor extends Actor with MDCContextAware {
|
|||
destination.Fit(item)
|
||||
) match {
|
||||
case (Some((source, Some(_))), Some(dest)) =>
|
||||
source.Actor ! Containable.MoveItem(destination, item, dest)
|
||||
ContainableMoveItem(taskResolver, player.Name, source, destination, item, dest)
|
||||
case (None, _) =>
|
||||
log.error(s"LootItem: can not find where $item is put currently")
|
||||
case (_, None) =>
|
||||
|
|
@ -4610,7 +4622,7 @@ class SessionActor extends Actor with MDCContextAware {
|
|||
itemType
|
||||
)
|
||||
)
|
||||
accessedContainer = Some(obj)
|
||||
AccessContainer(obj)
|
||||
}
|
||||
} else if (!unk3 && player.isAlive) { //potential kit use
|
||||
ValidObject(item_used_guid) match {
|
||||
|
|
@ -4759,13 +4771,12 @@ class SessionActor extends Actor with MDCContextAware {
|
|||
case None if locker.Faction == player.Faction || !locker.HackedBy.isEmpty =>
|
||||
log.trace(s"UseItem: ${player.Name} accessing a locker")
|
||||
CancelZoningProcessWithDescriptiveReason("cancel_use")
|
||||
val container = player.avatar.locker
|
||||
accessedContainer = Some(container)
|
||||
val playerLocker = player.avatar.locker
|
||||
sendResponse(
|
||||
UseItemMessage(
|
||||
avatar_guid,
|
||||
item_used_guid,
|
||||
container.GUID,
|
||||
playerLocker.GUID,
|
||||
unk2,
|
||||
unk3,
|
||||
unk4,
|
||||
|
|
@ -4776,6 +4787,7 @@ class SessionActor extends Actor with MDCContextAware {
|
|||
456
|
||||
)
|
||||
)
|
||||
AccessContainer(playerLocker)
|
||||
case _ => ;
|
||||
}
|
||||
|
||||
|
|
@ -4827,8 +4839,7 @@ class SessionActor extends Actor with MDCContextAware {
|
|||
) {
|
||||
CancelZoningProcessWithDescriptiveReason("cancel_use")
|
||||
obj.AccessingTrunk = player.GUID
|
||||
accessedContainer = Some(obj)
|
||||
AccessContents(obj)
|
||||
AccessContainer(obj)
|
||||
sendResponse(
|
||||
UseItemMessage(
|
||||
avatar_guid,
|
||||
|
|
@ -5044,19 +5055,16 @@ class SessionActor extends Actor with MDCContextAware {
|
|||
|
||||
case msg @ UnuseItemMessage(player_guid, object_guid) =>
|
||||
log.info(s"UnuseItem: $msg")
|
||||
//TODO check for existing accessedContainer value?
|
||||
ValidObject(object_guid) match {
|
||||
case Some(obj: Vehicle) =>
|
||||
if (obj.AccessingTrunk.contains(player.GUID)) {
|
||||
obj.AccessingTrunk = None
|
||||
UnAccessContents(obj)
|
||||
}
|
||||
case Some(obj: Player) =>
|
||||
UnaccessContainer(obj)
|
||||
TryDisposeOfLootedCorpse(obj)
|
||||
|
||||
case Some(obj: Container) =>
|
||||
UnaccessContainer(obj)
|
||||
|
||||
case _ => ;
|
||||
}
|
||||
accessedContainer = None
|
||||
|
||||
case msg @ DeployObjectMessage(guid, unk1, pos, orient, unk2) =>
|
||||
log.info(s"DeployObject: $msg")
|
||||
|
|
@ -5757,7 +5765,8 @@ class SessionActor extends Actor with MDCContextAware {
|
|||
}
|
||||
|
||||
/**
|
||||
* Construct tasking that registers all aspects of a `Player` avatar.
|
||||
* Construct tasking that registers all aspects of a `Player` avatar
|
||||
* as if that player is only just being introduced.
|
||||
* `Players` are complex objects that contain a variety of other register-able objects and each of these objects much be handled.
|
||||
* @param tplayer the avatar `Player`
|
||||
* @return a `TaskResolver.GiveTask` message
|
||||
|
|
@ -5785,7 +5794,7 @@ class SessionActor extends Actor with MDCContextAware {
|
|||
}
|
||||
|
||||
override def onFailure(ex: Throwable): Unit = {
|
||||
localAnnounce ! PlayerFailedToLoad(localPlayer) //alerts WorldSessionActor
|
||||
localAnnounce ! PlayerFailedToLoad(localPlayer) //alerts SessionActor
|
||||
}
|
||||
},
|
||||
List(GUIDTask.RegisterAvatar(tplayer)(continent.GUID))
|
||||
|
|
@ -5793,7 +5802,8 @@ class SessionActor extends Actor with MDCContextAware {
|
|||
}
|
||||
|
||||
/**
|
||||
* Construct tasking that registers all aspects of a `Player` avatar.
|
||||
* Construct tasking that registers all aspects of a `Player` avatar
|
||||
* as if that player was already introduced and is just being renewed.
|
||||
* `Players` are complex objects that contain a variety of other register-able objects and each of these objects much be handled.
|
||||
* @param tplayer the avatar `Player`
|
||||
* @return a `TaskResolver.GiveTask` message
|
||||
|
|
@ -5872,7 +5882,7 @@ class SessionActor extends Actor with MDCContextAware {
|
|||
* Additionally, the driver is only partially associated with the vehicle at this time.
|
||||
* `interstellarFerry` is properly keeping track of the vehicle during the transition
|
||||
* and the user who is the driver (second param) is properly seated
|
||||
* but the said driver does not know about the vehicle through his usual convention - VehicleSeated` - yet.
|
||||
* but the said driver does not know about the vehicle through his usual convention - `VehicleSeated` - yet.
|
||||
* @see `GlobalDefinitions.droppod`
|
||||
* @see `GUIDTask.RegisterObjectTask`
|
||||
* @see `interstellarFerry`
|
||||
|
|
@ -5975,10 +5985,10 @@ class SessionActor extends Actor with MDCContextAware {
|
|||
}
|
||||
|
||||
override def onFailure(ex: Throwable): Unit = {
|
||||
localAnnounce ! PlayerFailedToLoad(localDriver) //alerts WorldSessionActor
|
||||
localAnnounce ! PlayerFailedToLoad(localDriver) //alerts SessionActor
|
||||
}
|
||||
},
|
||||
List(GUIDTask.RegisterAvatar(driver)(continent.GUID), GUIDTask.RegisterVehicle(obj)(continent.GUID))
|
||||
List(RegisterNewAvatar(driver), GUIDTask.RegisterVehicle(obj)(continent.GUID))
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -6115,50 +6125,106 @@ class SessionActor extends Actor with MDCContextAware {
|
|||
)
|
||||
}
|
||||
|
||||
def AccessContainer(container: Container): Unit = {
|
||||
container match {
|
||||
case v: Vehicle =>
|
||||
AccessVehicleContents(v)
|
||||
case o: LockerContainer =>
|
||||
AccessGenericContainer(o)
|
||||
case p: PlanetSideServerObject with Container =>
|
||||
accessedContainer = Some(p)
|
||||
case _ => ;
|
||||
}
|
||||
}
|
||||
|
||||
def AccessGenericContainer(container: PlanetSideServerObject with Container): Unit = {
|
||||
accessedContainer = Some(container)
|
||||
DisplayContainerContents(container.GUID, container)
|
||||
}
|
||||
|
||||
/**
|
||||
* Common preparation for interfacing with a vehicle.
|
||||
* Join a vehicle-specific group for shared updates.
|
||||
* Construct every object in the vehicle's inventory for shared manipulation updates.
|
||||
* @param vehicle the vehicle
|
||||
*/
|
||||
def AccessContents(vehicle: Vehicle): Unit = {
|
||||
AccessContentsChannel(vehicle)
|
||||
val parent_guid = vehicle.GUID
|
||||
vehicle.Trunk.Items.foreach(entry => {
|
||||
val obj = entry.obj
|
||||
val objDef = obj.Definition
|
||||
sendResponse(
|
||||
ObjectCreateDetailedMessage(
|
||||
objDef.ObjectId,
|
||||
obj.GUID,
|
||||
ObjectCreateMessageParent(parent_guid, entry.start),
|
||||
objDef.Packet.DetailedConstructorData(obj).get
|
||||
)
|
||||
)
|
||||
})
|
||||
def AccessVehicleContents(vehicle: Vehicle): Unit = {
|
||||
accessedContainer = Some(vehicle)
|
||||
if(vehicle.AccessingTrunk.isEmpty) {
|
||||
vehicle.AccessingTrunk = Some(player.GUID)
|
||||
}
|
||||
AccessVehicleContainerChannel(vehicle)
|
||||
DisplayContainerContents(vehicle.GUID, vehicle)
|
||||
}
|
||||
|
||||
def AccessContentsChannel(container: PlanetSideServerObject): Unit = {
|
||||
def AccessVehicleContainerChannel(container: PlanetSideServerObject): Unit = {
|
||||
continent.VehicleEvents ! Service.Join(s"${container.Actor}")
|
||||
}
|
||||
|
||||
def DisplayContainerContents(containerId: PlanetSideGUID, container: Container): Unit = {
|
||||
container.Inventory.Items.foreach(entry => {
|
||||
val obj = entry.obj
|
||||
val objDef = obj.Definition
|
||||
sendResponse(
|
||||
ObjectCreateDetailedMessage(
|
||||
objDef.ObjectId,
|
||||
obj.GUID,
|
||||
ObjectCreateMessageParent(containerId, entry.start),
|
||||
objDef.Packet.DetailedConstructorData(obj).get
|
||||
)
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
def UnaccessContainer(): Unit = {
|
||||
accessedContainer match {
|
||||
case Some(container) => UnaccessContainer(container)
|
||||
case _ => ;
|
||||
}
|
||||
}
|
||||
|
||||
def UnaccessContainer(container: Container): Unit = {
|
||||
container match {
|
||||
case v: Vehicle =>
|
||||
UnaccessVehicleContainer(v)
|
||||
case o: LockerContainer =>
|
||||
UnaccessGenericContainer(o)
|
||||
case _: PlanetSideServerObject with Container =>
|
||||
accessedContainer = None
|
||||
case _ => ;
|
||||
}
|
||||
}
|
||||
|
||||
def UnaccessGenericContainer(container: Container): Unit = {
|
||||
accessedContainer = None
|
||||
HideContainerContents(container)
|
||||
}
|
||||
|
||||
/**
|
||||
* Common preparation for disengaging from a vehicle.
|
||||
* Leave the vehicle-specific group that was used for shared updates.
|
||||
* Deconstruct every object in the vehicle's inventory.
|
||||
* @param vehicle the vehicle
|
||||
*/
|
||||
def UnAccessContents(vehicle: Vehicle): Unit = {
|
||||
continent.VehicleEvents ! Service.Leave(Some(s"${vehicle.Actor}"))
|
||||
vehicle.Trunk.Items.foreach(entry => {
|
||||
sendResponse(ObjectDeleteMessage(entry.obj.GUID, 0))
|
||||
})
|
||||
def UnaccessVehicleContainer(vehicle: Vehicle): Unit = {
|
||||
accessedContainer = None
|
||||
if(vehicle.AccessingTrunk.contains(player.GUID)) {
|
||||
vehicle.AccessingTrunk = None
|
||||
}
|
||||
UnaccessVehicleContainerChannel(vehicle)
|
||||
HideContainerContents(vehicle)
|
||||
}
|
||||
|
||||
def UnAccessContentsChannel(container: PlanetSideServerObject): Unit = {
|
||||
def UnaccessVehicleContainerChannel(container: PlanetSideServerObject): Unit = {
|
||||
continent.VehicleEvents ! Service.Leave(Some(s"${container.Actor}"))
|
||||
}
|
||||
|
||||
def HideContainerContents(container: Container): Unit = {
|
||||
container.Inventory.Items.foreach(entry => {
|
||||
sendResponse(ObjectDeleteMessage(entry.obj.GUID, 0))
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Check two locations for a controlled piece of equipment that is associated with the `player`.<br>
|
||||
* <br>
|
||||
|
|
@ -6813,19 +6879,7 @@ class SessionActor extends Actor with MDCContextAware {
|
|||
progressBarUpdate.cancel()
|
||||
progressBarValue = None
|
||||
lastTerminalOrderFulfillment = true
|
||||
accessedContainer match {
|
||||
case Some(obj: Vehicle) =>
|
||||
if (obj.AccessingTrunk.contains(player.GUID)) {
|
||||
obj.AccessingTrunk = None
|
||||
UnAccessContents(obj)
|
||||
}
|
||||
accessedContainer = None
|
||||
|
||||
case Some(_) =>
|
||||
accessedContainer = None
|
||||
|
||||
case None => ;
|
||||
}
|
||||
UnaccessContainer()
|
||||
prefire.orElse(shooting) match {
|
||||
case Some(guid) =>
|
||||
sendResponse(ChangeFireStateMessage_Stop(guid))
|
||||
|
|
@ -7030,7 +7084,7 @@ class SessionActor extends Actor with MDCContextAware {
|
|||
* and is permitted to introduce the avatar to the vehicle's internal settings in a similar way.
|
||||
* Neither the player avatar nor the vehicle should be reconstructed before the next zone load operation
|
||||
* to avoid damaging the critical setup of this function.
|
||||
* @see `AccessContents`
|
||||
* @see `AccessContainer`
|
||||
* @see `UpdateWeaponAtSeatPosition`
|
||||
* @param tplayer the player avatar seated in the vehicle's seat
|
||||
* @param vehicle the vehicle the player is riding
|
||||
|
|
@ -7046,7 +7100,7 @@ class SessionActor extends Actor with MDCContextAware {
|
|||
sendResponse(ObjectCreateDetailedMessage(pdef.ObjectId, pguid, pdata))
|
||||
if (seat == 0 || vehicle.Seats(seat).ControlledWeapon.nonEmpty) {
|
||||
sendResponse(ObjectAttachMessage(vguid, pguid, seat))
|
||||
AccessContents(vehicle)
|
||||
AccessContainer(vehicle)
|
||||
UpdateWeaponAtSeatPosition(vehicle, seat)
|
||||
} else {
|
||||
interimUngunnedVehicle = Some(vguid)
|
||||
|
|
@ -7120,7 +7174,7 @@ class SessionActor extends Actor with MDCContextAware {
|
|||
//log.info(s"AvatarRejoin: $vguid -> $vdata")
|
||||
if (seat == 0 || vehicle.Seats(seat).ControlledWeapon.nonEmpty) {
|
||||
sendResponse(ObjectAttachMessage(vguid, pguid, seat))
|
||||
AccessContents(vehicle)
|
||||
AccessContainer(vehicle)
|
||||
UpdateWeaponAtSeatPosition(vehicle, seat)
|
||||
} else {
|
||||
interimUngunnedVehicle = Some(vguid)
|
||||
|
|
@ -8203,6 +8257,7 @@ class SessionActor extends Actor with MDCContextAware {
|
|||
/**
|
||||
* A simple object searching algorithm that is limited to containers currently known and accessible by the player.
|
||||
* If all relatively local containers are checked and the object is not found,
|
||||
* the player's locker inventory will be checked, and then
|
||||
* the game environment (items on the ground) will be checked too.
|
||||
* If the target object is discovered, it is removed from its current location and is completely destroyed.
|
||||
* @see `RequestDestroyMessage`<br>
|
||||
|
|
@ -8218,8 +8273,7 @@ class SessionActor extends Actor with MDCContextAware {
|
|||
: PlanetSideServerObject with Container => Option[(PlanetSideServerObject with Container, Option[Int])] =
|
||||
FindInLocalContainer(object_guid)
|
||||
|
||||
findFunc(player.avatar.locker)
|
||||
.orElse(findFunc(player))
|
||||
findFunc(player)
|
||||
.orElse(accessedContainer match {
|
||||
case Some(parent: PlanetSideServerObject) =>
|
||||
findFunc(parent)
|
||||
|
|
@ -8239,7 +8293,11 @@ class SessionActor extends Actor with MDCContextAware {
|
|||
true
|
||||
|
||||
case _ =>
|
||||
if (continent.EquipmentOnGround.contains(obj)) {
|
||||
if (player.avatar.locker.Inventory.Remove(object_guid)) {
|
||||
sendResponse(ObjectDeleteMessage(object_guid, 0))
|
||||
log.info(s"RequestDestroy: equipment $obj")
|
||||
true
|
||||
} else if (continent.EquipmentOnGround.contains(obj)) {
|
||||
obj.Position = Vector3.Zero
|
||||
continent.Ground ! Zone.Ground.RemoveItem(object_guid)
|
||||
continent.AvatarEvents ! AvatarServiceMessage.Ground(RemoverActor.ClearSpecific(List(obj), continent))
|
||||
|
|
@ -8440,7 +8498,7 @@ class SessionActor extends Actor with MDCContextAware {
|
|||
if (player.isBackpack) {
|
||||
session = session.copy(player = targetPlayer)
|
||||
taskThenZoneChange(
|
||||
GUIDTask.UnregisterLocker(original.avatar.locker)(continent.GUID),
|
||||
GUIDTask.UnregisterObjectTask(original.avatar.locker)(continent.GUID),
|
||||
InterstellarClusterService.FindZone(_.id == zoneId, context.self)
|
||||
)
|
||||
} else if (player.HasGUID) {
|
||||
|
|
@ -8545,12 +8603,12 @@ class SessionActor extends Actor with MDCContextAware {
|
|||
InterstellarClusterService.FindZone(_.id == zoneId, context.self)
|
||||
)
|
||||
} else {
|
||||
UnAccessContents(vehicle)
|
||||
UnaccessContainer(vehicle)
|
||||
LoadZoneCommonTransferActivity()
|
||||
player.VehicleSeated = vehicle.GUID
|
||||
player.Continent = zoneId //forward-set the continent id to perform a test
|
||||
interstellarFerryTopLevelGUID =
|
||||
(if (
|
||||
if (
|
||||
manifest.passengers.isEmpty && manifest.cargo.count { case (name, _) => !name.equals("MISSING_DRIVER") } == 0
|
||||
) {
|
||||
//do not delete if vehicle has passengers or cargo
|
||||
|
|
@ -8561,7 +8619,7 @@ class SessionActor extends Actor with MDCContextAware {
|
|||
None
|
||||
} else {
|
||||
Some(topLevel)
|
||||
})
|
||||
}
|
||||
//unregister vehicle and driver whole + GiveWorld
|
||||
continent.Transport ! Zone.Vehicle.Despawn(vehicle)
|
||||
taskThenZoneChange(
|
||||
|
|
@ -8586,12 +8644,12 @@ class SessionActor extends Actor with MDCContextAware {
|
|||
* A reference to the top-level ferrying vehicle's former globally unique identifier has been retained for this purpose.
|
||||
* This vehicle can be deleted for everyone if no more work can be detected.
|
||||
*
|
||||
* @see `GUIDTask.UnregisterAvatar`
|
||||
* @see `GUIDTask.UnregisterPlayer`
|
||||
* @see `LoadZoneCommonTransferActivity`
|
||||
* @see `Vehicles.AllGatedOccupantsInSameZone`
|
||||
* @see `PlayerLoaded`
|
||||
* @see `TaskBeforeZoneChange`
|
||||
* @see `UnAccessContents`
|
||||
* @see `UnaccessContainer`
|
||||
* @param vehicle the target vehicle being moved around
|
||||
* @param zoneId the zone in which the vehicle and driver will be placed
|
||||
* @return a tuple composed of an `ActorRef` destination and a message to send to that destination
|
||||
|
|
@ -9209,7 +9267,7 @@ class SessionActor extends Actor with MDCContextAware {
|
|||
}
|
||||
|
||||
/**
|
||||
* The upstream counter accumulates when the server receives sp[ecific messages from the client.
|
||||
* The upstream counter accumulates when the server receives specific messages from the client.
|
||||
* It counts upwards until it reach maximum value, and then starts over.
|
||||
* When it starts over, which should take an exceptionally long time to achieve,
|
||||
* it starts counting at one rather than zero.
|
||||
|
|
@ -9265,7 +9323,7 @@ class SessionActor extends Actor with MDCContextAware {
|
|||
case (Some(vehicle: Vehicle), Some(vguid), Some(seat)) =>
|
||||
//sit down
|
||||
sendResponse(ObjectAttachMessage(vguid, pguid, seat))
|
||||
AccessContents(vehicle)
|
||||
AccessContainer(vehicle)
|
||||
keepAliveFunc = KeepAlivePersistence
|
||||
case _ => ;
|
||||
//we can't find a vehicle? and we're still here? that's bad
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import akka.util.Timeout
|
|||
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.locker.LockerContainer
|
||||
import net.psforever.objects.serverobject.PlanetSideServerObject
|
||||
import net.psforever.objects.serverobject.containable.Containable
|
||||
import net.psforever.objects.zones.Zone
|
||||
|
|
@ -504,6 +505,215 @@ object WorldSession {
|
|||
result
|
||||
}
|
||||
|
||||
/**
|
||||
* Move an item from one container to another.
|
||||
* If the source or if the destination is a kind of container called a `LockerContainer`,
|
||||
* then a special procedure for the movement of the item must be respected.
|
||||
* If the source and the destination are both `LockerContainer` objects, however,
|
||||
* the normal operations for moving an item may be executed.
|
||||
* @see `ActorRef`
|
||||
* @see `Containable.MoveItem`
|
||||
* @see `Container`
|
||||
* @see `Equipment`
|
||||
* @see `LockerContainer`
|
||||
* @see `RemoveEquipmentFromLockerContainer`
|
||||
* @see `StowEquipmentInLockerContainer`
|
||||
* @see `TaskResolver`
|
||||
* @param taskResolver na
|
||||
* @param toChannel broadcast channel name for a manual packet callback
|
||||
* @param source the container in which the item is to be removed
|
||||
* @param destination the container into which the item is to be placed
|
||||
* @param item the item
|
||||
* @param dest where in the destination container the item is being placed
|
||||
*/
|
||||
def ContainableMoveItem(
|
||||
taskResolver: ActorRef,
|
||||
toChannel: String,
|
||||
source: PlanetSideServerObject with Container,
|
||||
destination: PlanetSideServerObject with Container,
|
||||
item: Equipment,
|
||||
dest: Int
|
||||
) : Unit = {
|
||||
(source, destination) match {
|
||||
case (locker: LockerContainer, _) if !destination.isInstanceOf[LockerContainer] =>
|
||||
RemoveEquipmentFromLockerContainer(taskResolver, toChannel, locker, destination, item, dest)
|
||||
case (_, locker: LockerContainer) =>
|
||||
StowEquipmentInLockerContainer(taskResolver, toChannel, source, locker, item, dest)
|
||||
case _ =>
|
||||
source.Actor ! Containable.MoveItem(destination, item, dest)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Move an item into a player's locker inventory.
|
||||
* Handle any swap item that might become involved in the transfer.
|
||||
* Failure of this process is not supported and may lead to irregular behavior.
|
||||
* @see `ActorRef`
|
||||
* @see `AvatarAction.ObjectDelete`
|
||||
* @see `AvatarServiceMessage`
|
||||
* @see `Containable.MoveItem`
|
||||
* @see `Container`
|
||||
* @see `Equipment`
|
||||
* @see `GridInventory.CheckCollisionsVar`
|
||||
* @see `GUIDTask.RegisterEquipment`
|
||||
* @see `GUIDTask.UnregisterEquipment`
|
||||
* @see `IdentifiableEntity.Invalidate`
|
||||
* @see `LockerContainer`
|
||||
* @see `Service`
|
||||
* @see `Task`
|
||||
* @see `TaskResolver`
|
||||
* @see `TaskResolver.GiveTask`
|
||||
* @see `Zone.AvatarEvents`
|
||||
* @param taskResolver na
|
||||
* @param toChannel broadcast channel name for a manual packet callback
|
||||
* @param source the container in which the item is to be removed
|
||||
* @param destination the container into which the item is to be placed
|
||||
* @param item the item
|
||||
* @param dest where in the destination container the item is being placed
|
||||
*/
|
||||
def StowEquipmentInLockerContainer(
|
||||
taskResolver: ActorRef,
|
||||
toChannel: String,
|
||||
source: PlanetSideServerObject with Container,
|
||||
destination: PlanetSideServerObject with Container,
|
||||
item: Equipment,
|
||||
dest: Int
|
||||
): Unit = {
|
||||
val registrationTask = GUIDTask.UnregisterEquipment(item)(source.Zone.GUID)
|
||||
//check for the existence of a swap item - account for that in advance
|
||||
val (subtasks, swapItemGUID): (List[TaskResolver.GiveTask], Option[PlanetSideGUID]) = {
|
||||
val tile = item.Definition.Tile
|
||||
destination.Inventory.CheckCollisionsVar(dest, tile.Width, tile.Height)
|
||||
} match {
|
||||
case Success(Nil) =>
|
||||
//no swap item
|
||||
(List(registrationTask), None)
|
||||
case Success(List(swapEntry: InventoryItem)) =>
|
||||
//the swap item is to be registered to the source's zone
|
||||
/*
|
||||
destination is a locker container that has its own internal unique number system
|
||||
the swap item is currently registered to this system
|
||||
the swap item will be moved into the system in which the source operates
|
||||
to facilitate the transfer, the item needs to be partially unregistered from the destination's system
|
||||
to facilitate the transfer, the item needs to be preemptively registered to the source's system
|
||||
invalidating the current unique number is sufficient for both of these steps
|
||||
*/
|
||||
val swapItem = swapEntry.obj
|
||||
swapItem.Invalidate()
|
||||
(List(GUIDTask.RegisterEquipment(swapItem)(source.Zone.GUID), registrationTask), Some(swapItem.GUID))
|
||||
case _ =>
|
||||
//too many swap items or other error; this attempt will probably fail
|
||||
(Nil, None)
|
||||
}
|
||||
taskResolver ! TaskResolver.GiveTask(
|
||||
new Task() {
|
||||
val localGUID = swapItemGUID //the swap item's original GUID, if any swap item
|
||||
val localChannel = toChannel
|
||||
val localSource = source
|
||||
val localDestination = destination
|
||||
val localItem = item
|
||||
val localSlot = dest
|
||||
|
||||
override def Description: String = s"unregistering $localItem before stowing in $localDestination"
|
||||
|
||||
override def isComplete: Task.Resolution.Value = {
|
||||
if (localItem.HasGUID && localDestination.Find(localItem).contains(localSlot)) {
|
||||
Task.Resolution.Success
|
||||
} else {
|
||||
Task.Resolution.Incomplete
|
||||
}
|
||||
}
|
||||
|
||||
def Execute(resolver: ActorRef): Unit = {
|
||||
localGUID match {
|
||||
case Some(guid) =>
|
||||
//see LockerContainerControl.RemoveItemFromSlotCallback
|
||||
val zone = localSource.Zone
|
||||
zone.AvatarEvents ! AvatarServiceMessage(localChannel, AvatarAction.ObjectDelete(Service.defaultPlayerGUID, guid))
|
||||
case None => ;
|
||||
}
|
||||
localSource.Actor ! Containable.MoveItem(localDestination, localItem, localSlot)
|
||||
resolver ! Success(this)
|
||||
}
|
||||
},
|
||||
subtasks
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an item from a player's locker inventory.
|
||||
* Failure of this process is not supported and may lead to irregular behavior.
|
||||
* @see `ActorRef`
|
||||
* @see `AvatarAction.ObjectDelete`
|
||||
* @see `AvatarServiceMessage`
|
||||
* @see `Containable.MoveItem`
|
||||
* @see `Container`
|
||||
* @see `Equipment`
|
||||
* @see `GridInventory.CheckCollisionsVar`
|
||||
* @see `GUIDTask.RegisterEquipment`
|
||||
* @see `GUIDTask.UnregisterEquipment`
|
||||
* @see `IdentifiableEntity.Invalidate`
|
||||
* @see `LockerContainer`
|
||||
* @see `Service`
|
||||
* @see `Task`
|
||||
* @see `TaskResolver`
|
||||
* @see `TaskResolver.GiveTask`
|
||||
* @see `Zone.AvatarEvents`
|
||||
* @param taskResolver na
|
||||
* @param toChannel broadcast channel name for a manual packet callback
|
||||
* @param source the container in which the item is to be removed
|
||||
* @param destination the container into which the item is to be placed
|
||||
* @param item the item
|
||||
* @param dest where in the destination container the item is being placed
|
||||
*/
|
||||
def RemoveEquipmentFromLockerContainer(
|
||||
taskResolver: ActorRef,
|
||||
toChannel: String,
|
||||
source: PlanetSideServerObject with Container,
|
||||
destination: PlanetSideServerObject with Container,
|
||||
item: Equipment,
|
||||
dest: Int
|
||||
): Unit = {
|
||||
taskResolver ! TaskResolver.GiveTask(
|
||||
new Task() {
|
||||
val localGUID = item.GUID //original GUID
|
||||
val localChannel = toChannel
|
||||
val localSource = source
|
||||
val localDestination = destination
|
||||
val localItem = item
|
||||
val localSlot = dest
|
||||
/*
|
||||
source is a locker container that has its own internal unique number system
|
||||
the item is currently registered to this system
|
||||
the item will be moved into the system in which the destination operates
|
||||
to facilitate the transfer, the item needs to be partially unregistered from the source's system
|
||||
to facilitate the transfer, the item needs to be preemptively registered to the destination's system
|
||||
invalidating the current unique number is sufficient for both of these steps
|
||||
*/
|
||||
localItem.Invalidate()
|
||||
|
||||
override def Description: String = s"registering $localItem in ${localDestination.Zone.id} before removing from $localSource"
|
||||
|
||||
override def isComplete: Task.Resolution.Value = {
|
||||
if (localItem.HasGUID && localDestination.Find(localItem).isEmpty) {
|
||||
Task.Resolution.Success
|
||||
} else {
|
||||
Task.Resolution.Incomplete
|
||||
}
|
||||
}
|
||||
|
||||
def Execute(resolver: ActorRef): Unit = {
|
||||
val zone = localSource.Zone
|
||||
//see LockerContainerControl.RemoveItemFromSlotCallback
|
||||
zone.AvatarEvents ! AvatarServiceMessage(localChannel, AvatarAction.ObjectDelete(Service.defaultPlayerGUID, localGUID))
|
||||
localSource.Actor ! Containable.MoveItem(localDestination, localItem, localSlot)
|
||||
resolver ! Success(this)
|
||||
}
|
||||
},
|
||||
List(GUIDTask.RegisterEquipment(item)(destination.Zone.GUID))
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* If a timeout occurs on the manipulation, declare a terminal transaction failure.
|
||||
* @see `AskTimeoutException`
|
||||
|
|
|
|||
37
src/main/scala/net/psforever/objects/LocalLockerItem.scala
Normal file
37
src/main/scala/net/psforever/objects/LocalLockerItem.scala
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
// Copyright (c) 2020 PSForever
|
||||
package net.psforever.objects
|
||||
|
||||
import akka.actor.ActorContext
|
||||
import net.psforever.objects.serverobject.PlanetSideServerObject
|
||||
import net.psforever.types.PlanetSideEmpire
|
||||
|
||||
/**
|
||||
* A `LocalLockerItem` is a server-side object designed to populate a fake unshared space within a shared space.
|
||||
* It is a placeholder intended to block out the existence of locker objects that may or may not exist.
|
||||
* All clients reserve the same internal range of user-generated GUID's from 40150 to 40449, inclusive.
|
||||
* All clients recognize this same range independent of each other as "equipment in their own locker."
|
||||
* The GUID's in this locker-space can be reflected onto the zone GUID.
|
||||
* The item of that GUID may exist to the client.
|
||||
* The item of that GUID does not formally exist to the server, but it can be searched.
|
||||
* @see `Zone.MakeReservedObjects`
|
||||
*/
|
||||
class LocalLockerItem extends PlanetSideServerObject {
|
||||
def Faction = PlanetSideEmpire.NEUTRAL
|
||||
|
||||
def Definition = LocalLockerItem.local
|
||||
}
|
||||
|
||||
object LocalLockerItem {
|
||||
import net.psforever.objects.definition.ObjectDefinition
|
||||
def local = new ObjectDefinition(0) { Name = "locker-equipment" }
|
||||
|
||||
/**
|
||||
* Instantiate and configure a `LocalProjectile` object.
|
||||
* @param id the unique id that will be assigned to this entity
|
||||
* @param context a context to allow the object to properly set up `ActorSystem` functionality
|
||||
* @return the `LocalProjectile` object
|
||||
*/
|
||||
def Constructor(id: Int, context: ActorContext): LocalLockerItem = {
|
||||
new LocalLockerItem()
|
||||
}
|
||||
}
|
||||
|
|
@ -2,8 +2,10 @@ package net.psforever.objects.avatar
|
|||
|
||||
import net.psforever.objects.definition.{AvatarDefinition, BasicDefinition}
|
||||
import net.psforever.objects.equipment.{EquipmentSize, EquipmentSlot}
|
||||
import net.psforever.objects.inventory.LocallyRegisteredInventory
|
||||
import net.psforever.objects.loadouts.{Loadout, SquadLoadout}
|
||||
import net.psforever.objects.{GlobalDefinitions, LockerContainer, LockerEquipment, OffhandEquipmentSlot}
|
||||
import net.psforever.objects.locker.{LockerContainer, LockerEquipment}
|
||||
import net.psforever.objects.{GlobalDefinitions, OffhandEquipmentSlot}
|
||||
import net.psforever.types._
|
||||
import org.joda.time.{Duration, LocalDateTime, Seconds}
|
||||
|
||||
|
|
@ -82,7 +84,11 @@ case class Avatar(
|
|||
loadouts: Seq[Option[Loadout]] = Seq.fill(15)(None),
|
||||
squadLoadouts: Seq[Option[SquadLoadout]] = Seq.fill(10)(None),
|
||||
implants: Seq[Option[Implant]] = Seq(None, None, None),
|
||||
locker: LockerContainer = new LockerContainer(), // TODO var bad
|
||||
locker: LockerContainer = new LockerContainer({
|
||||
val inv = new LocallyRegisteredInventory(numbers = 40150 until 40450) // TODO var bad
|
||||
inv.Resize(30,20)
|
||||
inv
|
||||
}),
|
||||
deployables: DeployableToolbox = new DeployableToolbox(), // TODO var bad
|
||||
lookingForSquad: Boolean = false,
|
||||
var vehicle: Option[PlanetSideGUID] = None, // TODO var bad
|
||||
|
|
|
|||
|
|
@ -27,6 +27,8 @@ import net.psforever.services.{RemoverActor, Service}
|
|||
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||
import net.psforever.services.local.{LocalAction, LocalServiceMessage}
|
||||
import akka.actor.typed
|
||||
import net.psforever.objects.locker.LockerContainerControl
|
||||
|
||||
import scala.concurrent.duration._
|
||||
|
||||
class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Command])
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ package net.psforever.objects.definition.converter
|
|||
import net.psforever.objects.Player
|
||||
import net.psforever.objects.equipment.{Equipment, EquipmentSlot}
|
||||
import net.psforever.packet.game.objectcreate._
|
||||
import net.psforever.types.{ExoSuitType, GrenadeState, PlanetSideGUID}
|
||||
import net.psforever.types.{ExoSuitType, GrenadeState, PlanetSideEmpire, PlanetSideGUID}
|
||||
|
||||
import scala.annotation.tailrec
|
||||
import scala.util.{Success, Try}
|
||||
|
|
@ -175,7 +175,9 @@ object AvatarConverter {
|
|||
|
||||
def MakeDetailedInventoryData(obj: Player): InventoryData = {
|
||||
InventoryData(
|
||||
(MakeHolsters(obj, BuildDetailedEquipment) ++ MakeFifthSlot(obj) ++ MakeInventory(obj)).sortBy(_.parentSlot)
|
||||
(MakeHolsters(obj, BuildDetailedEquipment) ++
|
||||
MakeFifthSlot(obj) ++
|
||||
MakeInventory(obj)).sortBy(_.parentSlot)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -222,7 +224,16 @@ object AvatarConverter {
|
|||
private def MakeFifthSlot(obj: Player): List[InternalSlot] = {
|
||||
obj.Slot(5).Equipment match {
|
||||
case Some(equip) =>
|
||||
BuildDetailedEquipment(5, equip) :: Nil
|
||||
//List(BuildDetailedEquipment(5, equip))
|
||||
List(InternalSlot(
|
||||
equip.Definition.ObjectId,
|
||||
equip.GUID,
|
||||
5,
|
||||
DetailedLockerContainerData(
|
||||
CommonFieldData(PlanetSideEmpire.NEUTRAL, false, false, true, None, false, None, None, PlanetSideGUID(0)),
|
||||
None
|
||||
)
|
||||
))
|
||||
case _ =>
|
||||
Nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package net.psforever.objects.definition.converter
|
||||
|
||||
import net.psforever.objects.LockerEquipment
|
||||
import net.psforever.objects.equipment.Equipment
|
||||
import net.psforever.objects.inventory.GridInventory
|
||||
import net.psforever.objects.locker.LockerEquipment
|
||||
import net.psforever.packet.game.objectcreate._
|
||||
import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import net.psforever.objects.entity.IdentifiableEntity
|
|||
import net.psforever.objects.equipment.{Equipment, EquipmentSlot}
|
||||
import net.psforever.objects._
|
||||
import net.psforever.objects.inventory.Container
|
||||
import net.psforever.objects.locker.{LockerContainer, LockerEquipment}
|
||||
import net.psforever.objects.serverobject.turret.WeaponTurret
|
||||
|
||||
import scala.annotation.tailrec
|
||||
|
|
@ -153,7 +154,7 @@ object GUIDTask {
|
|||
*/
|
||||
def RegisterAvatar(tplayer: Player)(implicit guid: ActorRef): TaskResolver.GiveTask = {
|
||||
val holsterTasks = VisibleSlotTaskBuilding(tplayer.Holsters(), RegisterEquipment)
|
||||
val lockerTask = List(RegisterLocker(tplayer.avatar.locker))
|
||||
val lockerTask = List(RegisterObjectTask(tplayer.avatar.locker))
|
||||
val inventoryTasks = RegisterInventory(tplayer)
|
||||
TaskResolver.GiveTask(RegisterObjectTask(tplayer).task, holsterTasks ++ lockerTask ++ inventoryTasks)
|
||||
}
|
||||
|
|
@ -311,7 +312,7 @@ object GUIDTask {
|
|||
*/
|
||||
def UnregisterAvatar(tplayer: Player)(implicit guid: ActorRef): TaskResolver.GiveTask = {
|
||||
val holsterTasks = VisibleSlotTaskBuilding(tplayer.Holsters(), UnregisterEquipment)
|
||||
val lockerTask = List(UnregisterLocker(tplayer.avatar.locker))
|
||||
val lockerTask = List(UnregisterObjectTask(tplayer.avatar.locker))
|
||||
val inventoryTasks = UnregisterInventory(tplayer)
|
||||
TaskResolver.GiveTask(UnregisterObjectTask(tplayer).task, holsterTasks ++ lockerTask ++ inventoryTasks)
|
||||
}
|
||||
|
|
@ -368,7 +369,7 @@ object GUIDTask {
|
|||
* @param guid implicit reference to a unique number system
|
||||
* @return a list of `TaskResolver.GiveTask` messages
|
||||
*/
|
||||
def VisibleSlotTaskBuilding(list: Iterable[EquipmentSlot], func: ((Equipment) => TaskResolver.GiveTask))(implicit
|
||||
def VisibleSlotTaskBuilding(list: Iterable[EquipmentSlot], func: Equipment => TaskResolver.GiveTask)(implicit
|
||||
guid: ActorRef
|
||||
): List[TaskResolver.GiveTask] = {
|
||||
recursiveVisibleSlotTaskBuilding(list.iterator, func)
|
||||
|
|
@ -386,7 +387,7 @@ object GUIDTask {
|
|||
*/
|
||||
@tailrec private def recursiveVisibleSlotTaskBuilding(
|
||||
iter: Iterator[EquipmentSlot],
|
||||
func: ((Equipment) => TaskResolver.GiveTask),
|
||||
func: Equipment => TaskResolver.GiveTask,
|
||||
list: List[TaskResolver.GiveTask] = Nil
|
||||
)(implicit guid: ActorRef): List[TaskResolver.GiveTask] = {
|
||||
if (!iter.hasNext) {
|
||||
|
|
|
|||
|
|
@ -24,8 +24,8 @@ class NumberPoolHub(private val source: NumberSource) {
|
|||
import scala.collection.mutable
|
||||
private val hash: mutable.HashMap[String, NumberPool] = mutable.HashMap[String, NumberPool]()
|
||||
private val bigpool: mutable.LongMap[String] = mutable.LongMap[String]()
|
||||
hash += "generic" -> new GenericPool(bigpool, source.Size)
|
||||
source.FinalizeRestrictions.foreach(i =>
|
||||
hash += "generic" -> new GenericPool(bigpool, source.size)
|
||||
source.finalizeRestrictions.foreach(i =>
|
||||
bigpool += i.toLong -> ""
|
||||
) //these numbers can never be pooled; the source can no longer restrict numbers
|
||||
|
||||
|
|
@ -47,7 +47,7 @@ class NumberPoolHub(private val source: NumberSource) {
|
|||
* @param number the unique number to attempt to retrieve from the `source`
|
||||
* @return the object that is assigned to the number
|
||||
*/
|
||||
def apply(number: Int): Option[IdentifiableEntity] = source.Get(number).orElse(return None).get.Object
|
||||
def apply(number: Int): Option[IdentifiableEntity] = source.get(number).orElse(return None).get.Object
|
||||
|
||||
def Numbers: List[Int] = bigpool.keys.map(key => key.toInt).toList
|
||||
|
||||
|
|
@ -74,14 +74,15 @@ class NumberPoolHub(private val source: NumberSource) {
|
|||
if (pool.isEmpty) {
|
||||
throw new IllegalArgumentException(s"can not add empty pool $name")
|
||||
}
|
||||
if (source.Size <= pool.max) {
|
||||
throw new IllegalArgumentException(s"can not add pool $name - max(pool) is greater than source.size")
|
||||
if (source.max < pool.max) {
|
||||
throw new IllegalArgumentException(s"can not add pool $name - pool.max is greater than source.max")
|
||||
}
|
||||
val collision = bigpool.keys.map(n => n.toInt).toSet.intersect(pool.toSet)
|
||||
if (collision.nonEmpty) {
|
||||
throw new IllegalArgumentException(
|
||||
s"can not add pool $name - it contains the following redundant numbers: ${collision.toString}"
|
||||
)
|
||||
bigpool.keys.map(n => n.toInt).toSet.intersect(pool.toSet).toSeq match {
|
||||
case Nil => ;
|
||||
case collisions =>
|
||||
throw new IllegalArgumentException(
|
||||
s"can not add pool $name - it contains the following redundant numbers: ${collisions.mkString(",")}"
|
||||
)
|
||||
}
|
||||
pool.foreach(i => bigpool += i.toLong -> name)
|
||||
hash += name -> new ExclusivePool(pool)
|
||||
|
|
@ -162,7 +163,7 @@ class NumberPoolHub(private val source: NumberSource) {
|
|||
def WhichPool(obj: IdentifiableEntity): Option[String] = {
|
||||
try {
|
||||
val number: Int = obj.GUID.guid
|
||||
val entry = source.Get(number)
|
||||
val entry = source.get(number)
|
||||
if (entry.isDefined && entry.get.Object.contains(obj)) { WhichPool(number) }
|
||||
else { None }
|
||||
} catch {
|
||||
|
|
@ -228,7 +229,7 @@ class NumberPoolHub(private val source: NumberSource) {
|
|||
}
|
||||
|
||||
private def register_GetAvailableNumberFromSource(number: Int): Try[LoanedKey] = {
|
||||
source.Available(number) match {
|
||||
source.getAvailable(number) match {
|
||||
case Some(key) =>
|
||||
Success(key)
|
||||
case None =>
|
||||
|
|
@ -243,22 +244,21 @@ class NumberPoolHub(private val source: NumberSource) {
|
|||
* @return the number the was given to the object
|
||||
*/
|
||||
def register(obj: IdentifiableEntity, name: String): Try[Int] = {
|
||||
try {
|
||||
if (obj.HasGUID) {
|
||||
register_CheckNumberAgainstDesiredPool(obj, name, obj.GUID.guid)
|
||||
} catch {
|
||||
case _: Exception =>
|
||||
register_GetPool(name) match {
|
||||
case Success(key) =>
|
||||
key.Object = obj
|
||||
Success(obj.GUID.guid)
|
||||
case Failure(ex) =>
|
||||
Failure(new Exception(s"trying to register an object but, ${ex.getMessage}"))
|
||||
}
|
||||
} else {
|
||||
register_GetPool(name) match {
|
||||
case Success(key) =>
|
||||
key.Object = obj
|
||||
Success(obj.GUID.guid)
|
||||
case Failure(ex) =>
|
||||
Failure(new Exception(s"trying to register an object but, ${ex.getMessage}"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def register_CheckNumberAgainstDesiredPool(obj: IdentifiableEntity, name: String, number: Int): Try[Int] = {
|
||||
val directKey = source.Get(number)
|
||||
val directKey = source.get(number)
|
||||
if (directKey.isEmpty || !directKey.get.Object.contains(obj)) {
|
||||
Failure(new Exception("object already registered, but not to this source"))
|
||||
} else if (!WhichPool(number).contains(name)) {
|
||||
|
|
@ -347,11 +347,34 @@ class NumberPoolHub(private val source: NumberSource) {
|
|||
case Success(pool) =>
|
||||
val number = obj.GUID.guid
|
||||
pool.Return(number)
|
||||
source.Return(number)
|
||||
source.returnNumber(number)
|
||||
obj.Invalidate()
|
||||
Success(number)
|
||||
case Failure(ex) =>
|
||||
Failure(new Exception(s"can not unregister this object: ${ex.getMessage}"))
|
||||
unregister_GetMonitorFromObject(obj, ex.getMessage)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister a specific object
|
||||
* by actually finding the object itself, if it exists.
|
||||
* @param obj an object being unregistered
|
||||
* @param msg custom error message;
|
||||
* has a vague default
|
||||
* @return the number associated with this object
|
||||
*/
|
||||
def unregister_GetMonitorFromObject(
|
||||
obj: IdentifiableEntity,
|
||||
msg: String = "can not find this object"
|
||||
): Try[Int] = {
|
||||
source.get(obj) match {
|
||||
case Some(key) =>
|
||||
val number = key.GUID
|
||||
GetPool(WhichPool(number).get).get.Return(number)
|
||||
source.returnNumber(number)
|
||||
Success(number)
|
||||
case _ =>
|
||||
Failure(new Exception(s"can not unregister this $obj - $msg"))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -360,7 +383,7 @@ class NumberPoolHub(private val source: NumberSource) {
|
|||
case Some(name) =>
|
||||
unregister_GetPool(name)
|
||||
case None =>
|
||||
Failure(throw new Exception("can not find a pool for this object"))
|
||||
Failure(new Exception(s"can not find a pool for this $obj"))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -379,7 +402,7 @@ class NumberPoolHub(private val source: NumberSource) {
|
|||
* @return the object, if any, previous associated with the number
|
||||
*/
|
||||
def unregister(number: Int): Try[Option[IdentifiableEntity]] = {
|
||||
if (source.Test(number)) {
|
||||
if (source.test(number)) {
|
||||
unregister_GetObjectFromSource(number)
|
||||
} else {
|
||||
Failure(new Exception(s"can not unregister a number $number that this source does not own"))
|
||||
|
|
@ -387,7 +410,7 @@ class NumberPoolHub(private val source: NumberSource) {
|
|||
}
|
||||
|
||||
private def unregister_GetObjectFromSource(number: Int): Try[Option[IdentifiableEntity]] = {
|
||||
source.Return(number) match {
|
||||
source.returnNumber(number) match {
|
||||
case Some(obj) =>
|
||||
unregister_ReturnObjectToPool(obj)
|
||||
case None =>
|
||||
|
|
@ -403,7 +426,7 @@ class NumberPoolHub(private val source: NumberSource) {
|
|||
obj.Invalidate()
|
||||
Success(Some(obj))
|
||||
case Failure(ex) =>
|
||||
source.Available(number) //undo
|
||||
source.getAvailable(number) //undo
|
||||
Failure(new Exception(s"started unregistering, but ${ex.getMessage}"))
|
||||
}
|
||||
}
|
||||
|
|
@ -432,11 +455,11 @@ class NumberPoolHub(private val source: NumberSource) {
|
|||
}
|
||||
|
||||
/**
|
||||
* For accessing the `Return` function of the contained `NumberSource` directly.
|
||||
* For accessing the `returnNumber` function of the contained `NumberSource` directly.
|
||||
* @param number the number to return.
|
||||
* @return any object previously using this number
|
||||
*/
|
||||
def latterPartUnregister(number: Int): Option[IdentifiableEntity] = source.Return(number)
|
||||
def latterPartUnregister(number: Int): Option[IdentifiableEntity] = source.returnNumber(number)
|
||||
|
||||
/**
|
||||
* Determines if the object is registered.<br>
|
||||
|
|
@ -451,7 +474,7 @@ class NumberPoolHub(private val source: NumberSource) {
|
|||
*/
|
||||
def isRegistered(obj: IdentifiableEntity): Boolean = {
|
||||
try {
|
||||
source.Get(obj.GUID.guid) match {
|
||||
source.get(obj.GUID.guid) match {
|
||||
case Some(monitor) =>
|
||||
monitor.Object.contains(obj)
|
||||
case None =>
|
||||
|
|
@ -474,7 +497,7 @@ class NumberPoolHub(private val source: NumberSource) {
|
|||
* @see `isRegistered(IdentifiableEntity)`
|
||||
*/
|
||||
def isRegistered(number: Int): Boolean = {
|
||||
source.Get(number) match {
|
||||
source.get(number) match {
|
||||
case Some(monitor) =>
|
||||
monitor.Policy == AvailabilityPolicy.Leased
|
||||
case None =>
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ object NumberPoolActor {
|
|||
final case class NoNumber(ex: Throwable, id: Option[Any] = None)
|
||||
|
||||
/**
|
||||
* A message to invoke the `Return` functionality of the current `NumberSelector`.
|
||||
* A message to invoke the `returnNumber` functionality of the current `NumberSelector`.
|
||||
* @param number the number
|
||||
*/
|
||||
final case class ReturnNumber(number: Int, id: Option[Any] = None)
|
||||
|
|
|
|||
|
|
@ -12,9 +12,9 @@ import net.psforever.objects.guid.AvailabilityPolicy
|
|||
class LoanedKey(private val guid: Int, private val key: Monitor) {
|
||||
def GUID: Int = guid
|
||||
|
||||
def Policy: AvailabilityPolicy.Value = key.Policy
|
||||
def Policy: AvailabilityPolicy.Value = key.policy
|
||||
|
||||
def Object: Option[IdentifiableEntity] = key.Object
|
||||
def Object: Option[IdentifiableEntity] = key.obj
|
||||
|
||||
/**
|
||||
* na
|
||||
|
|
@ -30,18 +30,18 @@ class LoanedKey(private val guid: Int, private val key: Monitor) {
|
|||
*/
|
||||
def Object_=(obj: Option[IdentifiableEntity]): Option[IdentifiableEntity] = {
|
||||
if (
|
||||
key.Policy == AvailabilityPolicy.Leased || (key.Policy == AvailabilityPolicy.Restricted && key.Object.isEmpty)
|
||||
key.policy == AvailabilityPolicy.Leased || (key.policy == AvailabilityPolicy.Restricted && key.obj.isEmpty)
|
||||
) {
|
||||
if (key.Object.isDefined) {
|
||||
key.Object.get.Invalidate()
|
||||
key.Object = None
|
||||
if (key.obj.isDefined) {
|
||||
key.obj.get.Invalidate()
|
||||
key.obj = None
|
||||
}
|
||||
key.Object = obj
|
||||
key.obj = obj
|
||||
if (obj.isDefined) {
|
||||
import net.psforever.types.PlanetSideGUID
|
||||
obj.get.GUID = PlanetSideGUID(guid)
|
||||
}
|
||||
}
|
||||
key.Object
|
||||
key.obj
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,9 +5,7 @@ import net.psforever.objects.entity.IdentifiableEntity
|
|||
import net.psforever.objects.guid.AvailabilityPolicy
|
||||
|
||||
trait Monitor {
|
||||
def Policy: AvailabilityPolicy.Value
|
||||
var policy: AvailabilityPolicy.Value
|
||||
|
||||
def Object: Option[IdentifiableEntity]
|
||||
|
||||
def Object_=(objct: Option[IdentifiableEntity]): Option[IdentifiableEntity]
|
||||
var obj: Option[IdentifiableEntity]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@ import net.psforever.objects.guid.AvailabilityPolicy
|
|||
final class SecureKey(private val guid: Int, private val key: Monitor) {
|
||||
def GUID: Int = guid
|
||||
|
||||
def Policy: AvailabilityPolicy.Value = key.Policy
|
||||
def Policy: AvailabilityPolicy.Value = key.policy
|
||||
|
||||
import net.psforever.objects.entity.IdentifiableEntity
|
||||
def Object: Option[IdentifiableEntity] = key.Object
|
||||
def Object: Option[IdentifiableEntity] = key.obj
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ abstract class NumberSelector {
|
|||
* By default, a simple policy for returning numbers has been provided.
|
||||
* This will not be sufficient for all selection actions that can be implemented so `override` where necessary.
|
||||
* <br>
|
||||
* `Return` is under no obligation to leave its parameter `Array` unmodified.
|
||||
* `returnNumber` is under no obligation to leave its parameter `Array` unmodified.
|
||||
* In fact, it should modify it by default to provide additional feedback of its process.
|
||||
* Pass a copy if data mutation is a concern.
|
||||
* @param number the number to be returned
|
||||
|
|
|
|||
|
|
@ -6,20 +6,7 @@ import net.psforever.objects.guid.AvailabilityPolicy
|
|||
import net.psforever.objects.guid.key.Monitor
|
||||
|
||||
private class Key extends Monitor {
|
||||
private var policy: AvailabilityPolicy.Value = AvailabilityPolicy.Available
|
||||
private var obj: Option[IdentifiableEntity] = None
|
||||
var policy: AvailabilityPolicy.Value = AvailabilityPolicy.Available
|
||||
|
||||
def Policy: AvailabilityPolicy.Value = policy
|
||||
|
||||
def Policy_=(pol: AvailabilityPolicy.Value): AvailabilityPolicy.Value = {
|
||||
policy = pol
|
||||
Policy
|
||||
}
|
||||
|
||||
def Object: Option[IdentifiableEntity] = obj
|
||||
|
||||
def Object_=(objct: Option[IdentifiableEntity]): Option[IdentifiableEntity] = {
|
||||
obj = objct
|
||||
Object
|
||||
}
|
||||
var obj: Option[IdentifiableEntity] = None
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,111 +0,0 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package net.psforever.objects.guid.source
|
||||
|
||||
import net.psforever.objects.entity.IdentifiableEntity
|
||||
import net.psforever.objects.guid.key.{LoanedKey, SecureKey}
|
||||
import net.psforever.objects.guid.AvailabilityPolicy
|
||||
|
||||
import scala.collection.mutable
|
||||
|
||||
/**
|
||||
* A `NumberSource` is considered a master "pool" of numbers from which all numbers are available to be drawn.
|
||||
* The numbers are considered to be exclusive.<br>
|
||||
* <br>
|
||||
* Produce a series of numbers from 0 to a maximum number (inclusive) to be used as globally unique identifiers (GUIDs).
|
||||
* @param max the highest number to be generated by this source;
|
||||
* must be a positive integer or zero
|
||||
* @throws IllegalArgumentException if `max` is less than zero (therefore the count of generated numbers is at most zero)
|
||||
* @throws java.lang.NegativeArraySizeException if the count of numbers generated due to max is negative
|
||||
*/
|
||||
class LimitedNumberSource(max: Int) extends NumberSource {
|
||||
if (max < 0) {
|
||||
throw new IllegalArgumentException(s"non-negative integers only, not $max")
|
||||
}
|
||||
private val ary: Array[Key] = Array.ofDim[Key](max + 1)
|
||||
(0 to max).foreach(x => { ary(x) = new Key })
|
||||
private var allowRestrictions: Boolean = true
|
||||
|
||||
def Size: Int = ary.length
|
||||
|
||||
def CountAvailable: Int = ary.count(key => key.Policy == AvailabilityPolicy.Available)
|
||||
|
||||
def CountUsed: Int = ary.count(key => key.Policy != AvailabilityPolicy.Available)
|
||||
|
||||
def Get(number: Int): Option[SecureKey] = {
|
||||
if (Test(number)) {
|
||||
Some(new SecureKey(number, ary(number)))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
def Available(number: Int): Option[LoanedKey] = {
|
||||
var out: Option[LoanedKey] = None
|
||||
if (Test(number)) {
|
||||
val key: Key = ary(number)
|
||||
if (key.Policy == AvailabilityPolicy.Available) {
|
||||
key.Policy = AvailabilityPolicy.Leased
|
||||
out = Some(new LoanedKey(number, key))
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/**
|
||||
* Consume the number of a `Monitor` and release that number from its previous assignment/use.
|
||||
* @param number the number
|
||||
* @return any object previously using this number
|
||||
*/
|
||||
def Return(number: Int): Option[IdentifiableEntity] = {
|
||||
var out: Option[IdentifiableEntity] = None
|
||||
if (Test(number)) {
|
||||
val existing: Key = ary(number)
|
||||
if (existing.Policy == AvailabilityPolicy.Leased) {
|
||||
out = existing.Object
|
||||
existing.Policy = AvailabilityPolicy.Available
|
||||
existing.Object = None
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/**
|
||||
* Produce a modifiable wrapper for the `Monitor` for this number, only if the number has not been used.
|
||||
* This wrapped `Monitor` can only be assigned once and the number may not be `Return`ed to this source.
|
||||
* @param number the number
|
||||
* @return the wrapped `Monitor`
|
||||
* @throws ArrayIndexOutOfBoundsException if the requested number is above or below the range
|
||||
*/
|
||||
def Restrict(number: Int): Option[LoanedKey] = {
|
||||
if (allowRestrictions && Test(number)) {
|
||||
val key: Key = ary(number)
|
||||
key.Policy = AvailabilityPolicy.Restricted
|
||||
Some(new LoanedKey(number, key))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
def FinalizeRestrictions: List[Int] = {
|
||||
allowRestrictions = false
|
||||
ary.zipWithIndex.filter(entry => entry._1.Policy == AvailabilityPolicy.Restricted).map(entry => entry._2).toList
|
||||
}
|
||||
|
||||
def Clear(): List[IdentifiableEntity] = {
|
||||
val outList: mutable.ListBuffer[IdentifiableEntity] = mutable.ListBuffer[IdentifiableEntity]()
|
||||
for (x <- ary.indices) {
|
||||
ary(x).Policy = AvailabilityPolicy.Available
|
||||
if (ary(x).Object.isDefined) {
|
||||
outList += ary(x).Object.get
|
||||
ary(x).Object = None
|
||||
}
|
||||
}
|
||||
outList.toList
|
||||
}
|
||||
}
|
||||
|
||||
object LimitedNumberSource {
|
||||
def apply(max: Int): LimitedNumberSource = {
|
||||
new LimitedNumberSource(max)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,121 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package net.psforever.objects.guid.source
|
||||
|
||||
import net.psforever.objects.entity.IdentifiableEntity
|
||||
import net.psforever.objects.guid.key.{LoanedKey, SecureKey}
|
||||
import net.psforever.objects.guid.AvailabilityPolicy
|
||||
|
||||
/**
|
||||
* A `NumberSource` is considered a master "pool" of numbers from which all numbers are available to be drawn.
|
||||
* Produce a series of numbers from 0 to a maximum number (inclusive) to be used as globally unique identifiers (GUID's).
|
||||
* @param max the highest number to be generated by this source;
|
||||
* must be a positive integer or zero
|
||||
* @throws IllegalArgumentException if `max` is less than zero (therefore the count of generated numbers is at most zero)
|
||||
*/
|
||||
class MaxNumberSource(val max: Int) extends NumberSource {
|
||||
if (max < 0) {
|
||||
throw new IllegalArgumentException(s"non-negative integers only, not $max")
|
||||
}
|
||||
private val ary: Array[Key] = Array.ofDim[Key](max + 1)
|
||||
(0 to max).foreach(x => { ary(x) = new Key })
|
||||
private var allowRestrictions: Boolean = true
|
||||
|
||||
def size: Int = ary.length
|
||||
|
||||
def countAvailable: Int = ary.count(key => key.policy == AvailabilityPolicy.Available)
|
||||
|
||||
def countUsed: Int = ary.count(_.policy != AvailabilityPolicy.Available)
|
||||
|
||||
def test(number: Int): Boolean = -1 < number && number < size
|
||||
|
||||
def get(number: Int): Option[SecureKey] = {
|
||||
if (test(number)) {
|
||||
Some(new SecureKey(number, ary(number)))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
def get(obj: IdentifiableEntity) : Option[SecureKey] = {
|
||||
ary.zipWithIndex.find { case (key, _) =>
|
||||
key.obj match {
|
||||
case Some(o) => o eq obj
|
||||
case _ => false
|
||||
}
|
||||
} match {
|
||||
case Some((key, number)) => Some(new SecureKey(number, key))
|
||||
case _=> None
|
||||
}
|
||||
}
|
||||
|
||||
def getAvailable(number: Int): Option[LoanedKey] = {
|
||||
var out: Option[LoanedKey] = None
|
||||
if (test(number)) {
|
||||
val key: Key = ary(number)
|
||||
if (key.policy == AvailabilityPolicy.Available) {
|
||||
key.policy = AvailabilityPolicy.Leased
|
||||
out = Some(new LoanedKey(number, key))
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/**
|
||||
* Consume the number of a `Monitor` and release that number from its previous assignment/use.
|
||||
* @param number the number
|
||||
* @return any object previously using this number
|
||||
*/
|
||||
def returnNumber(number: Int): Option[IdentifiableEntity] = {
|
||||
var out: Option[IdentifiableEntity] = None
|
||||
if (test(number)) {
|
||||
val existing: Key = ary(number)
|
||||
if (existing.policy == AvailabilityPolicy.Leased) {
|
||||
out = existing.obj
|
||||
existing.policy = AvailabilityPolicy.Available
|
||||
existing.obj = None
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/**
|
||||
* Produce a modifiable wrapper for the `Monitor` for this number, only if the number has not been used.
|
||||
* This wrapped `Monitor` can only be assigned once and the number may not be `returnNumber`ed to this source.
|
||||
* @param number the number
|
||||
* @return the wrapped `Monitor`
|
||||
* @throws ArrayIndexOutOfBoundsException if the requested number is above or below the range
|
||||
*/
|
||||
def restrictNumber(number: Int): Option[LoanedKey] = {
|
||||
ary.lift(number) match {
|
||||
case Some(key: Key) if allowRestrictions && key.policy != AvailabilityPolicy.Restricted =>
|
||||
key.policy = AvailabilityPolicy.Restricted
|
||||
Some(new LoanedKey(number, key))
|
||||
case None =>
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
def finalizeRestrictions: List[Int] = {
|
||||
allowRestrictions = false
|
||||
ary.zipWithIndex.filter(entry => entry._1.policy == AvailabilityPolicy.Restricted).map(entry => entry._2).toList
|
||||
}
|
||||
|
||||
def clear(): List[IdentifiableEntity] = {
|
||||
val leased = ary.filter(_.policy != AvailabilityPolicy.Available)
|
||||
leased collect { case key if key.obj.isEmpty =>
|
||||
key.policy = AvailabilityPolicy.Available
|
||||
}
|
||||
leased.toList collect { case key if key.obj.nonEmpty =>
|
||||
key.policy = AvailabilityPolicy.Available
|
||||
val out = key.obj.get
|
||||
key.obj = None
|
||||
out
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object MaxNumberSource {
|
||||
def apply(max: Int): MaxNumberSource = {
|
||||
new MaxNumberSource(max)
|
||||
}
|
||||
}
|
||||
|
|
@ -18,42 +18,51 @@ import net.psforever.objects.guid.key.{LoanedKey, SecureKey}
|
|||
* The purpose of a `NumberSource` is to help facilitate globally unique identifiers (GUID, pl. GUIDs).
|
||||
*/
|
||||
trait NumberSource {
|
||||
/**
|
||||
* The maximum number that can be produced by this source.
|
||||
* @return the max
|
||||
*/
|
||||
def max: Int
|
||||
|
||||
/**
|
||||
* The count of numbers allocated to this source.
|
||||
* @return the count
|
||||
*/
|
||||
def Size: Int
|
||||
def size: Int
|
||||
|
||||
/**
|
||||
* The count of numbers that can still be drawn.
|
||||
* @return the count
|
||||
*/
|
||||
def CountAvailable: Int
|
||||
def countAvailable: Int
|
||||
|
||||
/**
|
||||
* The count of numbers that can not be drawn.
|
||||
* @return the count
|
||||
*/
|
||||
def CountUsed: Int
|
||||
def countUsed: Int
|
||||
|
||||
/**
|
||||
* Is this number a member of this number source?
|
||||
* @param number the number
|
||||
* @return `true`, if it is a member; `false`, otherwise
|
||||
*/
|
||||
def Test(number: Int): Boolean = -1 < number && number < Size
|
||||
def test(number: Int): Boolean
|
||||
|
||||
/**
|
||||
* Produce an un-modifiable wrapper for the `Monitor` for this number.
|
||||
* @param number the number
|
||||
* @return the wrapped `Monitor`
|
||||
*/
|
||||
def Get(number: Int): Option[SecureKey]
|
||||
def get(number: Int): Option[SecureKey]
|
||||
|
||||
//def GetAll(list : List[Int]) : List[SecureKey]
|
||||
|
||||
//def GetAll(p : Key => Boolean) : List[SecureKey]
|
||||
/**
|
||||
* Produce an un-modifiable wrapper for the `Monitor` for this entity,
|
||||
* if the entity is discovered being represented in this source.
|
||||
* @param obj the entity
|
||||
* @return the wrapped `Monitor`
|
||||
*/
|
||||
def get(obj: IdentifiableEntity) : Option[SecureKey]
|
||||
|
||||
/**
|
||||
* Produce a modifiable wrapper for the `Monitor` for this number, only if the number has not been used.
|
||||
|
|
@ -61,15 +70,15 @@ trait NumberSource {
|
|||
* @param number the number
|
||||
* @return the wrapped `Monitor`, or `None`
|
||||
*/
|
||||
def Available(number: Int): Option[LoanedKey]
|
||||
def getAvailable(number: Int): Option[LoanedKey]
|
||||
|
||||
/**
|
||||
* Consume a wrapped `Monitor` and release its number from its previous assignment/use.
|
||||
* @param monitor the `Monitor`
|
||||
* @return any object previously using this `Monitor`
|
||||
*/
|
||||
def Return(monitor: SecureKey): Option[IdentifiableEntity] = {
|
||||
Return(monitor.GUID)
|
||||
def returnNumber(monitor: SecureKey): Option[IdentifiableEntity] = {
|
||||
returnNumber(monitor.GUID)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -77,8 +86,8 @@ trait NumberSource {
|
|||
* @param monitor the `Monitor`
|
||||
* @return any object previously using this `Monitor`
|
||||
*/
|
||||
def Return(monitor: LoanedKey): Option[IdentifiableEntity] = {
|
||||
Return(monitor.GUID)
|
||||
def returnNumber(monitor: LoanedKey): Option[IdentifiableEntity] = {
|
||||
returnNumber(monitor.GUID)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -86,21 +95,21 @@ trait NumberSource {
|
|||
* @param number the number
|
||||
* @return any object previously using this number
|
||||
*/
|
||||
def Return(number: Int): Option[IdentifiableEntity]
|
||||
def returnNumber(number: Int): Option[IdentifiableEntity]
|
||||
|
||||
/**
|
||||
* Produce a modifiable wrapper for the `Monitor` for this number, only if the number has not been used.
|
||||
* This wrapped `Monitor` can only be assigned once and the number may not be `Return`ed to this source.
|
||||
* This wrapped `Monitor` can only be assigned once and the number may not be `returnNumber`ed to this source.
|
||||
* @param number the number
|
||||
* @return the wrapped `Monitor`
|
||||
*/
|
||||
def Restrict(number: Int): Option[LoanedKey]
|
||||
def restrictNumber(number: Int): Option[LoanedKey]
|
||||
|
||||
/**
|
||||
* Numbers from this source may not longer be marked as `Restricted`.
|
||||
* @return the `List` of all numbers that have been restricted
|
||||
*/
|
||||
def FinalizeRestrictions: List[Int]
|
||||
def finalizeRestrictions: List[Int]
|
||||
|
||||
import net.psforever.objects.entity.IdentifiableEntity
|
||||
|
||||
|
|
@ -110,5 +119,5 @@ trait NumberSource {
|
|||
* This is the only way to free `Monitors` that are marked as `Restricted`.
|
||||
* @return a `List` of assignments maintained by all the currently-used number `Monitors`
|
||||
*/
|
||||
def Clear(): List[IdentifiableEntity]
|
||||
def clear(): List[IdentifiableEntity]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,112 @@
|
|||
// Copyright (c) 2020 PSForever
|
||||
package net.psforever.objects.guid.source
|
||||
|
||||
import net.psforever.objects.entity.IdentifiableEntity
|
||||
import net.psforever.objects.guid.AvailabilityPolicy
|
||||
import net.psforever.objects.guid.key.{LoanedKey, SecureKey}
|
||||
|
||||
/**
|
||||
* A `NumberSource` is considered a master "pool" of numbers from which all numbers are available to be drawn.
|
||||
* Produce a series of numbers from 0 to a maximum number (inclusive) to be used as globally unique identifiers (GUID's).
|
||||
* @param values the domain of numbers to be used by this source;
|
||||
* must only be positive integers or zero
|
||||
* @throws IllegalArgumentException if no numbers are provided
|
||||
* @throws IllegalArgumentException if any of the numbers provided are negative
|
||||
*/
|
||||
class SpecificNumberSource(values: Iterable[Int]) extends NumberSource {
|
||||
if (values.isEmpty) {
|
||||
throw new IllegalArgumentException(s"must provide one or more positive integers (or zero)")
|
||||
}
|
||||
values.filter(_ < 0) match {
|
||||
case Nil => ;
|
||||
case list => throw new IllegalArgumentException(s"non-negative integers only, not ${list.mkString(" ")}")
|
||||
}
|
||||
private val ary : Map[Int, Key] = values.map(index => (index, new Key)).toMap
|
||||
|
||||
def max : Int = ary.keys.max
|
||||
|
||||
def size : Int = ary.size
|
||||
|
||||
def countAvailable : Int = ary.values.count(key => key.policy == AvailabilityPolicy.Available)
|
||||
|
||||
def countUsed : Int = ary.values.count(_.policy != AvailabilityPolicy.Available)
|
||||
|
||||
def test(number : Int) : Boolean = ary.get(number).nonEmpty
|
||||
|
||||
def get(number : Int) : Option[SecureKey] = {
|
||||
ary.get(number) match {
|
||||
case Some(key) => Some(new SecureKey(number, key))
|
||||
case _ => None
|
||||
}
|
||||
}
|
||||
|
||||
def get(obj : IdentifiableEntity) : Option[SecureKey] = {
|
||||
ary.find { case (_, key) =>
|
||||
key.obj match {
|
||||
case Some(o) => o eq obj
|
||||
case _ => false
|
||||
}
|
||||
} match {
|
||||
case Some((number, key)) => Some(new SecureKey(number, key))
|
||||
case _ => None
|
||||
}
|
||||
}
|
||||
|
||||
def getAvailable(number : Int) : Option[LoanedKey] = {
|
||||
ary.get(number) match {
|
||||
case Some(key) if key.policy == AvailabilityPolicy.Available =>
|
||||
key.policy = AvailabilityPolicy.Leased
|
||||
Some(new LoanedKey(number, key))
|
||||
case _ =>
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
def returnNumber(number : Int) : Option[IdentifiableEntity] = {
|
||||
ary.get(number) match {
|
||||
case Some(key) if key.policy == AvailabilityPolicy.Leased =>
|
||||
val out = key.obj
|
||||
key.policy = AvailabilityPolicy.Available
|
||||
key.obj = None
|
||||
out
|
||||
case _ =>
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
def restrictNumber(number : Int) : Option[LoanedKey] = {
|
||||
ary.get(number) match {
|
||||
case Some(key) if key.policy != AvailabilityPolicy.Restricted =>
|
||||
key.policy = AvailabilityPolicy.Restricted
|
||||
Some(new LoanedKey(number, key))
|
||||
case _ =>
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
def finalizeRestrictions : List[Int] = {
|
||||
ary
|
||||
.filter { case (_, key : Key) => key.policy == AvailabilityPolicy.Restricted }
|
||||
.keys
|
||||
.toList
|
||||
}
|
||||
|
||||
def clear(): List[IdentifiableEntity] = {
|
||||
val leased = ary.values.filter(_.policy != AvailabilityPolicy.Available)
|
||||
leased collect { case key if key.obj.isEmpty =>
|
||||
key.policy = AvailabilityPolicy.Available
|
||||
}
|
||||
leased.toList collect { case key if key.obj.nonEmpty =>
|
||||
key.policy = AvailabilityPolicy.Available
|
||||
val out = key.obj.get
|
||||
key.obj = None
|
||||
out
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object SpecificNumberSource {
|
||||
def apply(values: Iterable[Int]): SpecificNumberSource = {
|
||||
new SpecificNumberSource(values)
|
||||
}
|
||||
}
|
||||
|
|
@ -98,94 +98,3 @@ trait Container {
|
|||
def Collisions(index: Int, width: Int, height: Int): Try[List[InventoryItem]] =
|
||||
Inventory.CheckCollisionsVar(index, width, height)
|
||||
}
|
||||
|
||||
//object Container {
|
||||
// type ValidContainer = PlanetSideServerObject with Container
|
||||
//
|
||||
// final case class GetMoveItem(where_src : Int, other : ValidContainer, where_other : Int, other_item : Option[Equipment])
|
||||
//
|
||||
// final case class GiveMoveItem(cont1 : ValidContainer, where_cont1 : Int, item : Option[Equipment], cont2 : ValidContainer, where_cont2 : Int, other_item : Option[Equipment])
|
||||
//
|
||||
//
|
||||
// final case class TakeMoveItem(source_index : Int, destination : ValidContainer, destination_index : Int)
|
||||
//
|
||||
// final case class TakeMoveItem2(item_guid : PlanetSideGUID, destination : ValidContainer, destination_index : Int)
|
||||
//
|
||||
// final case class GivingMoveItem(item : Equipment, source : ValidContainer, source_index : Int, destination : ValidContainer, destination_index : Int)
|
||||
//
|
||||
// final case class PutMoveItem(item : Equipment, target_index : Int, source : ValidContainer, source_index : Int)
|
||||
//
|
||||
// final case class DropMoveItem(item : Equipment, source : ValidContainer, source_index : Int)
|
||||
//
|
||||
// final case class ItemMoved(item : Equipment, location : ValidContainer, location_index : Int)
|
||||
//
|
||||
// final case class NoMoveItem(source : ValidContainer, source_index : Int)
|
||||
//}
|
||||
//
|
||||
//trait ContainerBehavior {
|
||||
// this : Actor =>
|
||||
//
|
||||
// def ContainableObject : Container.ValidContainer
|
||||
//
|
||||
// val containerBehavior : Receive = {
|
||||
// case Container.GetMoveItem(where_src, destination, where_dest, other_item) =>
|
||||
// val slot : EquipmentSlot = ContainableObject.Slot(where_src)
|
||||
// val equipment = slot.Equipment
|
||||
// slot.Equipment = None
|
||||
// sender ! Container.GiveMoveItem(ContainableObject, where_src, equipment, destination, where_dest, other_item)
|
||||
//
|
||||
// case Container.TakeMoveItem(source_index, destination, destination_index) =>
|
||||
// val slot : EquipmentSlot = ContainableObject.Slot(source_index)
|
||||
// val equipment : Option[Equipment] = slot.Equipment
|
||||
// slot.Equipment = None
|
||||
// sender ! (equipment match {
|
||||
// case Some(item) =>
|
||||
// Container.GivingMoveItem(item, ContainableObject, source_index, destination, destination_index)
|
||||
// case None =>
|
||||
// Container.NoMoveItem(ContainableObject, source_index)
|
||||
// })
|
||||
//
|
||||
// case Container.TakeMoveItem2(item_guid, destination, destination_index) =>
|
||||
// ContainableObject.Find(item_guid) match {
|
||||
// case Some(source_index) =>
|
||||
// val slot : EquipmentSlot = ContainableObject.Slot(source_index)
|
||||
// val equipment : Option[Equipment] = slot.Equipment
|
||||
// slot.Equipment = None
|
||||
// sender ! (equipment match {
|
||||
// case Some(item) =>
|
||||
// Container.GivingMoveItem(item, ContainableObject, source_index, destination, destination_index)
|
||||
// case None => ;
|
||||
// })
|
||||
//
|
||||
// case None =>
|
||||
// sender ! Container.NoMoveItem(ContainableObject, 65535)
|
||||
// }
|
||||
//
|
||||
// case Container.PutMoveItem(item, target_index, source, source_index) =>
|
||||
// val slot : EquipmentSlot = ContainableObject.Slot(target_index)
|
||||
// val equipment : Option[Equipment] = slot.Equipment
|
||||
// if( {
|
||||
// val tile = item.Definition.Tile
|
||||
// ContainableObject.Collisions(target_index, tile.Width, tile.Height) match {
|
||||
// case Success(Nil) => //no item swap
|
||||
// true
|
||||
// case Success(_ :: Nil) => //one item to swap
|
||||
// true
|
||||
// case Success(_) | Failure(_) =>
|
||||
// false //abort when too many items at destination or other failure case
|
||||
// }
|
||||
// }) {
|
||||
// slot.Equipment = None
|
||||
// slot.Equipment = item
|
||||
// equipment match {
|
||||
// case Some(swapItem) =>
|
||||
// sender ! Container.GivingMoveItem(swapItem, ContainableObject, target_index, source, source_index)
|
||||
// case None => ;
|
||||
// }
|
||||
// sender ! Container.ItemMoved(item, ContainableObject, target_index)
|
||||
// }
|
||||
// else {
|
||||
// sender ! Container.DropMoveItem(item, source, source_index)
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
|
|
|||
|
|
@ -191,8 +191,7 @@ class GridInventory extends Container {
|
|||
} else {
|
||||
val collisions: mutable.Set[InventoryItem] = mutable.Set[InventoryItem]()
|
||||
items
|
||||
.map { case (_, item: InventoryItem) => item }
|
||||
.foreach { item: InventoryItem =>
|
||||
.foreach { case (_, item: InventoryItem) =>
|
||||
val actualItemStart: Int = item.start - offset
|
||||
val itemx: Int = actualItemStart % width
|
||||
val itemy: Int = actualItemStart / width
|
||||
|
|
@ -542,8 +541,8 @@ class GridInventory extends Container {
|
|||
//lists with multiple entries represent a group of items that collide
|
||||
//perform a specific distinct on the list of lists of numeric id overlaps
|
||||
//THEN map each list of list's item id to an item
|
||||
val out = testGrid.collect {
|
||||
case (_, list) if list.size > 1 => list.sortWith(_ < _)
|
||||
val out = testGrid.values.collect {
|
||||
case list if list.size > 1 => list.sortWith(_ < _)
|
||||
}
|
||||
recursiveRelatedListCollisions(out.iterator, out.toList).map { list => list.map { items } }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,132 @@
|
|||
// Copyright (c) 2020 PSForever
|
||||
package net.psforever.objects.inventory
|
||||
|
||||
import net.psforever.objects.Tool
|
||||
import net.psforever.objects.equipment.Equipment
|
||||
import net.psforever.objects.guid.NumberPoolHub
|
||||
import net.psforever.objects.guid.selector.RandomSelector
|
||||
import net.psforever.objects.guid.source.SpecificNumberSource
|
||||
import net.psforever.types.PlanetSideGUID
|
||||
|
||||
import scala.util.{Failure, Success}
|
||||
|
||||
/**
|
||||
* An inventory that contains its own internal unique number system bound by a domain of numbers.
|
||||
* When equipment is inserted into this inventory,
|
||||
* the equipment is registered to it, assigned one of its internal unique numbers.
|
||||
* The equipment must not already be registered to another unique number system for that reason.
|
||||
* Upon being removed, the removed equipment is unregistered.
|
||||
* The registration system adds another unspoken layer to `Capacity`
|
||||
* as it imposes a total object count to the inventory.
|
||||
* @see `NumberSourceHub`
|
||||
* @see `RandomSelector`
|
||||
* @see `SpecificNumberSource`
|
||||
* @param numbers the numbers used as unique identifiers
|
||||
*/
|
||||
class LocallyRegisteredInventory(numbers: Iterable[Int])
|
||||
extends GridInventory {
|
||||
private val hub: NumberPoolHub = {
|
||||
val numHub = new NumberPoolHub(SpecificNumberSource(numbers))
|
||||
//only one pool composed of all of the numbers; randomized selection
|
||||
numHub.AddPool("internal", numbers.toList).Selector = new RandomSelector
|
||||
numHub
|
||||
}
|
||||
|
||||
override def Insert(start : Int, obj : Equipment) : Boolean = {
|
||||
if(!obj.HasGUID) {
|
||||
registerEquipment(obj) match {
|
||||
case true if super.Insert(start, obj) =>
|
||||
true
|
||||
case true =>
|
||||
unregisterEquipment(obj) //the item failed to be inserted; undo the previous registration
|
||||
false
|
||||
case _ =>
|
||||
false
|
||||
}
|
||||
}
|
||||
else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
override def InsertQuickly(start : Int, obj : Equipment) : Boolean = {
|
||||
if(!obj.HasGUID) {
|
||||
registerEquipment(obj) match {
|
||||
case true if super.InsertQuickly(start, obj) =>
|
||||
true
|
||||
case true =>
|
||||
unregisterEquipment(obj) //the item failed to be inserted; undo the previous registration
|
||||
false
|
||||
case _ =>
|
||||
false
|
||||
}
|
||||
}
|
||||
else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
override def Remove(guid : PlanetSideGUID) : Boolean = {
|
||||
hub(guid) match {
|
||||
case Some(obj: Equipment) if super.Remove(guid) =>
|
||||
unregisterEquipment(obj)
|
||||
case _ =>
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
override def Remove(index : Int) : Boolean = {
|
||||
Slot(index).Equipment match {
|
||||
case Some(obj: Equipment) if super.Remove(obj.GUID) =>
|
||||
unregisterEquipment(obj)
|
||||
case _ =>
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
override def Clear() : List[InventoryItem] = {
|
||||
val items = super.Clear()
|
||||
items.foreach { item => unregisterEquipment(item.obj) }
|
||||
items
|
||||
}
|
||||
|
||||
private def registerEquipment(obj: Equipment): Boolean = {
|
||||
obj match {
|
||||
case tool: Tool => registerTool(tool)
|
||||
case _ => registerObject(obj)
|
||||
}
|
||||
}
|
||||
|
||||
private def registerTool(obj: Tool): Boolean = {
|
||||
val parts = obj +: obj.AmmoSlots.map { _.Box }
|
||||
val tasks = parts.map { part => hub.register(part, "internal") }
|
||||
if(tasks.exists(o => o.isInstanceOf[Failure[Int]])) {
|
||||
tasks.zipWithIndex.collect { case (Success(_), index) =>
|
||||
unregisterEquipment(parts(index))
|
||||
}
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
private def registerObject(obj: Equipment): Boolean = {
|
||||
hub.register(obj, "internal").isSuccess
|
||||
}
|
||||
|
||||
private def unregisterEquipment(obj: Equipment): Boolean = {
|
||||
obj match {
|
||||
case tool: Tool => unregisterTool(tool)
|
||||
case _ => unregisterObject(obj)
|
||||
}
|
||||
}
|
||||
|
||||
private def unregisterTool(obj: Tool): Boolean = {
|
||||
val parts = obj +: obj.AmmoSlots.map { _.Box }
|
||||
parts.map { part => hub.unregister(part) }.forall(o => o.isInstanceOf[Success[Int]])
|
||||
}
|
||||
|
||||
private def unregisterObject(obj: Equipment): Boolean = {
|
||||
hub.unregister(obj).isSuccess
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package net.psforever.objects.locker
|
||||
|
||||
import net.psforever.objects.GlobalDefinitions
|
||||
import net.psforever.objects.definition.EquipmentDefinition
|
||||
import net.psforever.objects.inventory.{Container, GridInventory}
|
||||
import net.psforever.objects.serverobject.PlanetSideServerObject
|
||||
import net.psforever.types.PlanetSideEmpire
|
||||
|
||||
/**
|
||||
* The companion of a `Locker` that is carried with a player
|
||||
* masquerading as their sixth `EquipmentSlot` object and a sub-inventory item.
|
||||
* The inventory of this object is accessed indirectly using a game world `Locker` object (`mb_locker`) as a proxy.
|
||||
* The `Player` class refers to it as the "fifth slot".
|
||||
*/
|
||||
class LockerContainer(inventory: GridInventory)
|
||||
extends PlanetSideServerObject
|
||||
with Container {
|
||||
private var faction: PlanetSideEmpire.Value = PlanetSideEmpire.NEUTRAL
|
||||
private val inv: GridInventory = inventory
|
||||
|
||||
def Faction: PlanetSideEmpire.Value = faction
|
||||
|
||||
override def Faction_=(fact: PlanetSideEmpire.Value): PlanetSideEmpire.Value = {
|
||||
faction = fact
|
||||
Faction
|
||||
}
|
||||
|
||||
def Inventory: GridInventory = inv
|
||||
|
||||
def VisibleSlots: Set[Int] = Set.empty[Int]
|
||||
|
||||
def Definition: EquipmentDefinition = GlobalDefinitions.locker_container
|
||||
}
|
||||
|
||||
object LockerContainer {
|
||||
/**
|
||||
* Overloaded constructor for the standard Infantry locker container of size 30x20.
|
||||
* @return a `LockerContainer` object
|
||||
*/
|
||||
def apply(): LockerContainer = {
|
||||
new LockerContainer(GridInventory(30, 20))
|
||||
}
|
||||
}
|
||||
|
|
@ -1,69 +1,24 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package net.psforever.objects
|
||||
// Copyright (c) 2020 PSForever
|
||||
package net.psforever.objects.locker
|
||||
|
||||
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 net.psforever.packet.game.{ObjectAttachMessage, ObjectCreateDetailedMessage, ObjectDetachMessage}
|
||||
import net.psforever.services.Service
|
||||
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||
import net.psforever.types.{PlanetSideEmpire, Vector3}
|
||||
|
||||
/**
|
||||
* The companion of a `Locker` that is carried with a player
|
||||
* masquerading as their sixth `EquipmentSlot` object and a sub-inventory item.
|
||||
* 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`).
|
||||
* A control agency mainly for manipulating the equipment stowed by a player in a `LockerContainer`
|
||||
* and reporting back to a specific xchannel in the event system about these changes.
|
||||
* @param locker the governed player-facing locker component
|
||||
* @param toChannel the channel to which to publish events, typically the owning player's name
|
||||
*/
|
||||
class LockerContainer extends PlanetSideServerObject with Container {
|
||||
private var faction: PlanetSideEmpire.Value = PlanetSideEmpire.NEUTRAL
|
||||
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]
|
||||
|
||||
def Definition: EquipmentDefinition = GlobalDefinitions.locker_container
|
||||
}
|
||||
|
||||
object LockerContainer {
|
||||
def apply(): 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 {
|
||||
class LockerContainerControl(locker: LockerContainer, toChannel: String)
|
||||
extends Actor
|
||||
with ContainableBehavior {
|
||||
def ContainerObject = locker
|
||||
|
||||
def receive: Receive =
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
// Copyright (c) 2020 PSForever
|
||||
package net.psforever.objects.locker
|
||||
|
||||
import net.psforever.objects.definition.EquipmentDefinition
|
||||
import net.psforever.objects.equipment.Equipment
|
||||
import net.psforever.objects.inventory.{Container, GridInventory}
|
||||
import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID}
|
||||
|
||||
/**
|
||||
* A wrapper class that allows a player-facing locker component
|
||||
* to be treated standard `Equipment` object in the player's fifth (sixth) slot.
|
||||
* (The opposite is not true - the equipment does not get treated as a locker component.)
|
||||
* During packet conversion and registration and general access in terms of holsters or "equipment slots",
|
||||
* the component may be be treated the same as other existing objects at the same level.
|
||||
* The entity's ability to be utilized like an inventory-stowable entity is not compromised.
|
||||
* @see `EquipmentSlot`
|
||||
* @see `IdentifiableEntity`
|
||||
* @see `LockerContainer`
|
||||
* @param locker the player-facing locker
|
||||
*/
|
||||
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
|
||||
}
|
||||
|
|
@ -12,7 +12,7 @@ import net.psforever.objects.guid.NumberPoolHub
|
|||
import net.psforever.objects.guid.actor.UniqueNumberSystem
|
||||
import net.psforever.objects.guid.key.LoanedKey
|
||||
import net.psforever.objects.guid.selector.RandomSelector
|
||||
import net.psforever.objects.guid.source.LimitedNumberSource
|
||||
import net.psforever.objects.guid.source.MaxNumberSource
|
||||
import net.psforever.objects.inventory.Container
|
||||
import net.psforever.objects.serverobject.painbox.{Painbox, PainboxDefinition}
|
||||
import net.psforever.objects.serverobject.resourcesilo.ResourceSilo
|
||||
|
|
@ -73,7 +73,7 @@ class Zone(val id: String, val map: ZoneMap, zoneNumber: Int) {
|
|||
private var accessor: ActorRef = ActorRef.noSender
|
||||
|
||||
/** The basic support structure for the globally unique number system used by this `Zone`. */
|
||||
private var guid: NumberPoolHub = new NumberPoolHub(new LimitedNumberSource(65536))
|
||||
private var guid: NumberPoolHub = new NumberPoolHub(new MaxNumberSource(65536))
|
||||
|
||||
/** A synchronized `List` of items (`Equipment`) dropped by players on the ground and can be collected again. */
|
||||
private val equipmentOnGround: ListBuffer[Equipment] = ListBuffer[Equipment]()
|
||||
|
|
@ -322,6 +322,7 @@ class Zone(val id: String, val map: ZoneMap, zoneNumber: Int) {
|
|||
guid.AddPool("f", (30001 to 35000).toList).Selector = new RandomSelector
|
||||
guid.AddPool("g", (35001 until 40100).toList).Selector = new RandomSelector
|
||||
guid.AddPool("projectiles", (Projectile.baseUID until Projectile.rangeUID).toList)
|
||||
guid.AddPool("locker-contents", (40150 until 40450).toList).Selector = new RandomSelector
|
||||
//TODO disabled temporarily to lighten load times
|
||||
//guid.AddPool("h", (40150 to 45000).toList).Selector = new RandomSelector
|
||||
//guid.AddPool("i", (45001 to 50000).toList).Selector = new RandomSelector
|
||||
|
|
@ -358,12 +359,12 @@ class Zone(val id: String, val map: ZoneMap, zoneNumber: Int) {
|
|||
building match {
|
||||
case warpGate: WarpGate =>
|
||||
warpGate.Faction == faction || warpGate.Faction == PlanetSideEmpire.NEUTRAL || warpGate.Broadcast
|
||||
case building =>
|
||||
case _ =>
|
||||
building.Faction == faction
|
||||
}
|
||||
}
|
||||
.map {
|
||||
case (building, spawns) =>
|
||||
case (building, spawns: List[SpawnPoint]) =>
|
||||
(building, spawns.filter(!_.Offline))
|
||||
}
|
||||
.concat(
|
||||
|
|
|
|||
|
|
@ -364,13 +364,12 @@ class PersistenceMonitor(name: String, squadService: ActorRef, taskResolver: Act
|
|||
//player has released
|
||||
//our last body was turned into a corpse; just the avatar remains
|
||||
//TODO perform any last minute saving now ...
|
||||
AvatarLogout(avatar)
|
||||
inZone.GUID(avatar.vehicle) match {
|
||||
case Some(obj: Vehicle) if obj.OwnerName.contains(avatar.name) =>
|
||||
obj.Actor ! Vehicle.Ownership(None)
|
||||
case _ => ;
|
||||
}
|
||||
taskResolver.tell(GUIDTask.UnregisterLocker(avatar.locker)(inZone.GUID), context.parent)
|
||||
AvatarLogout(avatar)
|
||||
|
||||
case _ =>
|
||||
//user stalled during initial session, or was caught in between zone transfer
|
||||
|
|
@ -386,7 +385,7 @@ class PersistenceMonitor(name: String, squadService: ActorRef, taskResolver: Act
|
|||
* @see `Avatar`
|
||||
* @see `AvatarAction.ObjectDelete`
|
||||
* @see `AvatarServiceMessage`
|
||||
* @see `GUIDTask.UnregisterAvatar`
|
||||
* @see `GUIDTask.UnregisterPlayer`
|
||||
* @see `Player`
|
||||
* @see `Zone.AvatarEvents`
|
||||
* @see `Zone.Population.Release`
|
||||
|
|
@ -405,8 +404,8 @@ class PersistenceMonitor(name: String, squadService: ActorRef, taskResolver: Act
|
|||
}
|
||||
inZone.Population.tell(Zone.Population.Release(avatar), parent)
|
||||
inZone.AvatarEvents.tell(AvatarServiceMessage(inZone.id, AvatarAction.ObjectDelete(pguid, pguid)), parent)
|
||||
taskResolver.tell(GUIDTask.UnregisterPlayer(player)(inZone.GUID), parent)
|
||||
AvatarLogout(avatar)
|
||||
taskResolver.tell(GUIDTask.UnregisterAvatar(player)(inZone.GUID), parent)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -424,6 +423,7 @@ class PersistenceMonitor(name: String, squadService: ActorRef, taskResolver: Act
|
|||
squadService.tell(Service.Leave(Some(avatar.id.toString)), context.parent)
|
||||
Deployables.Disown(inZone, avatar, context.parent)
|
||||
inZone.Population.tell(Zone.Population.Leave(avatar), context.parent)
|
||||
taskResolver.tell(GUIDTask.UnregisterObjectTask(avatar.locker)(inZone.GUID), context.parent)
|
||||
log.info(s"logout of ${avatar.name}")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,21 +2,14 @@ package net.psforever.zones
|
|||
|
||||
import java.io.FileNotFoundException
|
||||
|
||||
import net.psforever.objects.serverobject.terminals.{
|
||||
CaptureTerminal,
|
||||
CaptureTerminalDefinition,
|
||||
ProximityTerminal,
|
||||
ProximityTerminalDefinition,
|
||||
Terminal,
|
||||
TerminalDefinition
|
||||
}
|
||||
import net.psforever.objects.serverobject.terminals.{CaptureTerminal, CaptureTerminalDefinition, ProximityTerminal, ProximityTerminalDefinition, Terminal, TerminalDefinition}
|
||||
import net.psforever.objects.serverobject.mblocker.Locker
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
import akka.actor.ActorContext
|
||||
import io.circe._
|
||||
import io.circe.parser._
|
||||
import net.psforever.objects.{GlobalDefinitions, LocalProjectile}
|
||||
import net.psforever.objects.{GlobalDefinitions, LocalLockerItem, LocalProjectile}
|
||||
import net.psforever.objects.ballistics.Projectile
|
||||
import net.psforever.objects.definition.BasicDefinition
|
||||
import net.psforever.objects.serverobject.doors.Door
|
||||
|
|
@ -25,18 +18,13 @@ import net.psforever.objects.serverobject.locks.IFFLock
|
|||
import net.psforever.objects.serverobject.pad.{VehicleSpawnPad, VehicleSpawnPadDefinition}
|
||||
import net.psforever.objects.serverobject.painbox.{Painbox, PainboxDefinition}
|
||||
import net.psforever.objects.serverobject.resourcesilo.ResourceSilo
|
||||
import net.psforever.objects.serverobject.structures.{
|
||||
Building,
|
||||
BuildingDefinition,
|
||||
FoundationBuilder,
|
||||
StructureType,
|
||||
WarpGate
|
||||
}
|
||||
import net.psforever.objects.serverobject.structures.{Building, BuildingDefinition, FoundationBuilder, StructureType, WarpGate}
|
||||
import net.psforever.objects.serverobject.tube.SpawnTube
|
||||
import net.psforever.objects.serverobject.turret.{FacilityTurret, FacilityTurretDefinition}
|
||||
import net.psforever.objects.zones.{MapInfo, Zone, ZoneInfo, ZoneMap}
|
||||
import net.psforever.types.{PlanetSideEmpire, Vector3}
|
||||
import net.psforever.util.DefinitionUtil
|
||||
|
||||
import scala.io.Source
|
||||
import scala.collection.parallel.CollectionConverters._
|
||||
|
||||
|
|
@ -281,6 +269,9 @@ object Zones {
|
|||
(Projectile.baseUID until Projectile.rangeUID) foreach {
|
||||
zoneMap.addLocalObject(_, LocalProjectile.Constructor)
|
||||
}
|
||||
40150 until 40450 foreach {
|
||||
zoneMap.addLocalObject(_, LocalLockerItem.Constructor)
|
||||
}
|
||||
|
||||
lattice.asObject.get(info.value).foreach { obj =>
|
||||
obj.asArray.get.foreach { entry =>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue