mirror of
https://github.com/psforever/PSF-LoginServer.git
synced 2026-01-19 18:44:45 +00:00
Unique Number System Operations (#906)
* propagation of the ask pattern into the unique number actor * TaskWorkflow as a replacement for TaskResolver; includes working tests * AvailabilityPolicy has been changed slightly; number source restriction mechanic has been completely removed * TaskResolver is gone and done and TaskWorkflow replaces it * number pool variety * every zone gets a custom tailored 'environment' number pool, as well as all other number pools; uns involves many more static functions and hard-defined variables * repairs to uns and guidtask tests; worked uns into unops, an actorless version, but did not integrate into server * shuffled around files in the guid package, causing import threshing; wrote extensive comments; repaired tests; classes related to the old unique number system have been removed * created straightforward tasks; simplified number pool actor calls; repaired tests due to modifications to generic pool * bad merge recovery
This commit is contained in:
parent
ce2a3f5422
commit
9841b7e97d
|
|
@ -491,11 +491,7 @@ class AvatarStowEquipmentTest extends ActorTest {
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Preparation for these three Release tests is involved.
|
Preparation for these three Release tests is involved.
|
||||||
The ServiceManager must not only be set up correctly, but must be given a TaskResolver.
|
The ServiceManager must be set up correctly.
|
||||||
The AvatarService is started and that starts CorpseRemovalActor, an essential part of this test.
|
|
||||||
The CorpseRemovalActor needs that TaskResolver created by the ServiceManager;
|
|
||||||
but, another independent TaskResolver will be needed for manual parts of the test.
|
|
||||||
(The ServiceManager's TaskResolver can be "borrowed" but that requires writing code to intercept it.)
|
|
||||||
The Zone needs to be set up and initialized properly with a ZoneActor.
|
The Zone needs to be set up and initialized properly with a ZoneActor.
|
||||||
The ZoneActor builds the GUID Actor and the ZonePopulationActor.
|
The ZoneActor builds the GUID Actor and the ZonePopulationActor.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -46,11 +46,6 @@ akka.actor.deployment {
|
||||||
dispatcher = galaxy-service
|
dispatcher = galaxy-service
|
||||||
}
|
}
|
||||||
|
|
||||||
# Isolate tasks
|
|
||||||
"/service/taskResolver*" {
|
|
||||||
dispatcher = task-dispatcher
|
|
||||||
}
|
|
||||||
|
|
||||||
# Bottleneck (dedicated thread)
|
# Bottleneck (dedicated thread)
|
||||||
"/service/cluster" {
|
"/service/cluster" {
|
||||||
dispatcher = interstellar-cluster-service
|
dispatcher = interstellar-cluster-service
|
||||||
|
|
|
||||||
8
src/main/resources/guid-pools/c1.json
Normal file
8
src/main/resources/guid-pools/c1.json
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"Name": "environment",
|
||||||
|
"Start": 0,
|
||||||
|
"Max": 937,
|
||||||
|
"Selector": "specific"
|
||||||
|
}
|
||||||
|
]
|
||||||
8
src/main/resources/guid-pools/c2.json
Normal file
8
src/main/resources/guid-pools/c2.json
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"Name": "environment",
|
||||||
|
"Start": 0,
|
||||||
|
"Max": 1615,
|
||||||
|
"Selector": "specific"
|
||||||
|
}
|
||||||
|
]
|
||||||
8
src/main/resources/guid-pools/c3.json
Normal file
8
src/main/resources/guid-pools/c3.json
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"Name": "environment",
|
||||||
|
"Start": 0,
|
||||||
|
"Max": 1128,
|
||||||
|
"Selector": "specific"
|
||||||
|
}
|
||||||
|
]
|
||||||
8
src/main/resources/guid-pools/c4.json
Normal file
8
src/main/resources/guid-pools/c4.json
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"Name": "environment",
|
||||||
|
"Start": 0,
|
||||||
|
"Max": 904,
|
||||||
|
"Selector": "specific"
|
||||||
|
}
|
||||||
|
]
|
||||||
8
src/main/resources/guid-pools/c5.json
Normal file
8
src/main/resources/guid-pools/c5.json
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"Name": "environment",
|
||||||
|
"Start": 0,
|
||||||
|
"Max": 635,
|
||||||
|
"Selector": "specific"
|
||||||
|
}
|
||||||
|
]
|
||||||
8
src/main/resources/guid-pools/c6.json
Normal file
8
src/main/resources/guid-pools/c6.json
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"Name": "environment",
|
||||||
|
"Start": 0,
|
||||||
|
"Max": 908,
|
||||||
|
"Selector": "specific"
|
||||||
|
}
|
||||||
|
]
|
||||||
74
src/main/resources/guid-pools/default.json
Normal file
74
src/main/resources/guid-pools/default.json
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"Name": "environment",
|
||||||
|
"Start": 0,
|
||||||
|
"Max": 3000,
|
||||||
|
"Selector": "specific"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "players",
|
||||||
|
"Start": 3001,
|
||||||
|
"Max": 4500,
|
||||||
|
"Selector": "random"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "lockers",
|
||||||
|
"Start": 4501,
|
||||||
|
"Max": 5000,
|
||||||
|
"Selector": "random"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "tools",
|
||||||
|
"Start": 5001,
|
||||||
|
"Max": 9500,
|
||||||
|
"Selector": "random"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "ammo",
|
||||||
|
"Start": 9501,
|
||||||
|
"Max": 23000,
|
||||||
|
"Selector": "random"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "kits",
|
||||||
|
"Start": 23001,
|
||||||
|
"Max": 36500,
|
||||||
|
"Selector": "random"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "items",
|
||||||
|
"Start": 36501,
|
||||||
|
"Max": 39500,
|
||||||
|
"Selector": "random"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "projectiles",
|
||||||
|
"Start": 40100,
|
||||||
|
"Max": 40149,
|
||||||
|
"Selector": "specific"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "locker-contents",
|
||||||
|
"Start": 40150,
|
||||||
|
"Max": 40450,
|
||||||
|
"Selector": "specific"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "vehicles",
|
||||||
|
"Start": 45001,
|
||||||
|
"Max": 47000,
|
||||||
|
"Selector": "random"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "terminals",
|
||||||
|
"Start": 47001,
|
||||||
|
"Max": 48000,
|
||||||
|
"Selector": "random"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "deployables",
|
||||||
|
"Start": 48001,
|
||||||
|
"Max": 64000,
|
||||||
|
"Selector": "random"
|
||||||
|
}
|
||||||
|
]
|
||||||
8
src/main/resources/guid-pools/home1.json
Normal file
8
src/main/resources/guid-pools/home1.json
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"Name": "environment",
|
||||||
|
"Start": 0,
|
||||||
|
"Max": 1161,
|
||||||
|
"Selector": "specific"
|
||||||
|
}
|
||||||
|
]
|
||||||
8
src/main/resources/guid-pools/home2.json
Normal file
8
src/main/resources/guid-pools/home2.json
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"Name": "environment",
|
||||||
|
"Start": 0,
|
||||||
|
"Max": 1096,
|
||||||
|
"Selector": "specific"
|
||||||
|
}
|
||||||
|
]
|
||||||
8
src/main/resources/guid-pools/home3.json
Normal file
8
src/main/resources/guid-pools/home3.json
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"Name": "environment",
|
||||||
|
"Start": 0,
|
||||||
|
"Max": 1074,
|
||||||
|
"Selector": "specific"
|
||||||
|
}
|
||||||
|
]
|
||||||
8
src/main/resources/guid-pools/i1.json
Normal file
8
src/main/resources/guid-pools/i1.json
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"Name": "environment",
|
||||||
|
"Start": 0,
|
||||||
|
"Max": 778,
|
||||||
|
"Selector": "specific"
|
||||||
|
}
|
||||||
|
]
|
||||||
8
src/main/resources/guid-pools/i2.json
Normal file
8
src/main/resources/guid-pools/i2.json
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"Name": "environment",
|
||||||
|
"Start": 0,
|
||||||
|
"Max": 918,
|
||||||
|
"Selector": "specific"
|
||||||
|
}
|
||||||
|
]
|
||||||
8
src/main/resources/guid-pools/i3.json
Normal file
8
src/main/resources/guid-pools/i3.json
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"Name": "environment",
|
||||||
|
"Start": 0,
|
||||||
|
"Max": 699,
|
||||||
|
"Selector": "specific"
|
||||||
|
}
|
||||||
|
]
|
||||||
8
src/main/resources/guid-pools/i4.json
Normal file
8
src/main/resources/guid-pools/i4.json
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"Name": "environment",
|
||||||
|
"Start": 0,
|
||||||
|
"Max": 318,
|
||||||
|
"Selector": "specific"
|
||||||
|
}
|
||||||
|
]
|
||||||
8
src/main/resources/guid-pools/z1.json
Normal file
8
src/main/resources/guid-pools/z1.json
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"Name": "environment",
|
||||||
|
"Start": 0,
|
||||||
|
"Max": 2088,
|
||||||
|
"Selector": "specific"
|
||||||
|
}
|
||||||
|
]
|
||||||
8
src/main/resources/guid-pools/z10.json
Normal file
8
src/main/resources/guid-pools/z10.json
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"Name": "environment",
|
||||||
|
"Start": 0,
|
||||||
|
"Max": 2657,
|
||||||
|
"Selector": "specific"
|
||||||
|
}
|
||||||
|
]
|
||||||
8
src/main/resources/guid-pools/z2.json
Normal file
8
src/main/resources/guid-pools/z2.json
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"Name": "environment",
|
||||||
|
"Start": 0,
|
||||||
|
"Max": 2515,
|
||||||
|
"Selector": "specific"
|
||||||
|
}
|
||||||
|
]
|
||||||
44
src/main/resources/guid-pools/z3.json
Normal file
44
src/main/resources/guid-pools/z3.json
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"Name": "environment",
|
||||||
|
"Start": 0,
|
||||||
|
"Max": 3732,
|
||||||
|
"Selector": "specific"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "players",
|
||||||
|
"Start": 4001,
|
||||||
|
"Max": 5500,
|
||||||
|
"Selector": "random"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "lockers",
|
||||||
|
"Start": 5501,
|
||||||
|
"Max": 6000,
|
||||||
|
"Selector": "random"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "tools",
|
||||||
|
"Start": 6001,
|
||||||
|
"Max": 10500,
|
||||||
|
"Selector": "random"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "ammo",
|
||||||
|
"Start": 10501,
|
||||||
|
"Max": 24000,
|
||||||
|
"Selector": "random"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "kits",
|
||||||
|
"Start": 24001,
|
||||||
|
"Max": 37500,
|
||||||
|
"Selector": "random"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "items",
|
||||||
|
"Start": 37501,
|
||||||
|
"Max": 40099,
|
||||||
|
"Selector": "random"
|
||||||
|
}
|
||||||
|
]
|
||||||
44
src/main/resources/guid-pools/z4.json
Normal file
44
src/main/resources/guid-pools/z4.json
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"Name": "environment",
|
||||||
|
"Start": 0,
|
||||||
|
"Max": 3076,
|
||||||
|
"Selector": "specific"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "players",
|
||||||
|
"Start": 3101,
|
||||||
|
"Max": 4600,
|
||||||
|
"Selector": "random"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "lockers",
|
||||||
|
"Start": 4601,
|
||||||
|
"Max": 5100,
|
||||||
|
"Selector": "random"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "tools",
|
||||||
|
"Start": 5101,
|
||||||
|
"Max": 9600,
|
||||||
|
"Selector": "random"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "ammo",
|
||||||
|
"Start": 9601,
|
||||||
|
"Max": 23100,
|
||||||
|
"Selector": "random"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "kits",
|
||||||
|
"Start": 23101,
|
||||||
|
"Max": 36600,
|
||||||
|
"Selector": "random"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "items",
|
||||||
|
"Start": 36601,
|
||||||
|
"Max": 39600,
|
||||||
|
"Selector": "random"
|
||||||
|
}
|
||||||
|
]
|
||||||
8
src/main/resources/guid-pools/z5.json
Normal file
8
src/main/resources/guid-pools/z5.json
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"Name": "environment",
|
||||||
|
"Start": 0,
|
||||||
|
"Max": 2112,
|
||||||
|
"Selector": "specific"
|
||||||
|
}
|
||||||
|
]
|
||||||
8
src/main/resources/guid-pools/z6.json
Normal file
8
src/main/resources/guid-pools/z6.json
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"Name": "environment",
|
||||||
|
"Start": 0,
|
||||||
|
"Max": 2422,
|
||||||
|
"Selector": "specific"
|
||||||
|
}
|
||||||
|
]
|
||||||
8
src/main/resources/guid-pools/z7.json
Normal file
8
src/main/resources/guid-pools/z7.json
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"Name": "environment",
|
||||||
|
"Start": 0,
|
||||||
|
"Max": 2816,
|
||||||
|
"Selector": "specific"
|
||||||
|
}
|
||||||
|
]
|
||||||
8
src/main/resources/guid-pools/z8.json
Normal file
8
src/main/resources/guid-pools/z8.json
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"Name": "environment",
|
||||||
|
"Start": 0,
|
||||||
|
"Max": 2139,
|
||||||
|
"Selector": "specific"
|
||||||
|
}
|
||||||
|
]
|
||||||
8
src/main/resources/guid-pools/z9.json
Normal file
8
src/main/resources/guid-pools/z9.json
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"Name": "environment",
|
||||||
|
"Start": 0,
|
||||||
|
"Max": 2921,
|
||||||
|
"Selector": "specific"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
@ -16,7 +16,7 @@ import net.psforever.objects.definition._
|
||||||
import net.psforever.objects.definition.converter.{CorpseConverter, DestroyedVehicleConverter}
|
import net.psforever.objects.definition.converter.{CorpseConverter, DestroyedVehicleConverter}
|
||||||
import net.psforever.objects.entity.{SimpleWorldEntity, WorldEntity}
|
import net.psforever.objects.entity.{SimpleWorldEntity, WorldEntity}
|
||||||
import net.psforever.objects.equipment._
|
import net.psforever.objects.equipment._
|
||||||
import net.psforever.objects.guid.{GUIDTask, Task, TaskResolver}
|
import net.psforever.objects.guid._
|
||||||
import net.psforever.objects.inventory.{Container, InventoryItem}
|
import net.psforever.objects.inventory.{Container, InventoryItem}
|
||||||
import net.psforever.objects.locker.LockerContainer
|
import net.psforever.objects.locker.LockerContainer
|
||||||
import net.psforever.objects.serverobject.affinity.FactionAffinity
|
import net.psforever.objects.serverobject.affinity.FactionAffinity
|
||||||
|
|
@ -1049,11 +1049,11 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
|
|
||||||
//!!only dispatched to SessionActor as cleanup if the target deployable was never fully introduced
|
//!!only dispatched to SessionActor as cleanup if the target deployable was never fully introduced
|
||||||
case Zone.Deployable.IsDismissed(obj: TurretDeployable) =>
|
case Zone.Deployable.IsDismissed(obj: TurretDeployable) =>
|
||||||
continent.tasks ! GUIDTask.UnregisterDeployableTurret(obj)(continent.GUID)
|
TaskWorkflow.execute(GUIDTask.unregisterDeployableTurret(continent.GUID, obj))
|
||||||
|
|
||||||
//!!only dispatched to SessionActor as cleanup if the target deployable was never fully introduced
|
//!!only dispatched to SessionActor as cleanup if the target deployable was never fully introduced
|
||||||
case Zone.Deployable.IsDismissed(obj) =>
|
case Zone.Deployable.IsDismissed(obj) =>
|
||||||
continent.tasks ! GUIDTask.UnregisterObjectTask(obj)(continent.GUID)
|
TaskWorkflow.execute(GUIDTask.unregisterObject(continent.GUID, obj))
|
||||||
|
|
||||||
case ICS.ZonesResponse(zones) =>
|
case ICS.ZonesResponse(zones) =>
|
||||||
zones.foreach { zone =>
|
zones.foreach { zone =>
|
||||||
|
|
@ -1156,9 +1156,9 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
player.avatar = avatar
|
player.avatar = avatar
|
||||||
interstellarFerry match {
|
interstellarFerry match {
|
||||||
case Some(vehicle) if vehicle.PassengerInSeat(player).contains(0) =>
|
case Some(vehicle) if vehicle.PassengerInSeat(player).contains(0) =>
|
||||||
continent.tasks ! RegisterDrivenVehicle(vehicle, player)
|
TaskWorkflow.execute(registerDrivenVehicle(vehicle, player))
|
||||||
case _ =>
|
case _ =>
|
||||||
continent.tasks ! RegisterNewAvatar(player)
|
TaskWorkflow.execute(registerNewAvatar(player))
|
||||||
}
|
}
|
||||||
|
|
||||||
case NewPlayerLoaded(tplayer) =>
|
case NewPlayerLoaded(tplayer) =>
|
||||||
|
|
@ -1997,13 +1997,13 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
case (_, guid) => sendResponse(ObjectDeleteMessage(guid, 0))
|
case (_, guid) => sendResponse(ObjectDeleteMessage(guid, 0))
|
||||||
}
|
}
|
||||||
//functionally delete
|
//functionally delete
|
||||||
delete.foreach { case (obj, _) => continent.tasks ! GUIDTask.UnregisterEquipment(obj)(continent.GUID) }
|
delete.foreach { case (obj, _) => TaskWorkflow.execute(GUIDTask.unregisterEquipment(continent.GUID, obj)) }
|
||||||
//redraw
|
//redraw
|
||||||
if (maxhand) {
|
if (maxhand) {
|
||||||
continent.tasks ! HoldNewEquipmentUp(player)(
|
TaskWorkflow.execute(HoldNewEquipmentUp(player)(
|
||||||
Tool(GlobalDefinitions.MAXArms(subtype, player.Faction)),
|
Tool(GlobalDefinitions.MAXArms(subtype, player.Faction)),
|
||||||
0
|
0
|
||||||
)
|
))
|
||||||
}
|
}
|
||||||
//draw free hand
|
//draw free hand
|
||||||
player.FreeHand.Equipment match {
|
player.FreeHand.Equipment match {
|
||||||
|
|
@ -2075,14 +2075,14 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
(old_holsters ++ old_inventory).foreach {
|
(old_holsters ++ old_inventory).foreach {
|
||||||
case (obj, guid) =>
|
case (obj, guid) =>
|
||||||
sendResponse(ObjectDeleteMessage(guid, 0))
|
sendResponse(ObjectDeleteMessage(guid, 0))
|
||||||
continent.tasks ! GUIDTask.UnregisterEquipment(obj)(continent.GUID)
|
TaskWorkflow.execute(GUIDTask.unregisterEquipment(continent.GUID, obj))
|
||||||
}
|
}
|
||||||
//redraw
|
//redraw
|
||||||
if (maxhand) {
|
if (maxhand) {
|
||||||
continent.tasks ! HoldNewEquipmentUp(player)(
|
TaskWorkflow.execute(HoldNewEquipmentUp(player)(
|
||||||
Tool(GlobalDefinitions.MAXArms(subtype, player.Faction)),
|
Tool(GlobalDefinitions.MAXArms(subtype, player.Faction)),
|
||||||
0
|
0
|
||||||
)
|
))
|
||||||
}
|
}
|
||||||
ApplyPurchaseTimersBeforePackingLoadout(player, player, holsters ++ inventory)
|
ApplyPurchaseTimersBeforePackingLoadout(player, player, holsters ++ inventory)
|
||||||
DropLeftovers(player)(drops)
|
DropLeftovers(player)(drops)
|
||||||
|
|
@ -2166,7 +2166,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
if (Avatar.purchaseCooldowns.contains(item.obj.Definition)) {
|
if (Avatar.purchaseCooldowns.contains(item.obj.Definition)) {
|
||||||
avatarActor ! AvatarActor.UpdatePurchaseTime(item.obj.Definition)
|
avatarActor ! AvatarActor.UpdatePurchaseTime(item.obj.Definition)
|
||||||
}
|
}
|
||||||
continent.tasks ! PutLoadoutEquipmentInInventory(target)(item.obj, item.start)
|
TaskWorkflow.execute(PutLoadoutEquipmentInInventory(target)(item.obj, item.start))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2574,11 +2574,11 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, false))
|
sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, false))
|
||||||
case None =>
|
case None =>
|
||||||
avatarActor ! AvatarActor.UpdatePurchaseTime(item.Definition)
|
avatarActor ! AvatarActor.UpdatePurchaseTime(item.Definition)
|
||||||
continent.tasks ! BuyNewEquipmentPutInInventory(
|
TaskWorkflow.execute(BuyNewEquipmentPutInInventory(
|
||||||
continent.GUID(tplayer.VehicleSeated) match { case Some(v : Vehicle) => v; case _ => player },
|
continent.GUID(tplayer.VehicleSeated) match { case Some(v : Vehicle) => v; case _ => player },
|
||||||
tplayer,
|
tplayer,
|
||||||
msg.terminal_guid
|
msg.terminal_guid
|
||||||
)(item)
|
)(item))
|
||||||
}
|
}
|
||||||
|
|
||||||
case Terminal.SellEquipment() =>
|
case Terminal.SellEquipment() =>
|
||||||
|
|
@ -2637,7 +2637,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
entry.obj.Faction = tplayer.Faction
|
entry.obj.Faction = tplayer.Faction
|
||||||
vTrunk.InsertQuickly(entry.start, entry.obj)
|
vTrunk.InsertQuickly(entry.start, entry.obj)
|
||||||
})
|
})
|
||||||
continent.tasks ! RegisterVehicleFromSpawnPad(vehicle, pad, term)
|
TaskWorkflow.execute(registerVehicleFromSpawnPad(vehicle, pad, term))
|
||||||
sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, true))
|
sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, true))
|
||||||
case _ =>
|
case _ =>
|
||||||
log.error(
|
log.error(
|
||||||
|
|
@ -2921,7 +2921,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
(old_weapons ++ old_inventory).foreach {
|
(old_weapons ++ old_inventory).foreach {
|
||||||
case (obj, eguid) =>
|
case (obj, eguid) =>
|
||||||
sendResponse(ObjectDeleteMessage(eguid, 0))
|
sendResponse(ObjectDeleteMessage(eguid, 0))
|
||||||
continent.tasks ! GUIDTask.UnregisterEquipment(obj)(continent.GUID)
|
TaskWorkflow.execute(GUIDTask.unregisterEquipment(continent.GUID, obj))
|
||||||
}
|
}
|
||||||
ApplyPurchaseTimersBeforePackingLoadout(player, vehicle, added_weapons ++ new_inventory)
|
ApplyPurchaseTimersBeforePackingLoadout(player, vehicle, added_weapons ++ new_inventory)
|
||||||
} else if (accessedContainer.contains(target)) {
|
} else if (accessedContainer.contains(target)) {
|
||||||
|
|
@ -4406,7 +4406,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
continent.id,
|
continent.id,
|
||||||
AvatarAction.ProjectileExplodes(player.GUID, obj.GUID, obj)
|
AvatarAction.ProjectileExplodes(player.GUID, obj.GUID, obj)
|
||||||
)
|
)
|
||||||
continent.tasks ! UnregisterProjectile(obj)
|
TaskWorkflow.execute(unregisterProjectile(obj))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -4965,13 +4965,13 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
dObj.Orientation = orient
|
dObj.Orientation = orient
|
||||||
dObj.Faction = player.Faction
|
dObj.Faction = player.Faction
|
||||||
dObj.AssignOwnership(player)
|
dObj.AssignOwnership(player)
|
||||||
val tasking: TaskResolver.GiveTask = dObj match {
|
val tasking: TaskBundle = dObj match {
|
||||||
case turret: TurretDeployable =>
|
case turret: TurretDeployable =>
|
||||||
GUIDTask.RegisterDeployableTurret(turret)(continent.GUID)
|
GUIDTask.registerDeployableTurret(continent.GUID, turret)
|
||||||
case _ =>
|
case _ =>
|
||||||
GUIDTask.RegisterObjectTask(dObj)(continent.GUID)
|
GUIDTask.registerObject(continent.GUID, dObj)
|
||||||
}
|
}
|
||||||
continent.tasks ! CallBackForTask(tasking, continent.Deployables, Zone.Deployable.BuildByOwner(dObj, player, obj))
|
TaskWorkflow.execute(CallBackForTask(tasking, continent.Deployables, Zone.Deployable.BuildByOwner(dObj, player, obj)))
|
||||||
|
|
||||||
case Some(obj) =>
|
case Some(obj) =>
|
||||||
log.warn(s"DeployObject: what is $obj, ${player.Name}? It's not a construction tool!")
|
log.warn(s"DeployObject: what is $obj, ${player.Name}? It's not a construction tool!")
|
||||||
|
|
@ -5724,35 +5724,22 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
* as if that player is only just being introduced.
|
* 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.
|
* `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`
|
* @param tplayer the avatar `Player`
|
||||||
* @return a `TaskResolver.GiveTask` message
|
* @return a `TaskBundle` message
|
||||||
*/
|
*/
|
||||||
private def RegisterNewAvatar(tplayer: Player): TaskResolver.GiveTask = {
|
private def registerNewAvatar(tplayer: Player): TaskBundle = {
|
||||||
TaskResolver.GiveTask(
|
TaskBundle(
|
||||||
new Task() {
|
new StraightforwardTask() {
|
||||||
private val localPlayer = tplayer
|
private val localPlayer = tplayer
|
||||||
private val localAnnounce = self
|
private val localAnnounce = self
|
||||||
|
|
||||||
override def Description: String = s"register new player avatar ${localPlayer.Name}"
|
override def description(): String = s"register new player avatar ${localPlayer.Name}"
|
||||||
|
|
||||||
override def isComplete: Task.Resolution.Value = {
|
def action(): Future[Any] = {
|
||||||
if (localPlayer.HasGUID) {
|
localAnnounce ! NewPlayerLoaded(localPlayer)
|
||||||
Task.Resolution.Success
|
Future(true)
|
||||||
} else {
|
|
||||||
Task.Resolution.Incomplete
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def Execute(resolver: ActorRef): Unit = {
|
|
||||||
log.trace(s"Player ${localPlayer.Name} is newly registered")
|
|
||||||
resolver ! Success(this)
|
|
||||||
localAnnounce ! NewPlayerLoaded(localPlayer) //alerts WorldSessionActor
|
|
||||||
}
|
|
||||||
|
|
||||||
override def onFailure(ex: Throwable): Unit = {
|
|
||||||
localAnnounce ! PlayerFailedToLoad(localPlayer) //alerts SessionActor
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
List(GUIDTask.RegisterAvatar(tplayer)(continent.GUID))
|
List(GUIDTask.registerAvatar(continent.GUID, tplayer))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -5761,35 +5748,22 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
* as if that player was already introduced and is just being renewed.
|
* 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.
|
* `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`
|
* @param tplayer the avatar `Player`
|
||||||
* @return a `TaskResolver.GiveTask` message
|
* @return a `TaskBundle` message
|
||||||
*/
|
*/
|
||||||
private def RegisterAvatar(tplayer: Player): TaskResolver.GiveTask = {
|
private def registerAvatar(tplayer: Player): TaskBundle = {
|
||||||
TaskResolver.GiveTask(
|
TaskBundle(
|
||||||
new Task() {
|
new StraightforwardTask() {
|
||||||
private val localPlayer = tplayer
|
private val localPlayer = tplayer
|
||||||
private val localAnnounce = self
|
private val localAnnounce = self
|
||||||
|
|
||||||
override def Description: String = s"register player avatar ${localPlayer.Name}"
|
override def description(): String = s"register player avatar ${localPlayer.Name}"
|
||||||
|
|
||||||
override def isComplete: Task.Resolution.Value = {
|
def action(): Future[Any] = {
|
||||||
if (localPlayer.HasGUID) {
|
localAnnounce ! PlayerLoaded(localPlayer)
|
||||||
Task.Resolution.Success
|
Future(true)
|
||||||
} else {
|
|
||||||
Task.Resolution.Incomplete
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def Execute(resolver: ActorRef): Unit = {
|
|
||||||
log.trace(s"Player $localPlayer is registered")
|
|
||||||
resolver ! Success(this)
|
|
||||||
localAnnounce ! PlayerLoaded(localPlayer) //alerts WorldSessionActor
|
|
||||||
}
|
|
||||||
|
|
||||||
override def onFailure(ex: Throwable): Unit = {
|
|
||||||
localAnnounce ! PlayerFailedToLoad(localPlayer) //alerts WorldSessionActor
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
List(GUIDTask.RegisterPlayer(tplayer)(continent.GUID))
|
List(GUIDTask.registerPlayer(continent.GUID, tplayer))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -5798,29 +5772,21 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
* Use this function to renew the globally unique identifiers on a vehicle that has already been added to the scene once.
|
* Use this function to renew the globally unique identifiers on a vehicle that has already been added to the scene once.
|
||||||
* @param vehicle the `Vehicle` object
|
* @param vehicle the `Vehicle` object
|
||||||
* @see `RegisterVehicleFromSpawnPad`
|
* @see `RegisterVehicleFromSpawnPad`
|
||||||
* @return a `TaskResolver.GiveTask` message
|
* @return a `TaskBundle` message
|
||||||
*/
|
*/
|
||||||
def RegisterVehicle(vehicle: Vehicle): TaskResolver.GiveTask = {
|
private def registerVehicle(vehicle: Vehicle): TaskBundle = {
|
||||||
TaskResolver.GiveTask(
|
TaskBundle(
|
||||||
new Task() {
|
new StraightforwardTask() {
|
||||||
private val localVehicle = vehicle
|
private val localVehicle = vehicle
|
||||||
|
private val localAnnounce = self
|
||||||
|
|
||||||
override def Description: String = s"register a ${localVehicle.Definition.Name}"
|
override def description(): String = s"register a ${localVehicle.Definition.Name}"
|
||||||
|
|
||||||
override def isComplete: Task.Resolution.Value = {
|
def action(): Future[Any] = {
|
||||||
if (localVehicle.HasGUID) {
|
Future(true)
|
||||||
Task.Resolution.Success
|
|
||||||
} else {
|
|
||||||
Task.Resolution.Incomplete
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def Execute(resolver: ActorRef): Unit = {
|
|
||||||
log.trace(s"Vehicle $localVehicle is registered")
|
|
||||||
resolver ! Success(this)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
List(GUIDTask.RegisterVehicle(vehicle)(continent.GUID))
|
List(GUIDTask.registerVehicle(continent.GUID, vehicle))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -5839,43 +5805,34 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
* and the user who is the driver (second param) is properly seated
|
* 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 `GlobalDefinitions.droppod`
|
||||||
* @see `GUIDTask.RegisterObjectTask`
|
* @see `GUIDTask.registerObject`
|
||||||
* @see `interstellarFerry`
|
* @see `interstellarFerry`
|
||||||
* @see `Player.VehicleSeated`
|
* @see `Player.VehicleSeated`
|
||||||
* @see `PlayerLoaded`
|
* @see `PlayerLoaded`
|
||||||
* @see `TaskResolver.GiveTask`
|
* @see `TaskBundle`
|
||||||
* @see `Vehicles.Own`
|
* @see `Vehicles.Own`
|
||||||
* @param vehicle the unregistered droppod
|
* @param vehicle the unregistered droppod
|
||||||
* @param tplayer the player using the droppod for instant action;
|
* @param tplayer the player using the droppod for instant action;
|
||||||
* should already be the driver of the droppod
|
* should already be the driver of the droppod
|
||||||
* @return a `TaskResolver.GiveTask` message
|
* @return a `TaskBundle` message
|
||||||
*/
|
*/
|
||||||
def RegisterDroppod(vehicle: Vehicle, tplayer: Player): TaskResolver.GiveTask = {
|
private def registerDroppod(vehicle: Vehicle, tplayer: Player): TaskBundle = {
|
||||||
TaskResolver.GiveTask(
|
TaskBundle(
|
||||||
new Task() {
|
new StraightforwardTask() {
|
||||||
private val localDriver = tplayer
|
private val localDriver = tplayer
|
||||||
private val localVehicle = vehicle
|
private val localVehicle = vehicle
|
||||||
private val localAnnounce = self
|
private val localAnnounce = self
|
||||||
|
|
||||||
override def Description: String = s"register a ${localVehicle.Definition.Name} manned by ${localDriver.Name}"
|
override def description(): String = s"register a ${localVehicle.Definition.Name} manned by ${localDriver.Name}"
|
||||||
|
|
||||||
override def isComplete: Task.Resolution.Value = {
|
def action(): Future[Any] = {
|
||||||
if (localVehicle.HasGUID) {
|
|
||||||
Task.Resolution.Success
|
|
||||||
} else {
|
|
||||||
Task.Resolution.Incomplete
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def Execute(resolver: ActorRef): Unit = {
|
|
||||||
log.trace(s"Vehicle $localVehicle is registered")
|
|
||||||
localDriver.VehicleSeated = localVehicle.GUID
|
localDriver.VehicleSeated = localVehicle.GUID
|
||||||
Vehicles.Own(localVehicle, localDriver)
|
Vehicles.Own(localVehicle, localDriver)
|
||||||
localAnnounce ! PlayerLoaded(localDriver)
|
localAnnounce ! PlayerLoaded(localDriver)
|
||||||
resolver ! Success(this)
|
Future(true)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
List(GUIDTask.RegisterObjectTask(vehicle)(continent.GUID))
|
List(GUIDTask.registerObject(continent.GUID, vehicle))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -5887,65 +5844,44 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
* the vehicle is being brought into existence from scratch and was never a member of any `Zone`.
|
* the vehicle is being brought into existence from scratch and was never a member of any `Zone`.
|
||||||
* @param obj the `Vehicle` object
|
* @param obj the `Vehicle` object
|
||||||
* @see `RegisterVehicle`
|
* @see `RegisterVehicle`
|
||||||
* @return a `TaskResolver.GiveTask` message
|
* @return a `TaskBundle` message
|
||||||
*/
|
*/
|
||||||
def RegisterVehicleFromSpawnPad(obj: Vehicle, pad: VehicleSpawnPad, terminal: Terminal): TaskResolver.GiveTask = {
|
private def registerVehicleFromSpawnPad(vehicle: Vehicle, pad: VehicleSpawnPad, terminal: Terminal): TaskBundle = {
|
||||||
TaskResolver.GiveTask(
|
TaskBundle(
|
||||||
new Task() {
|
new StraightforwardTask() {
|
||||||
private val localVehicle = obj
|
private val localVehicle = vehicle
|
||||||
private val localPad = pad.Actor
|
private val localPad = pad.Actor
|
||||||
private val localTerminal = terminal
|
private val localTerminal = terminal
|
||||||
private val localPlayer = player
|
private val localPlayer = player
|
||||||
|
|
||||||
override def Description: String = s"register a ${localVehicle.Definition.Name} for spawn pad"
|
override def description(): String = s"register a ${localVehicle.Definition.Name} for spawn pad"
|
||||||
|
|
||||||
override def isComplete: Task.Resolution.Value = {
|
def action(): Future[Any] = {
|
||||||
if (localVehicle.HasGUID) {
|
|
||||||
Task.Resolution.Success
|
|
||||||
} else {
|
|
||||||
Task.Resolution.Incomplete
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def Execute(resolver: ActorRef): Unit = {
|
|
||||||
localPad ! VehicleSpawnPad.VehicleOrder(localPlayer, localVehicle, localTerminal)
|
localPad ! VehicleSpawnPad.VehicleOrder(localPlayer, localVehicle, localTerminal)
|
||||||
resolver ! Success(this)
|
Future(true)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
List(RegisterVehicle(obj))
|
List(registerVehicle(vehicle))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
def RegisterDrivenVehicle(obj: Vehicle, driver: Player): TaskResolver.GiveTask = {
|
private def registerDrivenVehicle(vehicle: Vehicle, driver: Player): TaskBundle = {
|
||||||
TaskResolver.GiveTask(
|
TaskBundle(
|
||||||
new Task() {
|
new StraightforwardTask() {
|
||||||
private val localVehicle = obj
|
private val localVehicle = vehicle
|
||||||
private val localDriver = driver
|
private val localDriver = driver
|
||||||
private val localAnnounce = self
|
private val localAnnounce = self
|
||||||
|
|
||||||
override def Description: String = s"register a ${localVehicle.Definition.Name} driven by ${localDriver.Name}"
|
override def description(): String = s"register a ${localVehicle.Definition.Name} driven by ${localDriver.Name}"
|
||||||
|
|
||||||
override def isComplete: Task.Resolution.Value = {
|
def action(): Future[Any] = {
|
||||||
if (localVehicle.HasGUID && localDriver.HasGUID) {
|
|
||||||
Task.Resolution.Success
|
|
||||||
} else {
|
|
||||||
Task.Resolution.Incomplete
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def Execute(resolver: ActorRef): Unit = {
|
|
||||||
log.trace(s"${localDriver.Name} 's vehicle ${localVehicle.Definition.Name} is registered")
|
|
||||||
localDriver.VehicleSeated = localVehicle.GUID
|
localDriver.VehicleSeated = localVehicle.GUID
|
||||||
Vehicles.Own(localVehicle, localDriver)
|
Vehicles.Own(localVehicle, localDriver)
|
||||||
localAnnounce ! NewPlayerLoaded(localDriver) //alerts WorldSessionActor
|
localAnnounce ! NewPlayerLoaded(localDriver)
|
||||||
resolver ! Success(this)
|
Future(true)
|
||||||
}
|
|
||||||
|
|
||||||
override def onFailure(ex: Throwable): Unit = {
|
|
||||||
localAnnounce ! PlayerFailedToLoad(localDriver) //alerts SessionActor
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
List(GUIDTask.RegisterAvatar(driver)(continent.GUID), GUIDTask.RegisterVehicle(obj)(continent.GUID))
|
List(GUIDTask.registerAvatar(continent.GUID, driver), GUIDTask.registerVehicle(continent.GUID, vehicle))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -5954,55 +5890,39 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
* After the projectile is registered to the curent zone's global unique identifier system,
|
* After the projectile is registered to the curent zone's global unique identifier system,
|
||||||
* all connected clients save for the one that registered it will be informed about the projectile's "creation."
|
* all connected clients save for the one that registered it will be informed about the projectile's "creation."
|
||||||
* @param obj the projectile to be registered
|
* @param obj the projectile to be registered
|
||||||
* @return a `TaskResolver.GiveTask` message
|
* @return a `TaskBundle` message
|
||||||
*/
|
*/
|
||||||
def RegisterProjectile(obj: Projectile): TaskResolver.GiveTask = {
|
private def registerProjectile(obj: Projectile): TaskBundle = {
|
||||||
val definition = obj.Definition
|
TaskBundle(
|
||||||
TaskResolver.GiveTask(
|
new StraightforwardTask() {
|
||||||
new Task() {
|
|
||||||
private val globalProjectile = obj
|
private val globalProjectile = obj
|
||||||
private val localAnnounce = self
|
private val localAnnounce = self
|
||||||
|
|
||||||
override def Description: String = s"register a ${globalProjectile.profile.Name}"
|
override def description(): String = s"register a ${globalProjectile.profile.Name}"
|
||||||
|
|
||||||
override def isComplete: Task.Resolution.Value = {
|
def action(): Future[Any] = {
|
||||||
if (globalProjectile.HasGUID) {
|
|
||||||
Task.Resolution.Success
|
|
||||||
} else {
|
|
||||||
Task.Resolution.Incomplete
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def Execute(resolver: ActorRef): Unit = {
|
|
||||||
localAnnounce ! LoadedRemoteProjectile(globalProjectile.GUID, Some(globalProjectile))
|
localAnnounce ! LoadedRemoteProjectile(globalProjectile.GUID, Some(globalProjectile))
|
||||||
resolver ! Success(this)
|
Future(true)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
List(GUIDTask.RegisterObjectTask(obj)(continent.GUID))
|
List(GUIDTask.registerObject(continent.GUID, obj))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
def UnregisterDrivenVehicle(obj: Vehicle, driver: Player): TaskResolver.GiveTask = {
|
private def unregisterDrivenVehicle(vehicle: Vehicle, driver: Player): TaskBundle = {
|
||||||
TaskResolver.GiveTask(
|
TaskBundle(
|
||||||
new Task() {
|
new StraightforwardTask() {
|
||||||
private val localVehicle = obj
|
private val localVehicle = vehicle
|
||||||
private val localDriver = driver
|
private val localDriver = driver
|
||||||
|
private val localAnnounce = self
|
||||||
|
|
||||||
override def Description: String = s"unregister a ${localVehicle.Definition.Name} driven by ${localDriver.Name}"
|
override def description(): String = s"unregister a ${localVehicle.Definition.Name} driven by ${localDriver.Name}"
|
||||||
|
|
||||||
override def isComplete: Task.Resolution.Value = {
|
def action(): Future[Any] = {
|
||||||
if (!localVehicle.HasGUID && !localDriver.HasGUID) {
|
Future(true)
|
||||||
Task.Resolution.Success
|
|
||||||
} else {
|
|
||||||
Task.Resolution.Incomplete
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def Execute(resolver: ActorRef): Unit = {
|
|
||||||
resolver ! Success(this)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
List(GUIDTask.UnregisterAvatar(driver)(continent.GUID), GUIDTask.UnregisterVehicle(obj)(continent.GUID))
|
List(GUIDTask.unregisterAvatar(continent.GUID, driver), GUIDTask.unregisterVehicle(continent.GUID, vehicle))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -6011,52 +5931,42 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
* After the projectile is unregistered from the curent zone's global unique identifier system,
|
* After the projectile is unregistered from the curent zone's global unique identifier system,
|
||||||
* all connected clients save for the one that registered it will be informed about the projectile's "destruction."
|
* all connected clients save for the one that registered it will be informed about the projectile's "destruction."
|
||||||
* @param obj the projectile to be unregistered
|
* @param obj the projectile to be unregistered
|
||||||
* @return a `TaskResolver.GiveTask` message
|
* @return a `TaskBundle` message
|
||||||
*/
|
*/
|
||||||
def UnregisterProjectile(obj: Projectile): TaskResolver.GiveTask = {
|
private def unregisterProjectile(obj: Projectile): TaskBundle = {
|
||||||
TaskResolver.GiveTask(
|
TaskBundle(
|
||||||
new Task() {
|
new StraightforwardTask() {
|
||||||
private val globalProjectile = obj
|
private val globalProjectile = obj
|
||||||
private val localAnnounce = continent.AvatarEvents
|
private val localAnnounce = self
|
||||||
private val localMsg = AvatarServiceMessage(continent.id, AvatarAction.ObjectDelete(player.GUID, obj.GUID, 2))
|
private val localMsg = AvatarServiceMessage(continent.id, AvatarAction.ObjectDelete(player.GUID, obj.GUID, 2))
|
||||||
|
|
||||||
override def Description: String = s"unregister a ${globalProjectile.profile.Name}"
|
override def description(): String = s"unregister a ${globalProjectile.profile.Name}"
|
||||||
|
|
||||||
override def isComplete: Task.Resolution.Value = {
|
def action(): Future[Any] = {
|
||||||
if (!globalProjectile.HasGUID) {
|
|
||||||
Task.Resolution.Success
|
|
||||||
} else {
|
|
||||||
Task.Resolution.Incomplete
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def Execute(resolver: ActorRef): Unit = {
|
|
||||||
localAnnounce ! localMsg
|
localAnnounce ! localMsg
|
||||||
resolver ! Success(this)
|
Future(true)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
List(GUIDTask.UnregisterObjectTask(obj)(continent.GUID))
|
List(GUIDTask.unregisterObject(continent.GUID, obj))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If the projectile object is unregistered, register it.
|
* If the projectile object is unregistered, register it.
|
||||||
* If the projectile object is already registered, unregister it and then register it again.
|
* If the projectile object is already registered, unregister it and then register it again.
|
||||||
* @see `RegisterProjectile(Projectile)`
|
* @see `registerProjectile(Projectile)`
|
||||||
* @see `UnregisterProjectile(Projectile)`
|
* @see `unregisterProjectile(Projectile)`
|
||||||
* @param obj the projectile to be registered (a second time?)
|
* @param obj the projectile to be registered (a second time?)
|
||||||
* @return a `TaskResolver.GiveTask` message
|
* @return a `TaskBundle` message
|
||||||
*/
|
*/
|
||||||
def ReregisterProjectile(obj: Projectile): TaskResolver.GiveTask = {
|
def reregisterProjectile(obj: Projectile): TaskBundle = {
|
||||||
val reg = RegisterProjectile(obj)
|
val reg = registerProjectile(obj)
|
||||||
if (obj.HasGUID) {
|
if (obj.HasGUID) {
|
||||||
TaskResolver.GiveTask(
|
TaskBundle(
|
||||||
reg.task,
|
reg.mainTask,
|
||||||
List(
|
TaskBundle(
|
||||||
TaskResolver.GiveTask(
|
reg.subTasks(0).mainTask,
|
||||||
reg.subs(0).task,
|
unregisterProjectile(obj)
|
||||||
List(UnregisterProjectile(obj))
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -6371,13 +6281,13 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
case veh: Vehicle => ModifyAmmunitionInVehicle(veh)
|
case veh: Vehicle => ModifyAmmunitionInVehicle(veh)
|
||||||
case _ => ModifyAmmunition(obj)
|
case _ => ModifyAmmunition(obj)
|
||||||
}
|
}
|
||||||
val stowNewFunc: Equipment => TaskResolver.GiveTask = PutNewEquipmentInInventoryOrDrop(obj)
|
val stowNewFunc: Equipment => TaskBundle = PutNewEquipmentInInventoryOrDrop(obj)
|
||||||
val stowFunc: Equipment => Future[Any] = PutEquipmentInInventoryOrDrop(obj)
|
val stowFunc: Equipment => Future[Any] = PutEquipmentInInventoryOrDrop(obj)
|
||||||
|
|
||||||
xs.foreach(item => {
|
xs.foreach(item => {
|
||||||
obj.Inventory -= item.start
|
obj.Inventory -= item.start
|
||||||
sendResponse(ObjectDeleteMessage(item.obj.GUID, 0))
|
sendResponse(ObjectDeleteMessage(item.obj.GUID, 0))
|
||||||
continent.tasks ! GUIDTask.UnregisterObjectTask(item.obj)(continent.GUID)
|
TaskWorkflow.execute(GUIDTask.unregisterObject(continent.GUID, item.obj))
|
||||||
})
|
})
|
||||||
|
|
||||||
//box will be the replacement ammo; give it the discovered magazine and load it into the weapon
|
//box will be the replacement ammo; give it the discovered magazine and load it into the weapon
|
||||||
|
|
@ -6426,7 +6336,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
s"PerformToolAmmoChange: ${player.Name} takes ${originalBoxCapacity - splitReloadAmmo} from a box of $originalBoxCapacity $requestedAmmoType ammo"
|
s"PerformToolAmmoChange: ${player.Name} takes ${originalBoxCapacity - splitReloadAmmo} from a box of $originalBoxCapacity $requestedAmmoType ammo"
|
||||||
)
|
)
|
||||||
val boxForInventory = AmmoBox(box.Definition, splitReloadAmmo)
|
val boxForInventory = AmmoBox(box.Definition, splitReloadAmmo)
|
||||||
continent.tasks ! stowNewFunc(boxForInventory)
|
TaskWorkflow.execute(stowNewFunc(boxForInventory))
|
||||||
fullMagazine
|
fullMagazine
|
||||||
}
|
}
|
||||||
sendResponse(
|
sendResponse(
|
||||||
|
|
@ -6471,10 +6381,10 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
case Nil | List(_) => ; //done (the former case is technically not possible)
|
case Nil | List(_) => ; //done (the former case is technically not possible)
|
||||||
case _ :: toUpdate =>
|
case _ :: toUpdate =>
|
||||||
modifyFunc(previousBox, 0) //update to changed capacity value
|
modifyFunc(previousBox, 0) //update to changed capacity value
|
||||||
toUpdate.foreach(box => { continent.tasks ! stowNewFunc(box) })
|
toUpdate.foreach(box => { TaskWorkflow.execute(stowNewFunc(box)) })
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
continent.tasks ! GUIDTask.UnregisterObjectTask(previousBox)(continent.GUID)
|
TaskWorkflow.execute(GUIDTask.unregisterObject(continent.GUID, previousBox))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -6496,32 +6406,6 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
zone.Ground.tell(Zone.Ground.DropItem(item, obj.Position, Vector3.z(obj.Orientation.z)), obj.Actor)
|
zone.Ground.tell(Zone.Ground.DropItem(item, obj.Position, Vector3.z(obj.Orientation.z)), obj.Actor)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Register an `Equipment` item and then drop it on the ground.
|
|
||||||
* @see `NormalItemDrop`
|
|
||||||
* @param obj a `Container` object that represents where the item will be dropped;
|
|
||||||
* curried for callback
|
|
||||||
* @param zone the continent in which the item is being dropped;
|
|
||||||
* curried for callback
|
|
||||||
* @param item the item
|
|
||||||
*/
|
|
||||||
def NewItemDrop(obj: PlanetSideServerObject with Container, zone: Zone)(item: Equipment): TaskResolver.GiveTask = {
|
|
||||||
TaskResolver.GiveTask(
|
|
||||||
new Task() {
|
|
||||||
private val localItem = item
|
|
||||||
private val localFunc: Equipment => Unit = NormalItemDrop(obj, zone)
|
|
||||||
|
|
||||||
override def Description: String = s"dropping a new ${localItem.Definition.Name} on the ground"
|
|
||||||
|
|
||||||
def Execute(resolver: ActorRef): Unit = {
|
|
||||||
localFunc(localItem)
|
|
||||||
resolver ! Success(this)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
List(GUIDTask.RegisterEquipment(item)(zone.GUID))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* After a weapon has finished shooting, determine if it needs to be sorted in a special way.
|
* After a weapon has finished shooting, determine if it needs to be sorted in a special way.
|
||||||
* @param tool a weapon
|
* @param tool a weapon
|
||||||
|
|
@ -7274,7 +7158,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
* @see `AvatarAction.Release`
|
* @see `AvatarAction.Release`
|
||||||
* @see `AvatarServiceMessage`
|
* @see `AvatarServiceMessage`
|
||||||
* @see `FriskDeadBody`
|
* @see `FriskDeadBody`
|
||||||
* @see `GUIDTask.UnregisterPlayer`
|
* @see `GUIDTask.unregisterPlayer`
|
||||||
* @see `ObjectDeleteMessage`
|
* @see `ObjectDeleteMessage`
|
||||||
* @see `WellLootedDeadBody`
|
* @see `WellLootedDeadBody`
|
||||||
* @see `Zone.Corpse.Add`
|
* @see `Zone.Corpse.Add`
|
||||||
|
|
@ -7291,7 +7175,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
zone.Population ! Zone.Population.Release(avatar)
|
zone.Population ! Zone.Population.Release(avatar)
|
||||||
sendResponse(ObjectDeleteMessage(pguid, 0))
|
sendResponse(ObjectDeleteMessage(pguid, 0))
|
||||||
zone.AvatarEvents ! AvatarServiceMessage(zone.id, AvatarAction.ObjectDelete(pguid, pguid, 0))
|
zone.AvatarEvents ! AvatarServiceMessage(zone.id, AvatarAction.ObjectDelete(pguid, pguid, 0))
|
||||||
zone.tasks ! GUIDTask.UnregisterPlayer(tplayer)(zone.GUID)
|
TaskWorkflow.execute(GUIDTask.unregisterPlayer(zone.GUID, tplayer))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -7788,7 +7672,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
projectile.quality(quality)
|
projectile.quality(quality)
|
||||||
} else if (projectile.tool_def.Size == EquipmentSize.Melee) {
|
} else if (projectile.tool_def.Size == EquipmentSize.Melee) {
|
||||||
//melee
|
//melee
|
||||||
val quality = player.avatar.implants.flatten.find { entry => entry.definition == GlobalDefinitions.melee_booster } match {
|
val quality = player.avatar.implants.flatten.find { entry => entry.definition.implantType == ImplantType.MeleeBooster } match {
|
||||||
case Some(booster) if booster.active && player.avatar.stamina > 9 =>
|
case Some(booster) if booster.active && player.avatar.stamina > 9 =>
|
||||||
avatarActor ! AvatarActor.ConsumeStamina(10)
|
avatarActor ! AvatarActor.ConsumeStamina(10)
|
||||||
ProjectileQuality.Modified(25f)
|
ProjectileQuality.Modified(25f)
|
||||||
|
|
@ -8210,7 +8094,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
if (!zoneReload && zoneId == continent.id) {
|
if (!zoneReload && zoneId == continent.id) {
|
||||||
if (player.isBackpack) { // important! test the actor-wide player ref, not the parameter
|
if (player.isBackpack) { // important! test the actor-wide player ref, not the parameter
|
||||||
// respawning from unregistered player
|
// respawning from unregistered player
|
||||||
continent.tasks ! RegisterAvatar(targetPlayer)
|
TaskWorkflow.execute(registerAvatar(targetPlayer))
|
||||||
} else {
|
} else {
|
||||||
// move existing player; this is the one case where the original GUID is retained by the player
|
// move existing player; this is the one case where the original GUID is retained by the player
|
||||||
self ! PlayerLoaded(targetPlayer)
|
self ! PlayerLoaded(targetPlayer)
|
||||||
|
|
@ -8220,15 +8104,15 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
val original = player
|
val original = player
|
||||||
if (player.isBackpack) {
|
if (player.isBackpack) {
|
||||||
session = session.copy(player = targetPlayer)
|
session = session.copy(player = targetPlayer)
|
||||||
taskThenZoneChange(
|
TaskWorkflow.execute(taskThenZoneChange(
|
||||||
GUIDTask.UnregisterObjectTask(original.avatar.locker)(continent.GUID),
|
GUIDTask.unregisterObject(continent.GUID, original.avatar.locker),
|
||||||
ICS.FindZone(_.id == zoneId, context.self)
|
ICS.FindZone(_.id == zoneId, context.self)
|
||||||
)
|
))
|
||||||
} else if (player.HasGUID) {
|
} else if (player.HasGUID) {
|
||||||
taskThenZoneChange(
|
TaskWorkflow.execute(taskThenZoneChange(
|
||||||
GUIDTask.UnregisterAvatar(original)(continent.GUID),
|
GUIDTask.unregisterAvatar(continent.GUID, original),
|
||||||
ICS.FindZone(_.id == zoneId, context.self)
|
ICS.FindZone(_.id == zoneId, context.self)
|
||||||
)
|
))
|
||||||
} else {
|
} else {
|
||||||
cluster ! ICS.FindZone(_.id == zoneId, context.self)
|
cluster ! ICS.FindZone(_.id == zoneId, context.self)
|
||||||
}
|
}
|
||||||
|
|
@ -8317,7 +8201,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
if (!zoneReload && zoneId == continent.id) {
|
if (!zoneReload && zoneId == continent.id) {
|
||||||
if (vehicle.Definition == GlobalDefinitions.droppod) {
|
if (vehicle.Definition == GlobalDefinitions.droppod) {
|
||||||
//instant action droppod in the same zone
|
//instant action droppod in the same zone
|
||||||
continent.tasks ! RegisterDroppod(vehicle, player)
|
TaskWorkflow.execute(registerDroppod(vehicle, player))
|
||||||
} else {
|
} else {
|
||||||
//transferring a vehicle between spawn points (warp gates) in the same zone
|
//transferring a vehicle between spawn points (warp gates) in the same zone
|
||||||
self ! PlayerLoaded(player)
|
self ! PlayerLoaded(player)
|
||||||
|
|
@ -8325,10 +8209,10 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
} else if (vehicle.Definition == GlobalDefinitions.droppod) {
|
} else if (vehicle.Definition == GlobalDefinitions.droppod) {
|
||||||
LoadZoneCommonTransferActivity()
|
LoadZoneCommonTransferActivity()
|
||||||
player.Continent = zoneId //forward-set the continent id to perform a test
|
player.Continent = zoneId //forward-set the continent id to perform a test
|
||||||
taskThenZoneChange(
|
TaskWorkflow.execute(taskThenZoneChange(
|
||||||
GUIDTask.UnregisterAvatar(player)(continent.GUID),
|
GUIDTask.unregisterAvatar(continent.GUID, player),
|
||||||
ICS.FindZone(_.id == zoneId, context.self)
|
ICS.FindZone(_.id == zoneId, context.self)
|
||||||
)
|
))
|
||||||
} else {
|
} else {
|
||||||
UnaccessContainer(vehicle)
|
UnaccessContainer(vehicle)
|
||||||
LoadZoneCommonTransferActivity()
|
LoadZoneCommonTransferActivity()
|
||||||
|
|
@ -8347,10 +8231,10 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
}
|
}
|
||||||
//unregister vehicle and driver whole + GiveWorld
|
//unregister vehicle and driver whole + GiveWorld
|
||||||
continent.Transport ! Zone.Vehicle.Despawn(vehicle)
|
continent.Transport ! Zone.Vehicle.Despawn(vehicle)
|
||||||
taskThenZoneChange(
|
TaskWorkflow.execute(taskThenZoneChange(
|
||||||
UnregisterDrivenVehicle(vehicle, player),
|
unregisterDrivenVehicle(vehicle, player),
|
||||||
ICS.FindZone(_.id == zoneId, context.self)
|
ICS.FindZone(_.id == zoneId, context.self)
|
||||||
)
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -8369,7 +8253,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
* A reference to the top-level ferrying vehicle's former globally unique identifier has been retained for this purpose.
|
* 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.
|
* This vehicle can be deleted for everyone if no more work can be detected.
|
||||||
*
|
*
|
||||||
* @see `GUIDTask.UnregisterPlayer`
|
* @see `GUIDTask.unregisterPlayer`
|
||||||
* @see `LoadZoneCommonTransferActivity`
|
* @see `LoadZoneCommonTransferActivity`
|
||||||
* @see `Vehicles.AllGatedOccupantsInSameZone`
|
* @see `Vehicles.AllGatedOccupantsInSameZone`
|
||||||
* @see `PlayerLoaded`
|
* @see `PlayerLoaded`
|
||||||
|
|
@ -8393,10 +8277,10 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
val continentId = continent.id
|
val continentId = continent.id
|
||||||
interstellarFerryTopLevelGUID = None
|
interstellarFerryTopLevelGUID = None
|
||||||
|
|
||||||
taskThenZoneChange(
|
TaskWorkflow.execute(taskThenZoneChange(
|
||||||
GUIDTask.UnregisterAvatar(player)(continent.GUID),
|
GUIDTask.unregisterAvatar(continent.GUID, player),
|
||||||
ICS.FindZone(_.id == zoneId, context.self)
|
ICS.FindZone(_.id == zoneId, context.self)
|
||||||
)
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -8439,20 +8323,24 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
|
|
||||||
/** Before changing zones, perform the following task (which can be a nesting of subtasks). */
|
/** Before changing zones, perform the following task (which can be a nesting of subtasks). */
|
||||||
def taskThenZoneChange(
|
def taskThenZoneChange(
|
||||||
task: TaskResolver.GiveTask,
|
task: TaskBundle,
|
||||||
zoneMessage: ICS.FindZone
|
zoneMessage: ICS.FindZone
|
||||||
): Unit = {
|
): TaskBundle = {
|
||||||
continent.tasks ! TaskResolver.GiveTask(
|
TaskBundle(
|
||||||
new Task() {
|
new StraightforwardTask() {
|
||||||
override def isComplete: Task.Resolution.Value = task.task.isComplete
|
val localAvatar = avatar
|
||||||
|
val localZone = continent
|
||||||
|
val localCluster = cluster
|
||||||
|
|
||||||
def Execute(resolver: ActorRef): Unit = {
|
override def description() : String = s"doing ${task.description()} before transferring zones"
|
||||||
continent.Population ! Zone.Population.Leave(avatar)
|
|
||||||
|
def action(): Future[Any] = {
|
||||||
|
continent.Population ! Zone.Population.Leave(localAvatar)
|
||||||
cluster ! zoneMessage
|
cluster ! zoneMessage
|
||||||
resolver ! Success(this)
|
Future(true)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
List(task)
|
task
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -8474,7 +8362,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
avatarActor ! AvatarActor.SetVehicle(None)
|
avatarActor ! AvatarActor.SetVehicle(None)
|
||||||
}
|
}
|
||||||
RemoveBoomerTriggersFromInventory().foreach(obj => {
|
RemoveBoomerTriggersFromInventory().foreach(obj => {
|
||||||
continent.tasks ! GUIDTask.UnregisterObjectTask(obj)(continent.GUID)
|
TaskWorkflow.execute(GUIDTask.unregisterObject(continent.GUID, obj))
|
||||||
})
|
})
|
||||||
Deployables.Disown(continent, avatar, self)
|
Deployables.Disown(continent, avatar, self)
|
||||||
drawDeloyableIcon = RedrawDeployableIcons //important for when SetCurrentAvatar initializes the UI next zone
|
drawDeloyableIcon = RedrawDeployableIcons //important for when SetCurrentAvatar initializes the UI next zone
|
||||||
|
|
@ -8896,7 +8784,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
continent.id,
|
continent.id,
|
||||||
AvatarAction.ProjectileExplodes(player.GUID, projectile_guid, projectile)
|
AvatarAction.ProjectileExplodes(player.GUID, projectile_guid, projectile)
|
||||||
)
|
)
|
||||||
continent.tasks ! UnregisterProjectile(projectile)
|
TaskWorkflow.execute(unregisterProjectile(projectile))
|
||||||
projectiles(local_index) match {
|
projectiles(local_index) match {
|
||||||
case Some(obj) if !obj.isResolved => obj.Miss()
|
case Some(obj) if !obj.isResolved => obj.Miss()
|
||||||
case _ => ;
|
case _ => ;
|
||||||
|
|
@ -8933,7 +8821,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
tplayer.VehicleSeated = None
|
tplayer.VehicleSeated = None
|
||||||
zone.Population ! Zone.Population.Release(avatar)
|
zone.Population ! Zone.Population.Release(avatar)
|
||||||
sendResponse(ObjectDeleteMessage(tplayer.GUID, 0))
|
sendResponse(ObjectDeleteMessage(tplayer.GUID, 0))
|
||||||
zone.tasks ! GUIDTask.UnregisterPlayer(tplayer)(zone.GUID)
|
TaskWorkflow.execute(GUIDTask.unregisterPlayer(zone.GUID, tplayer))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -9144,15 +9032,15 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
log.trace(
|
log.trace(
|
||||||
s"WeaponFireMessage: ${player.Name}'s ${projectile_info.Name} is a remote projectile"
|
s"WeaponFireMessage: ${player.Name}'s ${projectile_info.Name} is a remote projectile"
|
||||||
)
|
)
|
||||||
continent.tasks ! (if (projectile.HasGUID) {
|
if (projectile.HasGUID) {
|
||||||
continent.AvatarEvents ! AvatarServiceMessage(
|
continent.AvatarEvents ! AvatarServiceMessage(
|
||||||
continent.id,
|
continent.id,
|
||||||
AvatarAction.ProjectileExplodes(player.GUID, projectile.GUID, projectile)
|
AvatarAction.ProjectileExplodes(player.GUID, projectile.GUID, projectile)
|
||||||
)
|
)
|
||||||
ReregisterProjectile(projectile)
|
TaskWorkflow.execute(reregisterProjectile(projectile))
|
||||||
} else {
|
} else {
|
||||||
RegisterProjectile(projectile)
|
TaskWorkflow.execute(registerProjectile(projectile))
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
projectilesToCleanUp(projectileIndex) = false
|
projectilesToCleanUp(projectileIndex) = false
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import akka.actor.ActorRef
|
||||||
import akka.pattern.{AskTimeoutException, ask}
|
import akka.pattern.{AskTimeoutException, ask}
|
||||||
import akka.util.Timeout
|
import akka.util.Timeout
|
||||||
import net.psforever.objects.equipment.{Ammo, Equipment}
|
import net.psforever.objects.equipment.{Ammo, Equipment}
|
||||||
import net.psforever.objects.guid.{GUIDTask, Task, TaskResolver}
|
import net.psforever.objects.guid._
|
||||||
import net.psforever.objects.inventory.{Container, InventoryItem}
|
import net.psforever.objects.inventory.{Container, InventoryItem}
|
||||||
import net.psforever.objects.locker.LockerContainer
|
import net.psforever.objects.locker.LockerContainer
|
||||||
import net.psforever.objects.serverobject.PlanetSideServerObject
|
import net.psforever.objects.serverobject.PlanetSideServerObject
|
||||||
|
|
@ -73,32 +73,29 @@ object WorldSession {
|
||||||
* Equipment will go wherever it fits in containing object, or be dropped if it fits nowhere.
|
* Equipment will go wherever it fits in containing object, or be dropped if it fits nowhere.
|
||||||
* Item swapping during the placement is not allowed.
|
* Item swapping during the placement is not allowed.
|
||||||
* @see `ChangeAmmoMessage`
|
* @see `ChangeAmmoMessage`
|
||||||
* @see `GUIDTask.RegisterEquipment`
|
* @see `GUIDTask.registerEquipment`
|
||||||
* @see `PutEquipmentInInventoryOrDrop`
|
* @see `PutEquipmentInInventoryOrDrop`
|
||||||
* @see `Task`
|
* @see `Task`
|
||||||
* @see `TaskResolver.GiveTask`
|
* @see `TaskBundle`
|
||||||
* @param obj the container
|
* @param obj the container
|
||||||
* @param item the item being manipulated
|
* @param item the item being manipulated
|
||||||
* @return a `TaskResolver` object
|
* @return a `TaskBundle` object
|
||||||
*/
|
*/
|
||||||
def PutNewEquipmentInInventorySlot(
|
def PutNewEquipmentInInventorySlot(
|
||||||
obj: PlanetSideServerObject with Container
|
obj: PlanetSideServerObject with Container
|
||||||
)(item: Equipment, slot: Int): TaskResolver.GiveTask = {
|
)(item: Equipment, slot: Int): TaskBundle = {
|
||||||
val localZone = obj.Zone
|
val localZone = obj.Zone
|
||||||
TaskResolver.GiveTask(
|
TaskBundle(
|
||||||
new Task() {
|
new StraightforwardTask() {
|
||||||
private val localContainer = obj
|
private val localContainer = obj
|
||||||
private val localItem = item
|
private val localItem = item
|
||||||
private val localSlot = slot
|
private val localSlot = slot
|
||||||
|
|
||||||
override def isComplete: Task.Resolution.Value = Task.Resolution.Success
|
def action(): Future[Any] = {
|
||||||
|
|
||||||
def Execute(resolver: ActorRef): Unit = {
|
|
||||||
PutEquipmentInInventorySlot(localContainer)(localItem, localSlot)
|
PutEquipmentInInventorySlot(localContainer)(localItem, localSlot)
|
||||||
resolver ! Success(this)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
List(GUIDTask.RegisterEquipment(item)(localZone.GUID))
|
GUIDTask.registerEquipment(localZone.GUID, item)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -108,31 +105,28 @@ object WorldSession {
|
||||||
* Equipment will go wherever it fits in containing object, or be dropped if it fits nowhere.
|
* Equipment will go wherever it fits in containing object, or be dropped if it fits nowhere.
|
||||||
* Item swapping during the placement is not allowed.
|
* Item swapping during the placement is not allowed.
|
||||||
* @see `ChangeAmmoMessage`
|
* @see `ChangeAmmoMessage`
|
||||||
* @see `GUIDTask.RegisterEquipment`
|
* @see `GUIDTask.registerEquipment`
|
||||||
* @see `PutEquipmentInInventoryOrDrop`
|
* @see `PutEquipmentInInventoryOrDrop`
|
||||||
* @see `Task`
|
* @see `Task`
|
||||||
* @see `TaskResolver.GiveTask`
|
* @see `TaskBundle`
|
||||||
* @param obj the container
|
* @param obj the container
|
||||||
* @param item the item being manipulated
|
* @param item the item being manipulated
|
||||||
* @return a `TaskResolver` object
|
* @return a `TaskBundle` object
|
||||||
*/
|
*/
|
||||||
def PutNewEquipmentInInventoryOrDrop(
|
def PutNewEquipmentInInventoryOrDrop(
|
||||||
obj: PlanetSideServerObject with Container
|
obj: PlanetSideServerObject with Container
|
||||||
)(item: Equipment): TaskResolver.GiveTask = {
|
)(item: Equipment): TaskBundle = {
|
||||||
val localZone = obj.Zone
|
val localZone = obj.Zone
|
||||||
TaskResolver.GiveTask(
|
TaskBundle(
|
||||||
new Task() {
|
new StraightforwardTask() {
|
||||||
private val localContainer = obj
|
private val localContainer = obj
|
||||||
private val localItem = item
|
private val localItem = item
|
||||||
|
|
||||||
override def isComplete: Task.Resolution.Value = Task.Resolution.Success
|
def action(): Future[Any] = {
|
||||||
|
|
||||||
def Execute(resolver: ActorRef): Unit = {
|
|
||||||
PutEquipmentInInventoryOrDrop(localContainer)(localItem)
|
PutEquipmentInInventoryOrDrop(localContainer)(localItem)
|
||||||
resolver ! Success(this)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
List(GUIDTask.RegisterEquipment(item)(localZone.GUID))
|
GUIDTask.registerEquipment(localZone.GUID, item)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -149,7 +143,7 @@ object WorldSession {
|
||||||
* @see `Containable.PutItemAway`
|
* @see `Containable.PutItemAway`
|
||||||
* @see `Future.onComplete`
|
* @see `Future.onComplete`
|
||||||
* @see `Future.recover`
|
* @see `Future.recover`
|
||||||
* @see `GUIDTask.UnregisterEquipment`
|
* @see `GUIDTask.unregisterEquipment`
|
||||||
* @see `tell`
|
* @see `tell`
|
||||||
* @see `Zone.AvatarEvents`
|
* @see `Zone.AvatarEvents`
|
||||||
* @param obj the container
|
* @param obj the container
|
||||||
|
|
@ -165,7 +159,7 @@ object WorldSession {
|
||||||
val result = ask(localContainer.Actor, Containable.PutItemInSlotOnly(localItem, slot))
|
val result = ask(localContainer.Actor, Containable.PutItemInSlotOnly(localItem, slot))
|
||||||
result.onComplete {
|
result.onComplete {
|
||||||
case Failure(_) | Success(_: Containable.CanNotPutItemInSlot) =>
|
case Failure(_) | Success(_: Containable.CanNotPutItemInSlot) =>
|
||||||
localContainer.Zone.tasks ! GUIDTask.UnregisterEquipment(localItem)(localContainer.Zone.GUID)
|
TaskWorkflow.execute(GUIDTask.unregisterEquipment(localContainer.Zone.GUID, localItem))
|
||||||
case _ => ;
|
case _ => ;
|
||||||
}
|
}
|
||||||
result
|
result
|
||||||
|
|
@ -177,43 +171,33 @@ object WorldSession {
|
||||||
* This request will (probably) be coincidental with a number of other such requests based on that loadout
|
* This request will (probably) be coincidental with a number of other such requests based on that loadout
|
||||||
* so items must be rigidly placed else cascade into a chaostic order.
|
* so items must be rigidly placed else cascade into a chaostic order.
|
||||||
* Item swapping during the placement is not allowed.
|
* Item swapping during the placement is not allowed.
|
||||||
* @see `GUIDTask.RegisterEquipment`
|
* @see `GUIDTask.registerEquipment`
|
||||||
* @see `PutEquipmentInInventorySlot`
|
* @see `PutEquipmentInInventorySlot`
|
||||||
* @see `Task`
|
* @see `Task`
|
||||||
* @see `TaskResolver.GiveTask`
|
* @see `TaskBundle`
|
||||||
* @param obj the container
|
* @param obj the container
|
||||||
* @param item the item being manipulated
|
* @param item the item being manipulated
|
||||||
* @param slot where the item will be placed in the container
|
* @param slot where the item will be placed in the container
|
||||||
* @return a `TaskResolver` object
|
* @return a `TaskBundle` object
|
||||||
*/
|
*/
|
||||||
def PutLoadoutEquipmentInInventory(
|
def PutLoadoutEquipmentInInventory(
|
||||||
obj: PlanetSideServerObject with Container
|
obj: PlanetSideServerObject with Container
|
||||||
)(item: Equipment, slot: Int): TaskResolver.GiveTask = {
|
)(item: Equipment, slot: Int): TaskBundle = {
|
||||||
val localZone = obj.Zone
|
val localZone = obj.Zone
|
||||||
TaskResolver.GiveTask(
|
TaskBundle(
|
||||||
new Task() {
|
new StraightforwardTask() {
|
||||||
private val localContainer = obj
|
private val localContainer = obj
|
||||||
private val localItem = item
|
private val localItem = item
|
||||||
private val localSlot = slot
|
private val localSlot = slot
|
||||||
private val localFunc: (Equipment, Int) => Future[Any] = PutEquipmentInInventorySlot(obj)
|
private val localFunc: (Equipment, Int) => Future[Any] = PutEquipmentInInventorySlot(obj)
|
||||||
|
|
||||||
override def Timeout: Long = 1000
|
override def description(): String = s"PutEquipmentInInventorySlot - ${localItem.Definition.Name}"
|
||||||
|
|
||||||
override def isComplete: Task.Resolution.Value = {
|
def action(): Future[Any] = {
|
||||||
if (localItem.HasGUID && localContainer.Find(localItem).nonEmpty)
|
|
||||||
Task.Resolution.Success
|
|
||||||
else
|
|
||||||
Task.Resolution.Incomplete
|
|
||||||
}
|
|
||||||
|
|
||||||
override def Description: String = s"PutEquipmentInInventorySlot - ${localItem.Definition.Name}"
|
|
||||||
|
|
||||||
def Execute(resolver: ActorRef): Unit = {
|
|
||||||
localFunc(localItem, localSlot)
|
localFunc(localItem, localSlot)
|
||||||
resolver ! Success(this)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
List(GUIDTask.RegisterEquipment(item)(localZone.GUID))
|
GUIDTask.registerEquipment(localZone.GUID, item)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -227,8 +211,8 @@ object WorldSession {
|
||||||
* @see `ask`
|
* @see `ask`
|
||||||
* @see `Containable.CanNotPutItemInSlot`
|
* @see `Containable.CanNotPutItemInSlot`
|
||||||
* @see `Containable.PutItemInSlotOnly`
|
* @see `Containable.PutItemInSlotOnly`
|
||||||
* @see `GUIDTask.RegisterEquipment`
|
* @see `GUIDTask.registerEquipment`
|
||||||
* @see `GUIDTask.UnregisterEquipment`
|
* @see `GUIDTask.unregisterEquipment`
|
||||||
* @see `Future.onComplete`
|
* @see `Future.onComplete`
|
||||||
* @see `PutEquipmentInInventorySlot`
|
* @see `PutEquipmentInInventorySlot`
|
||||||
* @see `TerminalMessageOnTimeout`
|
* @see `TerminalMessageOnTimeout`
|
||||||
|
|
@ -236,31 +220,22 @@ object WorldSession {
|
||||||
* @param player na
|
* @param player na
|
||||||
* @param term na
|
* @param term na
|
||||||
* @param item the item being manipulated
|
* @param item the item being manipulated
|
||||||
* @return a `TaskResolver` object
|
* @return a `TaskBundle` object
|
||||||
*/
|
*/
|
||||||
def BuyNewEquipmentPutInInventory(
|
def BuyNewEquipmentPutInInventory(
|
||||||
obj: PlanetSideServerObject with Container,
|
obj: PlanetSideServerObject with Container,
|
||||||
player: Player,
|
player: Player,
|
||||||
term: PlanetSideGUID
|
term: PlanetSideGUID
|
||||||
)(item: Equipment): TaskResolver.GiveTask = {
|
)(item: Equipment): TaskBundle = {
|
||||||
val localZone = obj.Zone
|
val localZone = obj.Zone
|
||||||
TaskResolver.GiveTask(
|
TaskBundle(
|
||||||
new Task() {
|
new StraightforwardTask() {
|
||||||
private val localContainer = obj
|
private val localContainer = obj
|
||||||
private val localItem = item
|
private val localItem = item
|
||||||
private val localPlayer = player
|
private val localPlayer = player
|
||||||
private val localTermMsg: Boolean => Unit = TerminalResult(term, localPlayer, TransactionType.Buy)
|
private val localTermMsg: Boolean => Unit = TerminalResult(term, localPlayer, TransactionType.Buy)
|
||||||
|
|
||||||
override def Timeout: Long = 1000
|
def action(): Future[Any] = {
|
||||||
|
|
||||||
override def isComplete: Task.Resolution.Value = {
|
|
||||||
if (localItem.HasGUID && localContainer.Find(localItem).nonEmpty)
|
|
||||||
Task.Resolution.Success
|
|
||||||
else
|
|
||||||
Task.Resolution.Incomplete
|
|
||||||
}
|
|
||||||
|
|
||||||
def Execute(resolver: ActorRef): Unit = {
|
|
||||||
TerminalMessageOnTimeout(
|
TerminalMessageOnTimeout(
|
||||||
ask(localContainer.Actor, Containable.PutItemAway(localItem)),
|
ask(localContainer.Actor, Containable.PutItemAway(localItem)),
|
||||||
localTermMsg
|
localTermMsg
|
||||||
|
|
@ -279,16 +254,16 @@ object WorldSession {
|
||||||
localTermMsg(true)
|
localTermMsg(true)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
localContainer.Zone.tasks ! GUIDTask.UnregisterEquipment(localItem)(localContainer.Zone.GUID)
|
TaskWorkflow.execute(GUIDTask.unregisterEquipment(localContainer.Zone.GUID, localItem))
|
||||||
localTermMsg(false)
|
localTermMsg(false)
|
||||||
}
|
}
|
||||||
case _ =>
|
case _ =>
|
||||||
localTermMsg(true)
|
localTermMsg(true)
|
||||||
}
|
}
|
||||||
resolver ! Success(this)
|
Future(true)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
List(GUIDTask.RegisterEquipment(item)(localZone.GUID))
|
GUIDTask.registerEquipment(localZone.GUID, item)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -306,44 +281,35 @@ object WorldSession {
|
||||||
* @see `AvatarAction.SendResponse`
|
* @see `AvatarAction.SendResponse`
|
||||||
* @see `Containable.CanNotPutItemInSlot`
|
* @see `Containable.CanNotPutItemInSlot`
|
||||||
* @see `Containable.PutItemInSlotOnly`
|
* @see `Containable.PutItemInSlotOnly`
|
||||||
* @see `GUIDTask.RegisterEquipment`
|
* @see `GUIDTask.registerEquipment`
|
||||||
* @see `GUIDTask.UnregisterEquipment`
|
* @see `GUIDTask.unregisterEquipment`
|
||||||
* @see `Future.onComplete`
|
* @see `Future.onComplete`
|
||||||
* @see `ObjectHeldMessage`
|
* @see `ObjectHeldMessage`
|
||||||
* @see `Player.DrawnSlot`
|
* @see `Player.DrawnSlot`
|
||||||
* @see `Player.LastDrawnSlot`
|
* @see `Player.LastDrawnSlot`
|
||||||
* @see `Service.defaultPlayerGUID`
|
* @see `Service.defaultPlayerGUID`
|
||||||
* @see `TaskResolver.GiveTask`
|
* @see `TaskBundle`
|
||||||
* @see `Zone.AvatarEvents`
|
* @see `Zone.AvatarEvents`
|
||||||
* @param player the player whose visible slot will be equipped and drawn
|
* @param player the player whose visible slot will be equipped and drawn
|
||||||
* @param item the item to equip
|
* @param item the item to equip
|
||||||
* @param slot the slot in which the item will be equipped
|
* @param slot the slot in which the item will be equipped
|
||||||
* @return a `TaskResolver` object
|
* @return a `TaskBundle` object
|
||||||
*/
|
*/
|
||||||
def HoldNewEquipmentUp(player: Player)(item: Equipment, slot: Int): TaskResolver.GiveTask = {
|
def HoldNewEquipmentUp(player: Player)(item: Equipment, slot: Int): TaskBundle = {
|
||||||
if (player.VisibleSlots.contains(slot)) {
|
if (player.VisibleSlots.contains(slot)) {
|
||||||
val localZone = player.Zone
|
val localZone = player.Zone
|
||||||
TaskResolver.GiveTask(
|
TaskBundle(
|
||||||
new Task() {
|
new StraightforwardTask() {
|
||||||
private val localPlayer = player
|
private val localPlayer = player
|
||||||
private val localGUID = player.GUID
|
private val localGUID = player.GUID
|
||||||
private val localItem = item
|
private val localItem = item
|
||||||
private val localSlot = slot
|
private val localSlot = slot
|
||||||
|
|
||||||
override def Timeout: Long = 1000
|
def action(): Future[Any] = {
|
||||||
|
|
||||||
override def isComplete: Task.Resolution.Value = {
|
|
||||||
if (localPlayer.DrawnSlot == localSlot)
|
|
||||||
Task.Resolution.Success
|
|
||||||
else
|
|
||||||
Task.Resolution.Incomplete
|
|
||||||
}
|
|
||||||
|
|
||||||
def Execute(resolver: ActorRef): Unit = {
|
|
||||||
ask(localPlayer.Actor, Containable.PutItemInSlotOnly(localItem, localSlot))
|
ask(localPlayer.Actor, Containable.PutItemInSlotOnly(localItem, localSlot))
|
||||||
.onComplete {
|
.onComplete {
|
||||||
case Failure(_) | Success(_: Containable.CanNotPutItemInSlot) =>
|
case Failure(_) | Success(_: Containable.CanNotPutItemInSlot) =>
|
||||||
localPlayer.Zone.tasks ! GUIDTask.UnregisterEquipment(localItem)(localZone.GUID)
|
TaskWorkflow.execute(GUIDTask.unregisterEquipment(localZone.GUID, localItem))
|
||||||
case _ =>
|
case _ =>
|
||||||
if (localPlayer.DrawnSlot != Player.HandsDownSlot) {
|
if (localPlayer.DrawnSlot != Player.HandsDownSlot) {
|
||||||
localPlayer.DrawnSlot = Player.HandsDownSlot
|
localPlayer.DrawnSlot = Player.HandsDownSlot
|
||||||
|
|
@ -365,10 +331,10 @@ object WorldSession {
|
||||||
AvatarAction.SendResponse(Service.defaultPlayerGUID, ObjectHeldMessage(localGUID, localSlot, false))
|
AvatarAction.SendResponse(Service.defaultPlayerGUID, ObjectHeldMessage(localGUID, localSlot, false))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
resolver ! Success(this)
|
Future(this)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
List(GUIDTask.RegisterEquipment(item)(localZone.GUID))
|
GUIDTask.registerEquipment(localZone.GUID, item)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
//TODO log.error
|
//TODO log.error
|
||||||
|
|
@ -460,7 +426,7 @@ object WorldSession {
|
||||||
* @see `Containable.RemoveItemFromSlot`
|
* @see `Containable.RemoveItemFromSlot`
|
||||||
* @see `Future.onComplete`
|
* @see `Future.onComplete`
|
||||||
* @see `Future.recover`
|
* @see `Future.recover`
|
||||||
* @see `GUIDTask.UnregisterEquipment`
|
* @see `GUIDTask.unregisterEquipment`
|
||||||
* @see `Zone.AvatarEvents`
|
* @see `Zone.AvatarEvents`
|
||||||
* @param obj the container to search
|
* @param obj the container to search
|
||||||
* @param item the item to find and remove from the container
|
* @param item the item to find and remove from the container
|
||||||
|
|
@ -474,7 +440,7 @@ object WorldSession {
|
||||||
val result = ask(localContainer.Actor, Containable.RemoveItemFromSlot(localItem))
|
val result = ask(localContainer.Actor, Containable.RemoveItemFromSlot(localItem))
|
||||||
result.onComplete {
|
result.onComplete {
|
||||||
case Success(Containable.ItemFromSlot(_, Some(_), Some(_))) =>
|
case Success(Containable.ItemFromSlot(_, Some(_), Some(_))) =>
|
||||||
localContainer.Zone.tasks ! GUIDTask.UnregisterEquipment(localItem)(localContainer.Zone.GUID)
|
TaskWorkflow.execute(GUIDTask.unregisterEquipment(localContainer.Zone.GUID, localItem))
|
||||||
case _ =>
|
case _ =>
|
||||||
}
|
}
|
||||||
result
|
result
|
||||||
|
|
@ -492,7 +458,7 @@ object WorldSession {
|
||||||
* @see `Containable.RemoveItemFromSlot`
|
* @see `Containable.RemoveItemFromSlot`
|
||||||
* @see `Future.onComplete`
|
* @see `Future.onComplete`
|
||||||
* @see `Future.recover`
|
* @see `Future.recover`
|
||||||
* @see `GUIDTask.UnregisterEquipment`
|
* @see `GUIDTask.unregisterEquipment`
|
||||||
* @see `RemoveOldEquipmentFromInventory`
|
* @see `RemoveOldEquipmentFromInventory`
|
||||||
* @see `TerminalMessageOnTimeout`
|
* @see `TerminalMessageOnTimeout`
|
||||||
* @see `TerminalResult`
|
* @see `TerminalResult`
|
||||||
|
|
@ -517,7 +483,7 @@ object WorldSession {
|
||||||
)
|
)
|
||||||
result.onComplete {
|
result.onComplete {
|
||||||
case Success(Containable.ItemFromSlot(_, Some(item), Some(_))) =>
|
case Success(Containable.ItemFromSlot(_, Some(item), Some(_))) =>
|
||||||
localContainer.Zone.tasks ! GUIDTask.UnregisterEquipment(item)(localContainer.Zone.GUID)
|
TaskWorkflow.execute(GUIDTask.unregisterEquipment(localContainer.Zone.GUID, item))
|
||||||
localTermMsg(true)
|
localTermMsg(true)
|
||||||
case _ =>
|
case _ =>
|
||||||
localTermMsg(false)
|
localTermMsg(false)
|
||||||
|
|
@ -538,7 +504,7 @@ object WorldSession {
|
||||||
* @see `LockerContainer`
|
* @see `LockerContainer`
|
||||||
* @see `RemoveEquipmentFromLockerContainer`
|
* @see `RemoveEquipmentFromLockerContainer`
|
||||||
* @see `StowEquipmentInLockerContainer`
|
* @see `StowEquipmentInLockerContainer`
|
||||||
* @see `TaskResolver`
|
* @see `TaskBundle`
|
||||||
* @param toChannel broadcast channel name for a manual packet callback
|
* @param toChannel broadcast channel name for a manual packet callback
|
||||||
* @param source the container in which the item is to be removed
|
* @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 destination the container into which the item is to be placed
|
||||||
|
|
@ -573,14 +539,14 @@ object WorldSession {
|
||||||
* @see `Container`
|
* @see `Container`
|
||||||
* @see `Equipment`
|
* @see `Equipment`
|
||||||
* @see `GridInventory.CheckCollisionsVar`
|
* @see `GridInventory.CheckCollisionsVar`
|
||||||
* @see `GUIDTask.RegisterEquipment`
|
* @see `GUIDTask.registerEquipment`
|
||||||
* @see `GUIDTask.UnregisterEquipment`
|
* @see `GUIDTask.unregisterEquipment`
|
||||||
* @see `IdentifiableEntity.Invalidate`
|
* @see `IdentifiableEntity.Invalidate`
|
||||||
* @see `LockerContainer`
|
* @see `LockerContainer`
|
||||||
* @see `Service`
|
* @see `Service`
|
||||||
* @see `Task`
|
* @see `Task`
|
||||||
* @see `TaskResolver`
|
* @see `TaskBundle`
|
||||||
* @see `TaskResolver.GiveTask`
|
* @see `TaskBundle`
|
||||||
* @see `Zone.AvatarEvents`
|
* @see `Zone.AvatarEvents`
|
||||||
* @param toChannel broadcast channel name for a manual packet callback
|
* @param toChannel broadcast channel name for a manual packet callback
|
||||||
* @param source the container in which the item is to be removed
|
* @param source the container in which the item is to be removed
|
||||||
|
|
@ -610,7 +576,7 @@ object WorldSession {
|
||||||
(false, None)
|
(false, None)
|
||||||
}
|
}
|
||||||
if (performSwap) {
|
if (performSwap) {
|
||||||
def moveItemTaskFunc(toSlot: Int): Task = new Task() {
|
def moveItemTaskFunc(toSlot: Int): Task = new StraightforwardTask() {
|
||||||
val localGUID = swapItemGUID //the swap item's original GUID, if any swap item
|
val localGUID = swapItemGUID //the swap item's original GUID, if any swap item
|
||||||
val localChannel = toChannel
|
val localChannel = toChannel
|
||||||
val localSource = source
|
val localSource = source
|
||||||
|
|
@ -621,21 +587,13 @@ object WorldSession {
|
||||||
val localMoveOnComplete: Try[Any] => Unit = {
|
val localMoveOnComplete: Try[Any] => Unit = {
|
||||||
case Success(Containable.ItemPutInSlot(_, _, _, Some(swapItem))) =>
|
case Success(Containable.ItemPutInSlot(_, _, _, Some(swapItem))) =>
|
||||||
//swapItem is not registered right now, we can not drop the item without re-registering it
|
//swapItem is not registered right now, we can not drop the item without re-registering it
|
||||||
localSource.Zone.tasks ! PutNewEquipmentInInventorySlot(localSource)(swapItem, localSrcSlot)
|
TaskWorkflow.execute(PutNewEquipmentInInventorySlot(localSource)(swapItem, localSrcSlot))
|
||||||
case _ => ;
|
case _ => ;
|
||||||
}
|
}
|
||||||
|
|
||||||
override def Description: String = s"unregistering $localItem before stowing in $localDestination"
|
override def description(): String = s"unregistering $localItem before stowing in $localDestination"
|
||||||
|
|
||||||
override def isComplete: Task.Resolution.Value = {
|
def action(): Future[Any] = {
|
||||||
if (localItem.HasGUID && localDestination.Find(localItem).contains(localDestSlot)) {
|
|
||||||
Task.Resolution.Success
|
|
||||||
} else {
|
|
||||||
Task.Resolution.Incomplete
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def Execute(resolver: ActorRef): Unit = {
|
|
||||||
localGUID match {
|
localGUID match {
|
||||||
case Some(guid) =>
|
case Some(guid) =>
|
||||||
//see LockerContainerControl.RemoveItemFromSlotCallback
|
//see LockerContainerControl.RemoveItemFromSlotCallback
|
||||||
|
|
@ -647,15 +605,15 @@ object WorldSession {
|
||||||
}
|
}
|
||||||
val moveResult = ask(localDestination.Actor, Containable.PutItemInSlotOrAway(localItem, Some(localDestSlot)))
|
val moveResult = ask(localDestination.Actor, Containable.PutItemInSlotOrAway(localItem, Some(localDestSlot)))
|
||||||
moveResult.onComplete(localMoveOnComplete)
|
moveResult.onComplete(localMoveOnComplete)
|
||||||
resolver ! Success(this)
|
moveResult
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val resultOnComplete: Try[Any] => Unit = {
|
val resultOnComplete: Try[Any] => Unit = {
|
||||||
case Success(Containable.ItemFromSlot(fromSource, Some(itemToMove), Some(fromSlot))) =>
|
case Success(Containable.ItemFromSlot(fromSource, Some(itemToMove), Some(fromSlot))) =>
|
||||||
destination.Zone.tasks ! TaskResolver.GiveTask(
|
TaskWorkflow.execute(TaskBundle(
|
||||||
moveItemTaskFunc(fromSlot),
|
moveItemTaskFunc(fromSlot),
|
||||||
List(GUIDTask.UnregisterEquipment(itemToMove)(fromSource.Zone.GUID))
|
GUIDTask.unregisterEquipment(fromSource.Zone.GUID, itemToMove)
|
||||||
)
|
))
|
||||||
case _ => ;
|
case _ => ;
|
||||||
}
|
}
|
||||||
val result = ask(source.Actor, Containable.RemoveItemFromSlot(item))
|
val result = ask(source.Actor, Containable.RemoveItemFromSlot(item))
|
||||||
|
|
@ -673,14 +631,14 @@ object WorldSession {
|
||||||
* @see `Container`
|
* @see `Container`
|
||||||
* @see `Equipment`
|
* @see `Equipment`
|
||||||
* @see `GridInventory.CheckCollisionsVar`
|
* @see `GridInventory.CheckCollisionsVar`
|
||||||
* @see `GUIDTask.RegisterEquipment`
|
* @see `GUIDTask.registerEquipment`
|
||||||
* @see `GUIDTask.UnregisterEquipment`
|
* @see `GUIDTask.unregisterEquipment`
|
||||||
* @see `IdentifiableEntity.Invalidate`
|
* @see `IdentifiableEntity.Invalidate`
|
||||||
* @see `LockerContainer`
|
* @see `LockerContainer`
|
||||||
* @see `Service`
|
* @see `Service`
|
||||||
* @see `Task`
|
* @see `Task`
|
||||||
* @see `TaskResolver`
|
* @see `TaskBundle`
|
||||||
* @see `TaskResolver.GiveTask`
|
* @see `TaskBundle`
|
||||||
* @see `Zone.AvatarEvents`
|
* @see `Zone.AvatarEvents`
|
||||||
* @param toChannel broadcast channel name for a manual packet callback
|
* @param toChannel broadcast channel name for a manual packet callback
|
||||||
* @param source the container in which the item is to be removed
|
* @param source the container in which the item is to be removed
|
||||||
|
|
@ -695,8 +653,8 @@ object WorldSession {
|
||||||
item: Equipment,
|
item: Equipment,
|
||||||
dest: Int
|
dest: Int
|
||||||
): Unit = {
|
): Unit = {
|
||||||
destination.Zone.tasks ! TaskResolver.GiveTask(
|
TaskWorkflow.execute(TaskBundle(
|
||||||
new Task() {
|
new StraightforwardTask() {
|
||||||
val localGUID = item.GUID //original GUID
|
val localGUID = item.GUID //original GUID
|
||||||
val localChannel = toChannel
|
val localChannel = toChannel
|
||||||
val localSource = source
|
val localSource = source
|
||||||
|
|
@ -717,25 +675,16 @@ object WorldSession {
|
||||||
case _ => ;
|
case _ => ;
|
||||||
}
|
}
|
||||||
|
|
||||||
override def Description: String = s"registering $localItem in ${localDestination.Zone.id} before removing from $localSource"
|
override def description(): String = s"registering $localItem in ${localDestination.Zone.id} before removing from $localSource"
|
||||||
|
|
||||||
override def isComplete: Task.Resolution.Value = {
|
def action(): Future[Any] = {
|
||||||
if (localItem.HasGUID && localDestination.Find(localItem).isEmpty) {
|
|
||||||
Task.Resolution.Success
|
|
||||||
} else {
|
|
||||||
Task.Resolution.Incomplete
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def Execute(resolver: ActorRef): Unit = {
|
|
||||||
val zone = localSource.Zone
|
val zone = localSource.Zone
|
||||||
//see LockerContainerControl.RemoveItemFromSlotCallback
|
//see LockerContainerControl.RemoveItemFromSlotCallback
|
||||||
zone.AvatarEvents ! AvatarServiceMessage(localChannel, AvatarAction.ObjectDelete(Service.defaultPlayerGUID, localGUID))
|
zone.AvatarEvents ! AvatarServiceMessage(localChannel, AvatarAction.ObjectDelete(Service.defaultPlayerGUID, localGUID))
|
||||||
localSource.Actor ! Containable.MoveItem(localDestination, localItem, localSlot)
|
ask(localSource.Actor, Containable.MoveItem(localDestination, localItem, localSlot))
|
||||||
resolver ! Success(this)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
List(GUIDTask.RegisterEquipment(item)(destination.Zone.GUID))
|
GUIDTask.registerEquipment(destination.Zone.GUID, item))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -884,21 +833,21 @@ object WorldSession {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def CallBackForTask(task: TaskResolver.GiveTask, sendTo: ActorRef, pass: Any): TaskResolver.GiveTask = {
|
def CallBackForTask(task: TaskBundle, sendTo: ActorRef, pass: Any): TaskBundle = {
|
||||||
TaskResolver.GiveTask(
|
TaskBundle(
|
||||||
new Task() {
|
new StraightforwardTask() {
|
||||||
private val localDesc = task.task.Description
|
private val localDesc = task.description()
|
||||||
private val destination = sendTo
|
private val destination = sendTo
|
||||||
private val passMsg = pass
|
private val passMsg = pass
|
||||||
|
|
||||||
override def Description: String = s"callback for tasking $localDesc"
|
override def description(): String = s"callback for tasking $localDesc"
|
||||||
|
|
||||||
def Execute(resolver: ActorRef): Unit = {
|
def action() : Future[Any] = {
|
||||||
destination ! passMsg
|
destination ! passMsg
|
||||||
resolver ! Success(this)
|
Future(this)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
List(task)
|
task
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ package net.psforever.objects
|
||||||
import akka.actor.{ActorContext, Props}
|
import akka.actor.{ActorContext, Props}
|
||||||
import net.psforever.objects.ballistics.{PlayerSource, SourceEntry}
|
import net.psforever.objects.ballistics.{PlayerSource, SourceEntry}
|
||||||
import net.psforever.objects.ce.{Deployable, DeployedItem}
|
import net.psforever.objects.ce.{Deployable, DeployedItem}
|
||||||
import net.psforever.objects.guid.GUIDTask
|
import net.psforever.objects.guid.{GUIDTask, TaskWorkflow}
|
||||||
import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject}
|
import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject}
|
||||||
import net.psforever.objects.vital.etc.TriggerUsedReason
|
import net.psforever.objects.vital.etc.TriggerUsedReason
|
||||||
import net.psforever.objects.vital.interaction.DamageInteraction
|
import net.psforever.objects.vital.interaction.DamageInteraction
|
||||||
|
|
@ -99,7 +99,7 @@ class BoomerDeployableControl(mine: BoomerDeployable)
|
||||||
zone.id,
|
zone.id,
|
||||||
AvatarAction.ObjectDelete(Service.defaultPlayerGUID, trigger.GUID)
|
AvatarAction.ObjectDelete(Service.defaultPlayerGUID, trigger.GUID)
|
||||||
)
|
)
|
||||||
zone.tasks ! GUIDTask.UnregisterObjectTask(trigger)(zone.GUID)
|
TaskWorkflow.execute(GUIDTask.unregisterObject(zone.GUID, trigger))
|
||||||
case None => ;
|
case None => ;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import net.psforever.objects.definition.converter._
|
||||||
import net.psforever.objects.equipment._
|
import net.psforever.objects.equipment._
|
||||||
import net.psforever.objects.geometry.GeometryForm
|
import net.psforever.objects.geometry.GeometryForm
|
||||||
import net.psforever.objects.inventory.InventoryTile
|
import net.psforever.objects.inventory.InventoryTile
|
||||||
|
import net.psforever.objects.locker.LockerContainerDefinition
|
||||||
import net.psforever.objects.serverobject.aura.Aura
|
import net.psforever.objects.serverobject.aura.Aura
|
||||||
import net.psforever.objects.serverobject.doors.DoorDefinition
|
import net.psforever.objects.serverobject.doors.DoorDefinition
|
||||||
import net.psforever.objects.serverobject.generator.GeneratorDefinition
|
import net.psforever.objects.serverobject.generator.GeneratorDefinition
|
||||||
|
|
@ -426,11 +427,7 @@ object GlobalDefinitions {
|
||||||
Equipment (locker_container, kits, ammunition, weapons)
|
Equipment (locker_container, kits, ammunition, weapons)
|
||||||
*/
|
*/
|
||||||
import net.psforever.packet.game.objectcreate.ObjectClass
|
import net.psforever.packet.game.objectcreate.ObjectClass
|
||||||
val locker_container = new EquipmentDefinition(456) {
|
val locker_container = new LockerContainerDefinition()
|
||||||
Name = "locker_container"
|
|
||||||
Size = EquipmentSize.Inventory
|
|
||||||
Packet = new LockerContainerConverter()
|
|
||||||
}
|
|
||||||
|
|
||||||
val medkit = KitDefinition(Kits.medkit)
|
val medkit = KitDefinition(Kits.medkit)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import net.psforever.objects.avatar.PlayerControl
|
||||||
import net.psforever.objects.ce.Deployable
|
import net.psforever.objects.ce.Deployable
|
||||||
import net.psforever.objects.definition.ExoSuitDefinition
|
import net.psforever.objects.definition.ExoSuitDefinition
|
||||||
import net.psforever.objects.equipment.EquipmentSlot
|
import net.psforever.objects.equipment.EquipmentSlot
|
||||||
import net.psforever.objects.guid.GUIDTask
|
import net.psforever.objects.guid.{GUIDTask, TaskWorkflow}
|
||||||
import net.psforever.objects.inventory.InventoryItem
|
import net.psforever.objects.inventory.InventoryItem
|
||||||
import net.psforever.objects.loadouts.InfantryLoadout
|
import net.psforever.objects.loadouts.InfantryLoadout
|
||||||
import net.psforever.objects.zones.Zone
|
import net.psforever.objects.zones.Zone
|
||||||
|
|
@ -341,7 +341,7 @@ object Players {
|
||||||
def commonDestroyConstructionItem(player: Player, tool: ConstructionItem, index: Int): Unit = {
|
def commonDestroyConstructionItem(player: Player, tool: ConstructionItem, index: Int): Unit = {
|
||||||
val zone = player.Zone
|
val zone = player.Zone
|
||||||
if (safelyRemoveConstructionItemFromSlot(player, tool, index, "CommonDestroyConstructionItem")) {
|
if (safelyRemoveConstructionItemFromSlot(player, tool, index, "CommonDestroyConstructionItem")) {
|
||||||
zone.tasks ! GUIDTask.UnregisterEquipment(tool)(zone.GUID)
|
TaskWorkflow.execute(GUIDTask.unregisterEquipment(zone.GUID, tool))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import net.psforever.objects.ce.{Deployable, DeployableBehavior, DeployedItem}
|
||||||
import net.psforever.objects.definition.DeployableDefinition
|
import net.psforever.objects.definition.DeployableDefinition
|
||||||
import net.psforever.objects.definition.converter.SmallTurretConverter
|
import net.psforever.objects.definition.converter.SmallTurretConverter
|
||||||
import net.psforever.objects.equipment.{JammableMountedWeapons, JammableUnit}
|
import net.psforever.objects.equipment.{JammableMountedWeapons, JammableUnit}
|
||||||
import net.psforever.objects.guid.GUIDTask
|
import net.psforever.objects.guid.{GUIDTask, TaskWorkflow}
|
||||||
import net.psforever.objects.serverobject.PlanetSideServerObject
|
import net.psforever.objects.serverobject.PlanetSideServerObject
|
||||||
import net.psforever.objects.serverobject.affinity.FactionAffinityBehavior
|
import net.psforever.objects.serverobject.affinity.FactionAffinityBehavior
|
||||||
import net.psforever.objects.serverobject.damage.Damageable.Target
|
import net.psforever.objects.serverobject.damage.Damageable.Target
|
||||||
|
|
@ -132,6 +132,6 @@ class TurretControl(turret: TurretDeployable)
|
||||||
|
|
||||||
override def unregisterDeployable(obj: Deployable): Unit = {
|
override def unregisterDeployable(obj: Deployable): Unit = {
|
||||||
val zone = obj.Zone
|
val zone = obj.Zone
|
||||||
zone.tasks ! GUIDTask.UnregisterDeployableTurret(turret)(zone.GUID)
|
TaskWorkflow.execute(GUIDTask.unregisterDeployableTurret(zone.GUID, turret))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import net.psforever.objects.ballistics.PlayerSource
|
||||||
import net.psforever.objects.ce.Deployable
|
import net.psforever.objects.ce.Deployable
|
||||||
import net.psforever.objects.definition.DeployAnimation
|
import net.psforever.objects.definition.DeployAnimation
|
||||||
import net.psforever.objects.equipment._
|
import net.psforever.objects.equipment._
|
||||||
import net.psforever.objects.guid.GUIDTask
|
import net.psforever.objects.guid.{GUIDTask, TaskWorkflow}
|
||||||
import net.psforever.objects.inventory.{GridInventory, InventoryItem}
|
import net.psforever.objects.inventory.{GridInventory, InventoryItem}
|
||||||
import net.psforever.objects.loadouts.Loadout
|
import net.psforever.objects.loadouts.Loadout
|
||||||
import net.psforever.objects.serverobject.aura.{Aura, AuraEffectBehavior}
|
import net.psforever.objects.serverobject.aura.{Aura, AuraEffectBehavior}
|
||||||
|
|
@ -284,7 +284,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
|
||||||
val zone = player.Zone
|
val zone = player.Zone
|
||||||
avatarActor ! AvatarActor.UpdateUseTime(kdef)
|
avatarActor ! AvatarActor.UpdateUseTime(kdef)
|
||||||
player.Slot(slot).Equipment = None //remove from slot immediately; must exist on client for now
|
player.Slot(slot).Equipment = None //remove from slot immediately; must exist on client for now
|
||||||
zone.tasks ! GUIDTask.UnregisterEquipment(kit)(zone.GUID)
|
TaskWorkflow.execute(GUIDTask.unregisterEquipment(zone.GUID, kit))
|
||||||
zone.AvatarEvents ! AvatarServiceMessage(
|
zone.AvatarEvents ! AvatarServiceMessage(
|
||||||
zone.id,
|
zone.id,
|
||||||
AvatarAction.PlanetsideAttributeToAll(player.GUID, attribute, value)
|
AvatarAction.PlanetsideAttributeToAll(player.GUID, attribute, value)
|
||||||
|
|
@ -482,17 +482,17 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
|
||||||
obj.Trigger = trigger
|
obj.Trigger = trigger
|
||||||
//TODO sufficiently delete the tool
|
//TODO sufficiently delete the tool
|
||||||
zone.AvatarEvents ! AvatarServiceMessage(zone.id, AvatarAction.ObjectDelete(player.GUID, tool.GUID))
|
zone.AvatarEvents ! AvatarServiceMessage(zone.id, AvatarAction.ObjectDelete(player.GUID, tool.GUID))
|
||||||
zone.tasks ! GUIDTask.UnregisterEquipment(tool)(zone.GUID)
|
TaskWorkflow.execute(GUIDTask.unregisterEquipment(zone.GUID, tool))
|
||||||
player.Find(tool) match {
|
player.Find(tool) match {
|
||||||
case Some(index) if player.VisibleSlots.contains(index) =>
|
case Some(index) if player.VisibleSlots.contains(index) =>
|
||||||
player.Slot(index).Equipment = None
|
player.Slot(index).Equipment = None
|
||||||
zone.tasks ! HoldNewEquipmentUp(player)(trigger, index)
|
TaskWorkflow.execute(HoldNewEquipmentUp(player)(trigger, index))
|
||||||
case Some(index) =>
|
case Some(index) =>
|
||||||
player.Slot(index).Equipment = None
|
player.Slot(index).Equipment = None
|
||||||
zone.tasks ! PutNewEquipmentInInventoryOrDrop(player)(trigger)
|
TaskWorkflow.execute(PutNewEquipmentInInventoryOrDrop(player)(trigger))
|
||||||
case None =>
|
case None =>
|
||||||
//don't know where boomer trigger "should" go
|
//don't know where boomer trigger "should" go
|
||||||
zone.tasks ! PutNewEquipmentInInventoryOrDrop(player)(trigger)
|
TaskWorkflow.execute(PutNewEquipmentInInventoryOrDrop(player)(trigger))
|
||||||
}
|
}
|
||||||
Players.buildCooldownReset(zone, player.Name, obj)
|
Players.buildCooldownReset(zone, player.Name, obj)
|
||||||
case _ => ;
|
case _ => ;
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
package net.psforever.objects.ce
|
package net.psforever.objects.ce
|
||||||
|
|
||||||
import akka.actor.{Actor, ActorRef, Cancellable}
|
import akka.actor.{Actor, ActorRef, Cancellable}
|
||||||
import net.psforever.objects.guid.GUIDTask
|
import net.psforever.objects.guid.{GUIDTask, TaskWorkflow}
|
||||||
import net.psforever.objects._
|
import net.psforever.objects._
|
||||||
import net.psforever.objects.definition.DeployAnimation
|
import net.psforever.objects.definition.DeployAnimation
|
||||||
import net.psforever.objects.zones.Zone
|
import net.psforever.objects.zones.Zone
|
||||||
|
|
@ -280,7 +280,7 @@ trait DeployableBehavior {
|
||||||
*/
|
*/
|
||||||
def unregisterDeployable(obj: Deployable): Unit = {
|
def unregisterDeployable(obj: Deployable): Unit = {
|
||||||
val zone = obj.Zone
|
val zone = obj.Zone
|
||||||
zone.tasks ! GUIDTask.UnregisterObjectTask(obj)(zone.GUID)
|
TaskWorkflow.execute(GUIDTask.unregisterObject(zone.GUID, obj))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -2,17 +2,17 @@
|
||||||
package net.psforever.objects.definition
|
package net.psforever.objects.definition
|
||||||
|
|
||||||
import net.psforever.objects.definition.converter.AmmoBoxConverter
|
import net.psforever.objects.definition.converter.AmmoBoxConverter
|
||||||
import net.psforever.objects.equipment.Ammo
|
import net.psforever.objects.equipment.{Ammo, EquipmentSize}
|
||||||
|
|
||||||
class AmmoBoxDefinition(objectId: Int) extends EquipmentDefinition(objectId) {
|
class AmmoBoxDefinition(objectId: Int) extends EquipmentDefinition(objectId) {
|
||||||
import net.psforever.objects.equipment.EquipmentSize
|
Name = "ammo_box"
|
||||||
|
Size = EquipmentSize.Inventory
|
||||||
|
Packet = AmmoBoxDefinition.converter
|
||||||
private val ammoType: Ammo.Value = Ammo(objectId) //let throw NoSuchElementException
|
private val ammoType: Ammo.Value = Ammo(objectId) //let throw NoSuchElementException
|
||||||
private var capacity: Int = 1
|
private var capacity: Int = 1
|
||||||
var repairAmount: Float = 0
|
var repairAmount: Float = 0
|
||||||
|
registerAs = "ammo"
|
||||||
|
|
||||||
Name = "ammo box"
|
|
||||||
Size = EquipmentSize.Inventory
|
|
||||||
Packet = AmmoBoxDefinition.converter
|
|
||||||
|
|
||||||
def AmmoType: Ammo.Value = ammoType
|
def AmmoType: Ammo.Value = ammoType
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ class AvatarDefinition(objectId: Int) extends ObjectDefinition(objectId) with Vi
|
||||||
Avatars(objectId) //let throw NoSuchElementException
|
Avatars(objectId) //let throw NoSuchElementException
|
||||||
Packet = AvatarDefinition.converter
|
Packet = AvatarDefinition.converter
|
||||||
Geometry = GeometryForm.representPlayerByCylinder(radius = 1.6f)
|
Geometry = GeometryForm.representPlayerByCylinder(radius = 1.6f)
|
||||||
|
registerAs = "players"
|
||||||
}
|
}
|
||||||
|
|
||||||
object AvatarDefinition {
|
object AvatarDefinition {
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ class ConstructionItemDefinition(objectId: Int) extends EquipmentDefinition(obje
|
||||||
CItem(objectId) //let throw NoSuchElementException
|
CItem(objectId) //let throw NoSuchElementException
|
||||||
private val modes: ListBuffer[ConstructionFireMode] = ListBuffer()
|
private val modes: ListBuffer[ConstructionFireMode] = ListBuffer()
|
||||||
Packet = new ACEConverter
|
Packet = new ACEConverter
|
||||||
|
registerAs = "items"
|
||||||
|
|
||||||
def Modes: ListBuffer[ConstructionFireMode] = modes
|
def Modes: ListBuffer[ConstructionFireMode] = modes
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,7 @@ abstract class DeployableDefinition(objectId: Int)
|
||||||
DamageUsing = DamageCalculations.AgainstVehicle
|
DamageUsing = DamageCalculations.AgainstVehicle
|
||||||
ResistUsing = NoResistanceSelection
|
ResistUsing = NoResistanceSelection
|
||||||
Packet = new SmallDeployableConverter
|
Packet = new SmallDeployableConverter
|
||||||
|
registerAs = "deployables"
|
||||||
|
|
||||||
def Item: DeployedItem.Value = item
|
def Item: DeployedItem.Value = item
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ class KitDefinition(objectId: Int) extends EquipmentDefinition(objectId) {
|
||||||
Tile = InventoryTile.Tile42
|
Tile = InventoryTile.Tile42
|
||||||
Name = "kit"
|
Name = "kit"
|
||||||
Packet = KitDefinition.converter
|
Packet = KitDefinition.converter
|
||||||
|
registerAs = "kits"
|
||||||
}
|
}
|
||||||
|
|
||||||
object KitDefinition {
|
object KitDefinition {
|
||||||
|
|
|
||||||
|
|
@ -21,10 +21,11 @@ import net.psforever.types.OxygenState
|
||||||
* @param objectId the object's identifier number
|
* @param objectId the object's identifier number
|
||||||
*/
|
*/
|
||||||
abstract class ObjectDefinition(private val objectId: Int) extends BasicDefinition {
|
abstract class ObjectDefinition(private val objectId: Int) extends BasicDefinition {
|
||||||
|
var registerAs: String = "generic"
|
||||||
|
|
||||||
/** a data converter for this type of object */
|
/** a data converter for this type of object */
|
||||||
protected var packet: PacketConverter = new ObjectCreateConverter[PlanetSideGameObject]() {}
|
protected var packet: PacketConverter = new ObjectCreateConverter[PlanetSideGameObject]() {}
|
||||||
Name = "object definition"
|
Name = "object_definition"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the conversion object.
|
* Get the conversion object.
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,7 @@ class ProjectileDefinition(objectId: Int)
|
||||||
private var finalVelocity: Float = 0f
|
private var finalVelocity: Float = 0f
|
||||||
Name = "projectile"
|
Name = "projectile"
|
||||||
Modifiers = DistanceDegrade
|
Modifiers = DistanceDegrade
|
||||||
|
registerAs = "projectiles"
|
||||||
|
|
||||||
def ProjectileType: Projectiles.Value = projectileType
|
def ProjectileType: Projectiles.Value = projectileType
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,9 @@ import net.psforever.objects.equipment.SItem
|
||||||
class SimpleItemDefinition(objectId: Int) extends EquipmentDefinition(objectId) {
|
class SimpleItemDefinition(objectId: Int) extends EquipmentDefinition(objectId) {
|
||||||
import net.psforever.objects.equipment.EquipmentSize
|
import net.psforever.objects.equipment.EquipmentSize
|
||||||
SItem(objectId) //let throw NoSuchElementException
|
SItem(objectId) //let throw NoSuchElementException
|
||||||
Name = "tool"
|
Name = "simple_item"
|
||||||
Size = EquipmentSize.Pistol //all items
|
Size = EquipmentSize.Pistol //all items
|
||||||
|
registerAs = "items"
|
||||||
}
|
}
|
||||||
|
|
||||||
object SimpleItemDefinition {
|
object SimpleItemDefinition {
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ class ToolDefinition(objectId: Int) extends EquipmentDefinition(objectId) {
|
||||||
private var defaultFireModeIndex: Option[Int] = None
|
private var defaultFireModeIndex: Option[Int] = None
|
||||||
Name = "tool"
|
Name = "tool"
|
||||||
Packet = ToolDefinition.converter
|
Packet = ToolDefinition.converter
|
||||||
|
registerAs = "tools"
|
||||||
|
|
||||||
def AmmoTypes: mutable.ListBuffer[AmmoBoxDefinition] = ammoTypes
|
def AmmoTypes: mutable.ListBuffer[AmmoBoxDefinition] = ammoTypes
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,7 @@ class VehicleDefinition(objectId: Int)
|
||||||
Model = VehicleResolutions.calculate
|
Model = VehicleResolutions.calculate
|
||||||
RepairDistance = 10
|
RepairDistance = 10
|
||||||
RepairRestoresAt = 1
|
RepairRestoresAt = 1
|
||||||
|
registerAs = "vehicles"
|
||||||
|
|
||||||
def MaxShields: Int = maxShields
|
def MaxShields: Int = maxShields
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
// Copyright (c) 2017 PSForever
|
|
||||||
package net.psforever.objects.guid
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The availability of individual GUIDs is maintained by the given policy.
|
|
||||||
*/
|
|
||||||
object AvailabilityPolicy extends Enumeration {
|
|
||||||
type Type = Value
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An `AVAILABLE` GUID is ready and waiting to be `LEASED` for use.
|
|
||||||
* A `LEASED` GUID has been issued and is currently being used.
|
|
||||||
* A `RESTRICTED` GUID can never be freed. It is allowed, however, to be assigned once as if it were `LEASED`.
|
|
||||||
*/
|
|
||||||
val Available, Leased, Restricted = Value
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
// Copyright (c) 2017 PSForever
|
// Copyright (c) 2017 PSForever
|
||||||
package net.psforever.objects.guid
|
package net.psforever.objects.guid
|
||||||
|
|
||||||
import akka.actor.ActorRef
|
import akka.util.Timeout
|
||||||
import net.psforever.objects.entity.IdentifiableEntity
|
import net.psforever.objects.entity.IdentifiableEntity
|
||||||
import net.psforever.objects.equipment.{Equipment, EquipmentSlot}
|
import net.psforever.objects.equipment.{Equipment, EquipmentSlot}
|
||||||
import net.psforever.objects._
|
import net.psforever.objects._
|
||||||
|
|
@ -10,6 +10,8 @@ import net.psforever.objects.locker.{LockerContainer, LockerEquipment}
|
||||||
import net.psforever.objects.serverobject.turret.WeaponTurret
|
import net.psforever.objects.serverobject.turret.WeaponTurret
|
||||||
|
|
||||||
import scala.annotation.tailrec
|
import scala.annotation.tailrec
|
||||||
|
import scala.concurrent.duration._
|
||||||
|
import scala.concurrent.Future
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The basic compiled tasks for assigning (registering) and revoking (unregistering) globally unique identifiers.<br>
|
* The basic compiled tasks for assigning (registering) and revoking (unregistering) globally unique identifiers.<br>
|
||||||
|
|
@ -22,43 +24,63 @@ import scala.annotation.tailrec
|
||||||
* It will get passed from the more complicated functions down into the less complicated functions,
|
* It will get passed from the more complicated functions down into the less complicated functions,
|
||||||
* until it has found the basic number assignment functionality.<br>
|
* until it has found the basic number assignment functionality.<br>
|
||||||
* <br>
|
* <br>
|
||||||
* All functions produce a `TaskResolver.GiveTask` container object
|
* All functions produce a `TaskBundle` container object
|
||||||
* or a list of `TaskResolver.GiveTask` container objects that is expected to be used by a `TaskResolver` `Actor`.
|
* or a list of `TaskBundle` container objects that is expected to be used by a `TaskBundle` container.
|
||||||
* These "task containers" can also be unpackaged into their component tasks, sorted into other containers,
|
* These "task containers" can also be unpackaged into their component tasks, sorted into other containers,
|
||||||
* and combined with other tasks to enact more complicated sequences of operations.
|
* and combined with other tasks to enact more complicated sequences of operations.
|
||||||
* Almost all tasks have an explicit registering and an unregistering activity defined for it.
|
* Almost all tasks have an explicit registering and an unregistering activity defined for it.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
object GUIDTask {
|
object GUIDTask {
|
||||||
|
private implicit val timeout = Timeout(2.seconds)
|
||||||
|
|
||||||
|
//registration tasking
|
||||||
|
protected case class RegisterObjectTask(
|
||||||
|
guid: UniqueNumberOps,
|
||||||
|
obj: IdentifiableEntity,
|
||||||
|
pool: String
|
||||||
|
) extends Task {
|
||||||
|
def action(): Future[Any] = {
|
||||||
|
guid.Register(obj, pool)
|
||||||
|
}
|
||||||
|
|
||||||
|
def undo(): Unit = {
|
||||||
|
guid.Unregister(obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
def isSuccessful() : Boolean = obj.HasGUID
|
||||||
|
|
||||||
|
override def description(): String = s"register $obj to $pool"
|
||||||
|
}
|
||||||
|
|
||||||
|
def RegisterObjectTask(guid: UniqueNumberOps, obj: IdentifiableEntity): RegisterObjectTask = obj match {
|
||||||
|
case o: PlanetSideGameObject => RegisterObjectTask(guid, o)
|
||||||
|
case _ => RegisterObjectTask(guid, obj, "generic")
|
||||||
|
}
|
||||||
|
|
||||||
|
def RegisterObjectTask(guid: UniqueNumberOps, obj: PlanetSideGameObject): RegisterObjectTask =
|
||||||
|
RegisterObjectTask(guid, obj, obj.Definition.registerAs)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct tasking that registers an object with a globally unique identifier selected from a pool of numbers.<br>
|
* Construct tasking that registers an object with a globally unique identifier selected from a pool of numbers.
|
||||||
* <br>
|
|
||||||
* Regardless of the complexity of the object provided to this function, only the current depth will be assigned a GUID.
|
* Regardless of the complexity of the object provided to this function, only the current depth will be assigned a GUID.
|
||||||
* This is the most basic operation that all objects that can be assigned a GUID must perform.
|
* This is the most basic operation that all objects that can be assigned a GUID must perform.
|
||||||
* @param obj the object being registered
|
* @param obj the object being registered
|
||||||
* @param guid implicit reference to a unique number system
|
* @param guid implicit reference to a unique number system
|
||||||
* @return a `TaskResolver.GiveTask` message
|
* @return a `TaskBundle` message
|
||||||
*/
|
*/
|
||||||
def RegisterObjectTask(obj: IdentifiableEntity)(implicit guid: ActorRef): TaskResolver.GiveTask = {
|
def registerObject(guid: UniqueNumberOps, obj: IdentifiableEntity): TaskBundle =
|
||||||
TaskResolver.GiveTask(new Task() {
|
TaskBundle(RegisterObjectTask(guid, obj, "generic"))
|
||||||
private val localObject = obj
|
|
||||||
private val localAccessor = guid
|
|
||||||
|
|
||||||
override def Description: String = s"register $localObject"
|
/**
|
||||||
|
* Construct tasking that registers an object with a globally unique identifier selected from a specific pool of numbers.
|
||||||
override def isComplete: Task.Resolution.Value =
|
* Regardless of the complexity of the object provided to this function, only the current depth will be assigned a GUID.
|
||||||
if (localObject.HasGUID) {
|
* @param obj the object being registered
|
||||||
Task.Resolution.Success
|
* @param guid implicit reference to a unique number system
|
||||||
} else {
|
* @return a `TaskBundle` message
|
||||||
Task.Resolution.Incomplete
|
*/
|
||||||
}
|
def registerObject(guid: UniqueNumberOps, obj: PlanetSideGameObject): TaskBundle =
|
||||||
|
TaskBundle(RegisterObjectTask(guid, obj, obj.Definition.registerAs))
|
||||||
def Execute(resolver: ActorRef): Unit = {
|
|
||||||
import net.psforever.objects.guid.actor.Register
|
|
||||||
localAccessor ! Register(localObject, "dynamic", resolver) //TODO pool should not be hardcoded
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct tasking that registers an object with a globally unique identifier selected from a pool of numbers, as a `Tool`.<br>
|
* Construct tasking that registers an object with a globally unique identifier selected from a pool of numbers, as a `Tool`.<br>
|
||||||
|
|
@ -74,41 +96,14 @@ object GUIDTask {
|
||||||
* else use a more general function to differentiate between simple and complex objects.
|
* else use a more general function to differentiate between simple and complex objects.
|
||||||
* @param obj the `Tool` object being registered
|
* @param obj the `Tool` object being registered
|
||||||
* @param guid implicit reference to a unique number system
|
* @param guid implicit reference to a unique number system
|
||||||
* @see `GUIDTask.RegisterEquipment`
|
* @see `GUIDTask.registerEquipment`
|
||||||
* @return a `TaskResolver.GiveTask` message
|
* @return a `TaskBundle` message
|
||||||
*/
|
*/
|
||||||
def RegisterTool(obj: Tool)(implicit guid: ActorRef): TaskResolver.GiveTask = {
|
def registerTool(guid: UniqueNumberOps, obj: Tool): TaskBundle = {
|
||||||
val ammoTasks: List[TaskResolver.GiveTask] =
|
TaskBundle(
|
||||||
(0 until obj.MaxAmmoSlot).map(ammoIndex => RegisterObjectTask(obj.AmmoSlots(ammoIndex).Box)).toList
|
RegisterObjectTask(guid, obj),
|
||||||
TaskResolver.GiveTask(RegisterObjectTask(obj).task, ammoTasks)
|
(0 until obj.MaxAmmoSlot).map(ammoIndex => registerObject(guid, obj.AmmoSlots(ammoIndex).Box))
|
||||||
}
|
)
|
||||||
|
|
||||||
/**
|
|
||||||
* Construct tasking that registers a `LockerContainer` object
|
|
||||||
* with a globally unique identifier selected from a pool of numbers.
|
|
||||||
* @param obj the object being registered
|
|
||||||
* @param guid implicit reference to a unique number system
|
|
||||||
* @see `GUIDTask.UnregisterLocker`
|
|
||||||
* @return a `TaskResolver.GiveTask` message
|
|
||||||
*/
|
|
||||||
def RegisterLocker(obj: LockerContainer)(implicit guid: ActorRef): TaskResolver.GiveTask = {
|
|
||||||
TaskResolver.GiveTask(RegisterObjectTask(obj).task, RegisterInventory(obj))
|
|
||||||
}
|
|
||||||
def RegisterLocker(obj: LockerEquipment)(implicit guid: ActorRef): TaskResolver.GiveTask = {
|
|
||||||
TaskResolver.GiveTask(RegisterObjectTask(obj).task, RegisterInventory(obj))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Construct tasking that registers the objects that are within the given container's inventory
|
|
||||||
* with a globally unique identifier selected from a pool of numbers for each object.
|
|
||||||
* @param container the storage unit in which objects can be found
|
|
||||||
* @param guid implicit reference to a unique number system
|
|
||||||
* @see `GUID.UnregisterInventory`<br>
|
|
||||||
* `Container`
|
|
||||||
* @return a list of `TaskResolver.GiveTask` messages
|
|
||||||
*/
|
|
||||||
def RegisterInventory(container: Container)(implicit guid: ActorRef): List[TaskResolver.GiveTask] = {
|
|
||||||
container.Inventory.Items.map(entry => { RegisterEquipment(entry.obj) })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -125,17 +120,52 @@ object GUIDTask {
|
||||||
* The type will be sorted and the object will be handled according to its complexity level.
|
* The type will be sorted and the object will be handled according to its complexity level.
|
||||||
* @param obj the `Equipment` object being registered
|
* @param obj the `Equipment` object being registered
|
||||||
* @param guid implicit reference to a unique number system
|
* @param guid implicit reference to a unique number system
|
||||||
* @return a `TaskResolver.GiveTask` message
|
* @return a `TaskBundle` message
|
||||||
*/
|
*/
|
||||||
def RegisterEquipment(obj: Equipment)(implicit guid: ActorRef): TaskResolver.GiveTask = {
|
def registerEquipment(guid: UniqueNumberOps, obj: Equipment): TaskBundle = {
|
||||||
obj match {
|
obj match {
|
||||||
case tool: Tool =>
|
case tool: Tool => registerTool(guid, tool)
|
||||||
RegisterTool(tool)
|
case _ => registerObject(guid, obj)
|
||||||
case _ =>
|
|
||||||
RegisterObjectTask(obj)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct tasking that registers the objects that are within the given container's inventory
|
||||||
|
* with a globally unique identifier selected from a pool of numbers for each object.
|
||||||
|
* @param container the storage unit in which objects can be found
|
||||||
|
* @param guid implicit reference to a unique number system
|
||||||
|
* @see `GUIDTask.unregisterInventory`<br>
|
||||||
|
* `Container`
|
||||||
|
* @return a list of `TaskBundle` messages
|
||||||
|
*/
|
||||||
|
def registerInventory(guid: UniqueNumberOps, container: Container): List[TaskBundle] = {
|
||||||
|
container.Inventory.Items.map{ entry => registerEquipment(guid, entry.obj) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct tasking that registers a `LockerContainer` object
|
||||||
|
* with a globally unique identifier selected from a pool of numbers.
|
||||||
|
* @param obj the object being registered
|
||||||
|
* @param guid implicit reference to a unique number system
|
||||||
|
* @see `GUIDTask.unregisterLocker`
|
||||||
|
* @return a `TaskBundle` message
|
||||||
|
*/
|
||||||
|
def registerLocker(guid: UniqueNumberOps, obj: LockerContainer): TaskBundle = {
|
||||||
|
TaskBundle(RegisterObjectTask(guid, obj), registerInventory(guid, obj))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct tasking that registers a `LockerContainer` object
|
||||||
|
* with a globally unique identifier selected from a pool of numbers.
|
||||||
|
* @param obj the object being registered
|
||||||
|
* @param guid implicit reference to a unique number system
|
||||||
|
* @see `GUIDTask.unregisterLocker`
|
||||||
|
* @return a `TaskBundle` message
|
||||||
|
*/
|
||||||
|
def registerLocker(guid: UniqueNumberOps, obj: LockerEquipment): TaskBundle = {
|
||||||
|
TaskBundle(RegisterObjectTask(guid, obj), registerInventory(guid, obj))
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct tasking that registers an object with a globally unique identifier selected from a pool of numbers, as a `Player`.<br>
|
* Construct tasking that registers an object with a globally unique identifier selected from a pool of numbers, as a `Player`.<br>
|
||||||
* <br>
|
* <br>
|
||||||
|
|
@ -150,13 +180,13 @@ object GUIDTask {
|
||||||
* a task built of lesser registration tasks and supporting tasks should be written instead.
|
* a task built of lesser registration tasks and supporting tasks should be written instead.
|
||||||
* @param tplayer the `Player` object being registered
|
* @param tplayer the `Player` object being registered
|
||||||
* @param guid implicit reference to a unique number system
|
* @param guid implicit reference to a unique number system
|
||||||
* @return a `TaskResolver.GiveTask` message
|
* @return a `TaskBundle` message
|
||||||
*/
|
*/
|
||||||
def RegisterAvatar(tplayer: Player)(implicit guid: ActorRef): TaskResolver.GiveTask = {
|
def registerAvatar(guid: UniqueNumberOps, tplayer: Player): TaskBundle = {
|
||||||
val holsterTasks = VisibleSlotTaskBuilding(tplayer.Holsters(), RegisterEquipment)
|
val holsterTasks = visibleSlotTaskBuilding(guid, tplayer.Holsters(), registerEquipment)
|
||||||
val lockerTask = List(RegisterObjectTask(tplayer.avatar.locker))
|
val lockerTask = List(registerObject(guid, tplayer.avatar.locker))
|
||||||
val inventoryTasks = RegisterInventory(tplayer)
|
val inventoryTasks = registerInventory(guid, tplayer)
|
||||||
TaskResolver.GiveTask(RegisterObjectTask(tplayer).task, holsterTasks ++ lockerTask ++ inventoryTasks)
|
TaskBundle(RegisterObjectTask(guid, tplayer), holsterTasks ++ lockerTask ++ inventoryTasks)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -165,12 +195,12 @@ object GUIDTask {
|
||||||
* Similar to `RegisterAvatar` but the locker components are skipped.
|
* Similar to `RegisterAvatar` but the locker components are skipped.
|
||||||
* @param tplayer the `Player` object being registered
|
* @param tplayer the `Player` object being registered
|
||||||
* @param guid implicit reference to a unique number system
|
* @param guid implicit reference to a unique number system
|
||||||
* @return a `TaskResolver.GiveTask` message
|
* @return a `TaskBundle` message
|
||||||
*/
|
*/
|
||||||
def RegisterPlayer(tplayer: Player)(implicit guid: ActorRef): TaskResolver.GiveTask = {
|
def registerPlayer(guid: UniqueNumberOps, tplayer: Player): TaskBundle = {
|
||||||
val holsterTasks = VisibleSlotTaskBuilding(tplayer.Holsters(), RegisterEquipment)
|
val holsterTasks = visibleSlotTaskBuilding(guid, tplayer.Holsters(), registerEquipment)
|
||||||
val inventoryTasks = RegisterInventory(tplayer)
|
val inventoryTasks = registerInventory(guid, tplayer)
|
||||||
TaskResolver.GiveTask(GUIDTask.RegisterObjectTask(tplayer)(guid).task, holsterTasks ++ inventoryTasks)
|
TaskBundle(RegisterObjectTask(guid, tplayer), holsterTasks ++ inventoryTasks)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -188,25 +218,39 @@ object GUIDTask {
|
||||||
* a task built of lesser registration tasks and supporting tasks should be written instead.
|
* a task built of lesser registration tasks and supporting tasks should be written instead.
|
||||||
* @param vehicle the `Vehicle` object being registered
|
* @param vehicle the `Vehicle` object being registered
|
||||||
* @param guid implicit reference to a unique number system
|
* @param guid implicit reference to a unique number system
|
||||||
* @return a `TaskResolver.GiveTask` message
|
* @return a `TaskBundle` message
|
||||||
*/
|
*/
|
||||||
def RegisterVehicle(vehicle: Vehicle)(implicit guid: ActorRef): TaskResolver.GiveTask = {
|
def registerVehicle(guid: UniqueNumberOps, vehicle: Vehicle): TaskBundle = {
|
||||||
val weaponTasks = VisibleSlotTaskBuilding(vehicle.Weapons.values, RegisterEquipment)
|
val weaponTasks = visibleSlotTaskBuilding(guid, vehicle.Weapons.values, registerEquipment)
|
||||||
val utilTasks =
|
val utilTasks =
|
||||||
Vehicle.EquipmentUtilities(vehicle.Utilities).values.map(util => { RegisterObjectTask(util()) }).toList
|
Vehicle.EquipmentUtilities(vehicle.Utilities).values.map(util => { registerObject(guid, util()) }).toList
|
||||||
val inventoryTasks = RegisterInventory(vehicle)
|
val inventoryTasks = registerInventory(guid, vehicle)
|
||||||
TaskResolver.GiveTask(RegisterObjectTask(vehicle).task, weaponTasks ++ utilTasks ++ inventoryTasks)
|
TaskBundle(RegisterObjectTask(guid, vehicle), weaponTasks ++ utilTasks ++ inventoryTasks)
|
||||||
}
|
}
|
||||||
|
|
||||||
def RegisterDeployableTurret(
|
def registerDeployableTurret(guid: UniqueNumberOps, obj: PlanetSideGameObject with WeaponTurret): TaskBundle = {
|
||||||
obj: PlanetSideGameObject with WeaponTurret
|
TaskBundle(
|
||||||
)(implicit guid: ActorRef): TaskResolver.GiveTask = {
|
RegisterObjectTask(guid, obj),
|
||||||
TaskResolver.GiveTask(
|
visibleSlotTaskBuilding(guid, obj.Weapons.values, registerEquipment) ++ registerInventory(guid, obj)
|
||||||
RegisterObjectTask(obj).task,
|
|
||||||
VisibleSlotTaskBuilding(obj.Weapons.values, GUIDTask.RegisterEquipment) ++ RegisterInventory(obj)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//unregistration tasking
|
||||||
|
protected case class UnregisterObjectTask(
|
||||||
|
guid: UniqueNumberOps,
|
||||||
|
obj: IdentifiableEntity
|
||||||
|
) extends Task {
|
||||||
|
def action(): Future[Any] = {
|
||||||
|
guid.Unregister(obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
def undo(): Unit = RegisterObjectTask(guid, obj)
|
||||||
|
|
||||||
|
def isSuccessful() : Boolean = !obj.HasGUID
|
||||||
|
|
||||||
|
override def description(): String = s"unregister $obj"
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct tasking that unregisters an object from a globally unique identifier system.<br>
|
* Construct tasking that unregisters an object from a globally unique identifier system.<br>
|
||||||
* <br>
|
* <br>
|
||||||
|
|
@ -214,73 +258,10 @@ object GUIDTask {
|
||||||
* It is the most basic operation that all objects that can have their GUIDs revoked must perform.
|
* It is the most basic operation that all objects that can have their GUIDs revoked must perform.
|
||||||
* @param obj the object being unregistered
|
* @param obj the object being unregistered
|
||||||
* @param guid implicit reference to a unique number system
|
* @param guid implicit reference to a unique number system
|
||||||
* @see `GUIDTask.RegisterObjectTask`
|
* @see `GUIDTask.registerObjectTask`
|
||||||
* @return a `TaskResolver.GiveTask` message
|
* @return a `TaskBundle` message
|
||||||
*/
|
*/
|
||||||
def UnregisterObjectTask(obj: IdentifiableEntity)(implicit guid: ActorRef): TaskResolver.GiveTask = {
|
def unregisterObject(guid: UniqueNumberOps, obj: IdentifiableEntity): TaskBundle = TaskBundle(UnregisterObjectTask(guid, obj))
|
||||||
TaskResolver.GiveTask(
|
|
||||||
new Task() {
|
|
||||||
private val localObject = obj
|
|
||||||
private val localAccessor = guid
|
|
||||||
|
|
||||||
override def Description: String = s"unregister $localObject"
|
|
||||||
|
|
||||||
override def isComplete: Task.Resolution.Value =
|
|
||||||
if (!localObject.HasGUID) {
|
|
||||||
Task.Resolution.Success
|
|
||||||
} else {
|
|
||||||
Task.Resolution.Incomplete
|
|
||||||
}
|
|
||||||
|
|
||||||
def Execute(resolver: ActorRef): Unit = {
|
|
||||||
import net.psforever.objects.guid.actor.Unregister
|
|
||||||
localAccessor ! Unregister(localObject, resolver)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Construct tasking that unregisters a `Tool` object from a globally unique identifier system.<br>
|
|
||||||
* <br>
|
|
||||||
* This task performs an operation that reverses the effect of `RegisterTool`.
|
|
||||||
* @param obj the `Tool` object being unregistered
|
|
||||||
* @param guid implicit reference to a unique number system
|
|
||||||
* @see `GUIDTask.RegisterTool`
|
|
||||||
* @return a `TaskResolver.GiveTask` message
|
|
||||||
*/
|
|
||||||
def UnregisterTool(obj: Tool)(implicit guid: ActorRef): TaskResolver.GiveTask = {
|
|
||||||
val ammoTasks: List[TaskResolver.GiveTask] =
|
|
||||||
(0 until obj.MaxAmmoSlot).map(ammoIndex => UnregisterObjectTask(obj.AmmoSlots(ammoIndex).Box)).toList
|
|
||||||
TaskResolver.GiveTask(UnregisterObjectTask(obj).task, ammoTasks)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Construct tasking that unregisters a `LockerContainer` object from a globally unique identifier system.
|
|
||||||
* @param obj the object being unregistered
|
|
||||||
* @param guid implicit reference to a unique number system
|
|
||||||
* @see `GUIDTask.RegisterLocker`
|
|
||||||
* @return a `TaskResolver.GiveTask` message
|
|
||||||
*/
|
|
||||||
def UnregisterLocker(obj: LockerContainer)(implicit guid: ActorRef): TaskResolver.GiveTask = {
|
|
||||||
TaskResolver.GiveTask(UnregisterObjectTask(obj).task, UnregisterInventory(obj))
|
|
||||||
}
|
|
||||||
def UnregisterLocker(obj: LockerEquipment)(implicit guid: ActorRef): TaskResolver.GiveTask = {
|
|
||||||
TaskResolver.GiveTask(RegisterObjectTask(obj).task, RegisterInventory(obj))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Construct tasking that unregisters the objects that are within the given container's inventory
|
|
||||||
* from a globally unique identifier system.
|
|
||||||
* @param container the storage unit in which objects can be found
|
|
||||||
* @param guid implicit reference to a unique number system
|
|
||||||
* @see `GUIDTask.RegisterInventory`<br>
|
|
||||||
* `Container`
|
|
||||||
* @return a list of `TaskResolver.GiveTask` messages
|
|
||||||
*/
|
|
||||||
def UnregisterInventory(container: Container)(implicit guid: ActorRef): List[TaskResolver.GiveTask] = {
|
|
||||||
container.Inventory.Items.map(entry => { UnregisterEquipment(entry.obj) })
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct tasking that unregisters an object from a globally unique identifier system
|
* Construct tasking that unregisters an object from a globally unique identifier system
|
||||||
|
|
@ -289,32 +270,88 @@ object GUIDTask {
|
||||||
* This task performs an operation that reverses the effect of `RegisterEquipment`.
|
* This task performs an operation that reverses the effect of `RegisterEquipment`.
|
||||||
* @param obj the `Equipment` object being unregistered
|
* @param obj the `Equipment` object being unregistered
|
||||||
* @param guid implicit reference to a unique number system
|
* @param guid implicit reference to a unique number system
|
||||||
* @see `GUIDTask.RegisterEquipment`
|
* @see `GUIDTask.registerEquipment`
|
||||||
* @return a `TaskResolver.GiveTask` message
|
* @return a `TaskBundle` message
|
||||||
*/
|
*/
|
||||||
def UnregisterEquipment(obj: Equipment)(implicit guid: ActorRef): TaskResolver.GiveTask = {
|
def unregisterTool(guid: UniqueNumberOps, obj: Tool): TaskBundle = {
|
||||||
|
TaskBundle(
|
||||||
|
UnregisterObjectTask(guid, obj),
|
||||||
|
(0 until obj.MaxAmmoSlot).map(ammoIndex => unregisterObject(guid, obj.AmmoSlots(ammoIndex).Box))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct tasking that registers an object with a globally unique identifier selected from a pool of numbers,
|
||||||
|
* after determining whether the object is complex (`Tool` or `Locker`) or is simple.<br>
|
||||||
|
* <br>
|
||||||
|
* The objects in this case are specifically `Equipment`, a subclass of the basic register-able `IdentifiableEntity`.
|
||||||
|
* About five subclasses of `Equipment` exist, but they decompose into two groups - "complex objects" and "simple objects."
|
||||||
|
* "Simple objects" are most groups of `Equipment` and just their own GUID to be registered.
|
||||||
|
* "Complex objects" are just the `Tool` category of `Equipment`.
|
||||||
|
* They have internal objects that must also have their GUID's registered to function.<br>
|
||||||
|
* <br>
|
||||||
|
* Using this function when passing unknown `Equipment` is recommended.
|
||||||
|
* The type will be sorted and the object will be handled according to its complexity level.
|
||||||
|
* @param obj the `Equipment` object being registered
|
||||||
|
* @param guid implicit reference to a unique number system
|
||||||
|
* @return a `TaskBundle` message
|
||||||
|
*/
|
||||||
|
def unregisterEquipment(guid: UniqueNumberOps, obj: Equipment): TaskBundle = {
|
||||||
obj match {
|
obj match {
|
||||||
case tool: Tool =>
|
case tool: Tool => unregisterTool(guid, tool)
|
||||||
UnregisterTool(tool)
|
case _ => unregisterObject(guid, obj)
|
||||||
case _ =>
|
|
||||||
UnregisterObjectTask(obj)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct tasking that unregisters the objects that are within the given container's inventory
|
||||||
|
* from a globally unique identifier system.
|
||||||
|
* @param container the storage unit in which objects can be found
|
||||||
|
* @param guid implicit reference to a unique number system
|
||||||
|
* @see `GUIDTask.registerInventory`<br>
|
||||||
|
* `Container`
|
||||||
|
* @return a list of `TaskBundle` messages
|
||||||
|
*/
|
||||||
|
def unregisterInventory(guid: UniqueNumberOps, container: Container): List[TaskBundle] = {
|
||||||
|
container.Inventory.Items.map{ entry => unregisterEquipment(guid, entry.obj) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct tasking that unregisters a `LockerContainer` object from a globally unique identifier system.
|
||||||
|
* @param obj the object being unregistered
|
||||||
|
* @param guid implicit reference to a unique number system
|
||||||
|
* @see `GUIDTask.registerLocker`
|
||||||
|
* @return a `TaskBundle` message
|
||||||
|
*/
|
||||||
|
def unregisterLocker(guid: UniqueNumberOps, obj: LockerContainer): TaskBundle = {
|
||||||
|
TaskBundle(UnregisterObjectTask(guid, obj), unregisterInventory(guid, obj))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct tasking that unregisters a `LockerContainer` object from a globally unique identifier system.
|
||||||
|
* @param obj the object being unregistered
|
||||||
|
* @param guid implicit reference to a unique number system
|
||||||
|
* @see `GUIDTask.registerLocker`
|
||||||
|
* @return a `TaskBundle` message
|
||||||
|
*/
|
||||||
|
def unregisterLocker(guid: UniqueNumberOps, obj: LockerEquipment): TaskBundle = {
|
||||||
|
TaskBundle(UnregisterObjectTask(guid, obj), unregisterInventory(guid, obj))
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct tasking that unregisters a `Player` object from a globally unique identifier system.<br>
|
* Construct tasking that unregisters a `Player` object from a globally unique identifier system.<br>
|
||||||
* <br>
|
* <br>
|
||||||
* This task performs an operation that reverses the effect of `RegisterAvatar`.
|
* This task performs an operation that reverses the effect of `RegisterAvatar`.
|
||||||
* @param tplayer the `Player` object being unregistered
|
* @param tplayer the `Player` object being unregistered
|
||||||
* @param guid implicit reference to a unique number system
|
* @param guid implicit reference to a unique number system
|
||||||
* @see `GUIDTask.RegisterAvatar`
|
* @see `GUIDTask.registerAvatar`
|
||||||
* @return a `TaskResolver.GiveTask` message
|
* @return a `TaskBundle` message
|
||||||
*/
|
*/
|
||||||
def UnregisterAvatar(tplayer: Player)(implicit guid: ActorRef): TaskResolver.GiveTask = {
|
def unregisterAvatar(guid: UniqueNumberOps, tplayer: Player): TaskBundle = {
|
||||||
val holsterTasks = VisibleSlotTaskBuilding(tplayer.Holsters(), UnregisterEquipment)
|
val holsterTasks = visibleSlotTaskBuilding(guid, tplayer.Holsters(), unregisterEquipment)
|
||||||
val lockerTask = List(UnregisterObjectTask(tplayer.avatar.locker))
|
val lockerTask = List(unregisterObject(guid, tplayer.avatar.locker))
|
||||||
val inventoryTasks = UnregisterInventory(tplayer)
|
val inventoryTasks = unregisterInventory(guid, tplayer)
|
||||||
TaskResolver.GiveTask(UnregisterObjectTask(tplayer).task, holsterTasks ++ lockerTask ++ inventoryTasks)
|
TaskBundle(UnregisterObjectTask(guid, tplayer), holsterTasks ++ lockerTask ++ inventoryTasks)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -324,13 +361,13 @@ object GUIDTask {
|
||||||
* This task performs an operation that reverses the effect of `RegisterPlayer`.
|
* This task performs an operation that reverses the effect of `RegisterPlayer`.
|
||||||
* @param tplayer the `Player` object being unregistered
|
* @param tplayer the `Player` object being unregistered
|
||||||
* @param guid implicit reference to a unique number system
|
* @param guid implicit reference to a unique number system
|
||||||
* @see `GUIDTask.RegisterAvatar`
|
* @see `GUIDTask.registerAvatar`
|
||||||
* @return a `TaskResolver.GiveTask` message
|
* @return a `TaskBundle` message
|
||||||
*/
|
*/
|
||||||
def UnregisterPlayer(tplayer: Player)(implicit guid: ActorRef): TaskResolver.GiveTask = {
|
def unregisterPlayer(guid: UniqueNumberOps, tplayer: Player): TaskBundle = {
|
||||||
val holsterTasks = VisibleSlotTaskBuilding(tplayer.Holsters(), UnregisterEquipment)
|
val holsterTasks = visibleSlotTaskBuilding(guid, tplayer.Holsters(), unregisterEquipment)
|
||||||
val inventoryTasks = UnregisterInventory(tplayer)
|
val inventoryTasks = unregisterInventory(guid, tplayer)
|
||||||
TaskResolver.GiveTask(GUIDTask.UnregisterObjectTask(tplayer).task, holsterTasks ++ inventoryTasks)
|
TaskBundle(UnregisterObjectTask(guid, tplayer), holsterTasks ++ inventoryTasks)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -339,26 +376,25 @@ object GUIDTask {
|
||||||
* This task performs an operation that reverses the effect of `RegisterVehicle`.
|
* This task performs an operation that reverses the effect of `RegisterVehicle`.
|
||||||
* @param vehicle the `Vehicle` object being unregistered
|
* @param vehicle the `Vehicle` object being unregistered
|
||||||
* @param guid implicit reference to a unique number system
|
* @param guid implicit reference to a unique number system
|
||||||
* @see `GUIDTask.RegisterVehicle`
|
* @see `GUIDTask.registerVehicle`
|
||||||
* @return a `TaskResolver.GiveTask` message
|
* @return a `TaskBundle` message
|
||||||
*/
|
*/
|
||||||
def UnregisterVehicle(vehicle: Vehicle)(implicit guid: ActorRef): TaskResolver.GiveTask = {
|
def unregisterVehicle(guid: UniqueNumberOps, vehicle: Vehicle): TaskBundle = {
|
||||||
val weaponTasks = VisibleSlotTaskBuilding(vehicle.Weapons.values, UnregisterEquipment)
|
val weaponTasks = visibleSlotTaskBuilding(guid, vehicle.Weapons.values, unregisterEquipment)
|
||||||
val utilTasks =
|
val utilTasks =
|
||||||
Vehicle.EquipmentUtilities(vehicle.Utilities).values.map(util => { UnregisterObjectTask(util()) }).toList
|
Vehicle.EquipmentUtilities(vehicle.Utilities).values.map(util => { unregisterObject(guid, util()) }).toList
|
||||||
val inventoryTasks = UnregisterInventory(vehicle)
|
val inventoryTasks = unregisterInventory(guid, vehicle)
|
||||||
TaskResolver.GiveTask(UnregisterObjectTask(vehicle).task, weaponTasks ++ utilTasks ++ inventoryTasks)
|
TaskBundle(UnregisterObjectTask(guid, vehicle), weaponTasks ++ utilTasks ++ inventoryTasks)
|
||||||
}
|
}
|
||||||
|
|
||||||
def UnregisterDeployableTurret(
|
def unregisterDeployableTurret(guid: UniqueNumberOps, obj: PlanetSideGameObject with WeaponTurret): TaskBundle = {
|
||||||
obj: PlanetSideGameObject with WeaponTurret
|
TaskBundle(
|
||||||
)(implicit guid: ActorRef): TaskResolver.GiveTask = {
|
UnregisterObjectTask(guid, obj),
|
||||||
TaskResolver.GiveTask(
|
visibleSlotTaskBuilding(guid, obj.Weapons.values, unregisterEquipment) ++ unregisterInventory(guid, obj)
|
||||||
UnregisterObjectTask(obj).task,
|
|
||||||
VisibleSlotTaskBuilding(obj.Weapons.values, GUIDTask.UnregisterEquipment) ++ UnregisterInventory(obj)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//support
|
||||||
/**
|
/**
|
||||||
* Construct tasking that allocates work upon encountered `Equipment` objects
|
* Construct tasking that allocates work upon encountered `Equipment` objects
|
||||||
* in reference to a globally unique identifier system of a pool of numbers.
|
* in reference to a globally unique identifier system of a pool of numbers.
|
||||||
|
|
@ -367,12 +403,14 @@ object GUIDTask {
|
||||||
* @param func the function used to build tasking from any discovered `Equipment`;
|
* @param func the function used to build tasking from any discovered `Equipment`;
|
||||||
* strictly either `RegisterEquipment` or `UnregisterEquipment`
|
* strictly either `RegisterEquipment` or `UnregisterEquipment`
|
||||||
* @param guid implicit reference to a unique number system
|
* @param guid implicit reference to a unique number system
|
||||||
* @return a list of `TaskResolver.GiveTask` messages
|
* @return a list of `TaskBundle` messages
|
||||||
*/
|
*/
|
||||||
def VisibleSlotTaskBuilding(list: Iterable[EquipmentSlot], func: Equipment => TaskResolver.GiveTask)(implicit
|
private def visibleSlotTaskBuilding(
|
||||||
guid: ActorRef
|
guid: UniqueNumberOps,
|
||||||
): List[TaskResolver.GiveTask] = {
|
list: Iterable[EquipmentSlot],
|
||||||
recursiveVisibleSlotTaskBuilding(list.iterator, func)
|
func: (UniqueNumberOps, Equipment) => TaskBundle
|
||||||
|
): List[TaskBundle] = {
|
||||||
|
recursiveVisibleSlotTaskBuilding(guid, list.iterator, func)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -386,18 +424,17 @@ object GUIDTask {
|
||||||
* @return a `List` of `Equipment` tasking
|
* @return a `List` of `Equipment` tasking
|
||||||
*/
|
*/
|
||||||
@tailrec private def recursiveVisibleSlotTaskBuilding(
|
@tailrec private def recursiveVisibleSlotTaskBuilding(
|
||||||
iter: Iterator[EquipmentSlot],
|
guid: UniqueNumberOps,
|
||||||
func: Equipment => TaskResolver.GiveTask,
|
iter: Iterator[EquipmentSlot],
|
||||||
list: List[TaskResolver.GiveTask] = Nil
|
func: (UniqueNumberOps, Equipment) => TaskBundle,
|
||||||
)(implicit guid: ActorRef): List[TaskResolver.GiveTask] = {
|
list: List[TaskBundle] = Nil
|
||||||
|
): List[TaskBundle] = {
|
||||||
if (!iter.hasNext) {
|
if (!iter.hasNext) {
|
||||||
list
|
list
|
||||||
} else {
|
} else {
|
||||||
iter.next().Equipment match {
|
iter.next().Equipment match {
|
||||||
case Some(item) =>
|
case Some(item) => recursiveVisibleSlotTaskBuilding(guid, iter, func, list :+ func(guid, item))
|
||||||
recursiveVisibleSlotTaskBuilding(iter, func, list :+ func(item))
|
case None => recursiveVisibleSlotTaskBuilding(guid, iter, func, list)
|
||||||
case None =>
|
|
||||||
recursiveVisibleSlotTaskBuilding(iter, func, list)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
package net.psforever.objects.guid
|
package net.psforever.objects.guid
|
||||||
|
|
||||||
import net.psforever.objects.entity.{IdentifiableEntity, NoGUIDException}
|
import net.psforever.objects.entity.{IdentifiableEntity, NoGUIDException}
|
||||||
import net.psforever.objects.guid.key.LoanedKey
|
import net.psforever.objects.guid.key.{AvailabilityPolicy, LoanedKey}
|
||||||
import net.psforever.objects.guid.pool.{ExclusivePool, GenericPool, NumberPool}
|
import net.psforever.objects.guid.pool.{ExclusivePool, GenericPool, NumberPool}
|
||||||
import net.psforever.objects.guid.source.NumberSource
|
import net.psforever.objects.guid.source.NumberSource
|
||||||
import net.psforever.types.PlanetSideGUID
|
import net.psforever.types.PlanetSideGUID
|
||||||
|
|
@ -24,10 +24,7 @@ class NumberPoolHub(private val source: NumberSource) {
|
||||||
import scala.collection.mutable
|
import scala.collection.mutable
|
||||||
private val hash: mutable.HashMap[String, NumberPool] = mutable.HashMap[String, NumberPool]()
|
private val hash: mutable.HashMap[String, NumberPool] = mutable.HashMap[String, NumberPool]()
|
||||||
private val bigpool: mutable.LongMap[String] = mutable.LongMap[String]()
|
private val bigpool: mutable.LongMap[String] = mutable.LongMap[String]()
|
||||||
hash += "generic" -> new GenericPool(bigpool, source.size)
|
hash += "generic" -> GenericPool(bigpool, source.size, poolName = "generic")
|
||||||
source.finalizeRestrictions.foreach(i =>
|
|
||||||
bigpool += i.toLong -> ""
|
|
||||||
) //these numbers can never be pooled; the source can no longer restrict numbers
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a globally unique identifier, return any object registered to it.<br>
|
* Given a globally unique identifier, return any object registered to it.<br>
|
||||||
|
|
@ -327,7 +324,7 @@ class NumberPoolHub(private val source: NumberSource) {
|
||||||
* @param number the number whose assignment is requested
|
* @param number the number whose assignment is requested
|
||||||
* @return an object that has been registered
|
* @return an object that has been registered
|
||||||
*/
|
*/
|
||||||
def latterPartRegister(obj: IdentifiableEntity, number: Int): Try[IdentifiableEntity] = {
|
private[guid] def latterPartRegister(obj: IdentifiableEntity, number: Int): Try[IdentifiableEntity] = {
|
||||||
register_GetMonitorFromSource(number) match {
|
register_GetMonitorFromSource(number) match {
|
||||||
case Success(monitor) =>
|
case Success(monitor) =>
|
||||||
monitor.Object = obj
|
monitor.Object = obj
|
||||||
|
|
@ -459,7 +456,7 @@ class NumberPoolHub(private val source: NumberSource) {
|
||||||
* @param number the number to return.
|
* @param number the number to return.
|
||||||
* @return any object previously using this number
|
* @return any object previously using this number
|
||||||
*/
|
*/
|
||||||
def latterPartUnregister(number: Int): Option[IdentifiableEntity] = source.returnNumber(number)
|
private[guid] def latterPartUnregister(number: Int): Option[IdentifiableEntity] = source.returnNumber(number)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines if the object is registered.<br>
|
* Determines if the object is registered.<br>
|
||||||
|
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
// Copyright (c) 2017 PSForever
|
|
||||||
package net.psforever.objects.guid
|
|
||||||
|
|
||||||
import akka.actor.ActorRef
|
|
||||||
|
|
||||||
trait Task {
|
|
||||||
def Description: String = "write_descriptive_task_message"
|
|
||||||
def Execute(resolver: ActorRef): Unit
|
|
||||||
def isComplete: Task.Resolution.Value = Task.Resolution.Incomplete
|
|
||||||
def Timeout: Long = 200L //milliseconds
|
|
||||||
def onSuccess(): Unit = {}
|
|
||||||
def onFailure(ex: Throwable): Unit = {}
|
|
||||||
def onTimeout(ex: Throwable): Unit = onFailure(ex)
|
|
||||||
def onAbort(ex: Throwable): Unit = {}
|
|
||||||
def Cleanup(): Unit = {}
|
|
||||||
}
|
|
||||||
|
|
||||||
object Task {
|
|
||||||
def TimeNow: Long = {
|
|
||||||
System.nanoTime()
|
|
||||||
//java.time.Instant.now().getEpochSecond
|
|
||||||
}
|
|
||||||
|
|
||||||
object Resolution extends Enumeration {
|
|
||||||
val Success, Incomplete, Failure = Value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,507 +0,0 @@
|
||||||
// Copyright (c) 2017 PSForever
|
|
||||||
package net.psforever.objects.guid
|
|
||||||
|
|
||||||
import java.util.concurrent.TimeoutException
|
|
||||||
|
|
||||||
import akka.actor.{Actor, ActorRef, Cancellable}
|
|
||||||
import akka.routing.Broadcast
|
|
||||||
import net.psforever.objects.Default
|
|
||||||
|
|
||||||
import scala.annotation.tailrec
|
|
||||||
import scala.collection.mutable.ListBuffer
|
|
||||||
import scala.concurrent.duration._
|
|
||||||
import scala.concurrent.ExecutionContext.Implicits.global
|
|
||||||
import scala.util.{Failure, Success}
|
|
||||||
|
|
||||||
class TaskResolver() extends Actor {
|
|
||||||
|
|
||||||
/** list of all work currently managed by this resolver */
|
|
||||||
private val tasks: ListBuffer[TaskResolver.TaskEntry] = new ListBuffer[TaskResolver.TaskEntry]
|
|
||||||
|
|
||||||
/** scheduled termination of tardy managed work */
|
|
||||||
private var timeoutCleanup: Cancellable = Default.Cancellable
|
|
||||||
|
|
||||||
/** logging utilities; default to tracing */
|
|
||||||
private[this] val log = org.log4s.getLogger
|
|
||||||
private def trace(msg: String) = log.trace(msg)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deal with any tasks that are still enqueued with this expiring `TaskResolver`.
|
|
||||||
*/
|
|
||||||
override def aroundPostStop() = {
|
|
||||||
/*
|
|
||||||
First, eliminate all timed-out tasks.
|
|
||||||
Secondly, deal with all tasks that have reported "success" but have not yet been handled.
|
|
||||||
Finally, all other remaining tasks should be treated as if they had failed.
|
|
||||||
*/
|
|
||||||
timeoutCleanup.cancel()
|
|
||||||
TimeoutCleanup()
|
|
||||||
tasks.filter(entry => entry.task.isComplete == Task.Resolution.Success).foreach(entry => OnSuccess(entry.task))
|
|
||||||
val ex: Throwable = new Exception(s"a task is being stopped")
|
|
||||||
tasks.foreach(entry => {
|
|
||||||
OnFailure(entry.task, ex)
|
|
||||||
})
|
|
||||||
super.aroundPostStop()
|
|
||||||
}
|
|
||||||
|
|
||||||
def receive: Receive = {
|
|
||||||
case TaskResolver.GiveTask(aTask, Nil) =>
|
|
||||||
GiveTask(aTask)
|
|
||||||
|
|
||||||
case TaskResolver.GiveTask(aTask, subtasks) =>
|
|
||||||
QueueSubtasks(aTask, subtasks)
|
|
||||||
|
|
||||||
case TaskResolver.GiveSubtask(aTask, subtasks, resolver) =>
|
|
||||||
QueueSubtasks(aTask, subtasks, resolver)
|
|
||||||
|
|
||||||
case TaskResolver.CompletedSubtask(obj) => //inter-resolver calls
|
|
||||||
ExecuteNewTasks(obj)
|
|
||||||
|
|
||||||
case Success(obj: Task) => //inter-resolver calls
|
|
||||||
OnSuccess(obj)
|
|
||||||
|
|
||||||
case Success | Success(_) => //success redirected from called event
|
|
||||||
OnSuccess()
|
|
||||||
|
|
||||||
case TaskResolver.Failure(obj, ex) => //inter-resolver calls
|
|
||||||
OnFailure(obj, ex)
|
|
||||||
|
|
||||||
case Failure(ex) => //failure redirected from called event
|
|
||||||
OnFailure(ex)
|
|
||||||
|
|
||||||
case TaskResolver.AbortTask(task, ex) =>
|
|
||||||
OnAbort(task, ex)
|
|
||||||
|
|
||||||
case TaskResolver.TimeoutCleanup() =>
|
|
||||||
TimeoutCleanup()
|
|
||||||
|
|
||||||
case msg =>
|
|
||||||
log.warn(s"$self received an unexpected message $msg from ${sender()}")
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Accept simple work and perform it.
|
|
||||||
* @param aTask the work to be completed
|
|
||||||
*/
|
|
||||||
private def GiveTask(aTask: Task): Unit = {
|
|
||||||
val entry: TaskResolver.TaskEntry = TaskResolver.TaskEntry(aTask)
|
|
||||||
tasks += entry
|
|
||||||
trace(s"enqueue and start task ${aTask.Description}")
|
|
||||||
entry.Execute(self)
|
|
||||||
StartTimeoutCheck()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Start the periodic checks for a task that has run for too long (timed-out), unless those checks are already running.
|
|
||||||
*/
|
|
||||||
private def StartTimeoutCheck(): Unit = {
|
|
||||||
if (timeoutCleanup.isCancelled) {
|
|
||||||
timeoutCleanup = context.system.scheduler.scheduleWithFixedDelay(
|
|
||||||
500 milliseconds,
|
|
||||||
500 milliseconds,
|
|
||||||
self,
|
|
||||||
TaskResolver.TimeoutCleanup()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Accept complicated work and divide it into a main task and tasks that must be handled before the main task.
|
|
||||||
* Do not start the main task until all of the aforementioned "sub-tasks" are completed.<br>
|
|
||||||
* <br>
|
|
||||||
* Sub-tasks can be nested many times.
|
|
||||||
* All immediate sub-tasks count as the primary sub-tasks for the current main task.
|
|
||||||
* Each pair of main task and sub-tasks, for every sub-task discovered, is passed on to another `TaskResolver` for completion.
|
|
||||||
* The parent of this `TaskResolver` is the router logic for all brethren `TaskResolver` `Actors`.
|
|
||||||
* @param task the work to be completed
|
|
||||||
* @param subtasks other work that needs to be completed first
|
|
||||||
* @param resolver the `TaskResolver` that distributed this work, thus determining that this work is a sub-task;
|
|
||||||
* by default, no one, as the work is identified as a main task
|
|
||||||
*/
|
|
||||||
private def QueueSubtasks(
|
|
||||||
task: Task,
|
|
||||||
subtasks: List[TaskResolver.GiveTask],
|
|
||||||
resolver: ActorRef = ActorRef.noSender
|
|
||||||
): Unit = {
|
|
||||||
val entry: TaskResolver.TaskEntry = TaskResolver.TaskEntry(task, subtasks.map(task => task.task), resolver)
|
|
||||||
tasks += entry
|
|
||||||
trace(s"enqueue task ${task.Description}")
|
|
||||||
if (subtasks.isEmpty) { //a leaf in terms of task dependency; so, not dependent on any other work
|
|
||||||
trace(s"start task ${task.Description}")
|
|
||||||
entry.Execute(self)
|
|
||||||
} else {
|
|
||||||
trace(s"enqueuing ${subtasks.length} substask(s) belonging to ${task.Description}")
|
|
||||||
subtasks.foreach({ subtask =>
|
|
||||||
context.parent ! TaskResolver.GiveSubtask(
|
|
||||||
subtask.task,
|
|
||||||
subtask.subs,
|
|
||||||
self
|
|
||||||
) //route back to submit subtask to pool
|
|
||||||
})
|
|
||||||
}
|
|
||||||
StartTimeoutCheck()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Perform these checks when a task has reported successful completion to this TaskResolver.
|
|
||||||
* Every task and subtask will be checked, starting from the end of the list of queued entries
|
|
||||||
* and only the first discovered one will be used.
|
|
||||||
*/
|
|
||||||
private def OnSuccess(): Unit = {
|
|
||||||
//by reversing the List, we find the most outstanding Task with the completion state
|
|
||||||
TaskResolver.filterCompletion(tasks.indices.reverseIterator, tasks.toList, Task.Resolution.Success) match {
|
|
||||||
case Some(index) =>
|
|
||||||
GeneralOnSuccess(index)
|
|
||||||
case None => ;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Perform these checks when a task has reported successful completion to this TaskResolver.
|
|
||||||
* @param task a `Task` object
|
|
||||||
*/
|
|
||||||
private def OnSuccess(task: Task): Unit = {
|
|
||||||
//find specific task and dequeue
|
|
||||||
TaskResolver.findTask(tasks.iterator, task) match {
|
|
||||||
case Some(index) =>
|
|
||||||
GeneralOnSuccess(index)
|
|
||||||
case None => ;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Perform these checks when a task has reported successful completion to this TaskResolver.
|
|
||||||
* This is what actually happens upon completion.
|
|
||||||
* @param index the `TaskEntry` index
|
|
||||||
*/
|
|
||||||
private def GeneralOnSuccess(index: Int): Unit = {
|
|
||||||
val entry = tasks(index)
|
|
||||||
entry.task.onSuccess()
|
|
||||||
trace(s"success with task ${entry.task.Description}")
|
|
||||||
if (entry.supertaskRef != ActorRef.noSender) {
|
|
||||||
entry.supertaskRef ! TaskResolver.CompletedSubtask(
|
|
||||||
entry.task
|
|
||||||
) //alert our dependent task's resolver that we have completed
|
|
||||||
}
|
|
||||||
TaskCleanup(index)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Scan across a group of sub-tasks and determine if the associated main `Task` may execute.
|
|
||||||
* All of the sub-tasks must report a `Success` completion status before the main work can begin.
|
|
||||||
* @param subtask a `Task` that is a subtask of some parent task in this resolver's group
|
|
||||||
*/
|
|
||||||
private def ExecuteNewTasks(subtask: Task): Unit = {
|
|
||||||
TaskResolver.findTaskWithSubtask(tasks.iterator, subtask) match {
|
|
||||||
case Some(index) =>
|
|
||||||
val entry = tasks(index)
|
|
||||||
if (TaskResolver.filterCompletionMatch(entry.subtasks.iterator, Task.Resolution.Success)) {
|
|
||||||
trace(s"start new task ${entry.task.Description}")
|
|
||||||
entry.Execute(self)
|
|
||||||
StartTimeoutCheck()
|
|
||||||
}
|
|
||||||
case None => ;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Perform these checks when a task has reported failure to this `TaskResolver`.
|
|
||||||
* Since the `Failure(Throwable)` can not be associated with a specific task,
|
|
||||||
* every task and subtask will be checked, starting from the end of the list of queued entries,
|
|
||||||
* and only the first discovered one will be used.
|
|
||||||
* Consequently, the specific `Throwable` that contains the error message may have nothing to do with the failed task.
|
|
||||||
* @param ex a `Throwable` that reports what happened to the task
|
|
||||||
*/
|
|
||||||
private def OnFailure(ex: Throwable): Unit = {
|
|
||||||
//by reversing the List, we find the most outstanding Task with the completion state
|
|
||||||
TaskResolver.filterCompletion(tasks.indices.reverseIterator, tasks.toList, Task.Resolution.Failure) match {
|
|
||||||
case Some(index) =>
|
|
||||||
GeneralOnFailure(index, ex)
|
|
||||||
case None => ;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Perform these checks when a task has reported failure to this `TaskResolver`.
|
|
||||||
* @param subtask the task that had reported failure from some other resolver
|
|
||||||
* @param ex a `Throwable` that reports what happened to the task
|
|
||||||
*/
|
|
||||||
private def OnFailure(subtask: Task, ex: Throwable): Unit = {
|
|
||||||
TaskResolver.findTaskWithSubtask(tasks.iterator, subtask) match {
|
|
||||||
case Some(index) =>
|
|
||||||
GeneralOnFailure(index, ex)
|
|
||||||
case None => ;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Perform these checks when a task has reported failure to this `TaskResolver`.
|
|
||||||
* This is what actually happens upon completion.
|
|
||||||
* @param index the `TaskEntry` index
|
|
||||||
* @param ex a `Throwable` that reports what happened to the task
|
|
||||||
*/
|
|
||||||
private def GeneralOnFailure(index: Int, ex: Throwable): Unit = {
|
|
||||||
val entry = tasks(index)
|
|
||||||
val task = entry.task
|
|
||||||
trace(s"failure with task ${task.Description}")
|
|
||||||
task.onAbort(ex)
|
|
||||||
task.onFailure(ex)
|
|
||||||
if (entry.supertaskRef != ActorRef.noSender) {
|
|
||||||
entry.supertaskRef ! TaskResolver.Failure(task, ex) //alert our superior task's resolver we have completed
|
|
||||||
}
|
|
||||||
FaultSubtasks(entry)
|
|
||||||
TaskCleanup(index)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Instruct all subtasks of a given `Task` to fail.
|
|
||||||
* @param entry the target parent entry (that has failed)
|
|
||||||
*/
|
|
||||||
private def FaultSubtasks(entry: TaskResolver.TaskEntry): Unit = {
|
|
||||||
val ex: Throwable = new Exception(s"a task ${entry.task} had a subtask that failed")
|
|
||||||
entry.subtasks.foreach(subtask => {
|
|
||||||
context.parent ! Broadcast(TaskResolver.Failure(subtask, ex)) //we have no clue where this subtask was hosted
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If a specific `Task` is governed by this `TaskResolver`, find its index and dispose of it and its known sub-tasks.
|
|
||||||
* @param task the work to be found
|
|
||||||
* @param ex a `Throwable` that reports what happened to the work
|
|
||||||
*/
|
|
||||||
private def OnAbort(task: Task, ex: Throwable): Unit = {
|
|
||||||
TaskResolver.findTask(tasks.iterator, task) match {
|
|
||||||
case Some(index) =>
|
|
||||||
PropagateAbort(index, ex)
|
|
||||||
TaskCleanup(index)
|
|
||||||
case None => ;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If a specific `Task` is governed by this `TaskResolver`, dispose of it and its known sub-tasks.
|
|
||||||
* @param index the index of the discovered work
|
|
||||||
* @param ex a `Throwable` that reports what happened to the work
|
|
||||||
*/
|
|
||||||
private def PropagateAbort(index: Int, ex: Throwable): Unit = {
|
|
||||||
tasks(index).subtasks.foreach({ subtask =>
|
|
||||||
if (subtask.isComplete == Task.Resolution.Success) {
|
|
||||||
trace(s"aborting task ${subtask.Description}")
|
|
||||||
subtask.onAbort(ex)
|
|
||||||
}
|
|
||||||
context.parent ! Broadcast(TaskResolver.AbortTask(subtask, ex))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find all tasks that have been running for too long and declare them as timed-out.
|
|
||||||
* Run periodically, as long as work is being performed.
|
|
||||||
*/
|
|
||||||
private def TimeoutCleanup(): Unit = {
|
|
||||||
TaskResolver
|
|
||||||
.filterTimeout(tasks.indices.reverseIterator, tasks.toList, Task.TimeNow)
|
|
||||||
.foreach({ index =>
|
|
||||||
val ex: Throwable = new TimeoutException(s"a task ${tasks(index).task} has timed out")
|
|
||||||
tasks(index).task.onTimeout(ex)
|
|
||||||
PropagateAbort(index, ex)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove a `Task` that has reported completion.
|
|
||||||
* @param index an index of work in the `List` of `Task`s
|
|
||||||
*/
|
|
||||||
private def TaskCleanup(index: Int): Unit = {
|
|
||||||
tasks(index).task.Cleanup()
|
|
||||||
tasks.remove(index)
|
|
||||||
if (tasks.isEmpty) {
|
|
||||||
timeoutCleanup.cancel()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
object TaskResolver {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Give this `TaskResolver` simple work to be performed.
|
|
||||||
* @param task the work to be completed
|
|
||||||
* @param subs other work that needs to be completed first
|
|
||||||
*/
|
|
||||||
final case class GiveTask(task: Task, subs: List[GiveTask] = Nil)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pass around complex work to be performed.
|
|
||||||
* @param task the work to be completed
|
|
||||||
* @param subs other work that needs to be completed first
|
|
||||||
* @param resolver the `TaskResolver` that will handle work that depends on the outcome of this work
|
|
||||||
*/
|
|
||||||
private final case class GiveSubtask(task: Task, subs: List[GiveTask], resolver: ActorRef)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Run a scheduled timed-out `Task` check.
|
|
||||||
*/
|
|
||||||
private final case class TimeoutCleanup()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A specific kind of `Failure` that reports on which specific `Task` has reported failure.
|
|
||||||
* @param obj a task object
|
|
||||||
* @param ex information about what went wrong
|
|
||||||
*/
|
|
||||||
private final case class Failure(obj: Task, ex: Throwable)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A specific kind of `Success` that reports on which specific `Task` has reported Success where that `Task` was some other `Task`'s subtask.
|
|
||||||
* @param obj a task object
|
|
||||||
*/
|
|
||||||
private final case class CompletedSubtask(obj: Task)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A `Broadcast` message designed to find and remove a particular task from this series of routed `Actors`.
|
|
||||||
* @param task the work to be removed
|
|
||||||
* @param ex an explanation why the work is being aborted
|
|
||||||
*/
|
|
||||||
private final case class AbortTask(task: Task, ex: Throwable)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Storage unit for a specific unit of work, plus extra information.
|
|
||||||
* @param task the work to be completed
|
|
||||||
* @param subtasks other work that needs to be completed first
|
|
||||||
* //@param isASubtask whether this work is intermediary or the last in a dependency chain
|
|
||||||
* @param supertaskRef the `TaskResolver` that will handle work that depends on the outcome of this work
|
|
||||||
*/
|
|
||||||
private final case class TaskEntry(
|
|
||||||
task: Task,
|
|
||||||
subtasks: List[Task] = Nil,
|
|
||||||
supertaskRef: ActorRef = ActorRef.noSender
|
|
||||||
) {
|
|
||||||
private var start: Long = 0L
|
|
||||||
private var isExecuting: Boolean = false
|
|
||||||
|
|
||||||
def Start: Long = start
|
|
||||||
|
|
||||||
def Executing: Boolean = isExecuting
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Only execute each task once.
|
|
||||||
* @param ref the `TaskResolver` currently handling this `Task`/`TaskEntry`
|
|
||||||
*/
|
|
||||||
def Execute(ref: ActorRef): Unit = {
|
|
||||||
if (!isExecuting) {
|
|
||||||
isExecuting = true
|
|
||||||
start = Task.TimeNow
|
|
||||||
task.Execute(ref)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Scan across a group of tasks to determine which ones match the target completion status.
|
|
||||||
* @param iter an `Iterator` of enqueued `TaskEntry` indices
|
|
||||||
* @param resolution the target completion status
|
|
||||||
* @return the first valid index when `TaskEntry` has its primary `Task` matching the completion status
|
|
||||||
*/
|
|
||||||
@tailrec private def filterCompletion(
|
|
||||||
iter: Iterator[Int],
|
|
||||||
tasks: List[TaskEntry],
|
|
||||||
resolution: Task.Resolution.Value
|
|
||||||
): Option[Int] = {
|
|
||||||
if (!iter.hasNext) {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
val index: Int = iter.next()
|
|
||||||
if (tasks(index).task.isComplete == resolution) {
|
|
||||||
Some(index)
|
|
||||||
} else {
|
|
||||||
filterCompletion(iter, tasks, resolution)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Scan across a group of sub-tasks to determine if they all match the target completion status.
|
|
||||||
* @param iter an `Iterator` of enqueued sub-tasks
|
|
||||||
* @param resolution the target completion status
|
|
||||||
* @return `true`, if all tasks match the complete status;
|
|
||||||
* `false`, otherwise
|
|
||||||
*/
|
|
||||||
@tailrec private def filterCompletionMatch(iter: Iterator[Task], resolution: Task.Resolution.Value): Boolean = {
|
|
||||||
if (!iter.hasNext) {
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
if (iter.next().isComplete == resolution) {
|
|
||||||
filterCompletionMatch(iter, resolution)
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find the indices of all enqueued work that has timed-out.
|
|
||||||
* @param iter an `Iterator` of enqueued `TaskEntry` indices
|
|
||||||
* @param now the current time in milliseconds
|
|
||||||
* @param indexList a persistent `List` of indices
|
|
||||||
* @return the `List` of all valid `Task` indices
|
|
||||||
*/
|
|
||||||
@tailrec private def filterTimeout(
|
|
||||||
iter: Iterator[Int],
|
|
||||||
tasks: List[TaskEntry],
|
|
||||||
now: Long,
|
|
||||||
indexList: List[Int] = Nil
|
|
||||||
): List[Int] = {
|
|
||||||
if (!iter.hasNext) {
|
|
||||||
indexList
|
|
||||||
} else {
|
|
||||||
val index: Int = iter.next()
|
|
||||||
val taskEntry = tasks(index)
|
|
||||||
if (
|
|
||||||
taskEntry.Executing && taskEntry.task.isComplete == Task.Resolution.Incomplete && now - taskEntry.Start > taskEntry.task.Timeout
|
|
||||||
) {
|
|
||||||
filterTimeout(iter, tasks, now, indexList :+ index)
|
|
||||||
} else {
|
|
||||||
filterTimeout(iter, tasks, now, indexList)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find the index of the targeted `Task`, if it is enqueued here.
|
|
||||||
* @param iter an `Iterator` of entries
|
|
||||||
* @param target a target `Task`
|
|
||||||
* @param index the current index in the aforementioned `List`;
|
|
||||||
* defaults to 0
|
|
||||||
* @return the index of the discovered task, or `None`
|
|
||||||
*/
|
|
||||||
@tailrec private def findTask(iter: Iterator[TaskEntry], target: Task, index: Int = 0): Option[Int] = {
|
|
||||||
if (!iter.hasNext) {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
if (iter.next().task == target) {
|
|
||||||
Some(index)
|
|
||||||
} else {
|
|
||||||
findTask(iter, target, index + 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find the index of the targeted `Task`, if it is enqueued here, given a specific "subtask" of that `Task`.
|
|
||||||
* @param iter an `Iterator` of entries
|
|
||||||
* @param target a target subtask
|
|
||||||
* @param index the current index in the aforementioned `List`;
|
|
||||||
* defaults to 0
|
|
||||||
* @return the index of the discovered task, or `None`
|
|
||||||
*/
|
|
||||||
@tailrec private def findTaskWithSubtask(iter: Iterator[TaskEntry], target: Task, index: Int = 0): Option[Int] = {
|
|
||||||
if (!iter.hasNext) {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
val tEntry = iter.next()
|
|
||||||
if (tEntry.subtasks.contains(target)) {
|
|
||||||
Some(index)
|
|
||||||
} else {
|
|
||||||
findTaskWithSubtask(iter, target, index + 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
215
src/main/scala/net/psforever/objects/guid/TaskWorkflow.scala
Normal file
215
src/main/scala/net/psforever/objects/guid/TaskWorkflow.scala
Normal file
|
|
@ -0,0 +1,215 @@
|
||||||
|
// Copyright (c) 2021 PSForever
|
||||||
|
package net.psforever.objects.guid
|
||||||
|
|
||||||
|
import scala.concurrent.ExecutionContext.Implicits.global
|
||||||
|
import scala.concurrent.{Future, Promise}
|
||||||
|
import scala.util.{Failure, Success}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parts of the task resolution lifecycle.
|
||||||
|
*/
|
||||||
|
sealed trait TaskBehaviors {
|
||||||
|
/** What the task is supposed to accomplish. */
|
||||||
|
def action(): Future[Any]
|
||||||
|
/** A reversal of 'what the task is supposed to accomplish'. */
|
||||||
|
def undo(): Unit
|
||||||
|
/** Has the task been successfully completed? */
|
||||||
|
def isSuccessful(): Boolean
|
||||||
|
/** Describe this task's actions. */
|
||||||
|
def description(): String = getClass.getSimpleName
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A primary unit of work in a workflow.
|
||||||
|
*/
|
||||||
|
trait Task
|
||||||
|
extends TaskBehaviors {
|
||||||
|
/** A careful determination if the task can be attempted.
|
||||||
|
* @see `Task.action`
|
||||||
|
*/
|
||||||
|
private[guid] def performAction(): Future[Any] = {
|
||||||
|
if (!isSuccessful()) {
|
||||||
|
action()
|
||||||
|
} else {
|
||||||
|
Future(Failure(new TaskNotExecutedException(task = this)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/** A careful determination if the task needs to be undone.
|
||||||
|
* @see `Task.undo`
|
||||||
|
*/
|
||||||
|
private[guid] def performUndo(): Unit = {
|
||||||
|
if (isSuccessful()) undo() else ()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A primary unit of work in a workflow that is set up to execute and never be taken back.
|
||||||
|
* Good for top-level tasking that only reports on the success of work carried out by subtasks.
|
||||||
|
*/
|
||||||
|
trait StraightforwardTask
|
||||||
|
extends Task {
|
||||||
|
def undo(): Unit = { /* blank */ }
|
||||||
|
|
||||||
|
def isSuccessful(): Boolean = false /* always primed to be executed */
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The packaging of a more complicated unit of work in a workflow
|
||||||
|
* in which one task relies on the successful completion of other tasks.
|
||||||
|
* @param mainTask the primary task
|
||||||
|
* @param subTasks tasks that are necessary to complete before starting on the primary one
|
||||||
|
*/
|
||||||
|
final case class TaskBundle(mainTask: Task, subTasks: Seq[TaskBundle])
|
||||||
|
extends TaskBehaviors {
|
||||||
|
/** Attempt 'what the [primary] task is supposed to accomplish'. */
|
||||||
|
def action(): Future[Any] = mainTask.performAction()
|
||||||
|
|
||||||
|
/** Attempt a reversal of what the all the connected tasks are 'supposed to accomplish'. */
|
||||||
|
def undo() : Unit = {
|
||||||
|
mainTask.performUndo()
|
||||||
|
subTasks.foreach { _.undo() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/** A hierarchical analysis of whether `the task been successfully completed`. */
|
||||||
|
def isSuccessful(): Boolean = mainTask.isSuccessful() && subTasks.forall { _.isSuccessful() }
|
||||||
|
|
||||||
|
override def description(): String = {
|
||||||
|
val subCount: String = if (subTasks.nonEmpty) s" (${subTasks.size} subtasks)" else ""
|
||||||
|
s"${mainTask.description()}$subCount"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object TaskBundle {
|
||||||
|
/**
|
||||||
|
* The packaging of a unit of work in a workflow.
|
||||||
|
* @param task the task
|
||||||
|
*/
|
||||||
|
def apply(task: Task): TaskBundle = TaskBundle(task, List())
|
||||||
|
/**
|
||||||
|
* The packaging of a unit of work in a workflow
|
||||||
|
* and a single task required to be completed first.
|
||||||
|
* @param task the primary task
|
||||||
|
* @param subTask the task that must be completed before the primary task
|
||||||
|
*/
|
||||||
|
def apply(task: Task, subTask: Task): TaskBundle = TaskBundle(task, TaskBundle(subTask))
|
||||||
|
/**
|
||||||
|
* The packaging of a unit of work in a workflow
|
||||||
|
* and the task(s) required to be completed first.
|
||||||
|
* @param task the primary task
|
||||||
|
* @param subTask the task(s) that must be completed before the primary task
|
||||||
|
*/
|
||||||
|
def apply(task: Task, subTask: TaskBundle): TaskBundle = TaskBundle(task, Seq(subTask))
|
||||||
|
}
|
||||||
|
|
||||||
|
class TaskNotExecutedException(task: TaskBehaviors, msg: String) extends Exception(msg) {
|
||||||
|
def this(task: Task) = {
|
||||||
|
this(task, s"task '${task.description()}' was not successful")
|
||||||
|
}
|
||||||
|
|
||||||
|
def this(task: TaskBundle) = {
|
||||||
|
this(task, s"task ${task.description()} was not successful")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object TaskWorkflow {
|
||||||
|
/**
|
||||||
|
* The entry into the task workflow resolution process.
|
||||||
|
* @param taskTree the packaged tasks that need to be completed
|
||||||
|
* @return the anticipation of a task to be completed
|
||||||
|
*/
|
||||||
|
def execute(taskTree: TaskBundle): Future[Any] = {
|
||||||
|
evaluateTaskAndSubs(taskTree)
|
||||||
|
}
|
||||||
|
|
||||||
|
private def evaluateTaskAndSubs(task: TaskBundle): Future[Any] = {
|
||||||
|
val promise = Promise[Any]()
|
||||||
|
val (result, subResults) = composeTaskAndSubs(task)
|
||||||
|
result.onComplete { _ =>
|
||||||
|
if (matchOnFutureFailure(result)) {
|
||||||
|
//every subtask that has already succeeded must be undone
|
||||||
|
subResults
|
||||||
|
.zip(task.subTasks)
|
||||||
|
.collect { case (a, b) if matchOnFutureSuccess(a) => b }
|
||||||
|
.foreach { _.undo() }
|
||||||
|
}
|
||||||
|
promise.completeWith(result)
|
||||||
|
}
|
||||||
|
promise.future
|
||||||
|
}
|
||||||
|
|
||||||
|
private def composeTaskAndSubs(task: TaskBundle): (Future[Any], Seq[Future[Any]]) = {
|
||||||
|
val promise = Promise[Any]()
|
||||||
|
val composedSubs = task.subTasks.map(evaluateTaskAndSubs)
|
||||||
|
composedSubs match {
|
||||||
|
case Nil =>
|
||||||
|
//no subtasks; just execute the main task
|
||||||
|
promise.completeWith(task.action())
|
||||||
|
case list =>
|
||||||
|
var unassignedCompletion: Boolean = true //shared mutex
|
||||||
|
//wait for subtasks to complete
|
||||||
|
list.foreach { result =>
|
||||||
|
result.onComplete { _ =>
|
||||||
|
unassignedCompletion.synchronized {
|
||||||
|
if (unassignedCompletion && composedSubs.forall(matchOnFutureCompletion)) {
|
||||||
|
unassignedCompletion = false
|
||||||
|
if (composedSubs.forall(matchOnFutureSuccess)) {
|
||||||
|
//if all subtasks passed, execute the main task
|
||||||
|
promise.completeWith(task.action())
|
||||||
|
} else {
|
||||||
|
//if some subtasks did not succeed, pass on wrapped failure
|
||||||
|
promise.completeWith(Future(Failure(new TaskNotExecutedException(task))))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(promise.future, composedSubs)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does this anticipation of a task report having completed?
|
||||||
|
* @param f the anticipation
|
||||||
|
* @return whether it has been completed (passed or failed)
|
||||||
|
*/
|
||||||
|
def matchOnFutureCompletion(f: Future[Any]): Boolean = {
|
||||||
|
/*
|
||||||
|
if 'matchOnFutureCompletion(FUTURE) == false' then 'matchOnFutureSuccess(FUTURE) == matchOnFutureFailure(FUTURE)'
|
||||||
|
if 'matchOnFutureCompletion(FUTURE) == true' then 'matchOnFutureSuccess(FUTURE) != matchOnFutureFailure(FUTURE)'
|
||||||
|
*/
|
||||||
|
f.value match {
|
||||||
|
case Some(_) => true
|
||||||
|
case None => false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does this anticipation of a task report having succeeded?
|
||||||
|
* The only true success is one where there is no `Failure` and no `Exception`.
|
||||||
|
* @param f the anticipation
|
||||||
|
* @return whether it has succeeded
|
||||||
|
*/
|
||||||
|
def matchOnFutureSuccess(f: Future[Any]): Boolean = {
|
||||||
|
f.value match {
|
||||||
|
case Some(Success(_: Exception)) => false
|
||||||
|
case Some(Success(Failure(_))) => false
|
||||||
|
case Some(Success(_)) => true
|
||||||
|
case _ => false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does this anticipation of a task report having failed?
|
||||||
|
* Having not yet completed does not count as a failure.
|
||||||
|
* @param f the anticipation
|
||||||
|
* @return whether it has failed
|
||||||
|
*/
|
||||||
|
def matchOnFutureFailure(f: Future[Any]): Boolean = {
|
||||||
|
f.value match {
|
||||||
|
case Some(Failure(_)) => true
|
||||||
|
case Some(Success(_: Exception)) => true
|
||||||
|
case Some(Success(Failure(_))) => true
|
||||||
|
case _ => false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
454
src/main/scala/net/psforever/objects/guid/UniqueNumberOps.scala
Normal file
454
src/main/scala/net/psforever/objects/guid/UniqueNumberOps.scala
Normal file
|
|
@ -0,0 +1,454 @@
|
||||||
|
// Copyright (c) 2017-2021 PSForever
|
||||||
|
package net.psforever.objects.guid
|
||||||
|
|
||||||
|
import akka.actor.{Actor, ActorContext, ActorRef, Props}
|
||||||
|
import akka.pattern.{AskTimeoutException, ask}
|
||||||
|
import akka.util.Timeout
|
||||||
|
import net.psforever.objects.entity.IdentifiableEntity
|
||||||
|
import net.psforever.objects.guid.uns.{
|
||||||
|
AlreadyRegisteredEntity,
|
||||||
|
AlreadyUnregisteredEntity,
|
||||||
|
NumberPoolActor,
|
||||||
|
RegisteredEntity,
|
||||||
|
UnregisteredEntity
|
||||||
|
}
|
||||||
|
|
||||||
|
import scala.concurrent.ExecutionContext.Implicits.global
|
||||||
|
import scala.concurrent.duration._
|
||||||
|
import scala.concurrent.{Future, Promise}
|
||||||
|
import scala.util.{Failure, Success}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrap around converted `NumberPool`s and synchronize a portion of the number registration process
|
||||||
|
* as a part of the global unique identifier (GUID, UID) number system (UNS, "unique number system").
|
||||||
|
* The ultimate goal is to manage a coherent group of unique identifiers for a given "region".
|
||||||
|
* Both parts of the UID system sit atop the `Zone` for easy external access.
|
||||||
|
* The plain part - the `NumberPoolHub` here - is used for low-priority requests
|
||||||
|
* such as checking for existing associations.<br>
|
||||||
|
* <br>
|
||||||
|
* A four part process is used for object registration tasks.
|
||||||
|
* First, the requested `NumberPool` is located among the list of known `NumberPool`s.
|
||||||
|
* Second, an asynchronous request is sent to that pool to retrieve a number.
|
||||||
|
* (Only any number. Only a failing case allows for selection of a specific number.)
|
||||||
|
* Third, the asynchronous request returns and the original information about the request is recovered.
|
||||||
|
* Fourth, both sides of the contract are completed by the object being assigned the number and
|
||||||
|
* the underlying "number source" is made to remember an association between the object and the number.
|
||||||
|
* Short circuits and recoveries as available on all steps though reporting is split between logging and callbacks.
|
||||||
|
* The process of removing the association between a number and object (unregistering) is a similar four part process.<br>
|
||||||
|
* <br>
|
||||||
|
* The important relationship between this `Actor` and the `Map` of `NumberPoolActors` is as a "gate."
|
||||||
|
* A single `Map` is constructed and shared between multiple entry points to the UID system where requests are messaged.
|
||||||
|
* Multiple entry points send messages to the same `NumberPool`.
|
||||||
|
* That `NumberPool` deals with the messages one at a time and sends reply to each entry point that communicated with it.
|
||||||
|
* This process is almost as fast as the process of the `NumberPool` selecting a number.
|
||||||
|
* (At least, both should be fast.)
|
||||||
|
* @param guid the supporting datatype for the unique number distribution
|
||||||
|
* @param poolActors a mapping created from the `NumberPool`s, to achieve synchronized access
|
||||||
|
*/
|
||||||
|
class UniqueNumberOps(
|
||||||
|
private val guid: NumberPoolHub,
|
||||||
|
private val poolActors: Map[String, ActorRef]
|
||||||
|
) {
|
||||||
|
/** The timeout used by all number pool `ask` messaging */
|
||||||
|
private implicit val timeout = UniqueNumberOps.timeout
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The entry point for the entity GUID registration process.
|
||||||
|
* A special check is made first to determine if the entity is already registered, and if so to where.
|
||||||
|
* If the entity is not registered, then the process continues.
|
||||||
|
* @param obj the entity to be assigned a GUID
|
||||||
|
* @param poolName the pool from which the entity wants a GUID to be selected
|
||||||
|
* @return the anticipation of this activity being completed
|
||||||
|
*/
|
||||||
|
def Register(
|
||||||
|
obj: IdentifiableEntity,
|
||||||
|
poolName: String
|
||||||
|
): Future[Any] = {
|
||||||
|
val result: Promise[Any] = Promise()
|
||||||
|
if (obj.HasGUID) {
|
||||||
|
alreadyRegisteredTo(obj, poolName) match {
|
||||||
|
case Some(pname) =>
|
||||||
|
result.success(AlreadyRegisteredEntity(RegisteredEntity(obj, pname, guid, obj.GUID.guid)))
|
||||||
|
case None =>
|
||||||
|
result.failure(new RegisteredToWrongPlaceException(obj, obj.GUID.guid))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result.completeWith(registrationProcess(obj, poolName))
|
||||||
|
}
|
||||||
|
result.future
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The entry point for the entity GUID unregistration process.
|
||||||
|
* A special check is made first to determine where, if at all, the entity is registered.
|
||||||
|
* Obviously, if the entity is not registered somewhere within purview of this UNS, the process can not continue.
|
||||||
|
* If the entity's registration number pool is found, then the process continues.
|
||||||
|
* @param obj the entity to be unassigned its GUID
|
||||||
|
* @return the anticipation of this activity being completed
|
||||||
|
*/
|
||||||
|
def Unregister(obj: IdentifiableEntity): Future[Any] = {
|
||||||
|
val result: Promise[Any] = Promise()
|
||||||
|
if (obj.HasGUID) {
|
||||||
|
val number = obj.GUID.guid
|
||||||
|
guid.WhichPool(number) match {
|
||||||
|
case Some(pname) =>
|
||||||
|
result.completeWith(unregistrationProcess(obj, number, pname))
|
||||||
|
case None =>
|
||||||
|
result.failure(new RegisteredToWrongPlaceException(obj, number))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
UniqueNumberOps.log.warn(s"$obj is already unregistered")
|
||||||
|
result.success(Future(AlreadyUnregisteredEntity(UnregisteredEntity(obj, "", guid, -1))))
|
||||||
|
}
|
||||||
|
result.future
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A step of the entity GUID registration process.
|
||||||
|
* Pass control through to the next step.
|
||||||
|
* @see `registrationProcess(IdentifiableEntity, NumberPoolHub, Map[String, ActorRef], String)`
|
||||||
|
* @param obj the entity to be assigned a GUID
|
||||||
|
* @param poolName the pool to which the object is trying to register
|
||||||
|
* @return the anticipation of this activity being completed
|
||||||
|
*/
|
||||||
|
private def registrationProcess(
|
||||||
|
obj: IdentifiableEntity,
|
||||||
|
poolName: String
|
||||||
|
): Future[Any] = {
|
||||||
|
registrationProcess(obj, guid, poolActors, poolName)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A step of the entity GUID registration process.
|
||||||
|
* Send a message to the `NumberPool` to request a number.
|
||||||
|
* If a number is received, continue with a successful registration process.
|
||||||
|
* If no number is received, or some other issue occurs, attempt to recover from the error and report it.
|
||||||
|
* This method is designed to be recursive as it is also utilized for the recovery attempt
|
||||||
|
* and must pass all of the necessary information on to that next attempt.
|
||||||
|
* @param obj the entity to be assigned a GUID
|
||||||
|
* @param hub the supporting datatype for the unique number distribution
|
||||||
|
* @param pools a mapping created from the `NumberPool`s, to achieve synchronized access
|
||||||
|
* @param poolName the pool to which the object is trying to register
|
||||||
|
* @return the anticipation of this activity being completed
|
||||||
|
*/
|
||||||
|
private def registrationProcess(
|
||||||
|
obj: IdentifiableEntity,
|
||||||
|
hub: NumberPoolHub,
|
||||||
|
pools: Map[String, ActorRef],
|
||||||
|
poolName: String
|
||||||
|
): Future[Any] = {
|
||||||
|
val promisingResult: Promise[Any] = Promise()
|
||||||
|
pools.get(poolName) match {
|
||||||
|
case Some(pool) =>
|
||||||
|
//cache
|
||||||
|
val localPromise = promisingResult
|
||||||
|
val localTarget = obj
|
||||||
|
val localUns = hub
|
||||||
|
val localPools = pools
|
||||||
|
val localPoolName = poolName
|
||||||
|
val localPool = pool
|
||||||
|
|
||||||
|
val result = ask(pool, NumberPoolActor.GetAnyNumber())(timeout)
|
||||||
|
result.onComplete {
|
||||||
|
case Success(NumberPoolActor.GiveNumber(number)) =>
|
||||||
|
UniqueNumberOps.processRegisterResult(
|
||||||
|
localPromise,
|
||||||
|
localTarget,
|
||||||
|
localUns,
|
||||||
|
localPoolName,
|
||||||
|
localPool,
|
||||||
|
number
|
||||||
|
)
|
||||||
|
case Success(NumberPoolActor.NoNumber(ex)) =>
|
||||||
|
registrationProcessRetry(localPromise, ex, localTarget, localUns, localPools, localPoolName)
|
||||||
|
case msg =>
|
||||||
|
UniqueNumberOps.log.warn(s"unexpected message during $localTarget's registration process - $msg")
|
||||||
|
}
|
||||||
|
result.recover {
|
||||||
|
case ex: AskTimeoutException =>
|
||||||
|
localPromise.failure(new RegisteringException(msg = s"did not register entity $localTarget in time", ex))
|
||||||
|
}
|
||||||
|
|
||||||
|
case None =>
|
||||||
|
//do not log
|
||||||
|
val ex = new Exception(s"can not find pool $poolName")
|
||||||
|
registrationProcessRetry(promisingResult, ex, obj, guid, pools, poolName)
|
||||||
|
}
|
||||||
|
promisingResult.future
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* na
|
||||||
|
* @param promise the ongoing promise to be fulfilled for the future
|
||||||
|
* @param exception an issue that has arrisen, forcing the retry attempt
|
||||||
|
* @param obj the entity to be assigned a GUID
|
||||||
|
* @param hub the supporting datatype for the unique number distribution
|
||||||
|
* @param pools a mapping created from the `NumberPool`s, to achieve synchronized access
|
||||||
|
* @param poolName the pool to which the object is trying to register
|
||||||
|
*/
|
||||||
|
def registrationProcessRetry(
|
||||||
|
promise: Promise[Any],
|
||||||
|
exception: Throwable,
|
||||||
|
obj: IdentifiableEntity,
|
||||||
|
hub: NumberPoolHub,
|
||||||
|
pools: Map[String, ActorRef],
|
||||||
|
poolName: String
|
||||||
|
): Unit = {
|
||||||
|
if (poolName.equals("generic")) {
|
||||||
|
promise.failure(new RegisteringException(msg = s"did not register entity $obj", exception))
|
||||||
|
} else {
|
||||||
|
org.log4s.getLogger("UniqueNumberOps").warn(s"${exception.getLocalizedMessage()} - $poolName")
|
||||||
|
promise.completeWith(registrationProcess(obj, guid, pools, poolName = "generic"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A step of the entity GUID unregistration process.
|
||||||
|
* Pass control through to the next step.
|
||||||
|
* @see `unregistrationProcess(IdentifiableEntity, NumberPoolHub, Map[String, ActorRef], Int, String)`
|
||||||
|
* @param obj the entity to be unassigned its GUID
|
||||||
|
* @param number the number that was previously drawn from the specified `NumberPool`
|
||||||
|
* @param poolName the pool to which the number will try to be returned
|
||||||
|
* @return the anticipation of this activity being completed
|
||||||
|
*/
|
||||||
|
private def unregistrationProcess(
|
||||||
|
obj: IdentifiableEntity,
|
||||||
|
number: Int,
|
||||||
|
poolName: String
|
||||||
|
): Future[Any] = {
|
||||||
|
unregistrationProcess(obj, guid, poolActors, number, poolName)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A step of the entity GUID unregistration process.
|
||||||
|
* ...
|
||||||
|
* @param obj the entity to be unassigned its GUID
|
||||||
|
* @param hub the supporting datatype for the unique number distribution
|
||||||
|
* @param pools a mapping created from the `NumberPool`s, to achieve synchronized access
|
||||||
|
* @param number the number that was previously drawn from the specified `NumberPool`
|
||||||
|
* @param poolName the pool to which the number will try to be returned
|
||||||
|
* @return the anticipation of this activity being completed
|
||||||
|
*/
|
||||||
|
private def unregistrationProcess(
|
||||||
|
obj: IdentifiableEntity,
|
||||||
|
hub: NumberPoolHub,
|
||||||
|
pools: Map[String, ActorRef],
|
||||||
|
number: Int,
|
||||||
|
poolName: String
|
||||||
|
): Future[Any] = {
|
||||||
|
val promisingResult: Promise[Any] = Promise()
|
||||||
|
pools.get(poolName) match {
|
||||||
|
case Some(pool) =>
|
||||||
|
//cache
|
||||||
|
val localPromise = promisingResult
|
||||||
|
val localTarget = obj
|
||||||
|
val localUns = hub
|
||||||
|
val localPoolName = poolName
|
||||||
|
val localPool = pool
|
||||||
|
val localNumber = number
|
||||||
|
|
||||||
|
val result = ask(pool, NumberPoolActor.ReturnNumber(number))
|
||||||
|
result.onComplete {
|
||||||
|
case Success(NumberPoolActor.ReturnNumberResult(_, None)) =>
|
||||||
|
UniqueNumberOps.processUnregisterResult(
|
||||||
|
localPromise,
|
||||||
|
localTarget,
|
||||||
|
localUns,
|
||||||
|
localPoolName,
|
||||||
|
localPool,
|
||||||
|
localNumber
|
||||||
|
)
|
||||||
|
case Success(NumberPoolActor.ReturnNumberResult(_, Some(ex))) => //if there is a problem when returning the number
|
||||||
|
localPromise.failure { new UnregisteringException(msg = s"could not unregister $localTarget with number $localNumber", ex) }
|
||||||
|
case msg =>
|
||||||
|
UniqueNumberOps.log.warn(s"unexpected message $msg during $localTarget's unregistration process")
|
||||||
|
}
|
||||||
|
result.recover {
|
||||||
|
case ex: AskTimeoutException =>
|
||||||
|
localPromise.failure { new UnregisteringException(msg = s"did not unregister entity $localTarget in time", ex) }
|
||||||
|
}
|
||||||
|
|
||||||
|
case None =>
|
||||||
|
//do not log; use callback
|
||||||
|
promisingResult.failure { new UnregisteringException(msg = s"can not find pool $poolName; $obj was not unregistered") }
|
||||||
|
}
|
||||||
|
promisingResult.future
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a relevant logging message for an object that is trying to register to this UNS
|
||||||
|
* but is actually already registered to this UNS.
|
||||||
|
* Also take note if the entity is (probably) not registered to this UNS.
|
||||||
|
* @param obj the object that was trying to register
|
||||||
|
* @param poolName the pool to which the object was trying to register
|
||||||
|
* @return the pool name to which the entity is registered, if it can be discovered
|
||||||
|
*/
|
||||||
|
private def alreadyRegisteredTo(obj: IdentifiableEntity, poolName: String): Option[String] = {
|
||||||
|
val (msg, determinedName) =
|
||||||
|
guid.WhichPool(obj) match {
|
||||||
|
case out @ Some(pname) =>
|
||||||
|
if (poolName.equals(pname)) {
|
||||||
|
(s"to pool $poolName", Some(poolName))
|
||||||
|
} else {
|
||||||
|
(s"but to different pool $pname", out)
|
||||||
|
}
|
||||||
|
case None =>
|
||||||
|
("but not to any pool known to this system", None)
|
||||||
|
}
|
||||||
|
UniqueNumberOps.log.warn(s"$obj already registered $msg")
|
||||||
|
determinedName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object UniqueNumberOps {
|
||||||
|
private val log = org.log4s.getLogger
|
||||||
|
private implicit val timeout = Timeout(2.seconds)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Final step of the object registration process.
|
||||||
|
* This step completes the registration by asking the `NumberPoolHub` to sort out its `NumberSource`.
|
||||||
|
* @param promise the ongoing promise to be fulfilled for the future
|
||||||
|
* @param obj the entity to be assigned a GUID
|
||||||
|
* @param guid the supporting datatype for the unique number distribution
|
||||||
|
* @param poolName the name of the pool to which the object is trying to register
|
||||||
|
* @param pool the pool to which the object is trying to register
|
||||||
|
* @param number the number that was drawn
|
||||||
|
*/
|
||||||
|
private def processRegisterResult(
|
||||||
|
promise: Promise[Any],
|
||||||
|
obj: IdentifiableEntity,
|
||||||
|
guid: NumberPoolHub,
|
||||||
|
poolName: String,
|
||||||
|
pool: ActorRef,
|
||||||
|
number: Int
|
||||||
|
): Unit = {
|
||||||
|
guid.latterPartRegister(obj, number) match {
|
||||||
|
case Success(_) =>
|
||||||
|
promise.success(RegisteredEntity(obj, poolName, guid, number))
|
||||||
|
case Failure(ex) =>
|
||||||
|
//do not log; use callback
|
||||||
|
returnNumberNoCallback(number, pool) //recovery?
|
||||||
|
promise.failure(ex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A step of the object unregistration process.
|
||||||
|
* This step completes revoking of the object's registration by consulting the `NumberSource`.
|
||||||
|
* @param promise the ongoing promise to be fulfilled for the future
|
||||||
|
* @param obj the entity to be unassigned its GUID
|
||||||
|
* @param guid the supporting datatype for the unique number distribution
|
||||||
|
* @param poolName the name of pool to which the number will try to be returned
|
||||||
|
* @param pool the pool to which the number will try to be returned
|
||||||
|
* @param number the number that was previously drawn from the specified `NumberPool`
|
||||||
|
*/
|
||||||
|
private def processUnregisterResult(
|
||||||
|
promise: Promise[Any],
|
||||||
|
obj: IdentifiableEntity,
|
||||||
|
guid: NumberPoolHub,
|
||||||
|
poolName: String,
|
||||||
|
pool: ActorRef,
|
||||||
|
number: Int
|
||||||
|
): Unit = {
|
||||||
|
guid.latterPartUnregister(number) match {
|
||||||
|
case Some(_) =>
|
||||||
|
obj.Invalidate()
|
||||||
|
promise.success(UnregisteredEntity(obj, poolName, guid, number))
|
||||||
|
case None =>
|
||||||
|
//do not log
|
||||||
|
requestSpecificNumberNoCallback(number, pool) //recovery?
|
||||||
|
promise.failure(new UnregisteringException(msg = s"failed to unregister $obj from number $number; this may be a critical error"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Access a specific `NumberPool` in a way that doesn't propagate a callback and reset one of its numbers.
|
||||||
|
* The `ask` pattern catches any reply message and ensures nothing happens because of it.
|
||||||
|
* @param number the number that was drawn from a `NumberPool`
|
||||||
|
* @param pool the `NumberPool` from which the `number` was drawn
|
||||||
|
*/
|
||||||
|
private def returnNumberNoCallback(number: Int, pool: ActorRef): Unit = {
|
||||||
|
val result = ask(pool, NumberPoolActor.ReturnNumber(number))
|
||||||
|
result.onComplete { _ => ; }
|
||||||
|
result.recover { case _ => ; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Access a specific `NumberPool` in a way that doesn't propagate a callback and claim one of its numbers.
|
||||||
|
* The `ask` pattern catches any reply message and ensures nothing happens because of it.
|
||||||
|
* @param number the number to be drawn from a `NumberPool`
|
||||||
|
* @param pool the `NumberPool` from which the `number` is to be drawn
|
||||||
|
*/
|
||||||
|
private def requestSpecificNumberNoCallback(number: Int, pool: ActorRef): Unit = {
|
||||||
|
val result = ask(pool, NumberPoolActor.GetSpecificNumber(number))
|
||||||
|
result.onComplete { _ => ; }
|
||||||
|
result.recover { case _ => ; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RegisteringException(msg: String) extends Exception(msg) {
|
||||||
|
def this(msg: String, cause: Throwable) = {
|
||||||
|
this(msg)
|
||||||
|
initCause(cause)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class UnregisteringException(msg: String) extends Exception(msg) {
|
||||||
|
def this(msg: String, cause: Throwable) = {
|
||||||
|
this(msg)
|
||||||
|
initCause(cause)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The entity was registered, but not to the target UNS.
|
||||||
|
* Rookie mistake.
|
||||||
|
* @param obj the entity to be assigned a GUID
|
||||||
|
* @param number the name associated with this entity
|
||||||
|
*/
|
||||||
|
class RegisteredToWrongPlaceException(obj: IdentifiableEntity, number: Int)
|
||||||
|
extends RuntimeException(s"$obj registered to number $number that is not part of a known or local number pool") {
|
||||||
|
|
||||||
|
def this(obj: IdentifiableEntity, number: Int, cause: Throwable) = {
|
||||||
|
this(obj, number)
|
||||||
|
initCause(cause)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class for spawning `Actor`s to manage the number pools and
|
||||||
|
* create a number system operations class to access those pools within the context of registering and unregistering.
|
||||||
|
* This `Actor` persists to maintain the number pool `Actor`s.
|
||||||
|
* Note the `final` do-nothing `receive` method.
|
||||||
|
* This `Actor` should do __nothing__ through message passing.
|
||||||
|
* @see `UniqueNumberOps`
|
||||||
|
* @param hub the number pool management class
|
||||||
|
* @param poolActorConversionFunc the number pool management class
|
||||||
|
*/
|
||||||
|
class UniqueNumberSetup(
|
||||||
|
hub: NumberPoolHub,
|
||||||
|
poolActorConversionFunc: (ActorContext, NumberPoolHub) => Map[String, ActorRef]
|
||||||
|
) extends Actor {
|
||||||
|
init()
|
||||||
|
|
||||||
|
final def receive: Receive = { case _ => ; }
|
||||||
|
|
||||||
|
def init(): UniqueNumberOps = {
|
||||||
|
new UniqueNumberOps(hub, poolActorConversionFunc(context, hub))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object UniqueNumberSetup {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform `NumberPool`s into `NumberPoolActor`s and pair them with their name.
|
||||||
|
* @param poolSource where the raw `NumberPools` are located
|
||||||
|
* @param context used to create the `NumberPoolActor` instances
|
||||||
|
* @return a `Map` of the pool names to the `ActorRef` created from the `NumberPool`
|
||||||
|
*/
|
||||||
|
def AllocateNumberPoolActors(context: ActorContext, poolSource: NumberPoolHub): Map[String, ActorRef] = {
|
||||||
|
poolSource.Pools
|
||||||
|
.map { case (pname, pool) => (pname, context.actorOf(Props(classOf[NumberPoolActor], pool), pname)) }
|
||||||
|
.toMap
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,86 +0,0 @@
|
||||||
// Copyright (c) 2017 PSForever
|
|
||||||
package net.psforever.objects.guid.actor
|
|
||||||
|
|
||||||
import akka.actor.ActorRef
|
|
||||||
import net.psforever.objects.entity.IdentifiableEntity
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A message for accepting object-number registration requests.<br>
|
|
||||||
* <br>
|
|
||||||
* The callback is actually an `ActorRef` to which a `RegisterSuccess` message or a `RegisterFailure` message is sent.
|
|
||||||
* This is as opposed to what a "callback" is normally - a function.
|
|
||||||
* @param obj the mandatory object
|
|
||||||
* @param name the optional name of the number pool to which this object is registered
|
|
||||||
* @param number the optional number pre-selected for registering this object
|
|
||||||
* @param callback the optional custom callback for the messages from the success or failure conditions
|
|
||||||
*/
|
|
||||||
final case class Register(
|
|
||||||
obj: IdentifiableEntity,
|
|
||||||
name: Option[String],
|
|
||||||
number: Option[Int],
|
|
||||||
callback: Option[ActorRef]
|
|
||||||
)
|
|
||||||
|
|
||||||
object Register {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Overloaded constructor, accepting just the object.
|
|
||||||
* @param obj the object to be registered
|
|
||||||
* @return a `Register` object
|
|
||||||
*/
|
|
||||||
def apply(obj: IdentifiableEntity): Register = {
|
|
||||||
new Register(obj, None, None, None)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Overloaded constructor, accepting the object and a callback.
|
|
||||||
* @param obj the object to be registered
|
|
||||||
* @param callback the custom callback for the messages from the success or failure conditions
|
|
||||||
* @return a `Register` object
|
|
||||||
*/
|
|
||||||
def apply(obj: IdentifiableEntity, callback: ActorRef): Register = {
|
|
||||||
new Register(obj, None, None, Some(callback))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Overloaded constructor, accepting an object and a pre-selected number.
|
|
||||||
* @param obj the object to be registered
|
|
||||||
* @param number the pre-selected number
|
|
||||||
* @return a `Register` object
|
|
||||||
*/
|
|
||||||
def apply(obj: IdentifiableEntity, number: Int): Register = {
|
|
||||||
new Register(obj, None, Some(number), None)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Overloaded constructor, accepting an object, a pre-selected number, and a callback.
|
|
||||||
* @param obj the object to be registered
|
|
||||||
* @param number the pre-selected number
|
|
||||||
* @param callback the custom callback for the messages from the success or failure conditions
|
|
||||||
* @return a `Register` object
|
|
||||||
*/
|
|
||||||
def apply(obj: IdentifiableEntity, number: Int, callback: ActorRef): Register = {
|
|
||||||
new Register(obj, None, Some(number), Some(callback))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Overloaded constructor, accepting an object and a number pool.
|
|
||||||
* @param obj the object to be registered
|
|
||||||
* @param name the number pool name
|
|
||||||
* @return a `Register` object
|
|
||||||
*/
|
|
||||||
def apply(obj: IdentifiableEntity, name: String): Register = {
|
|
||||||
new Register(obj, Some(name), None, None)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Overloaded constructor, accepting an object, a number pool, and a callback.
|
|
||||||
* @param obj the object to be registered
|
|
||||||
* @param name the number pool name
|
|
||||||
* @param callback the custom callback for the messages from the success or failure conditions
|
|
||||||
* @return a `Register` object
|
|
||||||
*/
|
|
||||||
def apply(obj: IdentifiableEntity, name: String, callback: ActorRef): Register = {
|
|
||||||
new Register(obj, Some(name), None, Some(callback))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,348 +0,0 @@
|
||||||
// Copyright (c) 2017 PSForever
|
|
||||||
package net.psforever.objects.guid.actor
|
|
||||||
|
|
||||||
import akka.actor.{Actor, ActorContext, ActorRef, Props}
|
|
||||||
import net.psforever.objects.entity.IdentifiableEntity
|
|
||||||
import net.psforever.objects.guid.NumberPoolHub
|
|
||||||
|
|
||||||
import scala.util.{Failure, Success}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An `Actor` that wraps around converted `NumberPool`s and synchronizes a portion of the number registration process.
|
|
||||||
* The ultimate goal is to manage a coherent group of unique identifiers for a given "region" (`Zone`).
|
|
||||||
* Both parts of the UID system sit atop the `Zone` for easy external access.
|
|
||||||
* The plain part - the `NumberPoolHub` here - is used for low-priority requests such as checking for existing associations.
|
|
||||||
* This `Actor` is the involved portion that paces registration and unregistration.<br>
|
|
||||||
* <br>
|
|
||||||
* A four part process is used for object registration tasks.
|
|
||||||
* First, the requested `NumberPool` is located among the list of known `NumberPool`s.
|
|
||||||
* Second, an asynchronous request is sent to that pool to retrieve a number.
|
|
||||||
* (Only any number. Only a failing case allows for selection of a specific number.)
|
|
||||||
* Third, the asynchronous request returns and the original information about the request is recovered.
|
|
||||||
* Fourth, both sides of the contract are completed by the object being assigned the number and
|
|
||||||
* the underlying "number source" is made to remember an association between the object and the number.
|
|
||||||
* Short circuits and recoveries as available on all steps though reporting is split between logging and callbacks.
|
|
||||||
* The process of removing the association between a number and object (unregistering) is a similar four part process.<br>
|
|
||||||
* <br>
|
|
||||||
* The important relationship between this `Actor` and the `Map` of `NumberPoolActors` is an "gate."
|
|
||||||
* A single `Map` is constructed and shared between multiple entry points to the UID system where requests are messaged.
|
|
||||||
* Multiple entry points send messages to the same `NumberPool`.
|
|
||||||
* That `NumberPool` deals with the messages one at a time and sends reply to each entry point that communicated with it.
|
|
||||||
* This process is almost as fast as the process of the `NumberPool` selecting a number.
|
|
||||||
* (At least, both should be fast.)
|
|
||||||
* @param guid the `NumberPoolHub` that is partially manipulated by this `Actor`
|
|
||||||
* @param poolActors a common mapping created from the `NumberPool`s in `guid`;
|
|
||||||
* there is currently no check for this condition save for requests failing
|
|
||||||
*/
|
|
||||||
class UniqueNumberSystem(private val guid: NumberPoolHub, private val poolActors: Map[String, ActorRef]) extends Actor {
|
|
||||||
|
|
||||||
/** Information about Register and Unregister requests that persists between messages to a specific `NumberPool`. */
|
|
||||||
private val requestQueue: collection.mutable.LongMap[UniqueNumberSystem.GUIDRequest] =
|
|
||||||
new collection.mutable.LongMap()
|
|
||||||
|
|
||||||
/** The current value for the next request entry's index. */
|
|
||||||
private var index: Long = Long.MinValue
|
|
||||||
private[this] val log = org.log4s.getLogger
|
|
||||||
|
|
||||||
def receive: Receive = {
|
|
||||||
case Register(obj, Some(pname), None, call) =>
|
|
||||||
val callback = call.getOrElse(sender())
|
|
||||||
if (obj.HasGUID) {
|
|
||||||
AlreadyRegistered(obj, pname)
|
|
||||||
callback ! Success(obj)
|
|
||||||
} else {
|
|
||||||
val id: Long = index
|
|
||||||
index += 1
|
|
||||||
requestQueue += id -> UniqueNumberSystem.GUIDRequest(obj, pname, callback)
|
|
||||||
RegistrationProcess(pname, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
//this message is automatically sent by NumberPoolActor
|
|
||||||
case NumberPoolActor.GiveNumber(number, id) =>
|
|
||||||
id match {
|
|
||||||
case Some(nid: Long) =>
|
|
||||||
RegistrationProcess(requestQueue.remove(nid), number, nid)
|
|
||||||
case _ =>
|
|
||||||
log.warn(s"received a number but there is no request to process it; returning number to pool")
|
|
||||||
NoCallbackReturnNumber(number) //recovery?
|
|
||||||
//no callback is possible
|
|
||||||
}
|
|
||||||
|
|
||||||
//this message is automatically sent by NumberPoolActor
|
|
||||||
case NumberPoolActor.NoNumber(ex, id) =>
|
|
||||||
id match {
|
|
||||||
case Some(nid: Long) =>
|
|
||||||
requestQueue.remove(nid) match {
|
|
||||||
case Some(entry) =>
|
|
||||||
entry.replyTo ! Failure(ex) //ONLY callback that is possible
|
|
||||||
case None => ;
|
|
||||||
log.warn(
|
|
||||||
s"failed number request and no record of number request - $ex"
|
|
||||||
) //neither a successful request nor an entry of making the request
|
|
||||||
}
|
|
||||||
case None => ;
|
|
||||||
log.warn(
|
|
||||||
s"failed number request and no record of number request - $ex"
|
|
||||||
) //neither a successful request nor an entry of making the request
|
|
||||||
case _ => ;
|
|
||||||
log.warn(s"unrecognized request $id accompanying a failed number request - $ex")
|
|
||||||
}
|
|
||||||
|
|
||||||
case Unregister(obj, call) =>
|
|
||||||
val callback = call.getOrElse(sender())
|
|
||||||
try {
|
|
||||||
val number = obj.GUID.guid
|
|
||||||
guid.WhichPool(number) match {
|
|
||||||
case Some(pname) =>
|
|
||||||
val id: Long = index
|
|
||||||
index += 1
|
|
||||||
requestQueue += id -> UniqueNumberSystem.GUIDRequest(obj, pname, callback)
|
|
||||||
UnregistrationProcess(pname, number, id)
|
|
||||||
case None =>
|
|
||||||
callback ! Failure(new Exception(s"the GUID of object $obj - $number - is not a part of this number pool"))
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
case _: Exception =>
|
|
||||||
log.warn(s"$obj is already unregistered")
|
|
||||||
callback ! Success(obj)
|
|
||||||
}
|
|
||||||
|
|
||||||
//this message is automatically sent by NumberPoolActor
|
|
||||||
case NumberPoolActor.ReturnNumberResult(number, None, id) =>
|
|
||||||
id match {
|
|
||||||
case Some(nid: Long) =>
|
|
||||||
UnregistrationProcess(requestQueue.remove(nid), number, nid)
|
|
||||||
case _ =>
|
|
||||||
log.error(s"returned a number but there is no request to process it; recovering the number from pool")
|
|
||||||
NoCallbackGetSpecificNumber(number) //recovery?
|
|
||||||
//no callback is possible
|
|
||||||
}
|
|
||||||
|
|
||||||
//this message is automatically sent by NumberPoolActor
|
|
||||||
case NumberPoolActor.ReturnNumberResult(number, Some(ex), id) => //if there is a problem when returning the number
|
|
||||||
id match {
|
|
||||||
case Some(nid: Long) =>
|
|
||||||
requestQueue.remove(nid) match {
|
|
||||||
case Some(entry) =>
|
|
||||||
entry.replyTo ! Failure(new Exception(s"for ${entry.target} with number $number, ${ex.getMessage}"))
|
|
||||||
case None => ;
|
|
||||||
log.error(s"could not find original request $nid that caused error $ex, but pool was ${sender()}")
|
|
||||||
//no callback is possible
|
|
||||||
}
|
|
||||||
case _ => ;
|
|
||||||
log.error(s"could not find original request $id that caused error $ex, but pool was ${sender()}")
|
|
||||||
//no callback is possible
|
|
||||||
}
|
|
||||||
|
|
||||||
case msg =>
|
|
||||||
log.warn(s"unexpected message received - $msg")
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A step of the object registration process.
|
|
||||||
* Send a message to the `NumberPool` to request a number back.
|
|
||||||
* @param poolName the pool to which the object is trying to register
|
|
||||||
* @param id a potential identifier to associate this request
|
|
||||||
*/
|
|
||||||
private def RegistrationProcess(poolName: String, id: Long): Unit = {
|
|
||||||
poolActors.get(poolName) match {
|
|
||||||
case Some(pool) =>
|
|
||||||
pool ! NumberPoolActor.GetAnyNumber(Some(id))
|
|
||||||
case None =>
|
|
||||||
//do not log; use callback
|
|
||||||
requestQueue.remove(id).get.replyTo ! Failure(
|
|
||||||
new Exception(s"can not find pool $poolName; nothing was registered")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A step of the object registration process.
|
|
||||||
* If there is a successful request object to be found, continue the registration request.
|
|
||||||
* @param request the original request data
|
|
||||||
* @param number the number that was drawn from a `NumberPool`
|
|
||||||
*/
|
|
||||||
private def RegistrationProcess(request: Option[UniqueNumberSystem.GUIDRequest], number: Int, id: Long): Unit = {
|
|
||||||
request match {
|
|
||||||
case Some(entry) =>
|
|
||||||
processRegisterResult(entry, number)
|
|
||||||
case None =>
|
|
||||||
log.error(s"returned a number but the rest of the request is missing (id:$id)")
|
|
||||||
if (id != Long.MinValue) { //check to ignore endless loop of error-catching
|
|
||||||
log.warn("returning number to pool")
|
|
||||||
NoCallbackReturnNumber(number) //recovery?
|
|
||||||
//no callback is possible
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A step of the object registration process.
|
|
||||||
* This step completes the registration by asking the `NumberPoolHub` to sort out its `NumberSource`.
|
|
||||||
* @param entry the original request data
|
|
||||||
* @param number the number to use
|
|
||||||
*/
|
|
||||||
private def processRegisterResult(entry: UniqueNumberSystem.GUIDRequest, number: Int): Unit = {
|
|
||||||
val obj = entry.target
|
|
||||||
guid.latterPartRegister(obj, number) match {
|
|
||||||
case Success(_) =>
|
|
||||||
entry.replyTo ! Success(obj)
|
|
||||||
case Failure(ex) =>
|
|
||||||
//do not log; use callback
|
|
||||||
NoCallbackReturnNumber(number, entry.targetPool) //recovery?
|
|
||||||
entry.replyTo ! Failure(ex)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A step of the object unregistration process.
|
|
||||||
* Send a message to the `NumberPool` to restore the availability of one of its numbers.
|
|
||||||
* @param poolName the pool to which the number will try to be returned
|
|
||||||
* @param number the number that was previously drawn from the specified `NumberPool`
|
|
||||||
* @param id a potential identifier to associate this request
|
|
||||||
*/
|
|
||||||
private def UnregistrationProcess(poolName: String, number: Int, id: Long): Unit = {
|
|
||||||
poolActors.get(poolName) match {
|
|
||||||
case Some(pool) =>
|
|
||||||
pool ! NumberPoolActor.ReturnNumber(number, Some(id))
|
|
||||||
case None =>
|
|
||||||
//do not log; use callback
|
|
||||||
requestQueue.remove(id).get.replyTo ! Failure(
|
|
||||||
new Exception(s"can not find pool $poolName; nothing was de-registered")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A step of the object unregistration process.
|
|
||||||
* If there is a successful request object to be found, continue the registration request.
|
|
||||||
* @param request the original request data
|
|
||||||
* @param number the number that was drawn from the `NumberPool`
|
|
||||||
*/
|
|
||||||
private def UnregistrationProcess(request: Option[UniqueNumberSystem.GUIDRequest], number: Int, id: Long): Unit = {
|
|
||||||
request match {
|
|
||||||
case Some(entry) =>
|
|
||||||
processUnregisterResult(entry, number)
|
|
||||||
case None =>
|
|
||||||
log.error(s"returned a number but the rest of the request is missing (id:$id)")
|
|
||||||
if (id != Long.MinValue) { //check to ignore endless loop of error-catching
|
|
||||||
log.error("recovering the number from pool")
|
|
||||||
NoCallbackGetSpecificNumber(number) //recovery?
|
|
||||||
//no callback is possible
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A step of the object unregistration process.
|
|
||||||
* This step completes revoking of the object's registration by consulting the `NumberSource`.
|
|
||||||
* @param entry the original request data
|
|
||||||
* @param number the number to use
|
|
||||||
*/
|
|
||||||
private def processUnregisterResult(entry: UniqueNumberSystem.GUIDRequest, number: Int): Unit = {
|
|
||||||
val obj = entry.target
|
|
||||||
guid.latterPartUnregister(number) match {
|
|
||||||
case Some(_) =>
|
|
||||||
obj.Invalidate()
|
|
||||||
entry.replyTo ! Success(obj)
|
|
||||||
case None =>
|
|
||||||
//do not log; use callback
|
|
||||||
NoCallbackGetSpecificNumber(number, entry.targetPool) //recovery?
|
|
||||||
entry.replyTo ! Failure(new Exception(s"failed to unregister a number; this may be a critical error"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate a relevant logging message for an object that is trying to register is actually already registered.
|
|
||||||
* @param obj the object that was trying to register
|
|
||||||
* @param poolName the pool to which the object was trying to register
|
|
||||||
*/
|
|
||||||
private def AlreadyRegistered(obj: IdentifiableEntity, poolName: String): Unit = {
|
|
||||||
val msg =
|
|
||||||
guid.WhichPool(obj) match {
|
|
||||||
case Some(pname) =>
|
|
||||||
if (poolName.equals(pname)) {
|
|
||||||
s"to pool $poolName"
|
|
||||||
} else {
|
|
||||||
s"but to different pool $pname"
|
|
||||||
}
|
|
||||||
case None =>
|
|
||||||
"but not to any pool known to this system"
|
|
||||||
}
|
|
||||||
log.warn(s"$obj already registered $msg")
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Access a specific `NumberPool` in a way that doesn't invoke a callback and reset one of its numbers.
|
|
||||||
* @param number the number that was drawn from a `NumberPool`
|
|
||||||
*/
|
|
||||||
private def NoCallbackReturnNumber(number: Int): Unit = {
|
|
||||||
guid.WhichPool(number) match {
|
|
||||||
case Some(pname) =>
|
|
||||||
NoCallbackReturnNumber(number, pname)
|
|
||||||
case None =>
|
|
||||||
log.error(s"critical: tried to return number $number but could not find containing pool")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Access a specific `NumberPool` in a way that doesn't invoke a callback and reset one of its numbers.
|
|
||||||
* To avoid fully processing the callback, an id of `Long.MinValue` is used to short circuit the routine.
|
|
||||||
* @param number the number that was drawn from a `NumberPool`
|
|
||||||
* @param poolName the `NumberPool` from which the `number` was drawn
|
|
||||||
* @see `UniqueNumberSystem.UnregistrationProcess(Option[GUIDRequest], Int, Int)`
|
|
||||||
*/
|
|
||||||
private def NoCallbackReturnNumber(number: Int, poolName: String): Unit = {
|
|
||||||
poolActors(poolName) ! NumberPoolActor.ReturnNumber(number, Some(Long.MinValue))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Access a specific `NumberPool` in a way that doesn't invoke a callback and claim one of its numbers.
|
|
||||||
* @param number the number to be drawn from a `NumberPool`
|
|
||||||
*/
|
|
||||||
private def NoCallbackGetSpecificNumber(number: Int): Unit = {
|
|
||||||
guid.WhichPool(number) match {
|
|
||||||
case Some(pname) =>
|
|
||||||
NoCallbackGetSpecificNumber(number, pname)
|
|
||||||
case None =>
|
|
||||||
log.error(s"critical: tried to re-register number $number but could not find containing pool")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Access a specific `NumberPool` in a way that doesn't invoke a callback and claim one of its numbers.
|
|
||||||
* To avoid fully processing the callback, an id of `Long.MinValue` is used to short circuit the routine.
|
|
||||||
* @param number the number to be drawn from a `NumberPool`
|
|
||||||
* @param poolName the `NumberPool` from which the `number` is to be drawn
|
|
||||||
* @see `UniqueNumberSystem.RegistrationProcess(Option[GUIDRequest], Int, Int)`
|
|
||||||
*/
|
|
||||||
private def NoCallbackGetSpecificNumber(number: Int, poolName: String): Unit = {
|
|
||||||
poolActors(poolName) ! NumberPoolActor.GetSpecificNumber(number, Some(Long.MinValue))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
object UniqueNumberSystem {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Persistent record of the important information between the time fo request and the time of reply.
|
|
||||||
* @param target the object
|
|
||||||
* @param targetPool the name of the `NumberPool` being used
|
|
||||||
* @param replyTo the callback `ActorRef`
|
|
||||||
*/
|
|
||||||
private final case class GUIDRequest(target: IdentifiableEntity, targetPool: String, replyTo: ActorRef)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Transform `NumberPool`s into `NumberPoolActor`s and pair them with their name.
|
|
||||||
* @param poolSource where the raw `NumberPools` are located
|
|
||||||
* @param context used to create the `NumberPoolActor` instances
|
|
||||||
* @return a `Map` of the pool names to the `ActorRef` created from the `NumberPool`
|
|
||||||
*/
|
|
||||||
def AllocateNumberPoolActors(poolSource: NumberPoolHub)(implicit context: ActorContext): Map[String, ActorRef] = {
|
|
||||||
poolSource.Pools
|
|
||||||
.map({
|
|
||||||
case ((pname, pool)) =>
|
|
||||||
pname -> context.actorOf(Props(classOf[NumberPoolActor], pool), pname)
|
|
||||||
})
|
|
||||||
.toMap
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
// Copyright (c) 2017 PSForever
|
|
||||||
package net.psforever.objects.guid.actor
|
|
||||||
|
|
||||||
import akka.actor.ActorRef
|
|
||||||
import net.psforever.objects.entity.IdentifiableEntity
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A message for accepting object-number unregistration requests.
|
|
||||||
* When given to a number pool (`NumberPoolAccessorActor`), that `Actor` assumes itself to have the object.
|
|
||||||
* When given to a hub object (`NumberPoolHubActor`), it will attempt to determine which pool currently has the object.<br>
|
|
||||||
* <br>
|
|
||||||
* The callback is actually an `ActorRef` to which a `RegisterSuccess` message or a `RegisterFailure` message is sent.
|
|
||||||
* This is as opposed to what a "callback" is normally - a function.
|
|
||||||
* @param obj the mandatory object
|
|
||||||
* @param callback the optional custom callback for the messages from the success or failure conditions
|
|
||||||
*/
|
|
||||||
final case class Unregister(obj: IdentifiableEntity, callback: Option[ActorRef] = None)
|
|
||||||
|
|
||||||
object Unregister {
|
|
||||||
def apply(obj: IdentifiableEntity, callback: ActorRef): Unregister = {
|
|
||||||
Unregister(obj, Some(callback))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
// Copyright (c) 2017 PSForever
|
||||||
|
package net.psforever.objects.guid.key
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The availability of individual global unique identifier (GUID) keys is maintained by the given policy.
|
||||||
|
*/
|
||||||
|
sealed trait AvailabilityPolicy
|
||||||
|
|
||||||
|
object AvailabilityPolicy {
|
||||||
|
/**An `Available` key is ready and waiting to be `Leased` for use. */
|
||||||
|
case object Available extends AvailabilityPolicy
|
||||||
|
|
||||||
|
/** A `Leased` key has been issued and is currently being used for some purpose.*/
|
||||||
|
case object Leased extends AvailabilityPolicy
|
||||||
|
|
||||||
|
/** A `Dangling` key ia a unique sort of key that has been `Leased` but has not yet been applied for any specific purpose.
|
||||||
|
* As a policy, it should be used as a status to check but should not be designated on any key. */
|
||||||
|
case object Dangling extends AvailabilityPolicy
|
||||||
|
}
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
package net.psforever.objects.guid.key
|
package net.psforever.objects.guid.key
|
||||||
|
|
||||||
import net.psforever.objects.entity.IdentifiableEntity
|
import net.psforever.objects.entity.IdentifiableEntity
|
||||||
import net.psforever.objects.guid.AvailabilityPolicy
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The only indirect public access a queued number monitor object (`Key`) is allowed.
|
* The only indirect public access a queued number monitor object (`Key`) is allowed.
|
||||||
|
|
@ -12,7 +11,7 @@ import net.psforever.objects.guid.AvailabilityPolicy
|
||||||
class LoanedKey(private val guid: Int, private val key: Monitor) {
|
class LoanedKey(private val guid: Int, private val key: Monitor) {
|
||||||
def GUID: Int = guid
|
def GUID: Int = guid
|
||||||
|
|
||||||
def Policy: AvailabilityPolicy.Value = key.policy
|
def Policy: AvailabilityPolicy = key.policy
|
||||||
|
|
||||||
def Object: Option[IdentifiableEntity] = key.obj
|
def Object: Option[IdentifiableEntity] = key.obj
|
||||||
|
|
||||||
|
|
@ -29,9 +28,7 @@ class LoanedKey(private val guid: Int, private val key: Monitor) {
|
||||||
* @return `true`, if the assignment worked; `false`, otherwise
|
* @return `true`, if the assignment worked; `false`, otherwise
|
||||||
*/
|
*/
|
||||||
def Object_=(obj: Option[IdentifiableEntity]): Option[IdentifiableEntity] = {
|
def Object_=(obj: Option[IdentifiableEntity]): Option[IdentifiableEntity] = {
|
||||||
if (
|
if (key.policy == AvailabilityPolicy.Leased) {
|
||||||
key.policy == AvailabilityPolicy.Leased || (key.policy == AvailabilityPolicy.Restricted && key.obj.isEmpty)
|
|
||||||
) {
|
|
||||||
if (key.obj.isDefined) {
|
if (key.obj.isDefined) {
|
||||||
key.obj.get.Invalidate()
|
key.obj.get.Invalidate()
|
||||||
key.obj = None
|
key.obj = None
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,9 @@
|
||||||
package net.psforever.objects.guid.key
|
package net.psforever.objects.guid.key
|
||||||
|
|
||||||
import net.psforever.objects.entity.IdentifiableEntity
|
import net.psforever.objects.entity.IdentifiableEntity
|
||||||
import net.psforever.objects.guid.AvailabilityPolicy
|
|
||||||
|
|
||||||
trait Monitor {
|
trait Monitor {
|
||||||
var policy: AvailabilityPolicy.Value
|
var policy: AvailabilityPolicy
|
||||||
|
|
||||||
var obj: Option[IdentifiableEntity]
|
var obj: Option[IdentifiableEntity]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
// Copyright (c) 2017 PSForever
|
// Copyright (c) 2017 PSForever
|
||||||
package net.psforever.objects.guid.key
|
package net.psforever.objects.guid.key
|
||||||
|
|
||||||
import net.psforever.objects.guid.AvailabilityPolicy
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An unmodifiable reference to an active number monitor object (`Key`).
|
* An unmodifiable reference to an active number monitor object (`Key`).
|
||||||
* @param guid the number (globally unique identifier)
|
* @param guid the number (globally unique identifier)
|
||||||
|
|
@ -11,7 +9,7 @@ import net.psforever.objects.guid.AvailabilityPolicy
|
||||||
final class SecureKey(private val guid: Int, private val key: Monitor) {
|
final class SecureKey(private val guid: Int, private val key: Monitor) {
|
||||||
def GUID: Int = guid
|
def GUID: Int = guid
|
||||||
|
|
||||||
def Policy: AvailabilityPolicy.Value = key.policy
|
def Policy: AvailabilityPolicy = key.policy
|
||||||
|
|
||||||
import net.psforever.objects.entity.IdentifiableEntity
|
import net.psforever.objects.entity.IdentifiableEntity
|
||||||
def Object: Option[IdentifiableEntity] = key.obj
|
def Object: Option[IdentifiableEntity] = key.obj
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,13 @@ import net.psforever.objects.guid.selector.{NumberSelector, SpecificSelector}
|
||||||
import scala.collection.mutable
|
import scala.collection.mutable
|
||||||
import scala.util.{Failure, Success, Try}
|
import scala.util.{Failure, Success, Try}
|
||||||
|
|
||||||
class GenericPool(private val hub: mutable.LongMap[String], private val max: Int) extends NumberPool {
|
class GenericPool(
|
||||||
val numbers: mutable.ListBuffer[Int] = mutable.ListBuffer[Int]()
|
private val hub: mutable.LongMap[String],
|
||||||
|
private val max: Int,
|
||||||
|
private val poolName: String,
|
||||||
|
private val selectionFunc: (List[Long], Int) => Int
|
||||||
|
) extends NumberPool {
|
||||||
|
private val numbers: mutable.ListBuffer[Int] = mutable.ListBuffer[Int]()
|
||||||
private val selector: SpecificSelector = new SpecificSelector
|
private val selector: SpecificSelector = new SpecificSelector
|
||||||
selector.SelectionIndex = -1
|
selector.SelectionIndex = -1
|
||||||
|
|
||||||
|
|
@ -17,22 +22,26 @@ class GenericPool(private val hub: mutable.LongMap[String], private val max: Int
|
||||||
|
|
||||||
def Selector: NumberSelector = selector
|
def Selector: NumberSelector = selector
|
||||||
|
|
||||||
def Selector_=(slctr: NumberSelector): Unit = {} //intentionally blank
|
def Selector_=(slctr: NumberSelector): Unit = { /* intentionally blank */ }
|
||||||
|
|
||||||
def Get(): Try[Int] = {
|
def Get(): Try[Int] = {
|
||||||
val specific = selector.SelectionIndex
|
val specific = selector.SelectionIndex
|
||||||
selector.SelectionIndex = -1 //clear
|
selector.SelectionIndex = -1 //clear
|
||||||
if (specific == -1) {
|
if (specific == -1) {
|
||||||
val number = GenericPool.rand(hub.keys.toList, max)
|
val number = selectionFunc(hub.keys.toList, max)
|
||||||
hub += number.toLong -> "generic"
|
if (number > -1) {
|
||||||
numbers += number
|
hub += number.toLong -> poolName
|
||||||
Success(number)
|
numbers += number
|
||||||
|
Success(number)
|
||||||
|
} else {
|
||||||
|
Failure(new Exception("no numbers available in this pool"))
|
||||||
|
}
|
||||||
} else if (hub.get(specific).isEmpty) {
|
} else if (hub.get(specific).isEmpty) {
|
||||||
hub += specific.toLong -> "generic"
|
hub += specific.toLong -> poolName
|
||||||
numbers += specific
|
numbers += specific
|
||||||
Success(specific)
|
Success(specific)
|
||||||
} else {
|
} else {
|
||||||
Failure(new Exception("selector was not initialized properly, or no numbers available in the pool"))
|
Failure(new Exception("selector may not have been initialized properly"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -49,6 +58,43 @@ class GenericPool(private val hub: mutable.LongMap[String], private val max: Int
|
||||||
}
|
}
|
||||||
|
|
||||||
object GenericPool {
|
object GenericPool {
|
||||||
|
/**
|
||||||
|
* Overloaded constructor that assigns a "numerical first discovery" function for number selection.
|
||||||
|
* @param hub na
|
||||||
|
* @param max na
|
||||||
|
* @param poolName na
|
||||||
|
* @return a `GenericPool` entity
|
||||||
|
*/
|
||||||
|
def apply(
|
||||||
|
hub: mutable.LongMap[String],
|
||||||
|
max: Int,
|
||||||
|
poolName: String
|
||||||
|
): GenericPool =
|
||||||
|
new GenericPool(hub, max, poolName, GenericPool.first)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get some number that is not accounted for in any other fixed pool, making it available in this generic one.<br>
|
||||||
|
* <br>
|
||||||
|
* Returns the first number that is detected as available between two sorted numbers.
|
||||||
|
* @param list all of the non-repeating numbers to be compared
|
||||||
|
* @param domainSize how many numbers can be supported
|
||||||
|
* @return the next available number, or -1
|
||||||
|
*/
|
||||||
|
def first(list: List[Long], domainSize: Int): Int = {
|
||||||
|
if (list.size < domainSize) {
|
||||||
|
val sortedList: List[Long] = 0L +: list.sorted :+ domainSize
|
||||||
|
var index: Int = 0
|
||||||
|
val listLen = sortedList.length - 1
|
||||||
|
while(index < listLen && index < domainSize) {
|
||||||
|
val curr = sortedList(index + 1) - sortedList(index)
|
||||||
|
if (curr > 1) {
|
||||||
|
return sortedList(index).toInt + 1
|
||||||
|
}
|
||||||
|
index += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
-1
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get some number that is not accounted for in any other fixed pool, making it available in this generic one.<br>
|
* Get some number that is not accounted for in any other fixed pool, making it available in this generic one.<br>
|
||||||
|
|
@ -63,7 +109,7 @@ object GenericPool {
|
||||||
* @param domainSize how many numbers can be supported
|
* @param domainSize how many numbers can be supported
|
||||||
* @return midpoint of the largest distance between any two of the existing numbers, or -1
|
* @return midpoint of the largest distance between any two of the existing numbers, or -1
|
||||||
*/
|
*/
|
||||||
private def rand(list: List[Long], domainSize: Int): Int = {
|
def rand(list: List[Long], domainSize: Int): Int = {
|
||||||
if (list.size < domainSize) {
|
if (list.size < domainSize) {
|
||||||
//get a list of all assigned numbers with an appended min and max
|
//get a list of all assigned numbers with an appended min and max
|
||||||
val sortedList: List[Long] = -1L +: list.sorted :+ domainSize.toLong
|
val sortedList: List[Long] = -1L +: list.sorted :+ domainSize.toLong
|
||||||
|
|
@ -78,7 +124,8 @@ object GenericPool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//find half of the distance between the two numbers with the greatest delta value
|
//find half of the distance between the two numbers with the greatest delta value
|
||||||
if (maxDelta > 1) { ((sortedList(maxDeltaIndex + 1) + sortedList(maxDeltaIndex)) / 2f).toInt }
|
if (maxDelta == 2) { sortedList(maxDeltaIndex).toInt + 1 }
|
||||||
|
else if (maxDelta > 1) { ((sortedList(maxDeltaIndex + 1) + sortedList(maxDeltaIndex)) / 2f).toInt }
|
||||||
else { -1 }
|
else { -1 }
|
||||||
} else {
|
} else {
|
||||||
-1
|
-1
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,10 @@
|
||||||
package net.psforever.objects.guid.source
|
package net.psforever.objects.guid.source
|
||||||
|
|
||||||
import net.psforever.objects.entity.IdentifiableEntity
|
import net.psforever.objects.entity.IdentifiableEntity
|
||||||
import net.psforever.objects.guid.AvailabilityPolicy
|
import net.psforever.objects.guid.key.{AvailabilityPolicy, Monitor}
|
||||||
import net.psforever.objects.guid.key.Monitor
|
|
||||||
|
|
||||||
private class Key extends Monitor {
|
private class Key extends Monitor {
|
||||||
var policy: AvailabilityPolicy.Value = AvailabilityPolicy.Available
|
var policy: AvailabilityPolicy = AvailabilityPolicy.Available
|
||||||
|
|
||||||
var obj: Option[IdentifiableEntity] = None
|
var obj: Option[IdentifiableEntity] = None
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,7 @@
|
||||||
package net.psforever.objects.guid.source
|
package net.psforever.objects.guid.source
|
||||||
|
|
||||||
import net.psforever.objects.entity.IdentifiableEntity
|
import net.psforever.objects.entity.IdentifiableEntity
|
||||||
import net.psforever.objects.guid.key.{LoanedKey, SecureKey}
|
import net.psforever.objects.guid.key.{AvailabilityPolicy, 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.
|
* A `NumberSource` is considered a master "pool" of numbers from which all numbers are available to be drawn.
|
||||||
|
|
@ -18,13 +17,14 @@ class MaxNumberSource(val max: Int) extends NumberSource {
|
||||||
}
|
}
|
||||||
private val ary: Array[Key] = Array.ofDim[Key](max + 1)
|
private val ary: Array[Key] = Array.ofDim[Key](max + 1)
|
||||||
(0 to max).foreach(x => { ary(x) = new Key })
|
(0 to max).foreach(x => { ary(x) = new Key })
|
||||||
private var allowRestrictions: Boolean = true
|
|
||||||
|
|
||||||
def size: Int = ary.length
|
def size: Int = ary.length
|
||||||
|
|
||||||
def countAvailable: Int = ary.count(key => key.policy == AvailabilityPolicy.Available)
|
def countAvailable: Int = ary.count { _.policy == AvailabilityPolicy.Available }
|
||||||
|
|
||||||
def countUsed: Int = ary.count(_.policy != AvailabilityPolicy.Available)
|
def countUsed: Int = ary.count { _.policy == AvailabilityPolicy.Leased }
|
||||||
|
|
||||||
|
def countDangling: Int = ary.count { key => key.policy == AvailabilityPolicy.Leased && key.obj.isEmpty }
|
||||||
|
|
||||||
def test(number: Int): Boolean = -1 < number && number < size
|
def test(number: Int): Boolean = -1 < number && number < size
|
||||||
|
|
||||||
|
|
@ -78,39 +78,14 @@ class MaxNumberSource(val max: Int) extends NumberSource {
|
||||||
out
|
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def finalizeRestrictions: List[Int] = {
|
|
||||||
allowRestrictions = false
|
|
||||||
ary.zipWithIndex.filter(entry => entry._1.policy == AvailabilityPolicy.Restricted).map(entry => entry._2).toList
|
|
||||||
}
|
|
||||||
|
|
||||||
def clear(): List[IdentifiableEntity] = {
|
def clear(): List[IdentifiableEntity] = {
|
||||||
val leased = ary.filter(_.policy != AvailabilityPolicy.Available)
|
ary.foreach { _.policy = AvailabilityPolicy.Available }
|
||||||
leased collect { case key if key.obj.isEmpty =>
|
ary.collect {
|
||||||
key.policy = AvailabilityPolicy.Available
|
case key if key.obj.nonEmpty =>
|
||||||
}
|
val obj = key.obj.get
|
||||||
leased.toList collect { case key if key.obj.nonEmpty =>
|
key.obj = None
|
||||||
key.policy = AvailabilityPolicy.Available
|
obj
|
||||||
val out = key.obj.get
|
}.toList
|
||||||
key.obj = None
|
|
||||||
out
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
package net.psforever.objects.guid.source
|
package net.psforever.objects.guid.source
|
||||||
|
|
||||||
import net.psforever.objects.entity.IdentifiableEntity
|
import net.psforever.objects.entity.IdentifiableEntity
|
||||||
import net.psforever.objects.guid.key.{LoanedKey, SecureKey}
|
import net.psforever.objects.guid.key.{AvailabilityPolicy, LoanedKey, SecureKey}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A `NumberSource` is considered a master "pool" of numbers from which all numbers are available to be drawn.
|
* A `NumberSource` is considered a master "pool" of numbers from which all numbers are available to be drawn.
|
||||||
|
|
@ -30,6 +30,20 @@ trait NumberSource {
|
||||||
*/
|
*/
|
||||||
def size: Int
|
def size: Int
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Select the type of count desired based on the allocation policy of the key.
|
||||||
|
* @param policy the allocation policy
|
||||||
|
* @return the number of keys belonging to this policy
|
||||||
|
*/
|
||||||
|
def count(policy: AvailabilityPolicy): Int = {
|
||||||
|
policy match {
|
||||||
|
case AvailabilityPolicy.Available => countAvailable
|
||||||
|
case AvailabilityPolicy.Leased => countUsed
|
||||||
|
case AvailabilityPolicy.Dangling => countDangling
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The count of numbers that can still be drawn.
|
* The count of numbers that can still be drawn.
|
||||||
* @return the count
|
* @return the count
|
||||||
|
|
@ -42,6 +56,13 @@ trait NumberSource {
|
||||||
*/
|
*/
|
||||||
def countUsed: Int
|
def countUsed: Int
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The count of numbers that can not be drawn but have not yet been assigned to an entity.
|
||||||
|
* Could only ever be a non-zero count if the number of used keys is a non-zero count.
|
||||||
|
* @return the count
|
||||||
|
*/
|
||||||
|
def countDangling: Int
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Is this number a member of this number source?
|
* Is this number a member of this number source?
|
||||||
* @param number the number
|
* @param number the number
|
||||||
|
|
@ -97,26 +118,9 @@ trait NumberSource {
|
||||||
*/
|
*/
|
||||||
def returnNumber(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 `returnNumber`ed to this source.
|
|
||||||
* @param number the number
|
|
||||||
* @return the wrapped `Monitor`
|
|
||||||
*/
|
|
||||||
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]
|
|
||||||
|
|
||||||
import net.psforever.objects.entity.IdentifiableEntity
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reset all number `Monitor`s so that their underlying number is not longer treated as assigned.
|
* Reset all number `Monitor`s so that their underlying number is not longer treated as assigned.
|
||||||
* Perform some level of housecleaning to ensure that all dependencies are resolved in some manner.
|
* Perform some level of housecleaning to ensure that all dependencies are resolved in some manner.
|
||||||
* 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`
|
* @return a `List` of assignments maintained by all the currently-used number `Monitors`
|
||||||
*/
|
*/
|
||||||
def clear(): List[IdentifiableEntity]
|
def clear(): List[IdentifiableEntity]
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,7 @@
|
||||||
package net.psforever.objects.guid.source
|
package net.psforever.objects.guid.source
|
||||||
|
|
||||||
import net.psforever.objects.entity.IdentifiableEntity
|
import net.psforever.objects.entity.IdentifiableEntity
|
||||||
import net.psforever.objects.guid.AvailabilityPolicy
|
import net.psforever.objects.guid.key.{AvailabilityPolicy, LoanedKey, SecureKey}
|
||||||
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.
|
* A `NumberSource` is considered a master "pool" of numbers from which all numbers are available to be drawn.
|
||||||
|
|
@ -27,9 +26,11 @@ class SpecificNumberSource(values: Iterable[Int]) extends NumberSource {
|
||||||
|
|
||||||
def size : Int = ary.size
|
def size : Int = ary.size
|
||||||
|
|
||||||
def countAvailable : Int = ary.values.count(key => key.policy == AvailabilityPolicy.Available)
|
def countAvailable : Int = ary.values.count { _.policy == AvailabilityPolicy.Available }
|
||||||
|
|
||||||
def countUsed : Int = ary.values.count(_.policy != AvailabilityPolicy.Available)
|
def countUsed : Int = ary.values.count { _.policy == AvailabilityPolicy.Leased }
|
||||||
|
|
||||||
|
def countDangling: Int = ary.values.count { key => key.policy == AvailabilityPolicy.Leased && key.obj.isEmpty }
|
||||||
|
|
||||||
def test(number : Int) : Boolean = ary.get(number).nonEmpty
|
def test(number : Int) : Boolean = ary.get(number).nonEmpty
|
||||||
|
|
||||||
|
|
@ -74,34 +75,14 @@ class SpecificNumberSource(values: Iterable[Int]) extends NumberSource {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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] = {
|
def clear(): List[IdentifiableEntity] = {
|
||||||
val leased = ary.values.filter(_.policy != AvailabilityPolicy.Available)
|
ary.values.foreach { _.policy = AvailabilityPolicy.Available }
|
||||||
leased collect { case key if key.obj.isEmpty =>
|
ary.values.collect {
|
||||||
key.policy = AvailabilityPolicy.Available
|
case key if key.obj.nonEmpty =>
|
||||||
}
|
val obj = key.obj.get
|
||||||
leased.toList collect { case key if key.obj.nonEmpty =>
|
key.obj = None
|
||||||
key.policy = AvailabilityPolicy.Available
|
obj
|
||||||
val out = key.obj.get
|
}.toList
|
||||||
key.obj = None
|
|
||||||
out
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
// Copyright (c) 2017 PSForever
|
// Copyright (c) 2017 PSForever
|
||||||
package net.psforever.objects.guid.actor
|
package net.psforever.objects.guid.uns
|
||||||
|
|
||||||
import akka.actor.Actor
|
import akka.actor.Actor
|
||||||
import net.psforever.objects.guid.pool.NumberPool
|
import net.psforever.objects.guid.pool.NumberPool
|
||||||
|
|
@ -21,27 +21,27 @@ class NumberPoolActor(pool: NumberPool) extends Actor {
|
||||||
private[this] val log = org.log4s.getLogger
|
private[this] val log = org.log4s.getLogger
|
||||||
|
|
||||||
def receive: Receive = {
|
def receive: Receive = {
|
||||||
case NumberPoolActor.GetAnyNumber(id) =>
|
case NumberPoolActor.GetAnyNumber() =>
|
||||||
sender() ! (pool.Get() match {
|
sender() ! (pool.Get() match {
|
||||||
case Success(value) =>
|
case Success(value) =>
|
||||||
NumberPoolActor.GiveNumber(value, id)
|
NumberPoolActor.GiveNumber(value)
|
||||||
case Failure(ex) => ;
|
case Failure(ex) =>
|
||||||
NumberPoolActor.NoNumber(ex, id)
|
NumberPoolActor.NoNumber(ex)
|
||||||
})
|
})
|
||||||
|
|
||||||
case NumberPoolActor.GetSpecificNumber(number, id) =>
|
case NumberPoolActor.GetSpecificNumber(number) =>
|
||||||
sender() ! (NumberPoolActor.GetSpecificNumber(pool, number) match {
|
sender() ! (NumberPoolActor.GetSpecificNumber(pool, number) match {
|
||||||
case Success(value) =>
|
case Success(value) =>
|
||||||
NumberPoolActor.GiveNumber(value, id)
|
NumberPoolActor.GiveNumber(value)
|
||||||
case Failure(ex) => ;
|
case Failure(ex) => ;
|
||||||
NumberPoolActor.NoNumber(ex, id)
|
NumberPoolActor.NoNumber(ex)
|
||||||
})
|
})
|
||||||
|
|
||||||
case NumberPoolActor.ReturnNumber(number, id) =>
|
case NumberPoolActor.ReturnNumber(number) =>
|
||||||
val result = pool.Return(number)
|
val result = pool.Return(number)
|
||||||
val ex: Option[Throwable] = if (!result) { Some(new Exception("number was not returned")) }
|
val ex: Option[Throwable] = if (!result) { Some(new Exception("number was not returned")) }
|
||||||
else { None }
|
else { None }
|
||||||
sender() ! NumberPoolActor.ReturnNumberResult(number, ex, id)
|
sender() ! NumberPoolActor.ReturnNumberResult(number, ex)
|
||||||
|
|
||||||
case msg =>
|
case msg =>
|
||||||
log.warn(s"Received an unexpected message - ${msg.toString}")
|
log.warn(s"Received an unexpected message - ${msg.toString}")
|
||||||
|
|
@ -52,36 +52,36 @@ object NumberPoolActor {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A message to invoke the current `NumberSelector`'s functionality.
|
* A message to invoke the current `NumberSelector`'s functionality.
|
||||||
* @param id a potential identifier to associate this request
|
|
||||||
*/
|
*/
|
||||||
final case class GetAnyNumber(id: Option[Any] = None)
|
final case class GetAnyNumber()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A message to invoke a `SpecificSelector` to acquire the specific `number`, if it is available in this pool.
|
* A message to invoke a `SpecificSelector` to acquire the specific `number`, if it is available in this pool.
|
||||||
* @param number the pre-selected number
|
* @param number the pre-selected number
|
||||||
* @param id a potential identifier to associate this request
|
|
||||||
*/
|
*/
|
||||||
final case class GetSpecificNumber(number: Int, id: Option[Any] = None)
|
final case class GetSpecificNumber(number: Int)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A message to distribute the `number` that was drawn.
|
* A message to distribute the `number` that was drawn.
|
||||||
* @param number the pre-selected number
|
* @param number the pre-selected number
|
||||||
* @param id a potential identifier to associate this request
|
|
||||||
*/
|
*/
|
||||||
final case class GiveNumber(number: Int, id: Option[Any] = None)
|
final case class GiveNumber(number: Int)
|
||||||
|
|
||||||
final case class NoNumber(ex: Throwable, id: Option[Any] = None)
|
final case class NoNumber(ex: Throwable)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A message to invoke the `returnNumber` functionality of the current `NumberSelector`.
|
* A message to invoke the `returnNumber` functionality of the current `NumberSelector`.
|
||||||
* @param number the number
|
* @param number the number
|
||||||
*/
|
*/
|
||||||
final case class ReturnNumber(number: Int, id: Option[Any] = None)
|
final case class ReturnNumber(number: Int)
|
||||||
|
|
||||||
final case class ReturnNumberResult(number: Int, ex: Option[Throwable], id: Option[Any] = None)
|
final case class ReturnNumberResult(number: Int, ex: Option[Throwable])
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Use the `SpecificSelector` on this pool to extract a specific object from the pool, if it is included and available.
|
* Use the `SpecificSelector` on this pool to extract a specific object from the pool, if it is included and available.
|
||||||
|
* Getting a specific number involves creating the appropriate selector and swapping with the current selector.
|
||||||
|
* This process may involve re-formatting the underlying number pool array once for each selector.
|
||||||
|
* @see `NumberSelector.Format`
|
||||||
* @param pool the `NumberPool` to draw from
|
* @param pool the `NumberPool` to draw from
|
||||||
* @param number the number requested
|
* @param number the number requested
|
||||||
* @return the number requested, or an error
|
* @return the number requested, or an error
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
// Copyright (c) 2021 PSForever
|
||||||
|
package net.psforever.objects.guid.uns
|
||||||
|
|
||||||
|
import net.psforever.objects.entity.IdentifiableEntity
|
||||||
|
import net.psforever.objects.guid.NumberPoolHub
|
||||||
|
|
||||||
|
final case class RegisteredEntity(
|
||||||
|
obj: IdentifiableEntity,
|
||||||
|
pool_name: String,
|
||||||
|
guid_system: NumberPoolHub,
|
||||||
|
number: Int
|
||||||
|
)
|
||||||
|
|
||||||
|
final case class AlreadyRegisteredEntity(msg: RegisteredEntity)
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
// Copyright (c) 2021 PSForever
|
||||||
|
package net.psforever.objects.guid.uns
|
||||||
|
|
||||||
|
import net.psforever.objects.entity.IdentifiableEntity
|
||||||
|
import net.psforever.objects.guid.NumberPoolHub
|
||||||
|
|
||||||
|
final case class UnregisteredEntity(
|
||||||
|
obj: IdentifiableEntity,
|
||||||
|
pool_name: String,
|
||||||
|
guid_system: NumberPoolHub,
|
||||||
|
number: Int
|
||||||
|
)
|
||||||
|
|
||||||
|
final case class AlreadyUnregisteredEntity(msg: UnregisteredEntity)
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
// Copyright (c) 2021 PSForever
|
||||||
|
package net.psforever.objects.locker
|
||||||
|
|
||||||
|
import net.psforever.objects.definition.EquipmentDefinition
|
||||||
|
import net.psforever.objects.definition.converter.LockerContainerConverter
|
||||||
|
import net.psforever.objects.equipment.EquipmentSize
|
||||||
|
|
||||||
|
class LockerContainerDefinition extends EquipmentDefinition(objectId = 456) {
|
||||||
|
Name = "locker_container"
|
||||||
|
Size = EquipmentSize.Inventory
|
||||||
|
Packet = LockerContainerDefinition.converter
|
||||||
|
registerAs = "lockers"
|
||||||
|
}
|
||||||
|
|
||||||
|
object LockerContainerDefinition {
|
||||||
|
val converter = new LockerContainerConverter()
|
||||||
|
}
|
||||||
|
|
@ -4,7 +4,7 @@ package net.psforever.objects.serverobject.pad
|
||||||
import akka.actor.{Cancellable, Props}
|
import akka.actor.{Cancellable, Props}
|
||||||
import net.psforever.objects.avatar.SpecialCarry
|
import net.psforever.objects.avatar.SpecialCarry
|
||||||
import net.psforever.objects.entity.WorldEntity
|
import net.psforever.objects.entity.WorldEntity
|
||||||
import net.psforever.objects.guid.GUIDTask.UnregisterVehicle
|
import net.psforever.objects.guid.{GUIDTask, TaskWorkflow}
|
||||||
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
|
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
|
||||||
import net.psforever.objects.serverobject.pad.process.{VehicleSpawnControlBase, VehicleSpawnControlConcealPlayer}
|
import net.psforever.objects.serverobject.pad.process.{VehicleSpawnControlBase, VehicleSpawnControlConcealPlayer}
|
||||||
import net.psforever.objects.zones.{Zone, ZoneAware, Zoning}
|
import net.psforever.objects.zones.{Zone, ZoneAware, Zoning}
|
||||||
|
|
@ -516,7 +516,7 @@ object VehicleSpawnControl {
|
||||||
if (zone.Vehicles.contains(vehicle)) { //already added to zone
|
if (zone.Vehicles.contains(vehicle)) { //already added to zone
|
||||||
vehicle.Actor ! Vehicle.Deconstruct(Some(0.seconds))
|
vehicle.Actor ! Vehicle.Deconstruct(Some(0.seconds))
|
||||||
} else { //just registered to zone
|
} else { //just registered to zone
|
||||||
zone.tasks ! UnregisterVehicle(vehicle)(zone.GUID)
|
TaskWorkflow.execute(GUIDTask.unregisterVehicle(zone.GUID, vehicle))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
package net.psforever.objects.serverobject.shuttle
|
package net.psforever.objects.serverobject.shuttle
|
||||||
|
|
||||||
import akka.actor.{Actor, ActorRef}
|
import akka.actor.{Actor, ActorRef}
|
||||||
import net.psforever.objects.guid.{GUIDTask, Task, TaskResolver}
|
import net.psforever.objects.guid._
|
||||||
import net.psforever.objects.{Player, Vehicle}
|
import net.psforever.objects.{Player, Vehicle}
|
||||||
import net.psforever.objects.serverobject.PlanetSideServerObject
|
import net.psforever.objects.serverobject.PlanetSideServerObject
|
||||||
import net.psforever.objects.serverobject.doors.Door
|
import net.psforever.objects.serverobject.doors.Door
|
||||||
|
|
@ -14,7 +14,7 @@ import net.psforever.services.hart.{HartTimer, HartTimerActions}
|
||||||
import net.psforever.services.{Service, ServiceManager}
|
import net.psforever.services.{Service, ServiceManager}
|
||||||
import net.psforever.types.ChatMessageType
|
import net.psforever.types.ChatMessageType
|
||||||
|
|
||||||
import scala.util.Success
|
import scala.concurrent.Future
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An `Actor` that handles messages being dispatched to a specific `OrbitalShuttlePad`.<br>
|
* An `Actor` that handles messages being dispatched to a specific `OrbitalShuttlePad`.<br>
|
||||||
|
|
@ -114,7 +114,7 @@ class OrbitalShuttlePadControl(pad: OrbitalShuttlePad) extends Actor {
|
||||||
newShuttle.Position = position + Vector3(0, -8.25f, 0).Rz(pad.Orientation.z) //magic offset number
|
newShuttle.Position = position + Vector3(0, -8.25f, 0).Rz(pad.Orientation.z) //magic offset number
|
||||||
newShuttle.Orientation = pad.Orientation
|
newShuttle.Orientation = pad.Orientation
|
||||||
newShuttle.Faction = pad.Faction
|
newShuttle.Faction = pad.Faction
|
||||||
zone.tasks ! OrbitalShuttlePadControl.registerShuttle(zone, newShuttle, self)
|
TaskWorkflow.execute(OrbitalShuttlePadControl.registerShuttle(zone, newShuttle, self))
|
||||||
context.become(shuttleTime)
|
context.become(shuttleTime)
|
||||||
|
|
||||||
case _ => ;
|
case _ => ;
|
||||||
|
|
@ -127,33 +127,23 @@ object OrbitalShuttlePadControl {
|
||||||
* @param zone the zone the shuttle and the pad will occupy
|
* @param zone the zone the shuttle and the pad will occupy
|
||||||
* @param shuttle the vehicle that will be the shuttle
|
* @param shuttle the vehicle that will be the shuttle
|
||||||
* @param ref a reference to the control agency for the orbital shuttle pad
|
* @param ref a reference to the control agency for the orbital shuttle pad
|
||||||
* @return a `TaskResolver.GiveTask` object
|
* @return a `TaskBundle` object
|
||||||
*/
|
*/
|
||||||
def registerShuttle(zone: Zone, shuttle: Vehicle, ref: ActorRef): TaskResolver.GiveTask = {
|
def registerShuttle(zone: Zone, shuttle: Vehicle, ref: ActorRef): TaskBundle = {
|
||||||
TaskResolver.GiveTask(
|
import scala.concurrent.ExecutionContext.Implicits.global
|
||||||
new Task() {
|
TaskBundle(
|
||||||
|
new StraightforwardTask() {
|
||||||
private val localZone = zone
|
private val localZone = zone
|
||||||
private val localShuttle = shuttle
|
private val localShuttle = shuttle
|
||||||
private val localSelf = ref
|
private val localSelf = ref
|
||||||
|
|
||||||
override def Description: String = s"register an orbital shuttle"
|
override def description(): String = s"register an orbital shuttle"
|
||||||
|
|
||||||
override def isComplete : Task.Resolution.Value = if (localShuttle.HasGUID) {
|
def action() : Future[Any] = {
|
||||||
Task.Resolution.Success
|
|
||||||
} else {
|
|
||||||
Task.Resolution.Incomplete
|
|
||||||
}
|
|
||||||
|
|
||||||
def Execute(resolver : ActorRef) : Unit = {
|
|
||||||
localZone.Transport.tell(Zone.Vehicle.Spawn(localShuttle), localSelf)
|
localZone.Transport.tell(Zone.Vehicle.Spawn(localShuttle), localSelf)
|
||||||
resolver ! Success(true)
|
Future(this)
|
||||||
}
|
}
|
||||||
|
}, GUIDTask.registerVehicle(zone.GUID, shuttle)
|
||||||
override def onFailure(ex : Throwable) : Unit = {
|
|
||||||
super.onFailure(ex)
|
|
||||||
localSelf ! Zone.Vehicle.CanNotSpawn(localZone, localShuttle, ex.getMessage)
|
|
||||||
}
|
|
||||||
}, List(GUIDTask.RegisterVehicle(shuttle)(zone.GUID))
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import net.psforever.objects.serverobject.structures.AmenityDefinition
|
||||||
abstract class TerminalDefinition(objectId: Int) extends AmenityDefinition(objectId) {
|
abstract class TerminalDefinition(objectId: Int) extends AmenityDefinition(objectId) {
|
||||||
Name = "terminal"
|
Name = "terminal"
|
||||||
Packet = new TerminalConverter
|
Packet = new TerminalConverter
|
||||||
|
registerAs = "terminals"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The unimplemented functionality for the entry function of form of activity
|
* The unimplemented functionality for the entry function of form of activity
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import net.psforever.objects.serverobject.structures.{Amenity, AmenityDefinition
|
||||||
*/
|
*/
|
||||||
class SpawnTubeDefinition(object_id: Int) extends AmenityDefinition(object_id) with SpawnPointDefinition {
|
class SpawnTubeDefinition(object_id: Int) extends AmenityDefinition(object_id) with SpawnPointDefinition {
|
||||||
Packet = new SpawnTubeConverter
|
Packet = new SpawnTubeConverter
|
||||||
|
registerAs = "terminals"
|
||||||
}
|
}
|
||||||
|
|
||||||
object SpawnTubeDefinition {
|
object SpawnTubeDefinition {
|
||||||
|
|
|
||||||
|
|
@ -228,6 +228,7 @@ object Utility {
|
||||||
extends AmenityDefinition(DeployedItem.router_telepad_deployable.id)
|
extends AmenityDefinition(DeployedItem.router_telepad_deployable.id)
|
||||||
with BaseDeployableDefinition {
|
with BaseDeployableDefinition {
|
||||||
Packet = new SmallDeployableConverter
|
Packet = new SmallDeployableConverter
|
||||||
|
registerAs = "terminals"
|
||||||
|
|
||||||
def Item: DeployedItem.Value = DeployedItem.router_telepad_deployable
|
def Item: DeployedItem.Value = DeployedItem.router_telepad_deployable
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import net.psforever.objects._
|
||||||
import net.psforever.objects.ballistics.VehicleSource
|
import net.psforever.objects.ballistics.VehicleSource
|
||||||
import net.psforever.objects.entity.WorldEntity
|
import net.psforever.objects.entity.WorldEntity
|
||||||
import net.psforever.objects.equipment.{Equipment, EquipmentSlot, JammableMountedWeapons}
|
import net.psforever.objects.equipment.{Equipment, EquipmentSlot, JammableMountedWeapons}
|
||||||
import net.psforever.objects.guid.GUIDTask
|
import net.psforever.objects.guid.{GUIDTask, TaskWorkflow}
|
||||||
import net.psforever.objects.inventory.{GridInventory, InventoryItem}
|
import net.psforever.objects.inventory.{GridInventory, InventoryItem}
|
||||||
import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject}
|
import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject}
|
||||||
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
|
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
|
||||||
|
|
@ -407,7 +407,7 @@ class VehicleControl(vehicle: Vehicle)
|
||||||
CancelJammeredSound(vehicle)
|
CancelJammeredSound(vehicle)
|
||||||
CancelJammeredStatus(vehicle)
|
CancelJammeredStatus(vehicle)
|
||||||
//unregister
|
//unregister
|
||||||
zone.tasks ! GUIDTask.UnregisterVehicle(vehicle)(zone.GUID)
|
TaskWorkflow.execute(GUIDTask.unregisterVehicle(zone.GUID, vehicle))
|
||||||
//banished to the shadow realm
|
//banished to the shadow realm
|
||||||
vehicle.Position = Vector3.Zero
|
vehicle.Position = Vector3.Zero
|
||||||
//queue final deletion
|
//queue final deletion
|
||||||
|
|
|
||||||
|
|
@ -2,16 +2,12 @@
|
||||||
package net.psforever.objects.zones
|
package net.psforever.objects.zones
|
||||||
|
|
||||||
import akka.actor.{ActorContext, ActorRef, Props}
|
import akka.actor.{ActorContext, ActorRef, Props}
|
||||||
import akka.routing.RandomPool
|
|
||||||
import net.psforever.objects.{PlanetSideGameObject, _}
|
import net.psforever.objects.{PlanetSideGameObject, _}
|
||||||
import net.psforever.objects.ballistics.{Projectile, SourceEntry}
|
import net.psforever.objects.ballistics.SourceEntry
|
||||||
import net.psforever.objects.ce.Deployable
|
import net.psforever.objects.ce.Deployable
|
||||||
import net.psforever.objects.entity.IdentifiableEntity
|
|
||||||
import net.psforever.objects.equipment.Equipment
|
import net.psforever.objects.equipment.Equipment
|
||||||
import net.psforever.objects.guid.{NumberPoolHub, TaskResolver}
|
import net.psforever.objects.guid.{NumberPoolHub, UniqueNumberOps, UniqueNumberSetup}
|
||||||
import net.psforever.objects.guid.actor.UniqueNumberSystem
|
|
||||||
import net.psforever.objects.guid.key.LoanedKey
|
import net.psforever.objects.guid.key.LoanedKey
|
||||||
import net.psforever.objects.guid.selector.RandomSelector
|
|
||||||
import net.psforever.objects.guid.source.MaxNumberSource
|
import net.psforever.objects.guid.source.MaxNumberSource
|
||||||
import net.psforever.objects.inventory.Container
|
import net.psforever.objects.inventory.Container
|
||||||
import net.psforever.objects.serverobject.painbox.{Painbox, PainboxDefinition}
|
import net.psforever.objects.serverobject.painbox.{Painbox, PainboxDefinition}
|
||||||
|
|
@ -39,6 +35,7 @@ import net.psforever.actors.session.AvatarActor
|
||||||
import net.psforever.actors.zone.ZoneActor
|
import net.psforever.actors.zone.ZoneActor
|
||||||
import net.psforever.objects.avatar.Avatar
|
import net.psforever.objects.avatar.Avatar
|
||||||
import net.psforever.objects.geometry.d3.VolumetricGeometry
|
import net.psforever.objects.geometry.d3.VolumetricGeometry
|
||||||
|
import net.psforever.objects.guid.pool.NumberPool
|
||||||
import net.psforever.objects.serverobject.PlanetSideServerObject
|
import net.psforever.objects.serverobject.PlanetSideServerObject
|
||||||
import net.psforever.objects.serverobject.affinity.FactionAffinity
|
import net.psforever.objects.serverobject.affinity.FactionAffinity
|
||||||
import net.psforever.objects.serverobject.doors.Door
|
import net.psforever.objects.serverobject.doors.Door
|
||||||
|
|
@ -79,14 +76,16 @@ class Zone(val id: String, val map: ZoneMap, zoneNumber: Int) {
|
||||||
/** Governs general synchronized external requests. */
|
/** Governs general synchronized external requests. */
|
||||||
var actor: typed.ActorRef[ZoneActor.Command] = _
|
var actor: typed.ActorRef[ZoneActor.Command] = _
|
||||||
|
|
||||||
/** Actor that handles SOI related functionality, for example if a player is in a SOI */
|
/** Actor that handles SOI related functionality, for example if a player is in an SOI */
|
||||||
private var soi = Default.Actor
|
private var soi = Default.Actor
|
||||||
|
|
||||||
/** Used by the globally unique identifier system to coordinate requests. */
|
|
||||||
private var accessor: ActorRef = ActorRef.noSender
|
|
||||||
|
|
||||||
/** The basic support structure for the globally unique number system used by this `Zone`. */
|
/** The basic support structure for the globally unique number system used by this `Zone`. */
|
||||||
private var guid: NumberPoolHub = new NumberPoolHub(new MaxNumberSource(65536))
|
private var guid: NumberPoolHub = new NumberPoolHub(new MaxNumberSource(65536))
|
||||||
|
/** The core of the unique number system, to which requests may be submitted.
|
||||||
|
* @see `UniqueNumberSys`
|
||||||
|
* @see `Zone.Init(ActorContext)`
|
||||||
|
*/
|
||||||
|
private[zones] var unops: UniqueNumberOps = _
|
||||||
|
|
||||||
/** The blockmap structure for partitioning entities and environmental aspects of the zone.
|
/** The blockmap structure for partitioning entities and environmental aspects of the zone.
|
||||||
* For a standard 8912`^`2 map, each of the four hundred formal map grids is 445.6m long and wide.
|
* For a standard 8912`^`2 map, each of the four hundred formal map grids is 445.6m long and wide.
|
||||||
|
|
@ -105,8 +104,6 @@ class Zone(val id: String, val map: ZoneMap, zoneNumber: Int) {
|
||||||
/** Used by the `Zone` to coordinate `Equipment` dropping and collection requests. */
|
/** Used by the `Zone` to coordinate `Equipment` dropping and collection requests. */
|
||||||
private var ground: ActorRef = Default.Actor
|
private var ground: ActorRef = Default.Actor
|
||||||
|
|
||||||
private var taskResolver: ActorRef = Default.Actor
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
private val constructions: ListBuffer[Deployable] = ListBuffer()
|
private val constructions: ListBuffer[Deployable] = ListBuffer()
|
||||||
|
|
@ -177,7 +174,7 @@ class Zone(val id: String, val map: ZoneMap, zoneNumber: Int) {
|
||||||
* First, the `Actor`-driven aspect of the globally unique identifier system for this `Zone` is finalized.
|
* First, the `Actor`-driven aspect of the globally unique identifier system for this `Zone` is finalized.
|
||||||
* Second, all supporting `Actor` agents are created, e.g., `ground`.
|
* Second, all supporting `Actor` agents are created, e.g., `ground`.
|
||||||
* Third, the `ZoneMap` server objects are loaded and constructed within that aforementioned system.
|
* Third, the `ZoneMap` server objects are loaded and constructed within that aforementioned system.
|
||||||
* To avoid being called more than once, there is a test whether the `accessor` for the globally unique identifier system has been changed.<br>
|
* To avoid being called more than once, there is a test whether the globally unique identifier system has been changed.<br>
|
||||||
* <br>
|
* <br>
|
||||||
* Execution of this operation should be fail-safe.
|
* Execution of this operation should be fail-safe.
|
||||||
* The chances of failure should be mitigated or skipped.
|
* The chances of failure should be mitigated or skipped.
|
||||||
|
|
@ -187,15 +184,9 @@ class Zone(val id: String, val map: ZoneMap, zoneNumber: Int) {
|
||||||
* @param context a reference to an `ActorContext` necessary for `Props`
|
* @param context a reference to an `ActorContext` necessary for `Props`
|
||||||
*/
|
*/
|
||||||
def init(implicit context: ActorContext): Unit = {
|
def init(implicit context: ActorContext): Unit = {
|
||||||
if (accessor == ActorRef.noSender) {
|
if (unops == null) {
|
||||||
SetupNumberPools()
|
SetupNumberPools()
|
||||||
taskResolver = CreateTaskResolvers(context)
|
context.actorOf(Props(classOf[UniqueNumberSys], this, this.guid), s"zone-$id-uns")
|
||||||
accessor = context.actorOf(
|
|
||||||
RandomPool(25).props(
|
|
||||||
Props(classOf[UniqueNumberSystem], this.guid, UniqueNumberSystem.AllocateNumberPoolActors(this.guid))
|
|
||||||
),
|
|
||||||
s"zone-$id-uns"
|
|
||||||
)
|
|
||||||
ground = context.actorOf(Props(classOf[ZoneGroundActor], this, equipmentOnGround), s"zone-$id-ground")
|
ground = context.actorOf(Props(classOf[ZoneGroundActor], this, equipmentOnGround), s"zone-$id-ground")
|
||||||
deployables = context.actorOf(Props(classOf[ZoneDeployableActor], this, constructions), s"zone-$id-deployables")
|
deployables = context.actorOf(Props(classOf[ZoneDeployableActor], this, constructions), s"zone-$id-deployables")
|
||||||
transport = context.actorOf(Props(classOf[ZoneVehicleActor], this, vehicles), s"zone-$id-vehicles")
|
transport = context.actorOf(Props(classOf[ZoneVehicleActor], this, vehicles), s"zone-$id-vehicles")
|
||||||
|
|
@ -334,26 +325,7 @@ class Zone(val id: String, val map: ZoneMap, zoneNumber: Int) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def SetupNumberPools(): Unit = {
|
def SetupNumberPools(): Unit = { /* override to tailor to suit requirements of zone */ }
|
||||||
guid.AddPool("environment", (0 to 3000).toList) //TODO tailor to suit requirements of zone
|
|
||||||
//TODO unlump pools later; do not make any single pool too big
|
|
||||||
guid.AddPool("dynamic", (3001 to 10000).toList).Selector =
|
|
||||||
new RandomSelector //TODO all things will be registered here, for now
|
|
||||||
guid.AddPool("b", (10001 to 15000).toList).Selector = new RandomSelector
|
|
||||||
guid.AddPool("c", (15001 to 20000).toList).Selector = new RandomSelector
|
|
||||||
guid.AddPool("d", (20001 to 25000).toList).Selector = new RandomSelector
|
|
||||||
guid.AddPool("e", (25001 to 30000).toList).Selector = new RandomSelector
|
|
||||||
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
|
|
||||||
//guid.AddPool("j", (50001 to 55000).toList).Selector = new RandomSelector
|
|
||||||
//guid.AddPool("k", (55001 to 60000).toList).Selector = new RandomSelector
|
|
||||||
//guid.AddPool("l", (60001 to 65535).toList).Selector = new RandomSelector
|
|
||||||
}
|
|
||||||
|
|
||||||
def findSpawns(
|
def findSpawns(
|
||||||
faction: PlanetSideEmpire.Value,
|
faction: PlanetSideEmpire.Value,
|
||||||
|
|
@ -436,14 +408,14 @@ class Zone(val id: String, val map: ZoneMap, zoneNumber: Int) {
|
||||||
def Number: Int = zoneNumber
|
def Number: Int = zoneNumber
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The globally unique identifier system is synchronized via an `Actor` to ensure that concurrent requests do not clash.
|
* The globally unique identifier system ensures that concurrent requests do not clash.
|
||||||
* A clash is merely when the same number is produced more than once by the same system due to concurrent requests.
|
* A clash is merely when the same number is produced more than once by the same system due to concurrent requests.
|
||||||
* @return synchronized reference to the globally unique identifier system
|
* @return reference to the globally unique identifier system
|
||||||
*/
|
*/
|
||||||
def GUID: ActorRef = accessor
|
def GUID: UniqueNumberOps = unops
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Replace the current globally unique identifier system with a new one.
|
* Replace the current globally unique identifier support structure with a new one.
|
||||||
* The replacement will not occur if the current system is populated or if its synchronized reference has been created.
|
* The replacement will not occur if the current system is populated or if its synchronized reference has been created.
|
||||||
* The primary use of this function should be testing.
|
* The primary use of this function should be testing.
|
||||||
* A warning will be issued.
|
* A warning will be issued.
|
||||||
|
|
@ -475,12 +447,14 @@ class Zone(val id: String, val map: ZoneMap, zoneNumber: Int) {
|
||||||
* @return `true`, if the new pool is created;
|
* @return `true`, if the new pool is created;
|
||||||
* `false`, if the new pool can not be created because the system has already been started
|
* `false`, if the new pool can not be created because the system has already been started
|
||||||
*/
|
*/
|
||||||
def AddPool(name: String, pool: Seq[Int]): Boolean = {
|
def AddPool(name: String, pool: Seq[Int]): Option[NumberPool] = {
|
||||||
if (accessor == Default.Actor || accessor == null) {
|
if (unops == null) {
|
||||||
guid.AddPool(name, pool.toList)
|
guid.AddPool(name, pool.toList) match {
|
||||||
true
|
case _: Exception => None
|
||||||
|
case out => Some(out)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
false
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -489,13 +463,12 @@ class Zone(val id: String, val map: ZoneMap, zoneNumber: Int) {
|
||||||
* Throws exceptions for specific reasons if the pool can not be removed before the system has been started.
|
* Throws exceptions for specific reasons if the pool can not be removed before the system has been started.
|
||||||
* @see `NumberPoolHub.RemovePool`
|
* @see `NumberPoolHub.RemovePool`
|
||||||
* @param name the name of the pool
|
* @param name the name of the pool
|
||||||
* @return `true`, if the new pool is un-made;
|
* @return `true`, if the pool is un-made;
|
||||||
* `false`, if the new pool can not be removed because the system has already been started
|
* `false`, if the pool can not be removed (because the system has already been started?)
|
||||||
*/
|
*/
|
||||||
def RemovePool(name: String): Boolean = {
|
def RemovePool(name: String): Boolean = {
|
||||||
if (accessor == Default.Actor) {
|
if (unops == null) {
|
||||||
guid.RemovePool(name)
|
guid.RemovePool(name).nonEmpty
|
||||||
true
|
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
@ -526,7 +499,7 @@ class Zone(val id: String, val map: ZoneMap, zoneNumber: Int) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Recover an object from the globally unique identifier system by the number that was assigned previously.
|
* Recover an object from the globally unique identifier system by the number that was assigned previously.
|
||||||
* The object must be upcast into due to the differtence between the storage type and the return type.
|
* The object must be upcast into due to the minor difference between the storage type and the return type.
|
||||||
* @param object_guid the globally unique identifier requested
|
* @param object_guid the globally unique identifier requested
|
||||||
* @return the associated object, if it exists
|
* @return the associated object, if it exists
|
||||||
* @see `NumberPoolHub(Int)`
|
* @see `NumberPoolHub(Int)`
|
||||||
|
|
@ -606,7 +579,7 @@ class Zone(val id: String, val map: ZoneMap, zoneNumber: Int) {
|
||||||
|
|
||||||
private def BuildSupportObjects(): Unit = {
|
private def BuildSupportObjects(): Unit = {
|
||||||
//guard against errors here, but don't worry about specifics; let ZoneActor.ZoneSetupCheck complain about problems
|
//guard against errors here, but don't worry about specifics; let ZoneActor.ZoneSetupCheck complain about problems
|
||||||
val other: ListBuffer[IdentifiableEntity] = new ListBuffer[IdentifiableEntity]()
|
val other: ListBuffer[PlanetSideGameObject] = new ListBuffer[PlanetSideGameObject]()
|
||||||
//turret to weapon
|
//turret to weapon
|
||||||
map.turretToWeapon.foreach({
|
map.turretToWeapon.foreach({
|
||||||
case (turret_guid, weapon_guid) =>
|
case (turret_guid, weapon_guid) =>
|
||||||
|
|
@ -634,7 +607,7 @@ class Zone(val id: String, val map: ZoneMap, zoneNumber: Int) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
//after all fixed GUID's are defined ...
|
//after all fixed GUID's are defined ...
|
||||||
other.foreach(obj => guid.register(obj, "dynamic"))
|
other.foreach(obj => guid.register(obj, obj.Definition.registerAs))
|
||||||
}
|
}
|
||||||
|
|
||||||
private def MakeBuildings(implicit context: ActorContext): PairMap[Int, Building] = {
|
private def MakeBuildings(implicit context: ActorContext): PairMap[Int, Building] = {
|
||||||
|
|
@ -836,11 +809,26 @@ class Zone(val id: String, val map: ZoneMap, zoneNumber: Int) {
|
||||||
vehicleEvents = bus
|
vehicleEvents = bus
|
||||||
VehicleEvents
|
VehicleEvents
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
def tasks: ActorRef = taskResolver
|
/**
|
||||||
|
* A local class for spawning `Actor`s to manage the number pools for this zone,
|
||||||
protected def CreateTaskResolvers(context: ActorContext, numberCreated: Int = 20): ActorRef = {
|
* create a number system operations class to access those pools within the context of registering and unregistering,
|
||||||
context.actorOf(RandomPool(numberCreated).props(Props[TaskResolver]()), s"zone-$id-taskResolver")
|
* and assign that number pool operations class to the containing zone
|
||||||
|
* through specific scope access.
|
||||||
|
* @see `UniqueNumberOps`
|
||||||
|
* @see `UniqueNumberSetup`
|
||||||
|
* @see `UniqueNumberSetup.AllocateNumberPoolActors`
|
||||||
|
* @see `Zone.unops`
|
||||||
|
* @param zone the zone in which the operations class will be referenced
|
||||||
|
* @param guid the number pool management class
|
||||||
|
*/
|
||||||
|
private class UniqueNumberSys(zone: Zone, guid: NumberPoolHub)
|
||||||
|
extends UniqueNumberSetup(guid, UniqueNumberSetup.AllocateNumberPoolActors) {
|
||||||
|
override def init(): UniqueNumberOps = {
|
||||||
|
val unsys = super.init()
|
||||||
|
zone.unops = unsys // zone.unops is accessible from here by virtue of being 'private[zones]`
|
||||||
|
unsys
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
// Copyright (c) 2017 PSForever
|
// Copyright (c) 2017 PSForever
|
||||||
package net.psforever.services
|
package net.psforever.services
|
||||||
|
|
||||||
import akka.actor.{ActorRef, Cancellable}
|
import akka.actor.Cancellable
|
||||||
import net.psforever.objects.guid.TaskResolver
|
import net.psforever.objects.guid.{StraightforwardTask, TaskBundle, TaskWorkflow}
|
||||||
import net.psforever.objects.zones.Zone
|
import net.psforever.objects.zones.Zone
|
||||||
import net.psforever.objects.{Default, PlanetSideGameObject}
|
import net.psforever.objects.{Default, PlanetSideGameObject}
|
||||||
import net.psforever.types.Vector3
|
import net.psforever.types.Vector3
|
||||||
import net.psforever.services.support.{SimilarityComparator, SupportActor, SupportActorCaseConversions}
|
import net.psforever.services.support.{SimilarityComparator, SupportActor, SupportActorCaseConversions}
|
||||||
|
|
||||||
|
import scala.concurrent.Future
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
import scala.util.Success
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The base class for a type of "destruction `Actor`" intended to be used for delaying object cleanup activity.
|
* The base class for a type of "destruction `Actor`" intended to be used for delaying object cleanup activity.
|
||||||
|
|
@ -30,7 +30,7 @@ import scala.util.Success
|
||||||
* and finally unregistering it.
|
* and finally unregistering it.
|
||||||
* Some types of object have (de-)implementation variations which should be made explicit through the overrides.
|
* Some types of object have (de-)implementation variations which should be made explicit through the overrides.
|
||||||
*/
|
*/
|
||||||
abstract class RemoverActor(val taskResolver: ActorRef) extends SupportActor[RemoverActor.Entry] {
|
abstract class RemoverActor() extends SupportActor[RemoverActor.Entry] {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The timer that checks whether entries in the first pool are still eligible for that pool.
|
* The timer that checks whether entries in the first pool are still eligible for that pool.
|
||||||
|
|
@ -249,30 +249,23 @@ abstract class RemoverActor(val taskResolver: ActorRef) extends SupportActor[Rem
|
||||||
|
|
||||||
def SecondJob(entry: RemoverActor.Entry): Unit = {
|
def SecondJob(entry: RemoverActor.Entry): Unit = {
|
||||||
entry.obj.Position = Vector3.Zero //somewhere it will not disturb anything
|
entry.obj.Position = Vector3.Zero //somewhere it will not disturb anything
|
||||||
taskResolver ! FinalTask(entry)
|
TaskWorkflow.execute(FinalTask(entry))
|
||||||
}
|
}
|
||||||
|
|
||||||
def FinalTask(entry: RemoverActor.Entry): TaskResolver.GiveTask = {
|
def FinalTask(entry: RemoverActor.Entry): TaskBundle = {
|
||||||
import net.psforever.objects.guid.Task
|
import scala.concurrent.ExecutionContext.Implicits.global
|
||||||
TaskResolver.GiveTask(
|
TaskBundle(
|
||||||
new Task() {
|
new StraightforwardTask() {
|
||||||
private val localEntry = entry
|
// private val localEntry = entry
|
||||||
private val localAnnounce = self
|
// private val localAnnounce = self
|
||||||
|
|
||||||
override def isComplete: Task.Resolution.Value =
|
def action(): Future[Any] = {
|
||||||
if (!localEntry.obj.HasGUID) {
|
Future(this)
|
||||||
Task.Resolution.Success
|
|
||||||
} else {
|
|
||||||
Task.Resolution.Incomplete
|
|
||||||
}
|
|
||||||
|
|
||||||
def Execute(resolver: ActorRef): Unit = {
|
|
||||||
resolver ! Success(this)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override def onFailure(ex: Throwable): Unit = {
|
// override def onFailure(ex: Throwable): Unit = {
|
||||||
localAnnounce ! RemoverActor.FailureToWork(localEntry, ex)
|
// localAnnounce ! RemoverActor.FailureToWork(localEntry, ex)
|
||||||
}
|
// }
|
||||||
},
|
},
|
||||||
List(DeletionTask(entry))
|
List(DeletionTask(entry))
|
||||||
)
|
)
|
||||||
|
|
@ -319,7 +312,7 @@ abstract class RemoverActor(val taskResolver: ActorRef) extends SupportActor[Rem
|
||||||
* @see `GUIDTask`
|
* @see `GUIDTask`
|
||||||
* @param entry the entry
|
* @param entry the entry
|
||||||
*/
|
*/
|
||||||
def DeletionTask(entry: RemoverActor.Entry): TaskResolver.GiveTask
|
def DeletionTask(entry: RemoverActor.Entry): TaskBundle
|
||||||
}
|
}
|
||||||
|
|
||||||
object RemoverActor extends SupportActorCaseConversions {
|
object RemoverActor extends SupportActorCaseConversions {
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import akka.actor.{Actor, ActorRef, Cancellable, Props}
|
||||||
import scala.collection.mutable
|
import scala.collection.mutable
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
import scala.concurrent.ExecutionContext.Implicits.global
|
import scala.concurrent.ExecutionContext.Implicits.global
|
||||||
import net.psforever.objects.guid.GUIDTask
|
import net.psforever.objects.guid.{GUIDTask, TaskWorkflow}
|
||||||
import net.psforever.objects._
|
import net.psforever.objects._
|
||||||
import net.psforever.objects.avatar.Avatar
|
import net.psforever.objects.avatar.Avatar
|
||||||
import net.psforever.objects.serverobject.mount.Mountable
|
import net.psforever.objects.serverobject.mount.Mountable
|
||||||
|
|
@ -389,7 +389,8 @@ class PersistenceMonitor(name: String, squadService: ActorRef) extends Actor {
|
||||||
}
|
}
|
||||||
inZone.Population.tell(Zone.Population.Release(avatar), parent)
|
inZone.Population.tell(Zone.Population.Release(avatar), parent)
|
||||||
inZone.AvatarEvents.tell(AvatarServiceMessage(inZone.id, AvatarAction.ObjectDelete(pguid, pguid)), parent)
|
inZone.AvatarEvents.tell(AvatarServiceMessage(inZone.id, AvatarAction.ObjectDelete(pguid, pguid)), parent)
|
||||||
inZone.tasks.tell(GUIDTask.UnregisterPlayer(player)(inZone.GUID), parent)
|
TaskWorkflow.execute(GUIDTask.unregisterPlayer(inZone.GUID, player))
|
||||||
|
//inZone.tasks.tell(GUIDTask.UnregisterPlayer(player)(inZone.GUID), parent)
|
||||||
AvatarLogout(avatar)
|
AvatarLogout(avatar)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -408,7 +409,8 @@ class PersistenceMonitor(name: String, squadService: ActorRef) extends Actor {
|
||||||
squadService.tell(Service.Leave(Some(avatar.id.toString)), context.parent)
|
squadService.tell(Service.Leave(Some(avatar.id.toString)), context.parent)
|
||||||
Deployables.Disown(inZone, avatar, context.parent)
|
Deployables.Disown(inZone, avatar, context.parent)
|
||||||
inZone.Population.tell(Zone.Population.Leave(avatar), context.parent)
|
inZone.Population.tell(Zone.Population.Leave(avatar), context.parent)
|
||||||
inZone.tasks.tell(GUIDTask.UnregisterObjectTask(avatar.locker)(inZone.GUID), context.parent)
|
TaskWorkflow.execute(GUIDTask.unregisterObject(inZone.GUID, avatar.locker))
|
||||||
|
//inZone.tasks.tell(GUIDTask.UnregisterObjectTask(avatar.locker)(inZone.GUID), context.parent)
|
||||||
log.info(s"Logout of ${avatar.name}")
|
log.info(s"Logout of ${avatar.name}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,8 @@ import net.psforever.services.avatar.support.{CorpseRemovalActor, DroppedItemRem
|
||||||
import net.psforever.services.{GenericEventBus, RemoverActor, Service}
|
import net.psforever.services.{GenericEventBus, RemoverActor, Service}
|
||||||
|
|
||||||
class AvatarService(zone: Zone) extends Actor {
|
class AvatarService(zone: Zone) extends Actor {
|
||||||
private val undertaker: ActorRef = context.actorOf(Props(classOf[CorpseRemovalActor], zone.tasks), s"${zone.id}-corpse-removal-agent")
|
private val undertaker: ActorRef = context.actorOf(Props[CorpseRemovalActor](), s"${zone.id}-corpse-removal-agent")
|
||||||
private val janitor = context.actorOf(Props(classOf[DroppedItemRemover], zone.tasks), s"${zone.id}-item-remover-agent")
|
private val janitor = context.actorOf(Props[DroppedItemRemover](), s"${zone.id}-item-remover-agent")
|
||||||
|
|
||||||
private[this] val log = org.log4s.getLogger
|
private[this] val log = org.log4s.getLogger
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
// Copyright (c) 2017 PSForever
|
// Copyright (c) 2017 PSForever
|
||||||
package net.psforever.services.avatar.support
|
package net.psforever.services.avatar.support
|
||||||
|
|
||||||
import akka.actor.ActorRef
|
import net.psforever.objects.guid.{GUIDTask, TaskBundle}
|
||||||
import net.psforever.objects.guid.{GUIDTask, TaskResolver}
|
|
||||||
import net.psforever.objects.Player
|
import net.psforever.objects.Player
|
||||||
import net.psforever.types.ExoSuitType
|
import net.psforever.types.ExoSuitType
|
||||||
import net.psforever.services.{RemoverActor, Service}
|
import net.psforever.services.{RemoverActor, Service}
|
||||||
|
|
@ -10,7 +9,7 @@ import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||||
|
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
|
|
||||||
class CorpseRemovalActor(taskResolver: ActorRef) extends RemoverActor(taskResolver) {
|
class CorpseRemovalActor extends RemoverActor() {
|
||||||
final val FirstStandardDuration: FiniteDuration = 1 minute
|
final val FirstStandardDuration: FiniteDuration = 1 minute
|
||||||
|
|
||||||
final val SecondStandardDuration: FiniteDuration = 500 milliseconds
|
final val SecondStandardDuration: FiniteDuration = 500 milliseconds
|
||||||
|
|
@ -32,9 +31,9 @@ class CorpseRemovalActor(taskResolver: ActorRef) extends RemoverActor(taskResolv
|
||||||
|
|
||||||
def ClearanceTest(entry: RemoverActor.Entry): Boolean = !entry.zone.Corpses.contains(entry.obj)
|
def ClearanceTest(entry: RemoverActor.Entry): Boolean = !entry.zone.Corpses.contains(entry.obj)
|
||||||
|
|
||||||
def DeletionTask(entry: RemoverActor.Entry): TaskResolver.GiveTask = {
|
def DeletionTask(entry: RemoverActor.Entry): TaskBundle = {
|
||||||
val player = entry.obj.asInstanceOf[Player]
|
val player = entry.obj.asInstanceOf[Player]
|
||||||
val task = GUIDTask.UnregisterPlayer(player)(entry.zone.GUID)
|
val task = GUIDTask.unregisterPlayer(entry.zone.GUID, player)
|
||||||
player.ExoSuit = ExoSuitType.Standard
|
player.ExoSuit = ExoSuitType.Standard
|
||||||
task
|
task
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,14 @@
|
||||||
// Copyright (c) 2017 PSForever
|
// Copyright (c) 2017 PSForever
|
||||||
package net.psforever.services.avatar.support
|
package net.psforever.services.avatar.support
|
||||||
|
|
||||||
import akka.actor.ActorRef
|
|
||||||
import net.psforever.objects.equipment.Equipment
|
import net.psforever.objects.equipment.Equipment
|
||||||
import net.psforever.objects.guid.{GUIDTask, TaskResolver}
|
import net.psforever.objects.guid.{GUIDTask, TaskBundle}
|
||||||
import net.psforever.services.{RemoverActor, Service}
|
import net.psforever.services.{RemoverActor, Service}
|
||||||
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
|
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||||
|
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
|
|
||||||
class DroppedItemRemover(taskResolver: ActorRef) extends RemoverActor(taskResolver) {
|
class DroppedItemRemover extends RemoverActor() {
|
||||||
final val FirstStandardDuration: FiniteDuration = 3 minutes
|
final val FirstStandardDuration: FiniteDuration = 3 minutes
|
||||||
|
|
||||||
final val SecondStandardDuration: FiniteDuration = 500 milliseconds
|
final val SecondStandardDuration: FiniteDuration = 500 milliseconds
|
||||||
|
|
@ -31,7 +30,7 @@ class DroppedItemRemover(taskResolver: ActorRef) extends RemoverActor(taskResolv
|
||||||
|
|
||||||
def ClearanceTest(entry: RemoverActor.Entry): Boolean = !entry.zone.EquipmentOnGround.contains(entry.obj)
|
def ClearanceTest(entry: RemoverActor.Entry): Boolean = !entry.zone.EquipmentOnGround.contains(entry.obj)
|
||||||
|
|
||||||
def DeletionTask(entry: RemoverActor.Entry): TaskResolver.GiveTask = {
|
def DeletionTask(entry: RemoverActor.Entry): TaskBundle = {
|
||||||
GUIDTask.UnregisterEquipment(entry.obj.asInstanceOf[Equipment])(entry.zone.GUID)
|
GUIDTask.unregisterEquipment(entry.zone.GUID, entry.obj.asInstanceOf[Equipment])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,10 +19,10 @@ class LocalService(zone: Zone) extends Actor {
|
||||||
Props[HackClearActor](), s"${zone.id}-local-hack-clearer"
|
Props[HackClearActor](), s"${zone.id}-local-hack-clearer"
|
||||||
)
|
)
|
||||||
private val hackCapturer = context.actorOf(
|
private val hackCapturer = context.actorOf(
|
||||||
Props(classOf[HackCaptureActor], zone.tasks), s"${zone.id}-local-hack-capturer"
|
Props[HackCaptureActor](), s"${zone.id}-local-hack-capturer"
|
||||||
)
|
)
|
||||||
private val captureFlagManager = context.actorOf(
|
private val captureFlagManager = context.actorOf(
|
||||||
Props(classOf[CaptureFlagManager], zone.tasks, zone), s"${zone.id}-local-capture-flag-manager"
|
Props(classOf[CaptureFlagManager], zone), s"${zone.id}-local-capture-flag-manager"
|
||||||
)
|
)
|
||||||
private[this] val log = org.log4s.getLogger
|
private[this] val log = org.log4s.getLogger
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
package net.psforever.services.local.support
|
package net.psforever.services.local.support
|
||||||
|
|
||||||
import akka.actor.{Actor, ActorRef, Cancellable}
|
import akka.actor.{Actor, ActorRef, Cancellable}
|
||||||
|
import net.psforever.login.WorldSession
|
||||||
import net.psforever.objects.{Default, Player}
|
import net.psforever.objects.{Default, Player}
|
||||||
import net.psforever.objects.guid.{GUIDTask, Task, TaskResolver}
|
import net.psforever.objects.guid.{GUIDTask, TaskWorkflow}
|
||||||
import net.psforever.objects.serverobject.llu.CaptureFlag
|
import net.psforever.objects.serverobject.llu.CaptureFlag
|
||||||
import net.psforever.objects.serverobject.structures.Building
|
import net.psforever.objects.serverobject.structures.Building
|
||||||
import net.psforever.objects.serverobject.terminals.capture.CaptureTerminal
|
import net.psforever.objects.serverobject.terminals.capture.CaptureTerminal
|
||||||
|
|
@ -16,14 +17,11 @@ import net.psforever.services.local.{LocalAction, LocalServiceMessage}
|
||||||
import net.psforever.types.{ChatMessageType, PlanetSideEmpire, PlanetSideGUID, Vector3}
|
import net.psforever.types.{ChatMessageType, PlanetSideEmpire, PlanetSideGUID, Vector3}
|
||||||
|
|
||||||
import scala.concurrent.duration.DurationInt
|
import scala.concurrent.duration.DurationInt
|
||||||
import scala.util.Success
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Responsible for handling capture flag related lifecycles
|
* Responsible for handling capture flag related lifecycles
|
||||||
* @param taskResolver A reference to a zone's task resolver actor
|
|
||||||
*/
|
*/
|
||||||
class CaptureFlagManager(val taskResolver: ActorRef, zone: Zone) extends Actor{
|
class CaptureFlagManager(zone: Zone) extends Actor{
|
||||||
private[this] val log = org.log4s.getLogger(self.path.name)
|
private[this] val log = org.log4s.getLogger(self.path.name)
|
||||||
|
|
||||||
var galaxyService: ActorRef = ActorRef.noSender
|
var galaxyService: ActorRef = ActorRef.noSender
|
||||||
|
|
@ -105,14 +103,14 @@ class CaptureFlagManager(val taskResolver: ActorRef, zone: Zone) extends Actor{
|
||||||
socket.captureFlag = flag
|
socket.captureFlag = flag
|
||||||
TrackFlag(flag)
|
TrackFlag(flag)
|
||||||
|
|
||||||
taskResolver ! CallBackForTask(
|
TaskWorkflow.execute(WorldSession.CallBackForTask(
|
||||||
TaskResolver.GiveTask(GUIDTask.RegisterObjectTask(flag)(socket.Zone.GUID).task),
|
GUIDTask.registerObject(socket.Zone.GUID, flag),
|
||||||
socket.Zone.LocalEvents,
|
socket.Zone.LocalEvents,
|
||||||
LocalServiceMessage(
|
LocalServiceMessage(
|
||||||
socket.Zone.id,
|
socket.Zone.id,
|
||||||
LocalAction.LluSpawned(PlanetSideGUID(-1), flag)
|
LocalAction.LluSpawned(PlanetSideGUID(-1), flag)
|
||||||
)
|
)
|
||||||
)
|
))
|
||||||
|
|
||||||
// Broadcast chat message for LLU spawn
|
// Broadcast chat message for LLU spawn
|
||||||
val owner = flag.Owner.asInstanceOf[Building]
|
val owner = flag.Owner.asInstanceOf[Building]
|
||||||
|
|
@ -184,7 +182,7 @@ class CaptureFlagManager(val taskResolver: ActorRef, zone: Zone) extends Actor{
|
||||||
// Unregister LLU from clients,
|
// Unregister LLU from clients,
|
||||||
flag.Zone.LocalEvents ! LocalServiceMessage(flag.Zone.id, LocalAction.LluDespawned(PlanetSideGUID(-1), flag))
|
flag.Zone.LocalEvents ! LocalServiceMessage(flag.Zone.id, LocalAction.LluDespawned(PlanetSideGUID(-1), flag))
|
||||||
// Then unregister it from the GUID pool
|
// Then unregister it from the GUID pool
|
||||||
taskResolver ! TaskResolver.GiveTask(GUIDTask.UnregisterObjectTask(flag)(flag.Zone.GUID).task)
|
TaskWorkflow.execute(GUIDTask.unregisterObject(flag.Zone.GUID,flag))
|
||||||
}
|
}
|
||||||
|
|
||||||
private def ChatBroadcast(zone: Zone, message: String, fanfare: Boolean = true): Unit = {
|
private def ChatBroadcast(zone: Zone, message: String, fanfare: Boolean = true): Unit = {
|
||||||
|
|
@ -202,25 +200,6 @@ class CaptureFlagManager(val taskResolver: ActorRef, zone: Zone) extends Actor{
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Todo: Duplicate from SessionActor. Make common.
|
|
||||||
def CallBackForTask(task: TaskResolver.GiveTask, sendTo: ActorRef, pass: Any): TaskResolver.GiveTask = {
|
|
||||||
TaskResolver.GiveTask(
|
|
||||||
new Task() {
|
|
||||||
private val localDesc = task.task.Description
|
|
||||||
private val destination = sendTo
|
|
||||||
private val passMsg = pass
|
|
||||||
|
|
||||||
override def Description: String = s"callback for tasking $localDesc"
|
|
||||||
|
|
||||||
def Execute(resolver: ActorRef): Unit = {
|
|
||||||
destination ! passMsg
|
|
||||||
resolver ! Success(this)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
List(task)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
object CaptureFlagManager {
|
object CaptureFlagManager {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
package net.psforever.services.local.support
|
package net.psforever.services.local.support
|
||||||
|
|
||||||
import akka.actor.{Actor, ActorRef, Cancellable}
|
import akka.actor.{Actor, Cancellable}
|
||||||
import net.psforever.actors.zone.{BuildingActor, ZoneActor}
|
import net.psforever.actors.zone.{BuildingActor, ZoneActor}
|
||||||
import net.psforever.objects.serverobject.CommonMessages
|
import net.psforever.objects.serverobject.CommonMessages
|
||||||
import net.psforever.objects.serverobject.hackable.Hackable
|
import net.psforever.objects.serverobject.hackable.Hackable
|
||||||
|
|
@ -20,7 +20,7 @@ import scala.util.Random
|
||||||
/**
|
/**
|
||||||
* Responsible for handling the aspects related to hacking control consoles and capturing bases.
|
* Responsible for handling the aspects related to hacking control consoles and capturing bases.
|
||||||
*/
|
*/
|
||||||
class HackCaptureActor(val taskResolver: ActorRef) extends Actor {
|
class HackCaptureActor extends Actor {
|
||||||
private[this] val log = org.log4s.getLogger
|
private[this] val log = org.log4s.getLogger
|
||||||
|
|
||||||
private var clearTrigger: Cancellable = Default.Cancellable
|
private var clearTrigger: Cancellable = Default.Cancellable
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
// Copyright (c) 2017 PSForever
|
// Copyright (c) 2017 PSForever
|
||||||
package net.psforever.services.vehicle.support
|
package net.psforever.services.vehicle.support
|
||||||
|
|
||||||
import akka.actor.{ActorRef, Cancellable}
|
import akka.actor.Cancellable
|
||||||
import net.psforever.objects.equipment.EquipmentSlot
|
import net.psforever.objects.equipment.EquipmentSlot
|
||||||
import net.psforever.objects.{AmmoBox, Default, PlanetSideGameObject, Tool}
|
import net.psforever.objects.{AmmoBox, Default, PlanetSideGameObject, Tool}
|
||||||
import net.psforever.objects.guid.{GUIDTask, Task, TaskResolver}
|
import net.psforever.objects.guid._
|
||||||
import net.psforever.objects.serverobject.PlanetSideServerObject
|
import net.psforever.objects.serverobject.PlanetSideServerObject
|
||||||
import net.psforever.objects.serverobject.turret.{FacilityTurret, TurretUpgrade, WeaponTurret}
|
import net.psforever.objects.serverobject.turret.{FacilityTurret, TurretUpgrade, WeaponTurret}
|
||||||
import net.psforever.objects.vehicles.MountedWeapons
|
import net.psforever.objects.vehicles.MountedWeapons
|
||||||
|
|
@ -13,8 +13,8 @@ import net.psforever.types.PlanetSideGUID
|
||||||
import net.psforever.services.support.{SimilarityComparator, SupportActor, SupportActorCaseConversions}
|
import net.psforever.services.support.{SimilarityComparator, SupportActor, SupportActorCaseConversions}
|
||||||
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
|
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
|
||||||
|
|
||||||
|
import scala.concurrent.Future
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
import scala.util.Success
|
|
||||||
|
|
||||||
class TurretUpgrader extends SupportActor[TurretUpgrader.Entry] {
|
class TurretUpgrader extends SupportActor[TurretUpgrader.Entry] {
|
||||||
var task: Cancellable = Default.Cancellable
|
var task: Cancellable = Default.Cancellable
|
||||||
|
|
@ -172,39 +172,34 @@ class TurretUpgrader extends SupportActor[TurretUpgrader.Entry] {
|
||||||
|
|
||||||
val oldBoxesTask = oldBoxes
|
val oldBoxesTask = oldBoxes
|
||||||
.filterNot { box => newBoxes.exists(_ eq box) }
|
.filterNot { box => newBoxes.exists(_ eq box) }
|
||||||
.map(box => GUIDTask.UnregisterEquipment(box)(guid))
|
.map(box => GUIDTask.unregisterEquipment(guid, box))
|
||||||
.toList
|
.toList
|
||||||
val newBoxesTask = TaskResolver.GiveTask(
|
import scala.concurrent.ExecutionContext.Implicits.global
|
||||||
new Task() {
|
val newBoxesTask = TaskBundle(
|
||||||
|
new StraightforwardTask() {
|
||||||
private val localFunc: () => Unit = FinishUpgradingTurret(entry)
|
private val localFunc: () => Unit = FinishUpgradingTurret(entry)
|
||||||
|
|
||||||
override def isComplete = Task.Resolution.Success
|
def action(): Future[Any] = {
|
||||||
|
|
||||||
def Execute(resolver: ActorRef): Unit = {
|
|
||||||
resolver ! Success(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
override def onSuccess(): Unit = {
|
|
||||||
super.onSuccess()
|
|
||||||
localFunc()
|
localFunc()
|
||||||
|
Future(this)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
newBoxes
|
newBoxes
|
||||||
.filterNot { box => oldBoxes.exists(_ eq box) }
|
.filterNot { box => oldBoxes.exists(_ eq box) }
|
||||||
.map(box => GUIDTask.RegisterEquipment(box)(guid))
|
.map(box => GUIDTask.registerEquipment(guid, box))
|
||||||
.toList
|
.toList
|
||||||
)
|
)
|
||||||
target.Zone.tasks ! TaskResolver.GiveTask(
|
TaskWorkflow.execute(TaskBundle(
|
||||||
new Task() {
|
new StraightforwardTask() {
|
||||||
private val tasks = oldBoxesTask
|
private val tasks = oldBoxesTask
|
||||||
|
|
||||||
def Execute(resolver: ActorRef): Unit = {
|
def action(): Future[Any] = {
|
||||||
tasks.foreach { resolver ! _ }
|
tasks.foreach { TaskWorkflow.execute }
|
||||||
resolver ! Success(this)
|
Future(this)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
List(newBoxesTask)
|
newBoxesTask
|
||||||
)
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import io.circe.parser._
|
||||||
import net.psforever.objects.{GlobalDefinitions, LocalLockerItem, LocalProjectile}
|
import net.psforever.objects.{GlobalDefinitions, LocalLockerItem, LocalProjectile}
|
||||||
import net.psforever.objects.ballistics.Projectile
|
import net.psforever.objects.ballistics.Projectile
|
||||||
import net.psforever.objects.definition.BasicDefinition
|
import net.psforever.objects.definition.BasicDefinition
|
||||||
|
import net.psforever.objects.guid.selector.{NumberSelector, RandomSelector, SpecificSelector}
|
||||||
import net.psforever.objects.serverobject.doors.{Door, DoorDefinition, SpawnTubeDoor}
|
import net.psforever.objects.serverobject.doors.{Door, DoorDefinition, SpawnTubeDoor}
|
||||||
import net.psforever.objects.serverobject.generator.Generator
|
import net.psforever.objects.serverobject.generator.Generator
|
||||||
import net.psforever.objects.serverobject.llu.{CaptureFlagSocket, CaptureFlagSocketDefinition}
|
import net.psforever.objects.serverobject.llu.{CaptureFlagSocket, CaptureFlagSocketDefinition}
|
||||||
|
|
@ -55,6 +56,25 @@ object Zones {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private case class GuidNumberPool(
|
||||||
|
name: String,
|
||||||
|
start: Int,
|
||||||
|
max: Int,
|
||||||
|
selector: String
|
||||||
|
) {
|
||||||
|
def getSelector() : NumberSelector = {
|
||||||
|
if (selector.equals("random")) new RandomSelector
|
||||||
|
else new SpecificSelector
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private implicit val decodeNumberPool: Decoder[GuidNumberPool] = Decoder.forProduct4(
|
||||||
|
"Name",
|
||||||
|
"Start",
|
||||||
|
"Max",
|
||||||
|
"Selector"
|
||||||
|
)(GuidNumberPool.apply)
|
||||||
|
|
||||||
private implicit val decodeZoneMapEntity: Decoder[ZoneMapEntity] = Decoder.forProduct11(
|
private implicit val decodeZoneMapEntity: Decoder[ZoneMapEntity] = Decoder.forProduct11(
|
||||||
"Id",
|
"Id",
|
||||||
"ObjectName",
|
"ObjectName",
|
||||||
|
|
@ -585,44 +605,84 @@ object Zones {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy val zones: Seq[Zone] = ZoneInfo.values.map { info =>
|
lazy val zones: Seq[Zone] = {
|
||||||
new Zone(info.id, zoneMaps.find(_.name == info.map.value).get, info.value) {
|
val defaultGuids =
|
||||||
override def init(implicit context: ActorContext): Unit = {
|
try {
|
||||||
super.init(context)
|
val res = Source.fromResource("guid-pools/default.json")
|
||||||
|
val json = res.mkString
|
||||||
|
res.close()
|
||||||
|
decode[Seq[GuidNumberPool]](json).toOption.get
|
||||||
|
} catch {
|
||||||
|
case _: Exception => Seq()
|
||||||
|
}
|
||||||
|
|
||||||
if (!info.id.startsWith("tz")) {
|
ZoneInfo.values.map { info =>
|
||||||
this.HotSpotCoordinateFunction = Zones.HotSpots.standardRemapping(info.map.scale, 80, 80)
|
val guids =
|
||||||
this.HotSpotTimeFunction = Zones.HotSpots.standardTimeRules
|
try {
|
||||||
Zones.initZoneAmenities(this)
|
val res = Source.fromResource(s"guid-pools/${info.id}.json")
|
||||||
|
val json = res.mkString
|
||||||
|
res.close()
|
||||||
|
val custom = decode[Seq[GuidNumberPool]](json).toOption.get
|
||||||
|
customizePools(defaultGuids, custom)
|
||||||
|
} catch {
|
||||||
|
case _: Exception => defaultGuids
|
||||||
}
|
}
|
||||||
|
|
||||||
info.id match {
|
new Zone(info.id, zoneMaps.find(_.name.equals(info.map.value)).get, info.value) {
|
||||||
case "home1" =>
|
private val addPoolsFunc: () => Unit = addPools(guids, zone = this)
|
||||||
this.Buildings.values.foreach(_.Faction = PlanetSideEmpire.NC)
|
|
||||||
case "home2" =>
|
|
||||||
this.Buildings.values.foreach(_.Faction = PlanetSideEmpire.TR)
|
|
||||||
case "home3" =>
|
|
||||||
this.Buildings.values.foreach(_.Faction = PlanetSideEmpire.VS)
|
|
||||||
case zoneid if zoneid.startsWith("c") =>
|
|
||||||
this.map.cavern = true
|
|
||||||
case _ => ()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set up warp gate factions aka "sanctuary link". Those names make no sense anymore, don't even ask.
|
override def SetupNumberPools() : Unit = addPoolsFunc()
|
||||||
this.Buildings.foreach {
|
|
||||||
case (_, building) if building.Name.startsWith("WG") =>
|
override def init(implicit context: ActorContext): Unit = {
|
||||||
building.Name match {
|
super.init(context)
|
||||||
case "WG_Amerish_to_Solsar" | "WG_Esamir_to_VSSanc" => building.Faction = PlanetSideEmpire.NC
|
|
||||||
case "WG_Hossin_to_VSSanc" | "WG_Solsar_to_Amerish" => building.Faction = PlanetSideEmpire.TR
|
if (!info.id.startsWith("tz")) {
|
||||||
case "WG_Ceryshen_to_Hossin" | "WG_Forseral_to_Solsar" => building.Faction = PlanetSideEmpire.VS
|
this.HotSpotCoordinateFunction = Zones.HotSpots.standardRemapping(info.map.scale, 80, 80)
|
||||||
case _ => ()
|
this.HotSpotTimeFunction = Zones.HotSpots.standardTimeRules
|
||||||
}
|
Zones.initZoneAmenities(this)
|
||||||
case _ => ()
|
}
|
||||||
|
|
||||||
|
info.id match {
|
||||||
|
case "home1" =>
|
||||||
|
this.Buildings.values.foreach(_.Faction = PlanetSideEmpire.NC)
|
||||||
|
case "home2" =>
|
||||||
|
this.Buildings.values.foreach(_.Faction = PlanetSideEmpire.TR)
|
||||||
|
case "home3" =>
|
||||||
|
this.Buildings.values.foreach(_.Faction = PlanetSideEmpire.VS)
|
||||||
|
case zoneid if zoneid.startsWith("c") =>
|
||||||
|
this.map.cavern = true
|
||||||
|
case _ => ;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up warp gate factions aka "sanctuary link". Those names make no sense anymore, don't even ask.
|
||||||
|
this.Buildings.foreach {
|
||||||
|
case (_, building) if building.Name.startsWith("WG") =>
|
||||||
|
building.Name match {
|
||||||
|
case "WG_Amerish_to_Solsar" | "WG_Esamir_to_VSSanc" => building.Faction = PlanetSideEmpire.NC
|
||||||
|
case "WG_Hossin_to_VSSanc" | "WG_Solsar_to_Amerish" => building.Faction = PlanetSideEmpire.TR
|
||||||
|
case "WG_Ceryshen_to_Hossin" | "WG_Forseral_to_Solsar" => building.Faction = PlanetSideEmpire.VS
|
||||||
|
case _ => ;
|
||||||
|
}
|
||||||
|
case _ => ;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private def customizePools(base: Seq[GuidNumberPool], custom: Seq[GuidNumberPool]): Seq[GuidNumberPool] = {
|
||||||
|
val exclude = custom.map { _.name }
|
||||||
|
val remainder = base.filterNot { entry => exclude.contains { entry.name } }
|
||||||
|
custom ++ remainder
|
||||||
|
}
|
||||||
|
|
||||||
|
private def addPools(guids: Seq[GuidNumberPool], zone: Zone)(): Unit = {
|
||||||
|
guids.foreach { entry =>
|
||||||
|
zone.AddPool(name = entry.name, (entry.start to entry.max).toList)
|
||||||
|
.foreach { _.Selector = entry.getSelector() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
def initZoneAmenities(zone: Zone): Unit = {
|
def initZoneAmenities(zone: Zone): Unit = {
|
||||||
initResourceSilos(zone)
|
initResourceSilos(zone)
|
||||||
initWarpGates(zone)
|
initWarpGates(zone)
|
||||||
|
|
|
||||||
|
|
@ -1614,7 +1614,6 @@ class DamageableVehicleDestroyMountedTest extends FreedContextActorTest {
|
||||||
override def Activity = activityProbe.ref
|
override def Activity = activityProbe.ref
|
||||||
override def AvatarEvents = avatarProbe.ref
|
override def AvatarEvents = avatarProbe.ref
|
||||||
override def VehicleEvents = vehicleProbe.ref
|
override def VehicleEvents = vehicleProbe.ref
|
||||||
override def tasks = catchall.ref
|
|
||||||
import akka.actor.typed.scaladsl.adapter._
|
import akka.actor.typed.scaladsl.adapter._
|
||||||
this.actor = catchall.ref.toTyped[ZoneActor.Command]
|
this.actor = catchall.ref.toTyped[ZoneActor.Command]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import net.psforever.actors.zone.ZoneActor
|
||||||
import net.psforever.objects.avatar.{Avatar, Certification, PlayerControl}
|
import net.psforever.objects.avatar.{Avatar, Certification, PlayerControl}
|
||||||
import net.psforever.objects.{ConstructionItem, Deployables, GlobalDefinitions, Player}
|
import net.psforever.objects.{ConstructionItem, Deployables, GlobalDefinitions, Player}
|
||||||
import net.psforever.objects.ce.{Deployable, DeployedItem}
|
import net.psforever.objects.ce.{Deployable, DeployedItem}
|
||||||
import net.psforever.objects.guid.{NumberPoolHub, TaskResolver}
|
import net.psforever.objects.guid.NumberPoolHub
|
||||||
import net.psforever.objects.guid.source.MaxNumberSource
|
import net.psforever.objects.guid.source.MaxNumberSource
|
||||||
import net.psforever.objects.zones.{Zone, ZoneDeployableActor, ZoneMap}
|
import net.psforever.objects.zones.{Zone, ZoneDeployableActor, ZoneMap}
|
||||||
import net.psforever.packet.game._
|
import net.psforever.packet.game._
|
||||||
|
|
@ -141,7 +141,6 @@ class DeployableBehaviorSetupOwnedP2Test extends FreedContextActorTest {
|
||||||
override def Deployables: ActorRef = deployables
|
override def Deployables: ActorRef = deployables
|
||||||
override def Players = List(avatar)
|
override def Players = List(avatar)
|
||||||
override def LivePlayers = List(player)
|
override def LivePlayers = List(player)
|
||||||
override def tasks: ActorRef = eventsProbe.ref
|
|
||||||
|
|
||||||
this.actor = new TestProbe(system).ref.toTyped[ZoneActor.Command]
|
this.actor = new TestProbe(system).ref.toTyped[ZoneActor.Command]
|
||||||
}
|
}
|
||||||
|
|
@ -165,7 +164,7 @@ class DeployableBehaviorSetupOwnedP2Test extends FreedContextActorTest {
|
||||||
assert(!avatar.deployables.Contains(jmine), "owned setup test, 2 - avatar already owns deployable")
|
assert(!avatar.deployables.Contains(jmine), "owned setup test, 2 - avatar already owns deployable")
|
||||||
zone.Deployables ! Zone.Deployable.BuildByOwner(jmine, player, citem)
|
zone.Deployables ! Zone.Deployable.BuildByOwner(jmine, player, citem)
|
||||||
//assert(false, "test needs to be fixed")
|
//assert(false, "test needs to be fixed")
|
||||||
val eventsMsgs = eventsProbe.receiveN(8, 10.seconds)
|
val eventsMsgs = eventsProbe.receiveN(7, 10.seconds)
|
||||||
eventsMsgs.head match {
|
eventsMsgs.head match {
|
||||||
case AvatarServiceMessage(
|
case AvatarServiceMessage(
|
||||||
"TestCharacter1",
|
"TestCharacter1",
|
||||||
|
|
@ -220,11 +219,6 @@ class DeployableBehaviorSetupOwnedP2Test extends FreedContextActorTest {
|
||||||
case _ =>
|
case _ =>
|
||||||
assert(false, "owned setup test, 2 - construction tool not deleted")
|
assert(false, "owned setup test, 2 - construction tool not deleted")
|
||||||
}
|
}
|
||||||
eventsMsgs(7) match {
|
|
||||||
case TaskResolver.GiveTask(_, _) => ;
|
|
||||||
case _ =>
|
|
||||||
assert(false, "owned setup test, 2 - construction tool not unregistered")
|
|
||||||
}
|
|
||||||
assert(player.Slot(slot = 0).Equipment.isEmpty, "owned setup test, 2 - player hand should be empty")
|
assert(player.Slot(slot = 0).Equipment.isEmpty, "owned setup test, 2 - player hand should be empty")
|
||||||
assert(deployableList.contains(jmine), "owned setup test, 2 - deployable not appended to list")
|
assert(deployableList.contains(jmine), "owned setup test, 2 - deployable not appended to list")
|
||||||
assert(avatar.deployables.Contains(jmine), "owned setup test, 2 - avatar does not own deployable")
|
assert(avatar.deployables.Contains(jmine), "owned setup test, 2 - avatar does not own deployable")
|
||||||
|
|
@ -244,7 +238,6 @@ class DeployableBehaviorDeconstructTest extends ActorTest {
|
||||||
GUID(guid)
|
GUID(guid)
|
||||||
override def AvatarEvents: ActorRef = eventsProbe.ref
|
override def AvatarEvents: ActorRef = eventsProbe.ref
|
||||||
override def LocalEvents: ActorRef = eventsProbe.ref
|
override def LocalEvents: ActorRef = eventsProbe.ref
|
||||||
override def tasks: ActorRef = eventsProbe.ref
|
|
||||||
override def Deployables: ActorRef = deployables
|
override def Deployables: ActorRef = deployables
|
||||||
|
|
||||||
this.actor = new TestProbe(system).ref.toTyped[ZoneActor.Command]
|
this.actor = new TestProbe(system).ref.toTyped[ZoneActor.Command]
|
||||||
|
|
@ -261,7 +254,7 @@ class DeployableBehaviorDeconstructTest extends ActorTest {
|
||||||
assert(deployableList.contains(jmine), "deconstruct test - deployable not appended to list")
|
assert(deployableList.contains(jmine), "deconstruct test - deployable not appended to list")
|
||||||
|
|
||||||
jmine.Actor ! Deployable.Deconstruct()
|
jmine.Actor ! Deployable.Deconstruct()
|
||||||
val eventsMsgs = eventsProbe.receiveN(3, 10.seconds)
|
val eventsMsgs = eventsProbe.receiveN(2, 10.seconds)
|
||||||
eventsMsgs.head match {
|
eventsMsgs.head match {
|
||||||
case LocalServiceMessage("test", LocalAction.EliminateDeployable(`jmine`, PlanetSideGUID(1), Vector3(1,2,3), 2)) => ;
|
case LocalServiceMessage("test", LocalAction.EliminateDeployable(`jmine`, PlanetSideGUID(1), Vector3(1,2,3), 2)) => ;
|
||||||
case _ => assert(false, "deconstruct test - not eliminating deployable")
|
case _ => assert(false, "deconstruct test - not eliminating deployable")
|
||||||
|
|
@ -277,10 +270,6 @@ class DeployableBehaviorDeconstructTest extends ActorTest {
|
||||||
) => ;
|
) => ;
|
||||||
case _ => assert(false, "owned deconstruct test - not removing icon")
|
case _ => assert(false, "owned deconstruct test - not removing icon")
|
||||||
}
|
}
|
||||||
eventsMsgs(2) match {
|
|
||||||
case TaskResolver.GiveTask(_, _) => ;
|
|
||||||
case _ => assert(false, "deconstruct test - not unregistering deployable")
|
|
||||||
}
|
|
||||||
assert(!deployableList.contains(jmine), "deconstruct test - deployable not removed from list")
|
assert(!deployableList.contains(jmine), "deconstruct test - deployable not removed from list")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -304,7 +293,6 @@ class DeployableBehaviorDeconstructOwnedTest extends FreedContextActorTest {
|
||||||
override def Deployables: ActorRef = deployables
|
override def Deployables: ActorRef = deployables
|
||||||
override def Players = List(avatar)
|
override def Players = List(avatar)
|
||||||
override def LivePlayers = List(player)
|
override def LivePlayers = List(player)
|
||||||
override def tasks: ActorRef = eventsProbe.ref
|
|
||||||
|
|
||||||
this.actor = new TestProbe(system).ref.toTyped[ZoneActor.Command]
|
this.actor = new TestProbe(system).ref.toTyped[ZoneActor.Command]
|
||||||
}
|
}
|
||||||
|
|
@ -324,12 +312,12 @@ class DeployableBehaviorDeconstructOwnedTest extends FreedContextActorTest {
|
||||||
"DeployableBehavior" should {
|
"DeployableBehavior" should {
|
||||||
"deconstruct and alert owner" in {
|
"deconstruct and alert owner" in {
|
||||||
zone.Deployables ! Zone.Deployable.BuildByOwner(jmine, player, citem)
|
zone.Deployables ! Zone.Deployable.BuildByOwner(jmine, player, citem)
|
||||||
eventsProbe.receiveN(8, 10.seconds)
|
eventsProbe.receiveN(7, 10.seconds)
|
||||||
assert(deployableList.contains(jmine), "owned deconstruct test - deployable not appended to list")
|
assert(deployableList.contains(jmine), "owned deconstruct test - deployable not appended to list")
|
||||||
assert(avatar.deployables.Contains(jmine), "owned deconstruct test - avatar does not own deployable")
|
assert(avatar.deployables.Contains(jmine), "owned deconstruct test - avatar does not own deployable")
|
||||||
|
|
||||||
jmine.Actor ! Deployable.Deconstruct()
|
jmine.Actor ! Deployable.Deconstruct()
|
||||||
val eventsMsgs = eventsProbe.receiveN(4, 10.seconds)
|
val eventsMsgs = eventsProbe.receiveN(3, 10.seconds)
|
||||||
eventsMsgs.head match {
|
eventsMsgs.head match {
|
||||||
case LocalServiceMessage("test", LocalAction.EliminateDeployable(`jmine`, PlanetSideGUID(1), Vector3(1,2,3), 2)) => ;
|
case LocalServiceMessage("test", LocalAction.EliminateDeployable(`jmine`, PlanetSideGUID(1), Vector3(1,2,3), 2)) => ;
|
||||||
case _ => assert(false, "owned deconstruct test - not eliminating deployable")
|
case _ => assert(false, "owned deconstruct test - not eliminating deployable")
|
||||||
|
|
@ -349,10 +337,6 @@ class DeployableBehaviorDeconstructOwnedTest extends FreedContextActorTest {
|
||||||
) => ;
|
) => ;
|
||||||
case _ => assert(false, "owned deconstruct test - not removing icon")
|
case _ => assert(false, "owned deconstruct test - not removing icon")
|
||||||
}
|
}
|
||||||
eventsMsgs(3) match {
|
|
||||||
case TaskResolver.GiveTask(_, _) => ;
|
|
||||||
case _ => assert(false, "owned deconstruct test - not unregistering deployable")
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(deployableList.isEmpty, "owned deconstruct test - deployable still in list")
|
assert(deployableList.isEmpty, "owned deconstruct test - deployable still in list")
|
||||||
assert(!avatar.deployables.Contains(jmine), "owned deconstruct test - avatar still owns deployable")
|
assert(!avatar.deployables.Contains(jmine), "owned deconstruct test - avatar still owns deployable")
|
||||||
|
|
|
||||||
|
|
@ -330,7 +330,6 @@ class ExplosiveDeployableJammerTest extends ActorTest {
|
||||||
override def Deployables: ActorRef = deployables
|
override def Deployables: ActorRef = deployables
|
||||||
override def Players = List(avatar1, avatar2)
|
override def Players = List(avatar1, avatar2)
|
||||||
override def LivePlayers = List(player1, player2)
|
override def LivePlayers = List(player1, player2)
|
||||||
override def tasks: ActorRef = eventsProbe.ref
|
|
||||||
}
|
}
|
||||||
player1.Spawn()
|
player1.Spawn()
|
||||||
player2.Spawn()
|
player2.Spawn()
|
||||||
|
|
@ -408,7 +407,6 @@ class ExplosiveDeployableJammerExplodeTest extends ActorTest {
|
||||||
override def Deployables: ActorRef = deployables
|
override def Deployables: ActorRef = deployables
|
||||||
override def Players = List(avatar1, avatar2)
|
override def Players = List(avatar1, avatar2)
|
||||||
override def LivePlayers = List(player1, player2)
|
override def LivePlayers = List(player1, player2)
|
||||||
override def tasks: ActorRef = eventsProbe.ref
|
|
||||||
|
|
||||||
this.actor = new TestProbe(system).ref.toTyped[ZoneActor.Command]
|
this.actor = new TestProbe(system).ref.toTyped[ZoneActor.Command]
|
||||||
}
|
}
|
||||||
|
|
@ -518,7 +516,6 @@ class ExplosiveDeployableDestructionTest extends ActorTest {
|
||||||
override def Deployables: ActorRef = deployables
|
override def Deployables: ActorRef = deployables
|
||||||
override def Players = List(avatar1, avatar2)
|
override def Players = List(avatar1, avatar2)
|
||||||
override def LivePlayers = List(player1, player2)
|
override def LivePlayers = List(player1, player2)
|
||||||
override def tasks: ActorRef = eventsProbe.ref
|
|
||||||
}
|
}
|
||||||
player1.Spawn()
|
player1.Spawn()
|
||||||
player1.Actor = player1Probe.ref
|
player1.Actor = player1Probe.ref
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,11 @@
|
||||||
package objects
|
package objects
|
||||||
|
|
||||||
import akka.actor.{ActorRef, Props}
|
import akka.actor.{ActorRef, Props}
|
||||||
import akka.routing.RandomPool
|
|
||||||
import akka.testkit.TestProbe
|
import akka.testkit.TestProbe
|
||||||
import base.FreedContextActorTest
|
import base.FreedContextActorTest
|
||||||
import net.psforever.actors.zone.{BuildingActor, ZoneActor}
|
import net.psforever.actors.zone.{BuildingActor, ZoneActor}
|
||||||
import net.psforever.objects.guid.actor.UniqueNumberSystem
|
|
||||||
import net.psforever.objects.{GlobalDefinitions, Vehicle}
|
import net.psforever.objects.{GlobalDefinitions, Vehicle}
|
||||||
import net.psforever.objects.guid.{NumberPoolHub, TaskResolver}
|
import net.psforever.objects.guid.{NumberPoolHub, UniqueNumberOps, UniqueNumberSetup}
|
||||||
import net.psforever.objects.guid.source.MaxNumberSource
|
import net.psforever.objects.guid.source.MaxNumberSource
|
||||||
import net.psforever.objects.serverobject.doors.Door
|
import net.psforever.objects.serverobject.doors.Door
|
||||||
import net.psforever.objects.serverobject.shuttle.{OrbitalShuttle, OrbitalShuttlePad, OrbitalShuttlePadControl, ShuttleAmenity}
|
import net.psforever.objects.serverobject.shuttle.{OrbitalShuttle, OrbitalShuttlePad, OrbitalShuttlePadControl, ShuttleAmenity}
|
||||||
|
|
@ -23,7 +21,7 @@ import scala.collection.concurrent.TrieMap
|
||||||
import scala.collection.mutable.ListBuffer
|
import scala.collection.mutable.ListBuffer
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
|
|
||||||
class OrbitalShuttlePadControltest extends FreedContextActorTest {
|
class OrbitalShuttlePadControlTest extends FreedContextActorTest {
|
||||||
import akka.actor.typed.scaladsl.adapter._
|
import akka.actor.typed.scaladsl.adapter._
|
||||||
system.spawn(InterstellarClusterService(Nil), InterstellarClusterService.InterstellarClusterServiceKey.id)
|
system.spawn(InterstellarClusterService(Nil), InterstellarClusterService.InterstellarClusterServiceKey.id)
|
||||||
val services = ServiceManager.boot(system)
|
val services = ServiceManager.boot(system)
|
||||||
|
|
@ -32,22 +30,17 @@ class OrbitalShuttlePadControltest extends FreedContextActorTest {
|
||||||
expectNoMessage(1000 milliseconds)
|
expectNoMessage(1000 milliseconds)
|
||||||
var buildingMap = new TrieMap[Int, Building]()
|
var buildingMap = new TrieMap[Int, Building]()
|
||||||
val vehicles = ListBuffer[Vehicle]()
|
val vehicles = ListBuffer[Vehicle]()
|
||||||
val guid = new NumberPoolHub(new MaxNumberSource(max = 15))
|
val guid = new NumberPoolHub(new MaxNumberSource(max = 20))
|
||||||
guid.AddPool("dynamic", (11 to 15).toList)
|
guid.AddPool("vehicles", (11 to 15).toList)
|
||||||
|
guid.AddPool("tools", (16 to 19).toList)
|
||||||
val catchall = new TestProbe(system).ref
|
val catchall = new TestProbe(system).ref
|
||||||
val resolver = context.actorOf(RandomPool(1).props(Props[TaskResolver]()), s"test-taskResolver")
|
val unops = new UniqueNumberOps(guid, UniqueNumberSetup.AllocateNumberPoolActors(context, guid))
|
||||||
val uns = context.actorOf(
|
|
||||||
RandomPool(1).props(
|
|
||||||
Props(classOf[UniqueNumberSystem], guid, UniqueNumberSystem.AllocateNumberPoolActors(this.guid))
|
|
||||||
),
|
|
||||||
s"test-uns"
|
|
||||||
)
|
|
||||||
val zone = new Zone("test", new ZoneMap("test-map"), 0) {
|
val zone = new Zone("test", new ZoneMap("test-map"), 0) {
|
||||||
val transport: ActorRef = context.actorOf(Props(classOf[ZoneVehicleActor], this, vehicles), s"zone-test-vehicles")
|
val transport: ActorRef = context.actorOf(Props(classOf[ZoneVehicleActor], this, vehicles), s"zone-test-vehicles")
|
||||||
|
|
||||||
override def SetupNumberPools() = {}
|
override def SetupNumberPools() = {}
|
||||||
GUID(guid)
|
GUID(guid)
|
||||||
override def GUID = { uns }
|
override def GUID = { unops }
|
||||||
override def AvatarEvents = catchall
|
override def AvatarEvents = catchall
|
||||||
override def LocalEvents = catchall
|
override def LocalEvents = catchall
|
||||||
override def VehicleEvents = catchall
|
override def VehicleEvents = catchall
|
||||||
|
|
@ -55,7 +48,6 @@ class OrbitalShuttlePadControltest extends FreedContextActorTest {
|
||||||
override def Transport = { transport }
|
override def Transport = { transport }
|
||||||
override def Vehicles = { vehicles.toList }
|
override def Vehicles = { vehicles.toList }
|
||||||
override def Buildings = { buildingMap.toMap }
|
override def Buildings = { buildingMap.toMap }
|
||||||
override def tasks = { resolver }
|
|
||||||
|
|
||||||
import akka.actor.typed.scaladsl.adapter._
|
import akka.actor.typed.scaladsl.adapter._
|
||||||
this.actor = new TestProbe(system).ref.toTyped[ZoneActor.Command]
|
this.actor = new TestProbe(system).ref.toTyped[ZoneActor.Command]
|
||||||
|
|
|
||||||
116
src/test/scala/objects/TaskWorkflowTest.scala
Normal file
116
src/test/scala/objects/TaskWorkflowTest.scala
Normal file
|
|
@ -0,0 +1,116 @@
|
||||||
|
// Copyright (c) 2021 PSForever
|
||||||
|
package objects
|
||||||
|
|
||||||
|
import net.psforever.objects.guid.{Task, TaskBundle, TaskWorkflow}
|
||||||
|
import org.scalatest.flatspec.AsyncFlatSpec
|
||||||
|
|
||||||
|
import scala.concurrent.Future
|
||||||
|
import scala.util.Failure
|
||||||
|
|
||||||
|
class AsyncTaskWorkflowTest extends AsyncFlatSpec {
|
||||||
|
case class StringAppendTask(product: StringBuilder, str: String) extends Task {
|
||||||
|
def action() = { Future({ product.append(str) }) }
|
||||||
|
def undo() = {
|
||||||
|
val index = product.indexOf(str)
|
||||||
|
product.replace(index, index + str.length, "[successful task undo]")
|
||||||
|
}
|
||||||
|
def isSuccessful() = { product.indexOf(str) > -1 }
|
||||||
|
}
|
||||||
|
|
||||||
|
case class FailedStringAppendTask(product: StringBuilder, str: String) extends Task {
|
||||||
|
def action() = { Future(Failure(new Exception("intentional failure"))) }
|
||||||
|
def undo() = {
|
||||||
|
val index = product.indexOf(str)
|
||||||
|
product.replace(index, index + str.length, "[failed task undo]")
|
||||||
|
}
|
||||||
|
def isSuccessful() = { product.indexOf(str) > -1 }
|
||||||
|
}
|
||||||
|
|
||||||
|
behavior of "TaskWorkFlow"
|
||||||
|
|
||||||
|
it should "append a string as a task" in {
|
||||||
|
val test: StringBuilder = new StringBuilder()
|
||||||
|
assert(test.mkString.isEmpty)
|
||||||
|
val result = TaskWorkflow.execute(TaskBundle(StringAppendTask(test, "hello")))
|
||||||
|
result map { _ =>
|
||||||
|
assert(test.mkString.equals("hello"), "async result does not equal 'hello'")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
it should "append the strings in order of subtask then main task" in {
|
||||||
|
val test: StringBuilder = new StringBuilder()
|
||||||
|
assert(test.mkString.isEmpty)
|
||||||
|
val result = TaskWorkflow.execute(TaskBundle(StringAppendTask(test, " world"), StringAppendTask(test, "hello")))
|
||||||
|
result map { _ =>
|
||||||
|
assert(test.mkString.equals("hello world"), "async result does not equal 'hello world'")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
it should "append the strings in order of subtasks then main task, with the subtasks being in either order" in {
|
||||||
|
val test: StringBuilder = new StringBuilder()
|
||||||
|
assert(test.mkString.isEmpty)
|
||||||
|
val result = TaskWorkflow.execute(TaskBundle(
|
||||||
|
StringAppendTask(test, " world"),
|
||||||
|
Seq(
|
||||||
|
TaskBundle(StringAppendTask(test, " hello")),
|
||||||
|
TaskBundle(StringAppendTask(test, " or goodbye"))
|
||||||
|
)
|
||||||
|
))
|
||||||
|
result map { _ =>
|
||||||
|
val output = test.mkString
|
||||||
|
assert(
|
||||||
|
output.equals(" or goodbye hello world") || output.equals(" hello or goodbye world"),
|
||||||
|
s"async result '$output' does not equal either pattern"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
it should "if a task fails, do not undo it" in {
|
||||||
|
val test: StringBuilder = new StringBuilder()
|
||||||
|
assert(test.mkString.isEmpty)
|
||||||
|
val result = TaskWorkflow.execute(TaskBundle(FailedStringAppendTask(test, " world")))
|
||||||
|
result map { _ =>
|
||||||
|
val output = test.mkString
|
||||||
|
assert(output.equals(""),"async result was written when should have not been written")
|
||||||
|
//see implementation of FailedStringAppendTask.undo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
it should "if a middling subtask fails, its parent task will not be executed or undone, but its own subtask will be undone (1)" in {
|
||||||
|
val test: StringBuilder = new StringBuilder()
|
||||||
|
assert(test.mkString.isEmpty)
|
||||||
|
val result = TaskWorkflow.execute(TaskBundle(
|
||||||
|
StringAppendTask(test, " world"),
|
||||||
|
TaskBundle(FailedStringAppendTask(test, "hello"), StringAppendTask(test, " or goodbye")))
|
||||||
|
)
|
||||||
|
result map { _ =>
|
||||||
|
val output = test.mkString
|
||||||
|
assert(output.equals("[successful task undo]"),s"async result, formerly successful, was written as if it had failed - $output")
|
||||||
|
//see implementation of StringAppendTask.undo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
it should "if a middling subtask fails, its parent task will not be executed or undone, but its own subtasks will be undone (2)" in {
|
||||||
|
val test: StringBuilder = new StringBuilder()
|
||||||
|
assert(test.mkString.isEmpty)
|
||||||
|
val result = TaskWorkflow.execute(TaskBundle(
|
||||||
|
StringAppendTask(test, " world"),
|
||||||
|
TaskBundle(FailedStringAppendTask(test, "hello"), List(
|
||||||
|
TaskBundle(StringAppendTask(test, " or goodbye")),
|
||||||
|
TaskBundle(StringAppendTask(test, " or something"))
|
||||||
|
))
|
||||||
|
))
|
||||||
|
result map { _ =>
|
||||||
|
val output = test.mkString
|
||||||
|
assert(
|
||||||
|
output.equals("[successful task undo][successful task undo]"),
|
||||||
|
s"async result, formerly successful, was written as if it had failed - $output"
|
||||||
|
)
|
||||||
|
//see implementation of StringAppendTask.undo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object TaskWorkflowTest {
|
||||||
|
/** placeholder */
|
||||||
|
}
|
||||||
|
|
@ -212,7 +212,6 @@ class VehicleControlPrepareForDeletionMountedCargoTest extends FreedContextActor
|
||||||
|
|
||||||
override def SetupNumberPools(): Unit = {}
|
override def SetupNumberPools(): Unit = {}
|
||||||
override def VehicleEvents = vehicleProbe.ref
|
override def VehicleEvents = vehicleProbe.ref
|
||||||
override def tasks = catchall.ref
|
|
||||||
}
|
}
|
||||||
zone.actor = system.spawn(ZoneActor(zone), "test-zone-actor")
|
zone.actor = system.spawn(ZoneActor(zone), "test-zone-actor")
|
||||||
// crappy workaround but without it the zone doesn't get initialized in time
|
// crappy workaround but without it the zone doesn't get initialized in time
|
||||||
|
|
|
||||||
|
|
@ -130,7 +130,7 @@ class ZoneActorTest extends ActorTest {
|
||||||
zone.actor = system.spawn(ZoneActor(zone), "test-add-pool-actor-init")
|
zone.actor = system.spawn(ZoneActor(zone), "test-add-pool-actor-init")
|
||||||
expectNoMessage(Duration.create(500, "ms"))
|
expectNoMessage(Duration.create(500, "ms"))
|
||||||
|
|
||||||
assert(!zone.AddPool("test1", 1 to 2))
|
assert(zone.AddPool("test1", 1 to 2).isEmpty)
|
||||||
}
|
}
|
||||||
|
|
||||||
"refuse to remove number pools after the Actor is started" in {
|
"refuse to remove number pools after the Actor is started" in {
|
||||||
|
|
|
||||||
|
|
@ -3,19 +3,21 @@ package objects.guidtask
|
||||||
|
|
||||||
import base.ActorTest
|
import base.ActorTest
|
||||||
import net.psforever.objects._
|
import net.psforever.objects._
|
||||||
import net.psforever.objects.guid.{GUIDTask, TaskResolver}
|
import net.psforever.objects.guid.{GUIDTask, TaskBundle, TaskWorkflow}
|
||||||
|
|
||||||
|
import scala.concurrent.duration._
|
||||||
|
|
||||||
class GUIDTaskRegisterAmmoTest extends ActorTest {
|
class GUIDTaskRegisterAmmoTest extends ActorTest {
|
||||||
"RegisterEquipment -> RegisterObjectTask" in {
|
"RegisterEquipment -> RegisterObjectTask" in {
|
||||||
val (_, uns, taskResolver, probe) = GUIDTaskTest.CommonTestSetup
|
val (_, uns, probe) = GUIDTaskTest.CommonTestSetup
|
||||||
val obj = AmmoBox(GlobalDefinitions.energy_cell)
|
val obj = AmmoBox(GlobalDefinitions.energy_cell)
|
||||||
|
|
||||||
assert(!obj.HasGUID)
|
assert(!obj.HasGUID)
|
||||||
taskResolver ! TaskResolver.GiveTask(
|
TaskWorkflow.execute(TaskBundle(
|
||||||
new GUIDTaskTest.RegisterTestTask(probe.ref),
|
new GUIDTaskTest.RegisterTestTask(probe.ref),
|
||||||
List(GUIDTask.RegisterEquipment(obj)(uns))
|
GUIDTask.registerEquipment(uns, obj)
|
||||||
)
|
))
|
||||||
probe.expectMsg(scala.util.Success)
|
probe.expectMsg(5.second, scala.util.Success(true))
|
||||||
assert(obj.HasGUID)
|
assert(obj.HasGUID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,15 +4,17 @@ package objects.guidtask
|
||||||
import base.ActorTest
|
import base.ActorTest
|
||||||
import net.psforever.objects._
|
import net.psforever.objects._
|
||||||
import net.psforever.objects.avatar.Avatar
|
import net.psforever.objects.avatar.Avatar
|
||||||
import net.psforever.objects.guid.{GUIDTask, TaskResolver}
|
import net.psforever.objects.guid.{GUIDTask, TaskBundle, TaskWorkflow}
|
||||||
import net.psforever.objects.locker.LockerEquipment
|
import net.psforever.objects.locker.LockerEquipment
|
||||||
import net.psforever.types.{CharacterSex, CharacterVoice, PlanetSideEmpire}
|
import net.psforever.types.{CharacterSex, CharacterVoice, PlanetSideEmpire}
|
||||||
|
|
||||||
|
import scala.concurrent.duration._
|
||||||
|
|
||||||
class GUIDTaskRegisterAvatarTest extends ActorTest {
|
class GUIDTaskRegisterAvatarTest extends ActorTest {
|
||||||
"RegisterAvatar" in {
|
"RegisterAvatar" in {
|
||||||
val (_, uns, taskResolver, probe) = GUIDTaskTest.CommonTestSetup
|
val (_, uns, probe) = GUIDTaskTest.CommonTestSetup
|
||||||
val obj = Player(Avatar(0, "test", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute))
|
val obj = Player(Avatar(0, "test", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute))
|
||||||
val obj_wep = Tool(GlobalDefinitions.beamer)
|
val obj_wep = Tool(GlobalDefinitions.beamer)
|
||||||
obj.Slot(0).Equipment = obj_wep
|
obj.Slot(0).Equipment = obj_wep
|
||||||
val obj_wep_ammo = AmmoBox(GlobalDefinitions.energy_cell)
|
val obj_wep_ammo = AmmoBox(GlobalDefinitions.energy_cell)
|
||||||
obj_wep.AmmoSlots.head.Box = obj_wep_ammo
|
obj_wep.AmmoSlots.head.Box = obj_wep_ammo
|
||||||
|
|
@ -28,11 +30,11 @@ class GUIDTaskRegisterAvatarTest extends ActorTest {
|
||||||
assert(!obj_inv_ammo.HasGUID)
|
assert(!obj_inv_ammo.HasGUID)
|
||||||
assert(!obj_locker.HasGUID)
|
assert(!obj_locker.HasGUID)
|
||||||
assert(obj_locker_ammo.HasGUID)
|
assert(obj_locker_ammo.HasGUID)
|
||||||
taskResolver ! TaskResolver.GiveTask(
|
TaskWorkflow.execute(TaskBundle(
|
||||||
new GUIDTaskTest.RegisterTestTask(probe.ref),
|
new GUIDTaskTest.RegisterTestTask(probe.ref),
|
||||||
List(GUIDTask.RegisterAvatar(obj)(uns))
|
GUIDTask.registerAvatar(uns, obj)
|
||||||
)
|
))
|
||||||
probe.expectMsg(scala.util.Success)
|
probe.expectMsg(5.second, scala.util.Success(true))
|
||||||
assert(obj.HasGUID)
|
assert(obj.HasGUID)
|
||||||
assert(obj_wep.HasGUID)
|
assert(obj_wep.HasGUID)
|
||||||
assert(obj_wep_ammo.HasGUID)
|
assert(obj_wep_ammo.HasGUID)
|
||||||
|
|
|
||||||
|
|
@ -2,19 +2,21 @@
|
||||||
package objects.guidtask
|
package objects.guidtask
|
||||||
|
|
||||||
import base.ActorTest
|
import base.ActorTest
|
||||||
import net.psforever.objects.guid.{GUIDTask, TaskResolver}
|
import net.psforever.objects.guid.{GUIDTask, TaskBundle, TaskWorkflow}
|
||||||
|
|
||||||
|
import scala.concurrent.duration._
|
||||||
|
|
||||||
class GUIDTaskRegisterObjectTest extends ActorTest {
|
class GUIDTaskRegisterObjectTest extends ActorTest {
|
||||||
"RegisterObjectTask" in {
|
"RegisterObjectTask" in {
|
||||||
val (_, uns, taskResolver, probe) = GUIDTaskTest.CommonTestSetup
|
val (_, uns, probe) = GUIDTaskTest.CommonTestSetup
|
||||||
val obj = new GUIDTaskTest.TestObject
|
val obj = new GUIDTaskTest.TestObject
|
||||||
|
|
||||||
assert(!obj.HasGUID)
|
assert(!obj.HasGUID)
|
||||||
taskResolver ! TaskResolver.GiveTask(
|
TaskWorkflow.execute(TaskBundle(
|
||||||
new GUIDTaskTest.RegisterTestTask(probe.ref),
|
new GUIDTaskTest.RegisterTestTask(probe.ref),
|
||||||
List(GUIDTask.RegisterObjectTask(obj)(uns))
|
GUIDTask.registerObject(uns, obj)
|
||||||
)
|
))
|
||||||
probe.expectMsg(scala.util.Success)
|
probe.expectMsg(5.second, scala.util.Success(true))
|
||||||
assert(obj.HasGUID)
|
assert(obj.HasGUID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,15 +4,17 @@ package objects.guidtask
|
||||||
import base.ActorTest
|
import base.ActorTest
|
||||||
import net.psforever.objects._
|
import net.psforever.objects._
|
||||||
import net.psforever.objects.avatar.Avatar
|
import net.psforever.objects.avatar.Avatar
|
||||||
import net.psforever.objects.guid.{GUIDTask, TaskResolver}
|
import net.psforever.objects.guid.{GUIDTask, TaskBundle, TaskWorkflow}
|
||||||
import net.psforever.objects.locker.LockerEquipment
|
import net.psforever.objects.locker.LockerEquipment
|
||||||
import net.psforever.types.{CharacterSex, CharacterVoice, PlanetSideEmpire}
|
import net.psforever.types.{CharacterSex, CharacterVoice, PlanetSideEmpire}
|
||||||
|
|
||||||
|
import scala.concurrent.duration._
|
||||||
|
|
||||||
class GUIDTaskRegisterPlayerTest extends ActorTest {
|
class GUIDTaskRegisterPlayerTest extends ActorTest {
|
||||||
"RegisterPlayer" in {
|
"RegisterPlayer" in {
|
||||||
val (_, uns, taskResolver, probe) = GUIDTaskTest.CommonTestSetup
|
val (_, uns, probe) = GUIDTaskTest.CommonTestSetup
|
||||||
val obj = Player(Avatar(0, "test", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute))
|
val obj = Player(Avatar(0, "test", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute))
|
||||||
val obj_wep = Tool(GlobalDefinitions.beamer)
|
val obj_wep = Tool(GlobalDefinitions.beamer)
|
||||||
obj.Slot(0).Equipment = obj_wep
|
obj.Slot(0).Equipment = obj_wep
|
||||||
val obj_wep_ammo = AmmoBox(GlobalDefinitions.energy_cell)
|
val obj_wep_ammo = AmmoBox(GlobalDefinitions.energy_cell)
|
||||||
obj_wep.AmmoSlots.head.Box = obj_wep_ammo
|
obj_wep.AmmoSlots.head.Box = obj_wep_ammo
|
||||||
|
|
@ -28,11 +30,11 @@ class GUIDTaskRegisterPlayerTest extends ActorTest {
|
||||||
assert(!obj_inv_ammo.HasGUID)
|
assert(!obj_inv_ammo.HasGUID)
|
||||||
assert(!obj_locker.HasGUID)
|
assert(!obj_locker.HasGUID)
|
||||||
assert(obj_locker_ammo.HasGUID)
|
assert(obj_locker_ammo.HasGUID)
|
||||||
taskResolver ! TaskResolver.GiveTask(
|
TaskWorkflow.execute(TaskBundle(
|
||||||
new GUIDTaskTest.RegisterTestTask(probe.ref),
|
new GUIDTaskTest.RegisterTestTask(probe.ref),
|
||||||
List(GUIDTask.RegisterPlayer(obj)(uns))
|
GUIDTask.registerPlayer(uns, obj)
|
||||||
)
|
))
|
||||||
probe.expectMsg(scala.util.Success)
|
probe.expectMsg(5.second, scala.util.Success(true))
|
||||||
assert(obj.HasGUID)
|
assert(obj.HasGUID)
|
||||||
assert(obj_wep.HasGUID)
|
assert(obj_wep.HasGUID)
|
||||||
assert(obj_wep_ammo.HasGUID)
|
assert(obj_wep_ammo.HasGUID)
|
||||||
|
|
|
||||||
|
|
@ -3,21 +3,23 @@ package objects.guidtask
|
||||||
|
|
||||||
import base.ActorTest
|
import base.ActorTest
|
||||||
import net.psforever.objects._
|
import net.psforever.objects._
|
||||||
import net.psforever.objects.guid.{GUIDTask, TaskResolver}
|
import net.psforever.objects.guid.{GUIDTask, TaskBundle, TaskWorkflow}
|
||||||
|
|
||||||
|
import scala.concurrent.duration._
|
||||||
|
|
||||||
class GUIDTaskRegisterToolTest extends ActorTest {
|
class GUIDTaskRegisterToolTest extends ActorTest {
|
||||||
"RegisterEquipment -> RegisterTool" in {
|
"RegisterEquipment -> RegisterTool" in {
|
||||||
val (_, uns, taskResolver, probe) = GUIDTaskTest.CommonTestSetup
|
val (_, uns, probe) = GUIDTaskTest.CommonTestSetup
|
||||||
val obj = Tool(GlobalDefinitions.beamer)
|
val obj = Tool(GlobalDefinitions.beamer)
|
||||||
obj.AmmoSlots.head.Box = AmmoBox(GlobalDefinitions.energy_cell)
|
obj.AmmoSlots.head.Box = AmmoBox(GlobalDefinitions.energy_cell)
|
||||||
|
|
||||||
assert(!obj.HasGUID)
|
assert(!obj.HasGUID)
|
||||||
assert(!obj.AmmoSlots.head.Box.HasGUID)
|
assert(!obj.AmmoSlots.head.Box.HasGUID)
|
||||||
taskResolver ! TaskResolver.GiveTask(
|
TaskWorkflow.execute(TaskBundle(
|
||||||
new GUIDTaskTest.RegisterTestTask(probe.ref),
|
new GUIDTaskTest.RegisterTestTask(probe.ref),
|
||||||
List(GUIDTask.RegisterEquipment(obj)(uns))
|
GUIDTask.registerEquipment(uns, obj)
|
||||||
)
|
))
|
||||||
probe.expectMsg(scala.util.Success)
|
probe.expectMsg(5.second, scala.util.Success(true))
|
||||||
assert(obj.HasGUID)
|
assert(obj.HasGUID)
|
||||||
assert(obj.AmmoSlots.head.Box.HasGUID)
|
assert(obj.AmmoSlots.head.Box.HasGUID)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,25 +3,27 @@ package objects.guidtask
|
||||||
|
|
||||||
import base.ActorTest
|
import base.ActorTest
|
||||||
import net.psforever.objects._
|
import net.psforever.objects._
|
||||||
import net.psforever.objects.guid.{GUIDTask, TaskResolver}
|
import net.psforever.objects.guid.{GUIDTask, TaskBundle, TaskWorkflow}
|
||||||
|
|
||||||
|
import scala.concurrent.duration._
|
||||||
|
|
||||||
class GUIDTaskRegisterTurretTest extends ActorTest {
|
class GUIDTaskRegisterTurretTest extends ActorTest {
|
||||||
"RegisterDeployableTurret" in {
|
"RegisterDeployableTurret" in {
|
||||||
val (_, uns, taskResolver, probe) = GUIDTaskTest.CommonTestSetup
|
val (_, uns, probe) = GUIDTaskTest.CommonTestSetup
|
||||||
val obj = new TurretDeployable(GlobalDefinitions.portable_manned_turret_vs)
|
val obj = new TurretDeployable(GlobalDefinitions.portable_manned_turret_vs)
|
||||||
val obj_wep = obj.Weapons(1).Equipment.get
|
val obj_wep = obj.Weapons(1).Equipment.get
|
||||||
val obj_ammo = obj_wep.asInstanceOf[Tool].AmmoSlot.Box
|
val obj_ammo = obj_wep.asInstanceOf[Tool].AmmoSlot.Box
|
||||||
val obj_res = obj.Inventory.Items.map(_.obj)
|
val obj_res = obj.Inventory.Items.map(_.obj)
|
||||||
|
|
||||||
assert(!obj.HasGUID)
|
assert(!obj.HasGUID)
|
||||||
assert(!obj_wep.HasGUID)
|
assert(!obj_wep.HasGUID)
|
||||||
assert(!obj_ammo.HasGUID)
|
assert(!obj_ammo.HasGUID)
|
||||||
obj_res.foreach(box => !box.HasGUID)
|
obj_res.foreach(box => !box.HasGUID)
|
||||||
taskResolver ! TaskResolver.GiveTask(
|
TaskWorkflow.execute(TaskBundle(
|
||||||
new GUIDTaskTest.RegisterTestTask(probe.ref),
|
new GUIDTaskTest.RegisterTestTask(probe.ref),
|
||||||
List(GUIDTask.RegisterDeployableTurret(obj)(uns))
|
GUIDTask.registerDeployableTurret(uns, obj)
|
||||||
)
|
))
|
||||||
probe.expectMsg(scala.util.Success)
|
probe.expectMsg(5.second, scala.util.Success(true))
|
||||||
assert(obj.HasGUID)
|
assert(obj.HasGUID)
|
||||||
assert(obj_wep.HasGUID)
|
assert(obj_wep.HasGUID)
|
||||||
assert(obj_ammo.HasGUID)
|
assert(obj_ammo.HasGUID)
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,15 @@ package objects.guidtask
|
||||||
|
|
||||||
import base.ActorTest
|
import base.ActorTest
|
||||||
import net.psforever.objects._
|
import net.psforever.objects._
|
||||||
import net.psforever.objects.guid.{GUIDTask, TaskResolver}
|
import net.psforever.objects.guid.{GUIDTask, TaskBundle, TaskWorkflow}
|
||||||
|
|
||||||
|
import scala.concurrent.duration._
|
||||||
|
|
||||||
class GUIDTaskRegisterVehicleTest extends ActorTest {
|
class GUIDTaskRegisterVehicleTest extends ActorTest {
|
||||||
"RegisterVehicle" in {
|
"RegisterVehicle" in {
|
||||||
val (_, uns, taskResolver, probe) = GUIDTaskTest.CommonTestSetup
|
val (_, uns, probe) = GUIDTaskTest.CommonTestSetup
|
||||||
val obj = Vehicle(GlobalDefinitions.fury)
|
val obj = Vehicle(GlobalDefinitions.fury)
|
||||||
val obj_wep = obj.WeaponControlledFromSeat(0).get
|
val obj_wep = obj.WeaponControlledFromSeat(0).get
|
||||||
val obj_wep_ammo = (obj.WeaponControlledFromSeat(0).get.asInstanceOf[Tool].AmmoSlots.head.Box =
|
val obj_wep_ammo = (obj.WeaponControlledFromSeat(0).get.asInstanceOf[Tool].AmmoSlots.head.Box =
|
||||||
AmmoBox(GlobalDefinitions.hellfire_ammo)).get
|
AmmoBox(GlobalDefinitions.hellfire_ammo)).get
|
||||||
obj.Trunk += 30 -> AmmoBox(GlobalDefinitions.hellfire_ammo)
|
obj.Trunk += 30 -> AmmoBox(GlobalDefinitions.hellfire_ammo)
|
||||||
|
|
@ -19,11 +21,11 @@ class GUIDTaskRegisterVehicleTest extends ActorTest {
|
||||||
assert(!obj_wep.HasGUID)
|
assert(!obj_wep.HasGUID)
|
||||||
assert(!obj_wep_ammo.HasGUID)
|
assert(!obj_wep_ammo.HasGUID)
|
||||||
assert(!obj_trunk_ammo.HasGUID)
|
assert(!obj_trunk_ammo.HasGUID)
|
||||||
taskResolver ! TaskResolver.GiveTask(
|
TaskWorkflow.execute(TaskBundle(
|
||||||
new GUIDTaskTest.RegisterTestTask(probe.ref),
|
new GUIDTaskTest.RegisterTestTask(probe.ref),
|
||||||
List(GUIDTask.RegisterVehicle(obj)(uns))
|
GUIDTask.registerVehicle(uns, obj)
|
||||||
)
|
))
|
||||||
probe.expectMsg(scala.util.Success)
|
probe.expectMsg(5.second, scala.util.Success(true))
|
||||||
assert(obj.HasGUID)
|
assert(obj.HasGUID)
|
||||||
assert(obj_wep.HasGUID)
|
assert(obj_wep.HasGUID)
|
||||||
assert(obj_wep_ammo.HasGUID)
|
assert(obj_wep_ammo.HasGUID)
|
||||||
|
|
|
||||||
|
|
@ -2,50 +2,54 @@
|
||||||
package objects.guidtask
|
package objects.guidtask
|
||||||
|
|
||||||
import java.util.logging.LogManager
|
import java.util.logging.LogManager
|
||||||
|
|
||||||
import scala.util.Success
|
import scala.util.Success
|
||||||
import akka.actor.{ActorRef, ActorSystem, Props}
|
import akka.actor.{ActorRef, ActorSystem, Props}
|
||||||
import akka.testkit.TestProbe
|
import akka.testkit.TestProbe
|
||||||
import net.psforever.objects.entity.IdentifiableEntity
|
import net.psforever.objects.entity.IdentifiableEntity
|
||||||
import net.psforever.objects.guid.actor.{NumberPoolActor, UniqueNumberSystem}
|
|
||||||
import net.psforever.objects.guid.selector.RandomSelector
|
import net.psforever.objects.guid.selector.RandomSelector
|
||||||
import net.psforever.objects.guid.source.MaxNumberSource
|
import net.psforever.objects.guid.source.MaxNumberSource
|
||||||
import net.psforever.objects.guid.{NumberPoolHub, Task, TaskResolver}
|
import net.psforever.objects.guid.uns.NumberPoolActor
|
||||||
|
import net.psforever.objects.guid.{NumberPoolHub, StraightforwardTask, UniqueNumberOps}
|
||||||
|
|
||||||
|
import scala.concurrent.Future
|
||||||
|
|
||||||
object GUIDTaskTest {
|
object GUIDTaskTest {
|
||||||
class TestObject extends IdentifiableEntity
|
class TestObject extends IdentifiableEntity
|
||||||
|
|
||||||
class RegisterTestTask(probe: ActorRef) extends Task {
|
class RegisterTestTask(probe: ActorRef) extends StraightforwardTask {
|
||||||
def Execute(resolver: ActorRef): Unit = {
|
def action(): Future[Any] = {
|
||||||
probe ! Success
|
probe ! Success(true)
|
||||||
resolver ! Success(this)
|
Future(this)(scala.concurrent.ExecutionContext.Implicits.global)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def CommonTestSetup(implicit system: ActorSystem): (NumberPoolHub, ActorRef, ActorRef, TestProbe) = {
|
def CommonTestSetup(implicit system: ActorSystem): (NumberPoolHub, UniqueNumberOps, TestProbe) = {
|
||||||
import akka.actor.Props
|
|
||||||
import akka.routing.RandomPool
|
|
||||||
import akka.testkit.TestProbe
|
import akka.testkit.TestProbe
|
||||||
|
|
||||||
val guid: NumberPoolHub = new NumberPoolHub(new MaxNumberSource(110))
|
val guid: NumberPoolHub = new NumberPoolHub(new MaxNumberSource(90))
|
||||||
guid.AddPool("dynamic", (1 to 100).toList).Selector = new RandomSelector //TODO name is hardcoded for now
|
guid.AddPool("players", (1 to 10).toList).Selector = new RandomSelector
|
||||||
val uns = system.actorOf(
|
guid.AddPool("lockers", (11 to 20).toList).Selector = new RandomSelector
|
||||||
RandomPool(25).props(Props(classOf[UniqueNumberSystem], guid, GUIDTaskTest.AllocateNumberPoolActors(guid))),
|
guid.AddPool("ammo", (21 to 30).toList).Selector = new RandomSelector
|
||||||
"uns"
|
guid.AddPool("tools", (31 to 40).toList).Selector = new RandomSelector
|
||||||
)
|
guid.AddPool("vehicles", (41 to 50).toList).Selector = new RandomSelector
|
||||||
val taskResolver = system.actorOf(RandomPool(15).props(Props[TaskResolver]()), "resolver")
|
guid.AddPool("terminals", (51 to 60).toList).Selector = new RandomSelector
|
||||||
|
guid.AddPool("items", (61 to 70).toList).Selector = new RandomSelector
|
||||||
|
guid.AddPool("deployables", (71 to 80).toList).Selector = new RandomSelector
|
||||||
|
val uns = new UniqueNumberOps(guid, AllocateNumberPoolActors(guid)(system))
|
||||||
LogManager.getLogManager.reset() //suppresses any internal loggers created by the above elements
|
LogManager.getLogManager.reset() //suppresses any internal loggers created by the above elements
|
||||||
(guid, uns, taskResolver, TestProbe())
|
(guid, uns, TestProbe())
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see `UniqueNumberSystem.AllocateNumberPoolActors(NumberPoolHub)(implicit ActorContext)`
|
* @see `UniqueNumberSetup.AllocateNumberPoolActors(NumberPoolHub)(implicit ActorContext)`
|
||||||
*/
|
*/
|
||||||
def AllocateNumberPoolActors(poolSource: NumberPoolHub)(implicit system: ActorSystem): Map[String, ActorRef] = {
|
def AllocateNumberPoolActors(poolSource: NumberPoolHub)(implicit system: ActorSystem): Map[String, ActorRef] = {
|
||||||
poolSource.Pools
|
poolSource.Pools
|
||||||
.map({
|
.map {
|
||||||
case ((pname, pool)) =>
|
case (pname, pool) =>
|
||||||
pname -> system.actorOf(Props(classOf[NumberPoolActor], pool), pname)
|
pname -> system.actorOf(Props(classOf[NumberPoolActor], pool), pname)
|
||||||
})
|
}
|
||||||
.toMap
|
.toMap
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,20 +3,22 @@ package objects.guidtask
|
||||||
|
|
||||||
import base.ActorTest
|
import base.ActorTest
|
||||||
import net.psforever.objects._
|
import net.psforever.objects._
|
||||||
import net.psforever.objects.guid.{GUIDTask, TaskResolver}
|
import net.psforever.objects.guid.{GUIDTask, TaskBundle, TaskWorkflow}
|
||||||
|
|
||||||
|
import scala.concurrent.duration._
|
||||||
|
|
||||||
class GUIDTaskUnregisterAmmoTest extends ActorTest {
|
class GUIDTaskUnregisterAmmoTest extends ActorTest {
|
||||||
"UnregisterEquipment -> UnregisterObjectTask" in {
|
"UnregisterEquipment -> UnregisterObjectTask" in {
|
||||||
val (guid, uns, taskResolver, probe) = GUIDTaskTest.CommonTestSetup
|
val (guid, uns, probe) = GUIDTaskTest.CommonTestSetup
|
||||||
val obj = AmmoBox(GlobalDefinitions.energy_cell)
|
val obj = AmmoBox(GlobalDefinitions.energy_cell)
|
||||||
guid.register(obj, "dynamic")
|
guid.register(obj, name = "ammo")
|
||||||
|
|
||||||
assert(obj.HasGUID)
|
assert(obj.HasGUID)
|
||||||
taskResolver ! TaskResolver.GiveTask(
|
TaskWorkflow.execute(TaskBundle(
|
||||||
new GUIDTaskTest.RegisterTestTask(probe.ref),
|
new GUIDTaskTest.RegisterTestTask(probe.ref),
|
||||||
List(GUIDTask.UnregisterEquipment(obj)(uns))
|
GUIDTask.unregisterEquipment(uns, obj)
|
||||||
)
|
))
|
||||||
probe.expectMsg(scala.util.Success)
|
probe.expectMsg(5.second, scala.util.Success(true))
|
||||||
assert(!obj.HasGUID)
|
assert(!obj.HasGUID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue