diff --git a/src/main/scala/net/psforever/actors/session/SessionActor.scala b/src/main/scala/net/psforever/actors/session/SessionActor.scala
index fd6c0e2a..a94834e0 100644
--- a/src/main/scala/net/psforever/actors/session/SessionActor.scala
+++ b/src/main/scala/net/psforever/actors/session/SessionActor.scala
@@ -20,7 +20,6 @@ import net.psforever.objects.inventory.{Container, InventoryItem}
import net.psforever.objects.locker.LockerContainer
import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.serverobject.containable.Containable
-import net.psforever.objects.serverobject.damage.Damageable
import net.psforever.objects.serverobject.deploy.Deployment
import net.psforever.objects.serverobject.doors.Door
import net.psforever.objects.serverobject.generator.Generator
@@ -57,10 +56,9 @@ import net.psforever.services.account.{AccountPersistenceService, PlayerToken, R
import net.psforever.services.avatar.{AvatarAction, AvatarResponse, AvatarServiceMessage, AvatarServiceResponse}
import net.psforever.services.chat.ChatService
import net.psforever.services.galaxy.{GalaxyAction, GalaxyResponse, GalaxyServiceMessage, GalaxyServiceResponse}
-import net.psforever.services.local.support.{CaptureFlagManager, HackCaptureActor, RouterTelepadActivation}
+import net.psforever.services.local.support.{CaptureFlagManager, HackCaptureActor}
import net.psforever.services.local.{LocalAction, LocalResponse, LocalServiceMessage, LocalServiceResponse}
import net.psforever.services.properties.PropertyOverrideManager
-import net.psforever.services.support.SupportActor
import net.psforever.services.teamwork.{SquadResponse, SquadServiceMessage, SquadServiceResponse, SquadAction => SquadServiceAction}
import net.psforever.services.hart.HartTimer
import net.psforever.services.vehicle.{VehicleAction, VehicleResponse, VehicleServiceMessage, VehicleServiceResponse}
@@ -150,7 +148,7 @@ object SessionActor {
private final case class NtuDischarging(tplayer: Player, vehicle: Vehicle, silo_guid: PlanetSideGUID)
private final case class FinalizeDeployable(
- obj: PlanetSideGameObject with Deployable,
+ obj: Deployable,
tool: ConstructionItem,
index: Int
)
@@ -167,7 +165,6 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
MDC("connectionId") = connectionId
private[this] val log = org.log4s.getLogger
- private[this] val damageLog = org.log4s.getLogger(Damageable.LogChannel)
var avatarActor: typed.ActorRef[AvatarActor.Command] = context.spawnAnonymous(AvatarActor(context.self))
var chatActor: typed.ActorRef[ChatActor.Command] = context.spawnAnonymous(ChatActor(context.self, avatarActor))
var accountIntermediary: ActorRef = Default.Actor
@@ -1038,140 +1035,12 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
case msg @ Zone.Vehicle.CanNotDespawn(zone, vehicle, reason) =>
log.warn(s"${player.Name}'s ${vehicle.Definition.Name} can not deconstruct in ${zone.id} because $reason")
- case Zone.Deployable.DeployableIsBuilt(obj, tool) =>
- val index = player.Find(tool) match {
- case Some(x) =>
- x
- case None =>
- player.LastDrawnSlot
- }
- if (avatar.deployables.Accept(obj) || (avatar.deployables.Valid(obj) && !avatar.deployables.Contains(obj))) {
- tool.Definition match {
- case GlobalDefinitions.ace =>
- continent.LocalEvents ! LocalServiceMessage(
- continent.id,
- LocalAction.TriggerEffectLocation(player.GUID, "spawn_object_effect", obj.Position, obj.Orientation)
- )
- case GlobalDefinitions.advanced_ace =>
- sendResponse(
- GenericObjectActionMessage(player.GUID, 53)
- ) //put fdu down; it will be removed from the client's holster
- continent.AvatarEvents ! AvatarServiceMessage(continent.id, AvatarAction.PutDownFDU(player.GUID))
- case GlobalDefinitions.router_telepad => ;
- case _ =>
- log.warn(
- s"Zone.Deployable.DeployableIsBuilt: not sure what kind of construction item to animate - ${tool.Definition.Name}"
- )
- }
- import scala.concurrent.ExecutionContext.Implicits.global
- context.system.scheduler.scheduleOnce(
- obj.Definition.DeployTime milliseconds,
- self,
- SessionActor.FinalizeDeployable(obj, tool, index)
- )
- } else {
- TryDropFDU(tool, index, obj.Position)
- sendResponse(ObjectDeployedMessage.Failure(obj.Definition.Name))
- obj.Position = Vector3.Zero
- obj.AssignOwnership(None)
- continent.Deployables ! Zone.Deployable.Dismiss(obj)
- }
-
- case SessionActor.FinalizeDeployable(obj: SensorDeployable, tool, index) =>
- //motion alarm sensor and sensor disruptor
- DeployableBuildActivity(obj)
- continent.LocalEvents ! LocalServiceMessage(
- continent.id,
- LocalAction.TriggerEffectInfo(player.GUID, "on", obj.GUID, true, 1000)
- )
- CommonDestroyConstructionItem(tool, index)
- FindReplacementConstructionItem(tool, index)
-
- case SessionActor.FinalizeDeployable(obj: BoomerDeployable, tool, index) =>
- //boomers
- DeployableBuildActivity(obj)
- //TODO sufficiently delete the tool
- sendResponse(ObjectDeleteMessage(tool.GUID, 0))
- continent.AvatarEvents ! AvatarServiceMessage(continent.id, AvatarAction.ObjectDelete(player.GUID, tool.GUID))
- continent.tasks ! GUIDTask.UnregisterEquipment(tool)(continent.GUID)
- val trigger = new BoomerTrigger
- trigger.Companion = obj.GUID
- obj.Trigger = trigger
- val holster = player.Slot(index)
- if (holster.Equipment.contains(tool)) {
- holster.Equipment = None
- continent.tasks ! HoldNewEquipmentUp(player)(trigger, index)
- } else {
- //don't know where boomer trigger should go; drop it on the ground
- continent.tasks ! NewItemDrop(player, continent)(trigger)
- }
-
- case SessionActor.FinalizeDeployable(obj: ExplosiveDeployable, tool, index) =>
- //mines
- DeployableBuildActivity(obj)
- CommonDestroyConstructionItem(tool, index)
- FindReplacementConstructionItem(tool, index)
-
- case SessionActor.FinalizeDeployable(obj: ComplexDeployable, tool, index) =>
- //tank_traps, spitfires, deployable field turrets and the deployable_shield_generator
- DeployableBuildActivity(obj)
- CommonDestroyConstructionItem(tool, index)
- FindReplacementConstructionItem(tool, index)
-
- case SessionActor.FinalizeDeployable(obj: TelepadDeployable, tool, index) =>
- if (obj.Health > 0) {
- val guid = obj.GUID
- //router telepad deployable
- val router = tool.asInstanceOf[Telepad].Router
- //router must exist and be deployed
- continent.GUID(router) match {
- case Some(vehicle: Vehicle) =>
- val routerGUID = router.get
- if (vehicle.Destroyed) {
- //the Telepad was successfully deployed; but, before it could configure, its Router was destroyed
- sendResponse(ChatMsg(ChatMessageType.UNK_229, false, "", "@Telepad_NoDeploy_RouterLost", None))
- continent.LocalEvents ! LocalServiceMessage.Deployables(
- RemoverActor.AddTask(obj, continent, Some(0 seconds))
- )
- } else {
- log.debug(s"FinalizeDeployable: setup for telepad #${guid.guid} in zone ${continent.id}")
- obj.Router = routerGUID //necessary; forwards link to the router
- DeployableBuildActivity(obj)
- RemoveOldEquipmentFromInventory(player)(tool)
- //it takes 60s for the telepad to become properly active
- continent.LocalEvents ! LocalServiceMessage.Telepads(RouterTelepadActivation.AddTask(obj, continent))
- }
-
- case _ =>
- //the Telepad was successfully deployed; but, before it could configure, its Router was deconstructed
- sendResponse(ChatMsg(ChatMessageType.UNK_229, false, "", "@Telepad_NoDeploy_RouterLost", None))
- continent.LocalEvents ! LocalServiceMessage.Deployables(
- RemoverActor.AddTask(obj, continent, Some(0 seconds))
- )
- }
- }
-
- case SessionActor.FinalizeDeployable(obj: PlanetSideGameObject with Deployable, tool, index) =>
- val guid = obj.GUID
- val definition = obj.Definition
- sendResponse(GenericObjectActionMessage(guid, 21)) //reset build cooldown
- sendResponse(ObjectDeployedMessage.Failure(definition.Name))
- log.warn(
- s"FinalizeDeployable: deployable ${definition.Item}@$guid not handled by specific case"
- )
- log.warn(
- s"FinalizeDeployable: deployable ${definition.Item}@$guid will be cleaned up, but may not get unregistered properly"
- )
- TryDropFDU(tool, index, obj.Position)
- obj.Position = Vector3.Zero
- continent.Deployables ! Zone.Deployable.Dismiss(obj)
-
- //!!only dispatch Zone.Deployable.Dismiss from WorldSessionActor as cleanup if the target deployable was never fully introduced
- case Zone.Deployable.DeployableIsDismissed(obj: TurretDeployable) =>
+ //!!only dispatched to SessionActor as cleanup if the target deployable was never fully introduced
+ case Zone.Deployable.IsDismissed(obj: TurretDeployable) =>
continent.tasks ! GUIDTask.UnregisterDeployableTurret(obj)(continent.GUID)
- //!!only dispatch Zone.Deployable.Dismiss from WorldSessionActor as cleanup if the target deployable was never fully introduced
- case Zone.Deployable.DeployableIsDismissed(obj) =>
+ //!!only dispatched to SessionActor as cleanup if the target deployable was never fully introduced
+ case Zone.Deployable.IsDismissed(obj) =>
continent.tasks ! GUIDTask.UnregisterObjectTask(obj)(continent.GUID)
case ICS.ZonesResponse(zones) =>
@@ -1445,19 +1314,6 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
}
}
- case Vitality.DamageResolution(target: TelepadDeployable, _) =>
- //telepads
- if (target.Health <= 0) {
- //update if destroyed
- target.Destroyed = true
- val guid = target.GUID
- continent.AvatarEvents ! AvatarServiceMessage(continent.id, AvatarAction.ObjectDelete(player.GUID, guid))
- Deployables.AnnounceDestroyDeployable(target, Some(0 seconds))
- }
-
- case Vitality.DamageResolution(target: PlanetSideGameObject, _) =>
- log.warn(s"DamageResolution: vital target ${target.Definition.Name} damage resolution not supported")
-
case ResponseToSelf(pkt) =>
sendResponse(pkt)
@@ -1873,6 +1729,15 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
DropSpecialSlotItem()
case AvatarResponse.Killed(mount) =>
+ val cause = (player.LastDamage match {
+ case Some(reason) => (Some(reason), reason.adversarial)
+ case None => (None, None)
+ }) match {
+ case (_, Some(adversarial)) => adversarial.attacker.Name
+ case (Some(reason), None) => s"a ${reason.interaction.cause.getClass.getSimpleName}"
+ case _ => "an unfortunate circumstance"
+ }
+ log.info(s"${player.Name} has died, killed by $cause")
val respawnTimer = 300.seconds
//drop free hand item
player.FreeHand.Equipment match {
@@ -2303,27 +2168,14 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
if (player.HasGUID) player.GUID
else PlanetSideGUID(0)
reply match {
- case LocalResponse.AlertDestroyDeployable(obj: BoomerDeployable) =>
- //the (former) owner (obj.OwnerName) should process this message
- obj.Trigger match {
- case Some(item: BoomerTrigger) =>
- FindEquipmentToDelete(item.GUID, item)
- item.Companion = None
- case _ => ;
- }
- avatar.deployables.Remove(obj)
- UpdateDeployableUIElements(avatar.deployables.UpdateUIElement(obj.Definition.Item))
-
- case LocalResponse.AlertDestroyDeployable(obj) =>
- //the (former) owner (obj.OwnerName) should process this message
- avatar.deployables.Remove(obj)
- UpdateDeployableUIElements(avatar.deployables.UpdateUIElement(obj.Definition.Item))
-
case LocalResponse.DeployableMapIcon(behavior, deployInfo) =>
if (tplayer_guid != guid) {
sendResponse(DeployableObjectsInfoMessage(behavior, deployInfo))
}
+ case LocalResponse.DeployableUIFor(item) =>
+ UpdateDeployableUIElements(avatar.deployables.UpdateUIElement(item))
+
case LocalResponse.Detonate(dguid, obj: BoomerDeployable) =>
sendResponse(TriggerEffectMessage(dguid, "detonate_boomer"))
sendResponse(PlanetsideAttributeMessage(dguid, 29, 1))
@@ -2345,9 +2197,9 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
case LocalResponse.DoorCloses(door_guid) => //door closes for everyone
sendResponse(GenericObjectStateMsg(door_guid, 17))
- case LocalResponse.EliminateDeployable(obj: TurretDeployable, dguid, pos) =>
+ case LocalResponse.EliminateDeployable(obj: TurretDeployable, dguid, pos, _) =>
if (obj.Destroyed) {
- DeconstructDeployable(obj, dguid, pos)
+ sendResponse(ObjectDeleteMessage(dguid, 0))
} else {
obj.Destroyed = true
DeconstructDeployable(
@@ -2355,64 +2207,39 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
dguid,
pos,
obj.Orientation,
- if (obj.MountPoints.isEmpty) 2
- else 1
+ if (obj.MountPoints.isEmpty) 2 else 1
)
}
- case LocalResponse.EliminateDeployable(obj: ExplosiveDeployable, dguid, pos) =>
+ case LocalResponse.EliminateDeployable(obj: ExplosiveDeployable, dguid, pos, effect) =>
if (obj.Destroyed || obj.Jammed || obj.Health == 0) {
- DeconstructDeployable(obj, dguid, pos)
+ sendResponse(ObjectDeleteMessage(dguid, 0))
} else {
obj.Destroyed = true
- DeconstructDeployable(obj, dguid, pos, obj.Orientation, 2)
+ DeconstructDeployable(obj, dguid, pos, obj.Orientation, effect)
}
- case LocalResponse.EliminateDeployable(obj: ComplexDeployable, dguid, pos) =>
- if (obj.Destroyed) {
- DeconstructDeployable(obj, dguid, pos)
- } else {
- obj.Destroyed = true
- DeconstructDeployable(obj, dguid, pos, obj.Orientation, 1)
- }
-
- case LocalResponse.EliminateDeployable(obj: TelepadDeployable, dguid, pos) =>
+ case LocalResponse.EliminateDeployable(obj: TelepadDeployable, dguid, pos, _) =>
//if active, deactivate
if (obj.Active) {
obj.Active = false
sendResponse(GenericObjectActionMessage(dguid, 29))
sendResponse(GenericObjectActionMessage(dguid, 30))
}
- //determine if no replacement teleport system exists
- continent.GUID(obj.Router) match {
- case Some(router: Vehicle) =>
- //if the telepad was replaced, the new system is physically in place but not yet functional
- if (
- router.Utility(UtilityType.internal_router_telepad_deployable) match {
- case Some(internalTelepad: Utility.InternalTelepad) =>
- internalTelepad.Telepad.contains(dguid) //same telepad
- case _ => true
- }
- ) {
- //there is no replacement telepad; shut down the system
- ToggleTeleportSystem(router, None)
- }
- case _ => ;
- }
//standard deployable elimination behavior
if (obj.Destroyed) {
- DeconstructDeployable(obj, dguid, pos)
+ sendResponse(ObjectDeleteMessage(dguid, 0))
} else {
obj.Destroyed = true
- DeconstructDeployable(obj, dguid, pos, obj.Orientation, 2)
+ DeconstructDeployable(obj, dguid, pos, obj.Orientation, deletionType = 2)
}
- case LocalResponse.EliminateDeployable(obj, dguid, pos) =>
+ case LocalResponse.EliminateDeployable(obj, dguid, pos, effect) =>
if (obj.Destroyed) {
- DeconstructDeployable(obj, dguid, pos)
+ sendResponse(ObjectDeleteMessage(dguid, 0))
} else {
obj.Destroyed = true
- DeconstructDeployable(obj, dguid, pos, obj.Orientation, 2)
+ DeconstructDeployable(obj, dguid, pos, obj.Orientation, effect)
}
case LocalResponse.SendHackMessageHackCleared(target_guid, unk1, unk2) =>
@@ -2479,12 +2306,12 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
case LocalResponse.RouterTelepadTransport(passenger_guid, src_guid, dest_guid) =>
UseRouterTelepadEffect(passenger_guid, src_guid, dest_guid)
+ case LocalResponse.SendResponse(msg) =>
+ sendResponse(msg)
+
case LocalResponse.SetEmpire(object_guid, empire) =>
sendResponse(SetEmpireMessage(object_guid, empire))
- case LocalResponse.SendResponse(pkt) =>
- sendResponse(pkt)
-
case LocalResponse.ShuttleEvent(ev) =>
val msg = OrbitalShuttleTimeMsg(
ev.u1,
@@ -3550,10 +3377,9 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
val guid = player.GUID
val foundDeployables =
continent.DeployableList.filter(obj => obj.OwnerName.contains(player.Name) && obj.Health > 0)
- continent.LocalEvents ! LocalServiceMessage.Deployables(RemoverActor.ClearSpecific(foundDeployables, continent))
foundDeployables.foreach(obj => {
- if (avatar.deployables.Add(obj)) {
- obj.Owner = guid
+ if (avatar.deployables.AddOverLimit(obj)) {
+ obj.Actor ! Deployable.Ownership(player)
}
})
//render deployable objects
@@ -4189,7 +4015,12 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
case msg @ ChangeAmmoMessage(item_guid, unk1) =>
FindContainedEquipment match {
case (Some(_), Some(obj: ConstructionItem)) =>
- PerformConstructionItemAmmoChange(obj, obj.AmmoTypeIndex)
+ if (Deployables.performConstructionItemAmmoChange(player.avatar.certifications, obj, obj.AmmoTypeIndex)) {
+ log.info(
+ s"${player.Name} switched ${player.Sex.possessive} ${obj.Definition.Name} to construct ${obj.AmmoType} (option #${obj.FireModeIndex})"
+ )
+ sendResponse(ChangeAmmoMessage(obj.GUID, obj.AmmoTypeIndex))
+ }
case (Some(obj: PlanetSideServerObject), Some(tool: Tool)) =>
PerformToolAmmoChange(tool, obj)
case (_, Some(obj)) =>
@@ -4202,23 +4033,28 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
FindEquipment match {
case Some(obj: PlanetSideGameObject with FireModeSwitch[_]) =>
val originalModeIndex = obj.FireModeIndex
- obj match {
- case cItem: ConstructionItem =>
- NextConstructionItemFireMode(cItem, originalModeIndex)
+ if (obj match {
+ case citem: ConstructionItem =>
+ val modeChanged = Deployables.performConstructionItemFireModeChange(
+ player.avatar.certifications,
+ citem,
+ originalModeIndex
+ )
+ modeChanged
case _ =>
- obj.NextFireMode
- }
- val modeIndex = obj.FireModeIndex
- val tool_guid = obj.GUID
- if (originalModeIndex == modeIndex) {
- obj.FireModeIndex = originalModeIndex
- sendResponse(ChangeFireModeMessage(tool_guid, originalModeIndex)) //reinforcement
- } else {
- log.info(s"${player.Name} is changing his ${obj.Definition.Name} to fire mode #$modeIndex")
- sendResponse(ChangeFireModeMessage(tool_guid, modeIndex))
+ obj.NextFireMode == originalModeIndex
+ }) {
+ val modeIndex = obj.FireModeIndex
+ obj match {
+ case citem: ConstructionItem =>
+ log.info(s"${player.Name} switched ${player.Sex.possessive} ${obj.Definition.Name} to construct ${citem.AmmoType} (mode #$modeIndex)")
+ case _ =>
+ log.info(s"${player.Name} changed ${player.Sex.possessive} her ${obj.Definition.Name}'s fire mode to #$modeIndex")
+ }
+ sendResponse(ChangeFireModeMessage(item_guid, modeIndex))
continent.AvatarEvents ! AvatarServiceMessage(
continent.id,
- AvatarAction.ChangeFireMode(player.GUID, tool_guid, modeIndex)
+ AvatarAction.ChangeFireMode(player.GUID, item_guid, modeIndex)
)
}
case Some(_) =>
@@ -4284,7 +4120,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
FindEquipment
} else {
FindEquipment match {
- case Some(tool: Tool) =>
+ case Some(tool: Tool) => //special cases
//the decimator does not send a ChangeFireState_Start on the last shot
if (
tool.Definition == GlobalDefinitions.phoenix &&
@@ -4309,9 +4145,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
)
Some(tool)
case _ =>
- log.warn(
- s"ChangeFireState_Stop: ${player.Name} never started firing item ${item_guid.guid} in the first place?"
- )
+ //log.warn(s"ChangeFireState_Stop: ${player.Name} never started firing item ${item_guid.guid} in the first place?")
None
}
}
@@ -4453,12 +4287,14 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
)
// Ignore non-equipment holsters
//todo: check current suit holster slots?
- if (held_holsters >= 0 && held_holsters < 5) {
- player.Holsters()(held_holsters).Equipment match {
+ val isHolsters = held_holsters >= 0 && held_holsters < 5
+ val equipment = player.Slot(held_holsters).Equipment.orElse { player.Slot(before).Equipment }
+ if (isHolsters) {
+ equipment match {
case Some(unholsteredItem: Equipment) =>
log.info(s"${player.Name} has drawn a $unholsteredItem from its holster")
if (unholsteredItem.Definition == GlobalDefinitions.remote_electronics_kit) {
- // Player has unholstered a REK - we need to set an atttribute on the REK itself to change the beam/icon colour to the correct one for the player's hack level
+ //rek beam/icon colour must match the player's correct hack level
continent.AvatarEvents ! AvatarServiceMessage(
player.Continent,
AvatarAction.PlanetsideAttribute(unholsteredItem.GUID, 116, player.avatar.hackingSkillLevel())
@@ -4466,6 +4302,13 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
}
case None => ;
}
+ } else {
+ equipment match {
+ case Some(holsteredEquipment) =>
+ log.info(s"${player.Name} has put ${player.Sex.possessive} ${holsteredEquipment.Definition.Name} down")
+ case None =>
+ log.info(s"${player.Name} lowers ${player.Sex.possessive} hand")
+ }
}
// Stop using proximity terminals if player unholsters a weapon (which should re-trigger the proximity effect and re-holster the weapon)
@@ -4539,24 +4382,6 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
log.warn(s"RequestDestroy: ${player.Name} must own vehicle in order to deconstruct it")
}
- case Some(obj: BoomerTrigger) =>
- if (FindEquipmentToDelete(object_guid, obj)) {
- continent.GUID(obj.Companion) match {
- case Some(boomer: BoomerDeployable) =>
- boomer.Trigger = None
- continent.LocalEvents ! LocalServiceMessage.Deployables(
- RemoverActor.AddTask(boomer, continent, Some(0 seconds))
- )
- //continent.Deployables ! Zone.Deployable.Dismiss(boomer)
- case Some(thing) =>
- log.warn(s"RequestDestroy: BoomerTrigger object connected to wrong object - $thing")
- case None => ;
- }
- }
-
- case Some(obj: Equipment) =>
- FindEquipmentToDelete(object_guid, obj)
-
case Some(obj: Projectile) =>
if (obj.isResolved) {
log.warn(
@@ -4573,47 +4398,30 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
}
}
- case Some(obj: BoomerDeployable) =>
- continent.LocalEvents ! LocalServiceMessage.Deployables(
- RemoverActor.AddTask(obj, continent, Some(0 seconds))
- )
- obj.Trigger match {
- case Some(trigger) =>
- obj.Trigger = None
- val guid = trigger.GUID
- Zone.EquipmentIs.Where(trigger, guid, continent) match {
- case Some(Zone.EquipmentIs.InContainer(container, index)) =>
- container.Slot(index).Equipment = None
- case Some(Zone.EquipmentIs.OnGround()) =>
- continent.Ground ! Zone.Ground.RemoveItem(guid)
- case Some(Zone.EquipmentIs.Orphaned()) =>
- log.warn(s"RequestDestroy: boomer_trigger@$guid has been found but it seems to be orphaned")
- case _ => ;
- }
- continent.AvatarEvents ! AvatarServiceMessage(
- continent.id,
- AvatarAction.ObjectDelete(PlanetSideGUID(0), guid)
- )
- GUIDTask.UnregisterObjectTask(trigger)(continent.GUID)
-
- case None => ;
+ case Some(obj: BoomerTrigger) =>
+ if (FindEquipmentToDelete(object_guid, obj)) {
+ continent.GUID(obj.Companion) match {
+ case Some(boomer: BoomerDeployable) =>
+ boomer.Trigger = None
+ boomer.Actor ! Deployable.Deconstruct()
+ case Some(thing) =>
+ log.warn(s"RequestDestroy: BoomerTrigger object connected to wrong object - $thing")
+ case None => ;
+ }
}
- case Some(obj: TelepadDeployable) =>
- continent.LocalEvents ! LocalServiceMessage.Telepads(SupportActor.ClearSpecific(List(obj), continent))
- continent.LocalEvents ! LocalServiceMessage.Deployables(RemoverActor.ClearSpecific(List(obj), continent))
- continent.LocalEvents ! LocalServiceMessage.Deployables(
- RemoverActor.AddTask(obj, continent, Some(0 seconds))
- )
+ case Some(obj: Deployable) =>
+ if (session.account.gm || obj.Owner.isEmpty || obj.Owner.contains(player.GUID) || obj.Destroyed) {
+ obj.Actor ! Deployable.Deconstruct()
+ } else {
+ log.warn(s"RequestDestroy: ${player.Name} must own the deployable in order to deconstruct it")
+ }
- case Some(obj: PlanetSideGameObject with Deployable) =>
- continent.LocalEvents ! LocalServiceMessage.Deployables(RemoverActor.ClearSpecific(List(obj), continent))
- continent.LocalEvents ! LocalServiceMessage.Deployables(
- RemoverActor.AddTask(obj, continent, Some(0 seconds))
- )
+ case Some(obj: Equipment) =>
+ FindEquipmentToDelete(object_guid, obj)
case Some(thing) =>
- log.warn(s"RequestDestroy: not allowed to delete object $thing")
+ log.warn(s"RequestDestroy: not allowed to delete this ${thing.Definition.Name}")
case None =>
log.warn(s"RequestDestroy: object ${object_guid.guid} not found")
@@ -5117,25 +4925,19 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
case _ => ;
}
- case msg @ DeployObjectMessage(guid, unk1, pos, orient, unk2) =>
- //the hand with the construction item is no longer drawn
- //TODO consider player.Slot(player.LastDrawnSlot)
+ case msg @ DeployObjectMessage(guid, _, pos, orient, _) =>
(player.Holsters().find(slot => slot.Equipment.nonEmpty && slot.Equipment.get.GUID == guid) match {
- case Some(slot) =>
- slot.Equipment
- case None =>
- None
+ case Some(slot) => slot.Equipment
+ case None => None
}) match {
case Some(obj: ConstructionItem) =>
val ammoType = obj.AmmoType match {
- case DeployedItem.portable_manned_turret =>
- GlobalDefinitions.PortableMannedTurret(player.Faction).Item //faction-specific turret
- case turret =>
- turret
+ case DeployedItem.portable_manned_turret => GlobalDefinitions.PortableMannedTurret(player.Faction).Item
+ case dtype => dtype
}
log.info(s"${player.Name} is constructing a $ammoType deployable")
CancelZoningProcessWithDescriptiveReason("cancel_use")
- val dObj: PlanetSideGameObject with Deployable = Deployables.Make(ammoType)()
+ val dObj: Deployable = Deployables.Make(ammoType)()
dObj.Position = pos
dObj.Orientation = orient
dObj.Faction = player.Faction
@@ -5146,7 +4948,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
case _ =>
GUIDTask.RegisterObjectTask(dObj)(continent.GUID)
}
- continent.tasks ! CallBackForTask(tasking, continent.Deployables, Zone.Deployable.Build(dObj, obj))
+ continent.tasks ! CallBackForTask(tasking, continent.Deployables, Zone.Deployable.BuildByOwner(dObj, player, obj))
case Some(obj) =>
log.warn(s"DeployObject: what is $obj, ${player.Name}? It's not a construction tool!")
@@ -5765,9 +5567,11 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
case Some(player: Player) if attribute_type == 106 =>
avatarActor ! AvatarActor.SetCosmetics(Cosmetic.valuesFromAttributeValue(attribute_value))
+ case Some(obj) =>
+ log.trace(s"PlanetsideAttribute: ${player.Name} does not know how to apply unknown attributes behavior $attribute_type to ${obj.Definition.Name}")
+
case _ =>
- log.warn(s"PlanetsideAttribute: echoing unknown attributes behavior $attribute_type back to ${player.Name}")
- sendResponse(PlanetsideAttributeMessage(object_guid, attribute_type, attribute_value))
+ log.warn(s"PlanetsideAttribute: ${player.Name} does not know how to apply unknown attributes behavior $attribute_type")
}
case msg @ FacilityBenefitShieldChargeRequestMessage(guid) =>
@@ -6200,24 +6004,6 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
}
}
- 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)
- )
- }
-
def AccessContainer(container: Container): Unit = {
container match {
case v: Vehicle =>
@@ -6772,10 +6558,6 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
/**
* For a given facility structure, configure a client by dispatching the appropriate packets.
- * Pay special attention to the details of `BuildingInfoUpdateMessage` when preparing this packet.
- *
- * 24 Janurtay 2019:
- * Manual `BIUM` construction to alleviate player login.
* @see `BuildingInfoUpdateMessage`
* @see `DensityLevelUpdateMessage`
* @param continentNumber the zone id
@@ -7411,13 +7193,11 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
boomers.foreach(boomer => {
continent.GUID(boomer) match {
case Some(obj: BoomerDeployable) =>
- obj.OwnerName = None
- continent.LocalEvents ! LocalServiceMessage.Deployables(RemoverActor.AddTask(obj, continent))
+ obj.Actor ! Deployable.Ownership(None)
case Some(_) | None => ;
}
})
- val triggers = RemoveBoomerTriggersFromInventory()
- triggers.foreach(trigger => { NormalItemDrop(obj, continent)(trigger) })
+ RemoveBoomerTriggersFromInventory()foreach(trigger => { NormalItemDrop(obj, continent)(trigger) })
}
}
@@ -8009,11 +7789,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
log.info(s"${player.Name} is attacking ${obj.OwnerName.getOrElse("someone")}'s ${obj.Definition.Name}")
obj.Actor ! Vitality.Damage(func)
case obj: Amenity if obj.CanDamage => obj.Actor ! Vitality.Damage(func)
- case obj: ComplexDeployable if obj.CanDamage => obj.Actor ! Vitality.Damage(func)
-
- case obj: SimpleDeployable if obj.CanDamage =>
- //damage is synchronized on `LSA` (results returned to and distributed from this `WorldSessionActor`)
- continent.LocalEvents ! Vitality.DamageOn(obj, func)
+ case obj: Deployable if obj.CanDamage => obj.Actor ! Vitality.Damage(func)
case _ => ;
}
}
@@ -8104,7 +7880,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
* @see `SetCurrentAvatar`
* @param obj a `Deployable` object
*/
- def RedrawDeployableIcons(obj: PlanetSideGameObject with Deployable): Unit = {
+ def RedrawDeployableIcons(obj: Deployable): Unit = {
val deployInfo = DeployableInfo(
obj.GUID,
Deployable.Icon(obj.Definition.Item),
@@ -8135,269 +7911,19 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
* @see `SetCurrentAvatar`
* @param obj a `Deployable` object
*/
- def DontRedrawIcons(obj: PlanetSideGameObject with Deployable): Unit = {}
-
- /**
- * The custom behavior responding to the message `ChangeFireModeMessage` for `ConstructionItem` game objects.
- * Each fire mode has sub-modes corresponding to a type of "deployable" as ammunition
- * and each of these sub-modes have certification requirements that must be met before they can be used.
- * Additional effort is exerted to ensure that the requirements for the given mode and given sub-mode are satisfied.
- * If no satisfactory combination is achieved, the original state will be restored.
- * @see `FireModeSwitch.NextFireMode`
- * @see `PerformConstructionItemAmmoChange`
- * @param obj the `ConstructionItem` object
- * @param originalModeIndex the starting point fire mode index
- * @return the changed fire mode
- */
- def NextConstructionItemFireMode(obj: ConstructionItem, originalModeIndex: Int): ConstructionFireMode = {
- do {
- obj.NextFireMode
- if (!Deployables.constructionItemPermissionComparison(player.avatar.certifications, obj.ModePermissions)) {
- PerformConstructionItemAmmoChange(obj, obj.AmmoTypeIndex)
- }
- sendResponse(ChangeFireModeMessage(obj.GUID, obj.FireModeIndex))
- } while (!Deployables.constructionItemPermissionComparison(
- player.avatar.certifications,
- obj.ModePermissions
- ) && originalModeIndex != obj.FireModeIndex)
- obj.FireMode
- }
-
- /**
- * The custom behavior responding to the message `ChangeAmmoMessage` for `ConstructionItem` game objects.
- * Iterate through sub-modes corresponding to a type of "deployable" as ammunition for this fire mode
- * and check each of these sub-modes for their certification requirements to be met before they can be used.
- * Additional effort is exerted to ensure that the requirements for the given ammunition are satisfied.
- * If no satisfactory combination is achieved, the original state will be restored.
- * @param obj the `ConstructionItem` object
- * @param originalAmmoIndex the starting point ammunition type mode index
- */
- def PerformConstructionItemAmmoChange(obj: ConstructionItem, originalAmmoIndex: Int): Unit = {
- do {
- obj.NextAmmoType
- } while (!Deployables.constructionItemPermissionComparison(
- player.avatar.certifications,
- obj.ModePermissions
- ) && originalAmmoIndex != obj.AmmoTypeIndex)
- log.info(
- s"${player.Name} switched construction object ${obj.Definition.Name} to ${obj.AmmoType} (mode #${obj.FireModeIndex})"
- )
- sendResponse(ChangeAmmoMessage(obj.GUID, obj.AmmoTypeIndex))
- }
+ def DontRedrawIcons(obj: Deployable): Unit = {}
/**
* Common actions related to constructing a new `Deployable` object in the game environment.
*
- * Besides the standard `ObjectCreateMessage` packet that produces the model and game object on the client,
- * two messages are dispatched in accordance with enforced deployable limits.
- * The first limit of note is the actual number of a specific type of deployable can be placed.
- * The second limit of note is the actual number of a specific group (category) of deployables that can be placed.
- * For example, the player can place 25 mines but that count adds up all types of mines;
- * specific mines have individual limits such as 25 and 5 and only that many of that type can be placed at once.
- * Depending on which limit is encountered, an "oldest entry" is struck from the list to make space.
- * This generates the first message - "@*OldestDestroyed."
- * The other message is generated if the number of that specific type of deployable
- * or the number of deployables available in its category
- * matches against the maximum count allowed.
- * This generates the second message - "@*LimitReached."
- * These messages are mutually exclusive, with "@*OldestDestroyed" taking priority over "@*LimitReached."
- *
* The map icon for the deployable just introduced is also created on the clients of all faction-affiliated players.
* This icon is important as, short of destroying it,
* the owner has no other means of controlling the created object that it is associated with.
* @param obj the `Deployable` object to be built
*/
- def DeployableBuildActivity(obj: PlanetSideGameObject with Deployable): Unit = {
- val guid = obj.GUID
- val definition = obj.Definition
- val item = definition.Item
- val deployables = avatar.deployables
- val (curr, max) = deployables.CountDeployable(item)
- //two potential messages related to numerical limitations of deployables
- if (!avatar.deployables.Available(obj)) {
- val (removed, msg) = {
- if (curr == max) { //too many of a specific type of deployable
- (deployables.DisplaceFirst(obj), max > 1)
- } else { //make room by eliminating a different type of deployable
- (deployables.DisplaceFirst(obj, { d => d.Definition.Item != item }), true)
- }
- }
- removed match {
- case Some(telepad: TelepadDeployable) =>
- telepad.AssignOwnership(None)
- continent.LocalEvents ! LocalServiceMessage.Deployables(RemoverActor.ClearSpecific(List(telepad), continent))
- continent.LocalEvents ! LocalServiceMessage.Deployables(
- RemoverActor.AddTask(telepad, continent, Some(0 seconds))
- ) //normal decay
- case Some(old) =>
- old.AssignOwnership(None)
- continent.LocalEvents ! LocalServiceMessage.Deployables(RemoverActor.ClearSpecific(List(old), continent))
- continent.LocalEvents ! LocalServiceMessage.Deployables(RemoverActor.AddTask(old, continent, Some(0 seconds)))
- if (msg) { //max test
- sendResponse(
- ChatMsg(ChatMessageType.UNK_229, false, "", s"@${definition.Descriptor}OldestDestroyed", None)
- )
- }
- case None => ; //should be an invalid case
- log.warn(
- s"DeployableBuildActivity: how awkward: ${player.Name} probably shouldn't be allowed to build this deployable right now"
- )
- }
- } else if (obj.isInstanceOf[TelepadDeployable]) {
- //always treat the telepad we are putting down as the first and only one
- sendResponse(ObjectDeployedMessage.Success(definition.Name, 1, 1))
- } else {
- sendResponse(ObjectDeployedMessage.Success(definition.Name, curr + 1, max))
- val (catCurr, catMax) = deployables.CountCategory(item)
- if ((max > 1 && curr + 1 == max) || (catMax > 1 && catCurr + 1 == catMax)) {
- sendResponse(ChatMsg(ChatMessageType.UNK_229, false, "", s"@${definition.Descriptor}LimitReached", None))
- }
- }
- avatar.deployables.Add(obj)
- UpdateDeployableUIElements(avatar.deployables.UpdateUIElement(item))
- sendResponse(GenericObjectActionMessage(guid, 21)) //reset build cooldown
- sendResponse(ObjectCreateMessage(definition.ObjectId, guid, definition.Packet.ConstructorData(obj).get))
- continent.AvatarEvents ! AvatarServiceMessage(continent.id, AvatarAction.DeployItem(player.GUID, obj))
- //map icon
- val deployInfo = DeployableInfo(guid, Deployable.Icon(item), obj.Position, obj.Owner.getOrElse(PlanetSideGUID(0)))
- sendResponse(DeployableObjectsInfoMessage(DeploymentAction.Build, deployInfo))
- continent.LocalEvents ! LocalServiceMessage(
- s"${player.Faction}",
- LocalAction.DeployableMapIcon(player.GUID, DeploymentAction.Build, deployInfo)
- )
- }
-
- /**
- * If the tool is a form of field deployment unit (FDU, also called an `advanced_ace`),
- * completely remove the object from its current position and place it on the ground.
- * In the case of a botched deployable construction, dropping the FDU is visually consistent
- * as it should already be depicted as on the ground as a part of its animation cycle.
- * @param tool the `ConstructionItem` object currently in the slot (checked)
- * @param index the slot index
- * @param pos where to drop the object in the game world
- */
- def TryDropFDU(tool: ConstructionItem, index: Int, pos: Vector3): Unit = {
- if (tool.Definition == GlobalDefinitions.advanced_ace) {
- DropEquipmentFromInventory(player)(tool, Some(pos))
- }
- }
-
- /**
- * Destroy a `ConstructionItem` object that can be found in the indexed slot.
- * @see `Player.Find`
- * @param tool the `ConstructionItem` object currently in the slot (checked)
- * @param index the slot index
- */
- def CommonDestroyConstructionItem(tool: ConstructionItem, index: Int): Unit = {
- if (SafelyRemoveConstructionItemFromSlot(tool, index, "CommonDestroyConstructionItem")) {
- continent.tasks ! GUIDTask.UnregisterEquipment(tool)(continent.GUID)
- }
- }
-
- /**
- * Find the target `ConstructionTool` object, either at the suggested slot or wherever it is on the `player`,
- * and remove it from the game world visually.
- *
- * Not finding the target object at its intended slot is an entirely recoverable situation
- * as long as the target object is discovered to be somewhere else in the player's holsters or inventory space.
- * If found after a more thorough search, merely log the discrepancy as a warning.
- * If the discrepancy becomes common, the developer messed up the function call
- * or he should not be using this function.
- * @param tool the `ConstructionItem` object currently in the slot (checked)
- * @param index the slot index
- * @param logDecorator what kind of designation to give any log entires originating from this function;
- * defaults to its own function name
- * @return `true`, if the target object was found and removed;
- * `false`, otherwise
- */
- def SafelyRemoveConstructionItemFromSlot(
- tool: ConstructionItem,
- index: Int,
- logDecorator: String = "SafelyRemoveConstructionItemFromSlot"
- ): Boolean = {
- if ({
- val holster = player.Slot(index)
- if (holster.Equipment.contains(tool)) {
- holster.Equipment = None
- true
- } else {
- player.Find(tool) match {
- case Some(newIndex) =>
- log.warn(
- s"$logDecorator: ${player.Name} was looking for an item in his hand $index, but item was found at $newIndex instead"
- )
- player.Slot(newIndex).Equipment = None
- true
- case None =>
- log.warn(s"$logDecorator: ${player.Name} could not find the target ${tool.Definition.Name}")
- false
- }
- }
- }) {
- sendResponse(ObjectDeleteMessage(tool.GUID, 0))
- continent.AvatarEvents ! AvatarServiceMessage(continent.id, AvatarAction.ObjectDelete(player.GUID, tool.GUID))
- true
- } else {
- false
- }
- }
-
- /**
- * Find a `ConstructionItem` object in player's inventory
- * that is the same type as a target `ConstructionItem` object and
- * transfer it into the designated slot index, usually a holster.
- * Draw that holster.
- * After being transferred, the replacement should be reconfigured to match the fire mode of the original.
- * The primary use of this operation is following the successful manifestation of a deployable in the game world.
- *
- * As this function should be used in response to some other action such as actually placing a deployable,
- * do not instigate bundling from within the function's scope.
- * @see `WorldSessionActor.FinalizeDeployable`
- * `FindEquipmentStock`
- * @param tool the `ConstructionItem` object to match
- * @param index where to put the discovered replacement
- */
- def FindReplacementConstructionItem(tool: ConstructionItem, index: Int): Unit = {
- val fireMode = tool.FireModeIndex
- val ammoType = tool.AmmoTypeIndex
- val definition = tool.Definition
-
- if (player.Slot(index).Equipment.isEmpty) {
- FindEquipmentStock(player, { e => e.Definition == definition }, 1) match {
- case x :: _ =>
- val guid = player.GUID
- val obj = x.obj.asInstanceOf[ConstructionItem]
- if ((player.Slot(index).Equipment = obj).contains(obj)) {
- player.Inventory -= x.start
- sendResponse(ObjectAttachMessage(guid, obj.GUID, index))
-
- if (obj.FireModeIndex != fireMode) {
- obj.FireModeIndex = fireMode
- sendResponse(ChangeFireModeMessage(obj.GUID, fireMode))
- }
- if (obj.AmmoTypeIndex != ammoType) {
- obj.AmmoTypeIndex = ammoType
- sendResponse(ChangeAmmoMessage(obj.GUID, ammoType))
- }
- if (player.VisibleSlots.contains(index)) {
- continent.AvatarEvents ! AvatarServiceMessage(
- continent.id,
- AvatarAction.EquipmentInHand(guid, guid, index, obj)
- )
- if (player.DrawnSlot == Player.HandsDownSlot) {
- player.DrawnSlot = index
- sendResponse(ObjectHeldMessage(guid, index, false))
- continent.AvatarEvents ! AvatarServiceMessage(continent.id, AvatarAction.ObjectHeld(guid, index))
- }
- }
- }
- case Nil => ; //no replacements found
- }
- } else {
- log.warn(
- s"FindReplacementConstructionItem: ${player.Name}, your $index hand needs to be empty before a replacement ${definition.Name} can be installed"
- )
- }
+ def DeployableBuildActivity(obj: Deployable): Unit = {
+ sendResponse(GenericObjectActionMessage(obj.GUID, 21)) //reset build cooldown
+ UpdateDeployableUIElements(avatar.deployables.UpdateUIElement(obj.Definition.Item))
}
/**
@@ -8406,8 +7932,8 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
* the player's locker inventory will be checked, and then
* the game environment (items on the ground) will be checked too.
* If the target object is discovered, it is removed from its current location and is completely destroyed.
- * @see `RequestDestroyMessage`
- * `Zone.ItemIs.Where`
+ * @see `RequestDestroyMessage`
+ * @see `Zone.ItemIs.Where`
* @param object_guid the target object's globally unique identifier;
* it is not expected that the object will be unregistered, but it is also not gauranteed
* @param obj the target object
@@ -8453,25 +7979,6 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
}
}
- /**
- * Common behavior for deconstructing expended explosive deployables in the game environment.
- * @param obj the deployable
- * @param guid the globally unique identifier for the deployable
- * @param pos the previous position of the deployable
- */
- def DeconstructDeployable(obj: PlanetSideGameObject with Deployable, guid: PlanetSideGUID, pos: Vector3): Unit = {
- sendResponse(SetEmpireMessage(guid, PlanetSideEmpire.NEUTRAL)) //for some, removes the green marker circle
- sendResponse(ObjectDeleteMessage(guid, 0))
- if (player.Faction == obj.Faction) {
- sendResponse(
- DeployableObjectsInfoMessage(
- DeploymentAction.Dismiss,
- DeployableInfo(guid, Deployable.Icon(obj.Definition.Item), pos, obj.Owner.getOrElse(PlanetSideGUID(0)))
- )
- )
- }
- }
-
/**
* Common behavior for deconstructing deployables in the game environment.
* @param obj the deployable
@@ -8481,24 +7988,15 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
* @param deletionType the value passed to `ObjectDeleteMessage` concerning the deconstruction animation
*/
def DeconstructDeployable(
- obj: PlanetSideGameObject with Deployable,
+ obj: Deployable,
guid: PlanetSideGUID,
pos: Vector3,
orient: Vector3,
deletionType: Int
): Unit = {
- sendResponse(SetEmpireMessage(guid, PlanetSideEmpire.NEUTRAL)) //for some, removes the green marker circle
sendResponse(TriggerEffectMessage("spawn_object_failed_effect", pos, orient))
sendResponse(PlanetsideAttributeMessage(guid, 29, 1)) //make deployable vanish
sendResponse(ObjectDeleteMessage(guid, deletionType))
- if (player.Faction == obj.Faction) {
- sendResponse(
- DeployableObjectsInfoMessage(
- DeploymentAction.Dismiss,
- DeployableInfo(guid, Deployable.Icon(obj.Definition.Item), pos, obj.Owner.getOrElse(PlanetSideGUID(0)))
- )
- )
- }
}
/**
@@ -8510,17 +8008,21 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
val events = continent.AvatarEvents
val zoneId = continent.id
(player.Inventory.Items ++ player.HolsterItems())
- .collect {
- case InventoryItem(obj: BoomerTrigger, index) =>
- player.Slot(index).Equipment = None
+ .collect { case InventoryItem(obj: BoomerTrigger, index) =>
+ player.Slot(index).Equipment = None
+ continent.GUID(obj.Companion) match {
+ case Some(mine: BoomerDeployable) => mine.Actor ! Deployable.Ownership(None)
+ case _ => ;
+ }
+ if (player.VisibleSlots.contains(index)) {
+ events ! AvatarServiceMessage(
+ zoneId,
+ AvatarAction.ObjectDelete(Service.defaultPlayerGUID, obj.GUID)
+ )
+ } else {
sendResponse(ObjectDeleteMessage(obj.GUID, 0))
- if (player.HasGUID && player.VisibleSlots.contains(index)) {
- events ! AvatarServiceMessage(
- zoneId,
- AvatarAction.ObjectDelete(player.GUID, obj.GUID)
- )
- }
- obj
+ }
+ obj
}
}
@@ -8914,7 +8416,9 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
def ToggleTeleportSystem(router: Vehicle, systemPlan: Option[(Utility.InternalTelepad, TelepadDeployable)]): Unit = {
systemPlan match {
case Some((internalTelepad, remoteTelepad)) =>
- LinkRouterToRemoteTelepad(router, internalTelepad, remoteTelepad)
+ internalTelepad.Telepad = remoteTelepad.GUID //necessary; backwards link to the (new) telepad
+ TelepadLike.StartRouterInternalTelepad(continent, router.GUID, internalTelepad)
+ TelepadLike.LinkTelepad(continent, remoteTelepad.GUID)
case _ =>
router.Utility(UtilityType.internal_router_telepad_deployable) match {
case Some(util: Utility.InternalTelepad) =>
@@ -8924,69 +8428,6 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
}
}
- /**
- * Link the router teleport system using the provided terminal information.
- * The internal telepad is made known of the remote telepad, creating the link.
- * @param router the vehicle that houses one end of the teleportation system (the `internalTelepad`)
- * @param internalTelepad the endpoint of the teleportation system housed by the router
- * @param remoteTelepad the endpoint of the teleportation system that exists in the environment
- */
- def LinkRouterToRemoteTelepad(
- router: Vehicle,
- internalTelepad: Utility.InternalTelepad,
- remoteTelepad: TelepadDeployable
- ): Unit = {
- internalTelepad.Telepad = remoteTelepad.GUID //necessary; backwards link to the (new) telepad
- CreateRouterInternalTelepad(router, internalTelepad)
- LinkRemoteTelepad(remoteTelepad.GUID)
- }
-
- /**
- * Create the mechanism that serves as one endpoint of the linked router teleportation system.
- *
- * Technically, the mechanism - an `InternalTelepad` object - is always made to exist
- * due to how the Router vehicle object is encoded into an `ObjectCreateMessage` packet.
- * Regardless, that internal mechanism is created anew each time the system links a new remote telepad.
- * @param router the vehicle that houses one end of the teleportation system (the `internalTelepad`)
- * @param internalTelepad the endpoint of the teleportation system housed by the router
- */
- def CreateRouterInternalTelepad(router: Vehicle, internalTelepad: PlanetSideGameObject with TelepadLike): Unit = {
- //create the interal telepad each time the link is made
- val rguid = router.GUID
- val uguid = internalTelepad.GUID
- val udef = internalTelepad.Definition
- /*
- the following instantiation and configuration creates the internal Router component
- normally dispatched while the Router is transitioned into its Deploying state
- it is safe, however, to perform these actions at any time during and after the Deploying state
- */
- sendResponse(
- ObjectCreateMessage(
- udef.ObjectId,
- uguid,
- ObjectCreateMessageParent(rguid, 2), //TODO stop assuming slot number
- udef.Packet.ConstructorData(internalTelepad).get
- )
- )
- sendResponse(GenericObjectActionMessage(uguid, 27))
- sendResponse(GenericObjectActionMessage(uguid, 30))
- /*
- the following configurations create the interactive beam underneath the Deployed Router
- normally dispatched after the warm-up timer has completed
- */
- sendResponse(GenericObjectActionMessage(uguid, 27))
- sendResponse(GenericObjectActionMessage(uguid, 28))
- }
-
- /**
- * na
- * @param telepadGUID na
- */
- def LinkRemoteTelepad(telepadGUID: PlanetSideGUID): Unit = {
- sendResponse(GenericObjectActionMessage(telepadGUID, 27))
- sendResponse(GenericObjectActionMessage(telepadGUID, 28))
- }
-
/**
* A player uses a fully-linked Router teleportation system.
* @param router the Router vehicle
diff --git a/src/main/scala/net/psforever/actors/zone/ZoneActor.scala b/src/main/scala/net/psforever/actors/zone/ZoneActor.scala
index 9b8546df..55762314 100644
--- a/src/main/scala/net/psforever/actors/zone/ZoneActor.scala
+++ b/src/main/scala/net/psforever/actors/zone/ZoneActor.scala
@@ -7,7 +7,7 @@ import net.psforever.objects.ce.Deployable
import net.psforever.objects.equipment.Equipment
import net.psforever.objects.serverobject.structures.{Building, StructureType}
import net.psforever.objects.zones.Zone
-import net.psforever.objects.{ConstructionItem, PlanetSideGameObject, Player, Vehicle}
+import net.psforever.objects.{ConstructionItem, Player, Vehicle}
import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID, Vector3}
import scala.collection.mutable.ListBuffer
@@ -44,10 +44,10 @@ object ZoneActor {
final case class PickupItem(guid: PlanetSideGUID) extends Command
- final case class BuildDeployable(obj: PlanetSideGameObject with Deployable, withTool: ConstructionItem)
+ final case class BuildDeployable(obj: Deployable, withTool: ConstructionItem)
extends Command
- final case class DismissDeployable(obj: PlanetSideGameObject with Deployable) extends Command
+ final case class DismissDeployable(obj: Deployable) extends Command
final case class SpawnVehicle(vehicle: Vehicle) extends Command
@@ -113,7 +113,7 @@ class ZoneActor(context: ActorContext[ZoneActor.Command], zone: Zone)
zone.Ground ! Zone.Ground.PickupItem(guid)
case BuildDeployable(obj, tool) =>
- zone.Deployables ! Zone.Deployable.Build(obj, tool)
+ zone.Deployables ! Zone.Deployable.Build(obj)
case DismissDeployable(obj) =>
zone.Deployables ! Zone.Deployable.Dismiss(obj)
diff --git a/src/main/scala/net/psforever/login/WorldSession.scala b/src/main/scala/net/psforever/login/WorldSession.scala
index 1a6b665b..a7bd88b2 100644
--- a/src/main/scala/net/psforever/login/WorldSession.scala
+++ b/src/main/scala/net/psforever/login/WorldSession.scala
@@ -883,4 +883,22 @@ object WorldSession {
false
}
}
+
+ 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)
+ )
+ }
}
diff --git a/src/main/scala/net/psforever/objects/BoomerDeployable.scala b/src/main/scala/net/psforever/objects/BoomerDeployable.scala
index 42b29399..b4fc98f3 100644
--- a/src/main/scala/net/psforever/objects/BoomerDeployable.scala
+++ b/src/main/scala/net/psforever/objects/BoomerDeployable.scala
@@ -1,7 +1,20 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects
-class BoomerDeployable(cdef: ExplosiveDeployableDefinition) extends ExplosiveDeployable(cdef) {
+import akka.actor.{ActorContext, Props}
+import net.psforever.objects.ballistics.{PlayerSource, SourceEntry}
+import net.psforever.objects.ce.{Deployable, DeployedItem}
+import net.psforever.objects.guid.GUIDTask
+import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject}
+import net.psforever.objects.vital.etc.TriggerUsedReason
+import net.psforever.objects.vital.interaction.DamageInteraction
+import net.psforever.objects.zones.Zone
+import net.psforever.services.Service
+import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
+import net.psforever.types.PlanetSideEmpire
+
+class BoomerDeployable(cdef: ExplosiveDeployableDefinition)
+ extends ExplosiveDeployable(cdef) {
private var trigger: Option[BoomerTrigger] = None
def Trigger: Option[BoomerTrigger] = trigger
@@ -20,3 +33,75 @@ class BoomerDeployable(cdef: ExplosiveDeployableDefinition) extends ExplosiveDep
Trigger
}
}
+
+class BoomerDeployableDefinition(private val objectId: Int) extends ExplosiveDeployableDefinition(objectId) {
+ override def Initialize(obj: Deployable, context: ActorContext) = {
+ obj.Actor =
+ context.actorOf(Props(classOf[BoomerDeployableControl], obj), PlanetSideServerObject.UniqueActorName(obj))
+ }
+}
+
+object BoomerDeployableDefinition {
+ def apply(dtype: DeployedItem.Value): BoomerDeployableDefinition = {
+ new BoomerDeployableDefinition(dtype.id)
+ }
+}
+
+class BoomerDeployableControl(mine: BoomerDeployable)
+ extends ExplosiveDeployableControl(mine) {
+
+ override def receive: Receive =
+ deployableBehavior
+ .orElse(takesDamage)
+ .orElse {
+ case CommonMessages.Use(player, Some(trigger: BoomerTrigger)) if mine.Trigger.contains(trigger) =>
+ // the trigger damages the mine, which sets it off, which causes an explosion
+ // think of this as an initiator to the proper explosion
+ mine.Destroyed = true
+ ExplosiveDeployableControl.DamageResolution(
+ mine,
+ DamageInteraction(
+ SourceEntry(mine),
+ TriggerUsedReason(PlayerSource(player), trigger.GUID),
+ mine.Position
+ ).calculate()(mine),
+ damage = 0
+ )
+
+ case _ => ;
+ }
+
+ override def loseOwnership(faction: PlanetSideEmpire.Value): Unit = {
+ super.loseOwnership(PlanetSideEmpire.NEUTRAL)
+ mine.OwnerName = None
+ }
+
+ override def gainOwnership(player: Player): Unit = {
+ mine.Faction = PlanetSideEmpire.NEUTRAL //force map icon redraw
+ super.gainOwnership(player, player.Faction)
+ }
+
+ override def dismissDeployable() : Unit = {
+ super.dismissDeployable()
+ val zone = mine.Zone
+ mine.Trigger match {
+ case Some(trigger) =>
+ mine.Trigger = None
+ trigger.Companion = None
+ val guid = trigger.GUID
+ Zone.EquipmentIs.Where(trigger, guid, zone) match {
+ case Some(Zone.EquipmentIs.InContainer(container, index)) =>
+ container.Slot(index).Equipment = None
+ case Some(Zone.EquipmentIs.OnGround()) =>
+ zone.Ground ! Zone.Ground.RemoveItem(guid)
+ case _ => ;
+ }
+ zone.AvatarEvents! AvatarServiceMessage(
+ zone.id,
+ AvatarAction.ObjectDelete(Service.defaultPlayerGUID, trigger.GUID)
+ )
+ zone.tasks ! GUIDTask.UnregisterObjectTask(trigger)(zone.GUID)
+ case None => ;
+ }
+ }
+}
diff --git a/src/main/scala/net/psforever/objects/ConstructionItem.scala b/src/main/scala/net/psforever/objects/ConstructionItem.scala
index 2f779c24..220c6f8b 100644
--- a/src/main/scala/net/psforever/objects/ConstructionItem.scala
+++ b/src/main/scala/net/psforever/objects/ConstructionItem.scala
@@ -24,7 +24,7 @@ class ConstructionItem(private val cItemDef: ConstructionItemDefinition)
extends Equipment
with FireModeSwitch[ConstructionFireMode] {
private var fireModeIndex: Int = 0
- private var ammoTypeIndex: Int = 0
+ private val ammoTypeIndices: Array[Int] = Array.fill[Int](cItemDef.Modes.size)(elem = 0)
def FireModeIndex: Int = fireModeIndex
@@ -37,29 +37,33 @@ class ConstructionItem(private val cItemDef: ConstructionItemDefinition)
def NextFireMode: ConstructionFireMode = {
FireModeIndex = FireModeIndex + 1
- ammoTypeIndex = 0
FireMode
}
- def AmmoTypeIndex: Int = ammoTypeIndex
+ def AmmoTypeIndex: Int = ammoTypeIndices(fireModeIndex)
def AmmoTypeIndex_=(index: Int): Int = {
- ammoTypeIndex = index % FireMode.Deployables.length
+ ammoTypeIndices(fireModeIndex) = index % FireMode.Deployables.length
AmmoTypeIndex
}
- def AmmoType: DeployedItem.Value = FireMode.Deployables(ammoTypeIndex)
+ def AmmoType: DeployedItem.Value = FireMode.Deployables(AmmoTypeIndex)
def NextAmmoType: DeployedItem.Value = {
AmmoTypeIndex = AmmoTypeIndex + 1
- FireMode.Deployables(ammoTypeIndex)
+ FireMode.Deployables(AmmoTypeIndex)
}
- def ModePermissions: Set[Certification] = FireMode.Permissions(ammoTypeIndex)
+ def ModePermissions: Set[Certification] = FireMode.Permissions(AmmoTypeIndex)
+
+ def resetAmmoTypes(): Unit = {
+ ammoTypeIndices.indices.foreach { index => ammoTypeIndices.update(index, 0) }
+ }
def Definition: ConstructionItemDefinition = cItemDef
}
+
object ConstructionItem {
def apply(cItemDef: ConstructionItemDefinition): ConstructionItem = {
new ConstructionItem(cItemDef)
diff --git a/src/main/scala/net/psforever/objects/Deployables.scala b/src/main/scala/net/psforever/objects/Deployables.scala
index 3799c18c..8e5b9d33 100644
--- a/src/main/scala/net/psforever/objects/Deployables.scala
+++ b/src/main/scala/net/psforever/objects/Deployables.scala
@@ -7,18 +7,17 @@ import net.psforever.objects.avatar.{Avatar, Certification}
import scala.concurrent.duration._
import net.psforever.objects.ce.{Deployable, DeployedItem}
import net.psforever.objects.zones.Zone
-import net.psforever.packet.game.{DeployableInfo, DeploymentAction}
+import net.psforever.packet.game._
import net.psforever.types.PlanetSideGUID
-import net.psforever.services.RemoverActor
import net.psforever.services.local.{LocalAction, LocalServiceMessage}
object Deployables {
//private val log = org.log4s.getLogger("Deployables")
object Make {
- def apply(item: DeployedItem.Value): () => PlanetSideGameObject with Deployable = cemap(item)
+ def apply(item: DeployedItem.Value): () => Deployable = cemap(item)
- private val cemap: Map[DeployedItem.Value, () => PlanetSideGameObject with Deployable] = Map(
+ private val cemap: Map[DeployedItem.Value, () => Deployable] = Map(
DeployedItem.boomer -> { () => new BoomerDeployable(GlobalDefinitions.boomer) },
DeployedItem.he_mine -> { () => new ExplosiveDeployable(GlobalDefinitions.he_mine) },
DeployedItem.jammer_mine -> { () => new ExplosiveDeployable(GlobalDefinitions.jammer_mine) },
@@ -48,6 +47,22 @@ object Deployables {
).withDefaultValue({ () => new ExplosiveDeployable(GlobalDefinitions.boomer) })
}
+ /**
+ * Distribute information that a deployable has been destroyed.
+ * Additionally, since the player who destroyed the deployable isn't necessarily the owner,
+ * and the real owner will still be aware of the existence of the deployable,
+ * that player must be informed of the loss of the deployable directly.
+ * @see `AnnounceDestroyDeployable(Deployable)`
+ * @see `Deployable.Deconstruct`
+ * @param target the deployable that is destroyed
+ * @param time length of time that the deployable is allowed to exist in the game world;
+ * `None` indicates the normal un-owned existence time (180 seconds)
+ */
+ def AnnounceDestroyDeployable(target: Deployable, time: Option[FiniteDuration]): Unit = {
+ AnnounceDestroyDeployable(target)
+ target.Actor ! Deployable.Deconstruct(time)
+ }
+
/**
* Distribute information that a deployable has been destroyed.
* The deployable may not have yet been eliminated from the game world (client or server),
@@ -58,36 +73,36 @@ object Deployables {
* This function eventually invokes the same routine
* but mainly goes into effect when the deployable has been destroyed
* and may still leave a physical component in the game world to be cleaned up later.
- * That is the task `EliminateDeployable` performs.
- * Additionally, since the player who destroyed the deployable isn't necessarily the owner,
- * and the real owner will still be aware of the existence of the deployable,
- * that player must be informed of the loss of the deployable directly.
- * @see `DeployableRemover`
- * @see `Vitality.DamageResolution`
- * @see `LocalResponse.EliminateDeployable`
- * @see `DeconstructDeployable`
+ * @see `DeployableInfo`
+ * @see `DeploymentAction`
+ * @see `LocalAction.DeployableMapIcon`
* @param target the deployable that is destroyed
- * @param time length of time that the deployable is allowed to exist in the game world;
- * `None` indicates the normal un-owned existence time (180 seconds)
- */
- def AnnounceDestroyDeployable(target: PlanetSideGameObject with Deployable, time: Option[FiniteDuration]): Unit = {
+ **/
+ def AnnounceDestroyDeployable(target: Deployable): Unit = {
val zone = target.Zone
+ val events = zone.LocalEvents
+ val item = target.Definition.Item
target.OwnerName match {
case Some(owner) =>
+ zone.Players.find { p => owner.equals(p.name) } match {
+ case Some(p) =>
+ if (p.deployables.Remove(target)) {
+ events ! LocalServiceMessage(owner, LocalAction.DeployableUIFor(item))
+ }
+ case None => ;
+ }
+ target.Owner = None
target.OwnerName = None
- zone.LocalEvents ! LocalServiceMessage(owner, LocalAction.AlertDestroyDeployable(PlanetSideGUID(0), target))
case None => ;
}
- zone.LocalEvents ! LocalServiceMessage(
+ events ! LocalServiceMessage(
s"${target.Faction}",
LocalAction.DeployableMapIcon(
PlanetSideGUID(0),
DeploymentAction.Dismiss,
- DeployableInfo(target.GUID, Deployable.Icon(target.Definition.Item), target.Position, PlanetSideGUID(0))
+ DeployableInfo(target.GUID, Deployable.Icon(item), target.Position, PlanetSideGUID(0))
)
)
- zone.LocalEvents ! LocalServiceMessage.Deployables(RemoverActor.ClearSpecific(List(target), zone))
- zone.LocalEvents ! LocalServiceMessage.Deployables(RemoverActor.AddTask(target, zone, time))
}
/**
@@ -99,28 +114,16 @@ object Deployables {
* @return all previously-owned deployables after they have been processed;
* boomers are listed before all other deployable types
*/
- def Disown(zone: Zone, avatar: Avatar, replyTo: ActorRef): List[PlanetSideGameObject with Deployable] = {
- val (boomers, deployables) =
- avatar.deployables
- .Clear()
- .map(zone.GUID)
- .collect { case Some(obj) => obj.asInstanceOf[PlanetSideGameObject with Deployable] }
- .partition(_.isInstanceOf[BoomerDeployable])
- //do not change the OwnerName field at this time
- boomers.collect({
- case obj: BoomerDeployable =>
- zone.LocalEvents.tell(
- LocalServiceMessage.Deployables(RemoverActor.AddTask(obj, zone, Some(0 seconds))),
- replyTo
- ) //near-instant
- obj.Owner = None
- obj.Trigger = None
- })
- deployables.foreach(obj => {
- zone.LocalEvents.tell(LocalServiceMessage.Deployables(RemoverActor.AddTask(obj, zone)), replyTo) //normal decay
- obj.Owner = None
- })
- boomers ++ deployables
+ def Disown(zone: Zone, avatar: Avatar, replyTo: ActorRef): List[Deployable] = {
+ avatar.deployables
+ .Clear()
+ .map(zone.GUID)
+ .collect {
+ case Some(obj: Deployable) =>
+ obj.Actor ! Deployable.Ownership(None)
+ obj.Owner = None //fast-forward the effect
+ obj
+ }
}
/**
@@ -139,6 +142,89 @@ object Deployables {
avatar.deployables.UpdateUI()
}
+ /**
+ * If the default ammunition mode for the `ConstructionTool` is not supported by the given certifications,
+ * find a suitable ammunition mode and switch to it internally.
+ * No special complaint is raised if the `ConstructionItem` itself is completely unsupported.
+ * @param certs the certification baseline being compared against
+ * @param obj the `ConstructionItem` entity
+ * @return `true`, if the ammunition mode of the item has been changed;
+ * `false`, otherwise
+ */
+ def initializeConstructionAmmoMode(
+ certs: Set[Certification],
+ obj: ConstructionItem
+ ): Boolean = {
+ if (!Deployables.constructionItemPermissionComparison(certs, obj.ModePermissions)) {
+ Deployables.performConstructionItemAmmoChange(certs, obj, obj.AmmoTypeIndex)
+ } else {
+ false
+ }
+ }
+
+ /**
+ * The custom behavior responding to the packet `ChangeAmmoMessage` for `ConstructionItem` game objects.
+ * Iterate through sub-modes corresponding to a type of "deployable" as ammunition for this fire mode
+ * and check each of these sub-modes for their certification requirements to be met before they can be used.
+ * Additional effort is exerted to ensure that the requirements for the given ammunition are satisfied.
+ * If no satisfactory combination is achieved, the original state will be restored.
+ * @see `Certification`
+ * @see `ChangeAmmoMessage`
+ * @see `ConstructionItem.ModePermissions`
+ * @see `Deployables.constructionItemPermissionComparison`
+ * @param certs the certification baseline being compared against
+ * @param obj the `ConstructionItem` entity
+ * @param originalAmmoIndex the starting point ammunition type mode index
+ * @return `true`, if the ammunition mode of the item has been changed;
+ * `false`, otherwise
+ */
+ def performConstructionItemAmmoChange(
+ certs: Set[Certification],
+ obj: ConstructionItem,
+ originalAmmoIndex: Int
+ ): Boolean = {
+ do {
+ obj.NextAmmoType
+ } while (
+ !Deployables.constructionItemPermissionComparison(certs, obj.ModePermissions) &&
+ originalAmmoIndex != obj.AmmoTypeIndex
+ )
+ obj.AmmoTypeIndex != originalAmmoIndex
+ }
+
+ /**
+ * The custom behavior responding to the message `ChangeFireModeMessage` for `ConstructionItem` game objects.
+ * Each fire mode has sub-modes corresponding to a type of "deployable" as ammunition
+ * and each of these sub-modes have certification requirements that must be met before they can be used.
+ * Additional effort is exerted to ensure that the requirements for the given mode and given sub-mode are satisfied.
+ * If no satisfactory combination is achieved, the original state will be restored.
+ * @see `Deployables.constructionItemPermissionComparison`
+ * @see `Deployables.performConstructionItemAmmoChange`
+ * @see `FireModeSwitch.NextFireMode`
+ * @param certs the certification baseline being compared against
+ * @param obj the `ConstructionItem` entity
+ * @param originalModeIndex the starting point fire mode index
+ * @return `true`, if the ammunition mode of the item has been changed;
+ * `false`, otherwise
+ */
+ def performConstructionItemFireModeChange(
+ certs: Set[Certification],
+ obj: ConstructionItem,
+ originalModeIndex: Int
+ ): Boolean = {
+ /*
+ if any of the fire modes possess an initial option that is not valid for a given set of certifications,
+ but a subsequent option is valid, the do...while loop has to be modified to traverse and compare each option
+ */
+ do {
+ obj.NextFireMode
+ } while (
+ !Deployables.constructionItemPermissionComparison(certs, obj.ModePermissions) &&
+ originalModeIndex != obj.FireModeIndex
+ )
+ originalModeIndex != obj.FireModeIndex
+ }
+
/**
* Compare sets of certifications to determine if
* the requested `Engineering`-like certification requirements of the one group can be found in a another group.
diff --git a/src/main/scala/net/psforever/objects/ExplosiveDeployable.scala b/src/main/scala/net/psforever/objects/ExplosiveDeployable.scala
index 381ffd5f..00322346 100644
--- a/src/main/scala/net/psforever/objects/ExplosiveDeployable.scala
+++ b/src/main/scala/net/psforever/objects/ExplosiveDeployable.scala
@@ -2,19 +2,17 @@
package net.psforever.objects
import akka.actor.{Actor, ActorContext, Props}
-import net.psforever.objects.ballistics.{PlayerSource, SourceEntry}
import net.psforever.objects.ce._
-import net.psforever.objects.definition.{ComplexDeployableDefinition, SimpleDeployableDefinition}
+import net.psforever.objects.definition.DeployableDefinition
import net.psforever.objects.definition.converter.SmallDeployableConverter
import net.psforever.objects.equipment.JammableUnit
import net.psforever.objects.geometry.Geometry3D
import net.psforever.objects.serverobject.affinity.FactionAffinity
-import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject}
+import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.serverobject.damage.{Damageable, DamageableEntity}
import net.psforever.objects.serverobject.damage.Damageable.Target
import net.psforever.objects.vital.resolution.ResolutionCalculations.Output
import net.psforever.objects.vital.{SimpleResolutions, Vitality}
-import net.psforever.objects.vital.etc.TriggerUsedReason
import net.psforever.objects.vital.interaction.{DamageInteraction, DamageResult}
import net.psforever.objects.vital.projectile.ProjectileReason
import net.psforever.objects.zones.Zone
@@ -26,13 +24,13 @@ import net.psforever.services.local.{LocalAction, LocalServiceMessage}
import scala.concurrent.duration._
class ExplosiveDeployable(cdef: ExplosiveDeployableDefinition)
- extends ComplexDeployable(cdef)
- with JammableUnit {
+ extends Deployable(cdef)
+ with JammableUnit {
override def Definition: ExplosiveDeployableDefinition = cdef
}
-class ExplosiveDeployableDefinition(private val objectId: Int) extends ComplexDeployableDefinition(objectId) {
+class ExplosiveDeployableDefinition(private val objectId: Int) extends DeployableDefinition(objectId) {
Name = "explosive_deployable"
DeployCategory = DeployableCategory.Mines
Model = SimpleResolutions.calculate
@@ -47,14 +45,10 @@ class ExplosiveDeployableDefinition(private val objectId: Int) extends ComplexDe
DetonateOnJamming
}
- override def Initialize(obj: PlanetSideServerObject with Deployable, context: ActorContext) = {
+ override def Initialize(obj: Deployable, context: ActorContext) = {
obj.Actor =
context.actorOf(Props(classOf[ExplosiveDeployableControl], obj), PlanetSideServerObject.UniqueActorName(obj))
}
-
- override def Uninitialize(obj: PlanetSideServerObject with Deployable, context: ActorContext) = {
- SimpleDeployableDefinition.SimpleUninitialize(obj, context)
- }
}
object ExplosiveDeployableDefinition {
@@ -63,30 +57,22 @@ object ExplosiveDeployableDefinition {
}
}
-class ExplosiveDeployableControl(mine: ExplosiveDeployable) extends Actor with Damageable {
+class ExplosiveDeployableControl(mine: ExplosiveDeployable)
+ extends Actor
+ with DeployableBehavior
+ with Damageable {
+ def DeployableObject = mine
def DamageableObject = mine
+ override def postStop(): Unit = {
+ super.postStop()
+ deployableBehaviorPostStop()
+ }
+
def receive: Receive =
- takesDamage
+ deployableBehavior
+ .orElse(takesDamage)
.orElse {
- case CommonMessages.Use(player, Some(trigger: BoomerTrigger)) if {
- mine match {
- case boomer: BoomerDeployable => boomer.Trigger.contains(trigger) && mine.Definition.Damageable
- case _ => false
- }
- } =>
- // the trigger damages the mine, which sets it off, which causes an explosion
- // think of this as an initiator to the proper explosion
- mine.Destroyed = true
- ExplosiveDeployableControl.DamageResolution(
- mine,
- DamageInteraction(
- SourceEntry(mine),
- TriggerUsedReason(PlayerSource(player), trigger.GUID),
- mine.Position
- ).calculate()(mine),
- damage = 0
- )
case _ => ;
}
diff --git a/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/src/main/scala/net/psforever/objects/GlobalDefinitions.scala
index 2d538571..7db52cd5 100644
--- a/src/main/scala/net/psforever/objects/GlobalDefinitions.scala
+++ b/src/main/scala/net/psforever/objects/GlobalDefinitions.scala
@@ -947,7 +947,7 @@ object GlobalDefinitions {
/*
combat engineering deployables
*/
- val boomer = ExplosiveDeployableDefinition(DeployedItem.boomer)
+ val boomer = BoomerDeployableDefinition(DeployedItem.boomer)
val he_mine = ExplosiveDeployableDefinition(DeployedItem.he_mine)
@@ -975,7 +975,7 @@ object GlobalDefinitions {
val deployable_shield_generator = new ShieldGeneratorDefinition
- val router_telepad_deployable = SimpleDeployableDefinition(DeployedItem.router_telepad_deployable)
+ val router_telepad_deployable = TelepadDeployableDefinition(DeployedItem.router_telepad_deployable)
//this is only treated like a deployable
val internal_router_telepad_deployable = InternalTelepadDefinition() //objectId: 744
@@ -4993,28 +4993,35 @@ object GlobalDefinitions {
ace.Name = "ace"
ace.Size = EquipmentSize.Pistol
- ace.Modes += new ConstructionFireMode
- ace.Modes.head.Item(DeployedItem.boomer, Set(Certification.CombatEngineering))
- ace.Modes += new ConstructionFireMode
- ace.Modes(1).Item(DeployedItem.he_mine, Set(Certification.CombatEngineering))
- ace.Modes(1).Item(DeployedItem.jammer_mine, Set(Certification.AssaultEngineering))
- ace.Modes += new ConstructionFireMode
- ace.Modes(2).Item(DeployedItem.spitfire_turret, Set(Certification.CombatEngineering))
- ace.Modes(2).Item(DeployedItem.spitfire_cloaked, Set(Certification.FortificationEngineering))
- ace.Modes(2).Item(DeployedItem.spitfire_aa, Set(Certification.FortificationEngineering))
- ace.Modes += new ConstructionFireMode
- ace.Modes(3).Item(DeployedItem.motionalarmsensor, Set(Certification.CombatEngineering))
- ace.Modes(3).Item(DeployedItem.sensor_shield, Set(Certification.AdvancedHacking, Certification.CombatEngineering))
+ ace.Modes += new ConstructionFireMode {
+ Item(DeployedItem.boomer, Set(Certification.CombatEngineering))
+ }
+ ace.Modes += new ConstructionFireMode {
+ Item(DeployedItem.he_mine, Set(Certification.CombatEngineering))
+ Item(DeployedItem.jammer_mine, Set(Certification.AssaultEngineering))
+ }
+ ace.Modes += new ConstructionFireMode {
+ Item(DeployedItem.spitfire_turret, Set(Certification.CombatEngineering))
+ Item(DeployedItem.spitfire_cloaked, Set(Certification.FortificationEngineering))
+ Item(DeployedItem.spitfire_aa, Set(Certification.FortificationEngineering))
+ }
+ ace.Modes += new ConstructionFireMode {
+ Item(DeployedItem.motionalarmsensor, Set(Certification.CombatEngineering))
+ Item(DeployedItem.sensor_shield, Set(Certification.AdvancedHacking, Certification.CombatEngineering))
+ }
ace.Tile = InventoryTile.Tile33
advanced_ace.Name = "advanced_ace"
advanced_ace.Size = EquipmentSize.Rifle
- advanced_ace.Modes += new ConstructionFireMode
- advanced_ace.Modes.head.Item(DeployedItem.tank_traps, Set(Certification.FortificationEngineering))
- advanced_ace.Modes += new ConstructionFireMode
- advanced_ace.Modes(1).Item(DeployedItem.portable_manned_turret, Set(Certification.AssaultEngineering))
- advanced_ace.Modes += new ConstructionFireMode
- advanced_ace.Modes(2).Item(DeployedItem.deployable_shield_generator, Set(Certification.AssaultEngineering))
+ advanced_ace.Modes += new ConstructionFireMode {
+ Item(DeployedItem.portable_manned_turret, Set(Certification.AssaultEngineering))
+ }
+ advanced_ace.Modes += new ConstructionFireMode {
+ Item(DeployedItem.tank_traps, Set(Certification.FortificationEngineering))
+ }
+ advanced_ace.Modes += new ConstructionFireMode {
+ Item(DeployedItem.deployable_shield_generator, Set(Certification.AssaultEngineering))
+ }
advanced_ace.Tile = InventoryTile.Tile93
router_telepad.Name = "router_telepad"
@@ -7041,6 +7048,7 @@ object GlobalDefinitions {
val smallTurret = GeometryForm.representByCylinder(radius = 0.48435f, height = 1.23438f) _
val sensor = GeometryForm.representByCylinder(radius = 0.1914f, height = 1.21875f) _
val largeTurret = GeometryForm.representByCylinder(radius = 0.8437f, height = 2.29687f) _
+
boomer.Name = "boomer"
boomer.Descriptor = "Boomers"
boomer.MaxHealth = 100
@@ -7049,6 +7057,7 @@ object GlobalDefinitions {
boomer.Repairable = false
boomer.DeployCategory = DeployableCategory.Boomers
boomer.DeployTime = Duration.create(1000, "ms")
+ boomer.deployAnimation = DeployAnimation.Standard
boomer.explodes = true
boomer.innateDamage = new DamageWithPosition {
CausesDamageType = DamageType.Splash
@@ -7063,6 +7072,7 @@ object GlobalDefinitions {
Modifiers = ExplodingRadialDegrade
}
boomer.Geometry = mine
+
he_mine.Name = "he_mine"
he_mine.Descriptor = "Mines"
he_mine.MaxHealth = 100
@@ -7070,6 +7080,7 @@ object GlobalDefinitions {
he_mine.DamageableByFriendlyFire = false
he_mine.Repairable = false
he_mine.DeployTime = Duration.create(1000, "ms")
+ he_mine.deployAnimation = DeployAnimation.Standard
he_mine.explodes = true
he_mine.innateDamage = new DamageWithPosition {
CausesDamageType = DamageType.Splash
@@ -7084,6 +7095,7 @@ object GlobalDefinitions {
Modifiers = ExplodingRadialDegrade
}
he_mine.Geometry = mine
+
jammer_mine.Name = "jammer_mine"
jammer_mine.Descriptor = "JammerMines"
jammer_mine.MaxHealth = 100
@@ -7091,8 +7103,10 @@ object GlobalDefinitions {
jammer_mine.DamageableByFriendlyFire = false
jammer_mine.Repairable = false
jammer_mine.DeployTime = Duration.create(1000, "ms")
+ jammer_mine.deployAnimation = DeployAnimation.Standard
jammer_mine.DetonateOnJamming = false
jammer_mine.Geometry = mine
+
spitfire_turret.Name = "spitfire_turret"
spitfire_turret.Descriptor = "Spitfires"
spitfire_turret.MaxHealth = 100
@@ -7105,7 +7119,7 @@ object GlobalDefinitions {
spitfire_turret.DeployCategory = DeployableCategory.SmallTurrets
spitfire_turret.DeployTime = Duration.create(5000, "ms")
spitfire_turret.Model = ComplexDeployableResolutions.calculate
- spitfire_turret.explodes = true
+ spitfire_turret.deployAnimation = DeployAnimation.Standard
spitfire_turret.explodes = true
spitfire_turret.innateDamage = new DamageWithPosition {
CausesDamageType = DamageType.One
@@ -7116,6 +7130,7 @@ object GlobalDefinitions {
Modifiers = ExplodingRadialDegrade
}
spitfire_turret.Geometry = smallTurret
+
spitfire_cloaked.Name = "spitfire_cloaked"
spitfire_cloaked.Descriptor = "CloakingSpitfires"
spitfire_cloaked.MaxHealth = 100
@@ -7127,6 +7142,7 @@ object GlobalDefinitions {
spitfire_cloaked.ReserveAmmunition = false
spitfire_cloaked.DeployCategory = DeployableCategory.SmallTurrets
spitfire_cloaked.DeployTime = Duration.create(5000, "ms")
+ spitfire_cloaked.deployAnimation = DeployAnimation.Standard
spitfire_cloaked.Model = ComplexDeployableResolutions.calculate
spitfire_cloaked.explodes = true
spitfire_cloaked.innateDamage = new DamageWithPosition {
@@ -7138,6 +7154,7 @@ object GlobalDefinitions {
Modifiers = ExplodingRadialDegrade
}
spitfire_cloaked.Geometry = smallTurret
+
spitfire_aa.Name = "spitfire_aa"
spitfire_aa.Descriptor = "FlakSpitfires"
spitfire_aa.MaxHealth = 100
@@ -7149,6 +7166,7 @@ object GlobalDefinitions {
spitfire_aa.ReserveAmmunition = false
spitfire_aa.DeployCategory = DeployableCategory.SmallTurrets
spitfire_aa.DeployTime = Duration.create(5000, "ms")
+ spitfire_aa.deployAnimation = DeployAnimation.Standard
spitfire_aa.Model = ComplexDeployableResolutions.calculate
spitfire_aa.explodes = true
spitfire_aa.innateDamage = new DamageWithPosition {
@@ -7160,6 +7178,7 @@ object GlobalDefinitions {
Modifiers = ExplodingRadialDegrade
}
spitfire_aa.Geometry = smallTurret
+
motionalarmsensor.Name = "motionalarmsensor"
motionalarmsensor.Descriptor = "MotionSensors"
motionalarmsensor.MaxHealth = 100
@@ -7167,7 +7186,9 @@ object GlobalDefinitions {
motionalarmsensor.Repairable = true
motionalarmsensor.RepairIfDestroyed = false
motionalarmsensor.DeployTime = Duration.create(1000, "ms")
+ motionalarmsensor.deployAnimation = DeployAnimation.Standard
motionalarmsensor.Geometry = sensor
+
sensor_shield.Name = "sensor_shield"
sensor_shield.Descriptor = "SensorShields"
sensor_shield.MaxHealth = 100
@@ -7175,7 +7196,9 @@ object GlobalDefinitions {
sensor_shield.Repairable = true
sensor_shield.RepairIfDestroyed = false
sensor_shield.DeployTime = Duration.create(5000, "ms")
+ sensor_shield.deployAnimation = DeployAnimation.Standard
sensor_shield.Geometry = sensor
+
tank_traps.Name = "tank_traps"
tank_traps.Descriptor = "TankTraps"
tank_traps.MaxHealth = 5000
@@ -7184,6 +7207,7 @@ object GlobalDefinitions {
tank_traps.RepairIfDestroyed = false
tank_traps.DeployCategory = DeployableCategory.TankTraps
tank_traps.DeployTime = Duration.create(6000, "ms")
+ tank_traps.deployAnimation = DeployAnimation.Fdu
//tank_traps do not explode
tank_traps.innateDamage = new DamageWithPosition {
CausesDamageType = DamageType.One
@@ -7194,6 +7218,7 @@ object GlobalDefinitions {
Modifiers = ExplodingRadialDegrade
}
tank_traps.Geometry = GeometryForm.representByCylinder(radius = 2.89680997f, height = 3.57812f)
+
val fieldTurretConverter = new FieldTurretConverter
portable_manned_turret.Name = "portable_manned_turret"
portable_manned_turret.Descriptor = "FieldTurrets"
@@ -7211,6 +7236,7 @@ object GlobalDefinitions {
portable_manned_turret.Packet = fieldTurretConverter
portable_manned_turret.DeployCategory = DeployableCategory.FieldTurrets
portable_manned_turret.DeployTime = Duration.create(6000, "ms")
+ portable_manned_turret.deployAnimation = DeployAnimation.Fdu
portable_manned_turret.Model = ComplexDeployableResolutions.calculate
portable_manned_turret.explodes = true
portable_manned_turret.innateDamage = new DamageWithPosition {
@@ -7222,6 +7248,7 @@ object GlobalDefinitions {
Modifiers = ExplodingRadialDegrade
}
portable_manned_turret.Geometry = largeTurret
+
portable_manned_turret_nc.Name = "portable_manned_turret_nc"
portable_manned_turret_nc.Descriptor = "FieldTurrets"
portable_manned_turret_nc.MaxHealth = 1000
@@ -7238,6 +7265,7 @@ object GlobalDefinitions {
portable_manned_turret_nc.Packet = fieldTurretConverter
portable_manned_turret_nc.DeployCategory = DeployableCategory.FieldTurrets
portable_manned_turret_nc.DeployTime = Duration.create(6000, "ms")
+ portable_manned_turret_nc.deployAnimation = DeployAnimation.Fdu
portable_manned_turret_nc.Model = ComplexDeployableResolutions.calculate
portable_manned_turret_nc.explodes = true
portable_manned_turret_nc.innateDamage = new DamageWithPosition {
@@ -7249,6 +7277,7 @@ object GlobalDefinitions {
Modifiers = ExplodingRadialDegrade
}
portable_manned_turret_nc.Geometry = largeTurret
+
portable_manned_turret_tr.Name = "portable_manned_turret_tr"
portable_manned_turret_tr.Descriptor = "FieldTurrets"
portable_manned_turret_tr.MaxHealth = 1000
@@ -7265,6 +7294,7 @@ object GlobalDefinitions {
portable_manned_turret_tr.Packet = fieldTurretConverter
portable_manned_turret_tr.DeployCategory = DeployableCategory.FieldTurrets
portable_manned_turret_tr.DeployTime = Duration.create(6000, "ms")
+ portable_manned_turret_tr.deployAnimation = DeployAnimation.Fdu
portable_manned_turret_tr.Model = ComplexDeployableResolutions.calculate
portable_manned_turret_tr.explodes = true
portable_manned_turret_tr.innateDamage = new DamageWithPosition {
@@ -7276,6 +7306,7 @@ object GlobalDefinitions {
Modifiers = ExplodingRadialDegrade
}
portable_manned_turret_tr.Geometry = largeTurret
+
portable_manned_turret_vs.Name = "portable_manned_turret_vs"
portable_manned_turret_vs.Descriptor = "FieldTurrets"
portable_manned_turret_vs.MaxHealth = 1000
@@ -7292,6 +7323,7 @@ object GlobalDefinitions {
portable_manned_turret_vs.Packet = fieldTurretConverter
portable_manned_turret_vs.DeployCategory = DeployableCategory.FieldTurrets
portable_manned_turret_vs.DeployTime = Duration.create(6000, "ms")
+ portable_manned_turret_vs.deployAnimation = DeployAnimation.Fdu
portable_manned_turret_vs.Model = ComplexDeployableResolutions.calculate
portable_manned_turret_vs.explodes = true
portable_manned_turret_vs.innateDamage = new DamageWithPosition {
@@ -7303,6 +7335,7 @@ object GlobalDefinitions {
Modifiers = ExplodingRadialDegrade
}
portable_manned_turret_vs.Geometry = largeTurret
+
deployable_shield_generator.Name = "deployable_shield_generator"
deployable_shield_generator.Descriptor = "ShieldGenerators"
deployable_shield_generator.MaxHealth = 1700
@@ -7310,8 +7343,10 @@ object GlobalDefinitions {
deployable_shield_generator.Repairable = true
deployable_shield_generator.RepairIfDestroyed = false
deployable_shield_generator.DeployTime = Duration.create(6000, "ms")
+ deployable_shield_generator.deployAnimation = DeployAnimation.Fdu
deployable_shield_generator.Model = ComplexDeployableResolutions.calculate
deployable_shield_generator.Geometry = GeometryForm.representByCylinder(radius = 0.6562f, height = 2.17188f)
+
router_telepad_deployable.Name = "router_telepad_deployable"
router_telepad_deployable.MaxHealth = 100
router_telepad_deployable.Damageable = true
@@ -7321,6 +7356,7 @@ object GlobalDefinitions {
router_telepad_deployable.Packet = new TelepadDeployableConverter
router_telepad_deployable.Model = SimpleResolutions.calculate
router_telepad_deployable.Geometry = GeometryForm.representByRaisedSphere(radius = 1.2344f)
+
internal_router_telepad_deployable.Name = "router_telepad_deployable"
internal_router_telepad_deployable.MaxHealth = 1
internal_router_telepad_deployable.Damageable = false
diff --git a/src/main/scala/net/psforever/objects/Player.scala b/src/main/scala/net/psforever/objects/Player.scala
index 3f4a71f5..e86d576f 100644
--- a/src/main/scala/net/psforever/objects/Player.scala
+++ b/src/main/scala/net/psforever/objects/Player.scala
@@ -2,6 +2,7 @@
package net.psforever.objects
import net.psforever.objects.avatar.{Avatar, LoadoutManager, SpecialCarry}
+import net.psforever.objects.ce.Deployable
import net.psforever.objects.definition.{AvatarDefinition, ExoSuitDefinition, SpecialExoSuitDefinition}
import net.psforever.objects.equipment.{Equipment, EquipmentSize, EquipmentSlot, JammableUnit}
import net.psforever.objects.inventory.{Container, GridInventory, InventoryItem}
@@ -202,7 +203,7 @@ class Player(var avatar: Avatar)
def HolsterItems(): List[InventoryItem] = holsters
.zipWithIndex
.collect {
- case out @ (slot: EquipmentSlot, index: Int) if slot.Equipment.nonEmpty => InventoryItem(slot.Equipment.get, index)
+ case (slot: EquipmentSlot, index: Int) if slot.Equipment.nonEmpty => InventoryItem(slot.Equipment.get, index)
}.toList
def Inventory: GridInventory = inventory
@@ -538,11 +539,11 @@ class Player(var avatar: Avatar)
override def toString: String = {
val guid = if (HasGUID) {
- s" ${Continent}-${GUID.guid}"
+ s" $Continent-${GUID.guid}"
} else {
""
}
- s"${avatar.name}$guid ${avatar.faction} H: ${Health}/${MaxHealth} A: ${Armor}/${MaxArmor}"
+ s"${avatar.name}$guid ${avatar.faction} H: $Health/$MaxHealth A: $Armor/$MaxArmor"
}
}
@@ -551,6 +552,10 @@ object Player {
final val FreeHandSlot: Int = 250
final val HandsDownSlot: Int = 255
+ final case class BuildDeployable(obj: Deployable, withTool: ConstructionItem)
+
+ final case class LoseDeployable(obj: Deployable)
+
final case class Die(reason: Option[DamageInteraction])
object Die {
diff --git a/src/main/scala/net/psforever/objects/Players.scala b/src/main/scala/net/psforever/objects/Players.scala
index 2eaac667..9317937a 100644
--- a/src/main/scala/net/psforever/objects/Players.scala
+++ b/src/main/scala/net/psforever/objects/Players.scala
@@ -2,14 +2,20 @@
package net.psforever.objects
import net.psforever.objects.avatar.Certification
+import net.psforever.login.WorldSession.FindEquipmentStock
+import net.psforever.objects.avatar.PlayerControl
+import net.psforever.objects.ce.Deployable
import net.psforever.objects.definition.ExoSuitDefinition
import net.psforever.objects.equipment.EquipmentSlot
+import net.psforever.objects.guid.GUIDTask
import net.psforever.objects.inventory.InventoryItem
import net.psforever.objects.loadouts.InfantryLoadout
-import net.psforever.packet.game.{InventoryStateMessage, RepairMessage}
-import net.psforever.types.{ExoSuitType, Vector3}
+import net.psforever.objects.zones.Zone
+import net.psforever.packet.game._
+import net.psforever.types.{ChatMessageType, ExoSuitType, Vector3}
import net.psforever.services.Service
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
+import net.psforever.services.local.{LocalAction, LocalServiceMessage}
import scala.annotation.tailrec
@@ -123,6 +129,17 @@ object Players {
}
}
+ /**
+ * The player may don this exo-suit if the exo-suit has no requirements
+ * or if the player has fulfilled the requirements of the exo-suit.
+ * The "requirements" are certification purchases.
+ * @param player the player
+ * @param exosuit the exo-suit the player is trying to wear
+ * @param subtype the variant of this exo-suit type;
+ * matters for mechanized assault exo-suits, mainly
+ * @return `true`, if the player and the exo-suit are compatible;
+ * `false`, otherwise
+ */
def CertificationToUseExoSuit(player: Player, exosuit: ExoSuitType.Value, subtype: Int): Boolean = {
ExoSuitDefinition.Select(exosuit, player.Faction).Permissions match {
case Nil =>
@@ -148,16 +165,296 @@ object Players {
*/
def repairModifierLevel(player: Player): Int = {
val certs = player.avatar.certifications
- if(certs.contains(Certification.AdvancedEngineering) ||
- certs.contains(Certification.AssaultEngineering) ||
- certs.contains(Certification.FortificationEngineering)) {
+ if (certs.contains(Certification.AdvancedEngineering) ||
+ certs.contains(Certification.AssaultEngineering) ||
+ certs.contains(Certification.FortificationEngineering)) {
3
- } else if (certs.contains(Certification.CombatEngineering)) {
+ }
+ else if (certs.contains(Certification.CombatEngineering)) {
2
- } else if (certs.contains(Certification.Engineering)) {
+ }
+ else if (certs.contains(Certification.Engineering)) {
1
- } else {
+ }
+ else {
0
}
}
+
+ /**
+ * Test whether this deployable can be constructed by this given player.
+ * The test actually involves a number of checks against numerical limits for supporting the deployable
+ * (the first of which is whether there is any limit at all).
+ * Depending on the result against limits successfully, various status messages can be dispatched to the client
+ * and the deployable will be considered permitted to be constructed.
+ *
+ * The first placement limit is the actual number of a specific type of deployable.
+ * The second placement limit is the actual number of a specific group (category) of deployables.
+ * Depending on which limit is encountered, an "oldest entry" is struck from the list to make space.
+ * This generates the first message - "@*OldestDestroyed."
+ * Another message is generated if the number of that specific type of deployable
+ * or the number of deployables available in its category matches against the maximum count allowed.
+ * This generates the second message - "@*LimitReached."
+ * These messages are mutually exclusive, with "@*OldestDestroyed" taking priority over "@*LimitReached."
+ *
+ * Finally, the player needs to actually manage the deployable.
+ * Once that responsibility is proven, all tests are considered passed.
+ * @see `ChatMsg`
+ * @see `DeployableToolbox`
+ * @see `DeployableToolbox.Add`
+ * @see `DeployableToolbox.Available`
+ * @see `DeployableToolbox.CountDeployable`
+ * @see `DeployableToolbox.DisplaceFirst`
+ * @see `Deployable.Deconstruct`
+ * @see `Deployable.Ownership`
+ * @see `gainDeployableOwnership`
+ * @see `ObjectDeployedMessage`
+ * @see `PlayerControl.sendResponse`
+ * @param player the player that would manage the deployable
+ * @param obj the deployable
+ * @return `true`, if the deployable can be constructed under the control of and be supported by the player;
+ * `false`, otherwise
+ */
+ def deployableWithinBuildLimits(player: Player, obj: Deployable): Boolean = {
+ val zone = obj.Zone
+ val channel = player.Name
+ val definition = obj.Definition
+ val item = definition.Item
+ val deployables = player.avatar.deployables
+ val (curr, max) = deployables.CountDeployable(item)
+ val tryAddToOwnedDeployables = if (!deployables.Available(obj)) {
+ val (removed, msg) = {
+ if (curr == max) { //too many of a specific type of deployable
+ (deployables.DisplaceFirst(obj), max > 1)
+ } else if (curr > max) { //somehow we have too many deployables
+ (None, true)
+ } else { //make room by eliminating a different type of deployable
+ (deployables.DisplaceFirst(obj, { d => d.Definition.Item != item }), true)
+ }
+ }
+ removed match {
+ case Some(telepad: TelepadDeployable) =>
+ //telepad is not explicitly deconstructed
+ telepad.Actor ! Deployable.Ownership(None)
+ true
+ case Some(old) =>
+ old.Actor ! Deployable.Deconstruct()
+ if (msg) { //max test
+ PlayerControl.sendResponse(
+ zone,
+ channel,
+ ChatMsg(ChatMessageType.UNK_229, false, "", s"@${definition.Descriptor}OldestDestroyed", None)
+ )
+ }
+ true
+ case None =>
+ org.log4s.getLogger(name = "Deployables").warn(
+ s"${player.Name} has no allowance for ${definition.DeployCategory} deployables; is something wrong?"
+ )
+ PlayerControl.sendResponse(zone, channel, ObjectDeployedMessage.Failure(definition.Name))
+ false
+ }
+ } else if (obj.isInstanceOf[TelepadDeployable]) {
+ //always treat the telepad we are putting down as the first and only one
+ PlayerControl.sendResponse(zone, channel, ObjectDeployedMessage.Success(definition.Name, count = 1, max = 1))
+ true
+ } else {
+ PlayerControl.sendResponse(zone, channel, ObjectDeployedMessage.Success(definition.Name, curr + 1, max))
+ val (catCurr, catMax) = deployables.CountCategory(item)
+ if ((max > 1 && curr + 1 == max) || (catMax > 1 && catCurr + 1 == catMax)) {
+ PlayerControl.sendResponse(
+ zone,
+ channel,
+ ChatMsg(ChatMessageType.UNK_229, false, "", s"@${definition.Descriptor}LimitReached", None)
+ )
+ }
+ true
+ }
+ tryAddToOwnedDeployables && gainDeployableOwnership(player, obj, player.avatar.deployables.Add)
+ }
+
+ /**
+ * Grant ownership over a deployable to a player and calls for an update to the UI for that deployable.
+ * Although the formal the ownership change is delayed slightly by messaging protocol,
+ * the outcome of this function is reliant more on the function parameter
+ * used to append the deployable to the management system of the to-be-owning player.
+ * The difference is between technical ownership and indirect knowledge of ownership
+ * and how these ownership awareness states operate differently on management of the deployable.
+ * @see `Deployable.Ownership`
+ * @see `LocalAction.DeployableUIFor`
+ * @param player the player who would own the deployable
+ * @param obj the deployable
+ * @param addFunc the process for assigning management of the deployable to the player
+ * @return `true`, if the player was assignment management of the deployable;
+ * `false`, otherwise
+ */
+ def gainDeployableOwnership(
+ player: Player,
+ obj: Deployable,
+ addFunc: Deployable=>Boolean
+ ): Boolean = {
+ if (player.Zone == obj.Zone && addFunc(obj)) {
+ obj.Actor ! Deployable.Ownership(player)
+ player.Zone.LocalEvents ! LocalServiceMessage(player.Name, LocalAction.DeployableUIFor(obj.Definition.Item))
+ true
+ } else {
+ false
+ }
+ }
+
+ /**
+ * Common actions related to constructing a new `Deployable` object in the game environment.
+ * @param zone in which zone these messages apply
+ * @param channel to whom to send the messages
+ * @param obj the `Deployable` object
+ */
+ def successfulBuildActivity(zone: Zone, channel: String, obj: Deployable): Unit = {
+ //sent to avatar event bus to preempt additional tool management
+ buildCooldownReset(zone, channel, obj)
+ //sent to local event bus to cooperate with deployable management
+ zone.LocalEvents ! LocalServiceMessage(
+ channel,
+ LocalAction.DeployableUIFor(obj.Definition.Item)
+ )
+ }
+
+ /**
+ * Common actions related to constructing a new `Deployable` object in the game environment.
+ * @param zone in which zone these messages apply
+ * @param channel to whom to send the messages
+ * @param obj the `Deployable` object
+ */
+ def buildCooldownReset(zone: Zone, channel: String, obj: Deployable): Unit = {
+ //sent to avatar event bus to preempt additional tool management
+ zone.AvatarEvents ! AvatarServiceMessage(
+ channel,
+ AvatarAction.SendResponse(Service.defaultPlayerGUID, GenericObjectActionMessage(obj.GUID, 21))
+ )
+ }
+
+ /**
+ * Destroy a `ConstructionItem` object that can be found in the indexed slot.
+ * @see `Player.Find`
+ * @param tool the `ConstructionItem` object currently in the slot (checked)
+ * @param index the slot index
+ */
+ def commonDestroyConstructionItem(player: Player, tool: ConstructionItem, index: Int): Unit = {
+ val zone = player.Zone
+ if (safelyRemoveConstructionItemFromSlot(player, tool, index, "CommonDestroyConstructionItem")) {
+ zone.tasks ! GUIDTask.UnregisterEquipment(tool)(zone.GUID)
+ }
+ }
+
+ /**
+ * Find the target `ConstructionTool` object, either at the suggested slot or wherever it is on the `player`,
+ * and remove it from the game world visually.
+ *
+ * Not finding the target object at its intended slot is an entirely recoverable situation
+ * as long as the target object is discovered to be somewhere else in the player's holsters or inventory space.
+ * If found after a more thorough search, merely log the discrepancy as a warning.
+ * If the discrepancy becomes common, the developer messed up the function call
+ * or he should not be using this function.
+ * @param tool the `ConstructionItem` object currently in the slot (checked)
+ * @param index the slot index
+ * @param logDecorator what kind of designation to give any log entires originating from this function;
+ * defaults to its own function name
+ * @return `true`, if the target object was found and removed;
+ * `false`, otherwise
+ */
+ def safelyRemoveConstructionItemFromSlot(
+ player: Player,
+ tool: ConstructionItem,
+ index: Int,
+ logDecorator: String = "SafelyRemoveConstructionItemFromSlot"
+ ): Boolean = {
+ if ({
+ val holster = player.Slot(index)
+ if (holster.Equipment.contains(tool)) {
+ holster.Equipment = None
+ true
+ } else {
+ player.Find(tool) match {
+ case Some(newIndex) =>
+ log.warn(s"$logDecorator: ${player.Name} was looking for an item in his hand $index, but item was found at $newIndex instead")
+ player.Slot(newIndex).Equipment = None
+ true
+ case None =>
+ log.warn(s"$logDecorator: ${player.Name} could not find the target ${tool.Definition.Name}")
+ false
+ }
+ }
+ }) {
+ val zone = player.Zone
+ zone.AvatarEvents ! AvatarServiceMessage(
+ zone.id,
+ AvatarAction.ObjectDelete(Service.defaultPlayerGUID, tool.GUID, 0)
+ )
+ true
+ } else {
+ false
+ }
+ }
+
+ /**
+ * Find a `ConstructionItem` object in player's inventory
+ * that is the same type as a target `ConstructionItem` object and
+ * transfer it into the designated slot index, usually a holster.
+ * Draw that holster.
+ * After being transferred, the replacement should be reconfigured to match the fire mode of the original.
+ * The primary use of this operation is following the successful manifestation of a deployable in the game world.
+ *
+ * As this function should be used in response to some other action such as actually placing a deployable,
+ * do not instigate bundling from within the function's scope.
+ * @see `WorldSessionActor.FinalizeDeployable`
+ * `FindEquipmentStock`
+ * @param tool the `ConstructionItem` object to match
+ * @param index where to put the discovered replacement
+ */
+ def findReplacementConstructionItem(player: Player, tool: ConstructionItem, index: Int): Unit = {
+ val definition = tool.Definition
+ if (player.Slot(index).Equipment.isEmpty) {
+ FindEquipmentStock(player, { e => e.Definition == definition }, 1) match {
+ case x :: _ =>
+ val zone = player.Zone
+ val events = zone.AvatarEvents
+ val name = player.Name
+ val pguid = player.GUID
+ val obj = x.obj.asInstanceOf[ConstructionItem]
+ if ((player.Slot(index).Equipment = obj).contains(obj)) {
+ val fireMode = tool.FireModeIndex
+ val ammoType = tool.AmmoTypeIndex
+ player.Inventory -= x.start
+ obj.FireModeIndex = fireMode
+ //TODO any penalty for being handed an OCM version of the tool?
+ events ! AvatarServiceMessage(
+ zone.id,
+ AvatarAction.EquipmentInHand(Service.defaultPlayerGUID, pguid, index, obj)
+ )
+ if (obj.AmmoTypeIndex != ammoType) {
+ obj.AmmoTypeIndex = ammoType
+ events ! AvatarServiceMessage(
+ name,
+ AvatarAction.SendResponse(Service.defaultPlayerGUID, ChangeAmmoMessage(obj.GUID, ammoType))
+ )
+ }
+ if (player.DrawnSlot == Player.HandsDownSlot) {
+ player.DrawnSlot = index
+ events ! AvatarServiceMessage(
+ name,
+ AvatarAction.SendResponse(Service.defaultPlayerGUID, ObjectHeldMessage(pguid, index, true))
+ )
+ events ! AvatarServiceMessage(
+ zone.id,
+ AvatarAction.ObjectHeld(pguid, index)
+ )
+ }
+ }
+ case Nil => ; //no replacements found
+ }
+ } else {
+ log.warn(
+ s"FindReplacementConstructionItem: ${player.Name}, your $index hand needs to be empty before a replacement ${definition.Name} can be installed"
+ )
+ }
+ }
}
diff --git a/src/main/scala/net/psforever/objects/SensorDeployable.scala b/src/main/scala/net/psforever/objects/SensorDeployable.scala
index 24e268d9..ff6d5a91 100644
--- a/src/main/scala/net/psforever/objects/SensorDeployable.scala
+++ b/src/main/scala/net/psforever/objects/SensorDeployable.scala
@@ -1,10 +1,10 @@
// Copyright (c) 2019 PSForever
package net.psforever.objects
-import akka.actor.{Actor, ActorContext, Props}
+import akka.actor.{Actor, ActorContext, ActorRef, Props}
import net.psforever.objects.ce._
import net.psforever.objects.definition.converter.SmallDeployableConverter
-import net.psforever.objects.definition.{ComplexDeployableDefinition, SimpleDeployableDefinition}
+import net.psforever.objects.definition.DeployableDefinition
import net.psforever.objects.equipment.{JammableBehavior, JammableUnit}
import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.serverobject.damage.{Damageable, DamageableEntity}
@@ -19,22 +19,18 @@ import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
import scala.concurrent.duration._
-class SensorDeployable(cdef: SensorDeployableDefinition) extends ComplexDeployable(cdef) with Hackable with JammableUnit
+class SensorDeployable(cdef: SensorDeployableDefinition) extends Deployable(cdef) with Hackable with JammableUnit
-class SensorDeployableDefinition(private val objectId: Int) extends ComplexDeployableDefinition(objectId) {
+class SensorDeployableDefinition(private val objectId: Int) extends DeployableDefinition(objectId) {
Name = "sensor_deployable"
DeployCategory = DeployableCategory.Sensors
Model = SimpleResolutions.calculate
Packet = new SmallDeployableConverter
- override def Initialize(obj: PlanetSideServerObject with Deployable, context: ActorContext) = {
+ override def Initialize(obj: Deployable, context: ActorContext) = {
obj.Actor =
context.actorOf(Props(classOf[SensorDeployableControl], obj), PlanetSideServerObject.UniqueActorName(obj))
}
-
- override def Uninitialize(obj: PlanetSideServerObject with Deployable, context: ActorContext) = {
- SimpleDeployableDefinition.SimpleUninitialize(obj, context)
- }
}
object SensorDeployableDefinition {
@@ -45,15 +41,23 @@ object SensorDeployableDefinition {
class SensorDeployableControl(sensor: SensorDeployable)
extends Actor
+ with DeployableBehavior
with JammableBehavior
with DamageableEntity
with RepairableEntity {
+ def DeployableObject = sensor
def JammableObject = sensor
def DamageableObject = sensor
def RepairableObject = sensor
+ override def postStop(): Unit = {
+ super.postStop()
+ deployableBehaviorPostStop()
+ }
+
def receive: Receive =
- jammableBehavior
+ deployableBehavior
+ .orElse(jammableBehavior)
.orElse(takesDamage)
.orElse(canBeRepairedByNanoDispenser)
.orElse {
@@ -106,14 +110,24 @@ class SensorDeployableControl(sensor: SensorDeployable)
override def CancelJammeredStatus(target: Any): Unit = {
target match {
case obj: PlanetSideServerObject with JammableUnit if obj.Jammed =>
- sensor.Zone.LocalEvents ! LocalServiceMessage(
- sensor.Zone.id,
+ val zone = sensor.Zone
+ zone.LocalEvents ! LocalServiceMessage(
+ zone.id,
LocalAction.TriggerEffectInfo(Service.defaultPlayerGUID, "on", obj.GUID, true, 1000)
)
case _ => ;
}
super.CancelJammeredStatus(target)
}
+
+ override def finalizeDeployable(callback: ActorRef) : Unit = {
+ super.finalizeDeployable(callback)
+ val zone = sensor.Zone
+ zone.LocalEvents ! LocalServiceMessage(
+ zone.id,
+ LocalAction.TriggerEffectInfo(Service.defaultPlayerGUID, "on", sensor.GUID, true, 1000)
+ )
+ }
}
object SensorDeployableControl {
@@ -123,7 +137,7 @@ object SensorDeployableControl {
* @param target na
* @param attribution na
*/
- def DestructionAwareness(target: Damageable.Target with Deployable, attribution: PlanetSideGUID): Unit = {
+ def DestructionAwareness(target: Deployable, attribution: PlanetSideGUID): Unit = {
Deployables.AnnounceDestroyDeployable(target, Some(1 seconds))
val zone = target.Zone
zone.LocalEvents ! LocalServiceMessage(
diff --git a/src/main/scala/net/psforever/objects/ShieldGeneratorDeployable.scala b/src/main/scala/net/psforever/objects/ShieldGeneratorDeployable.scala
index d8b8f8bb..2206eda8 100644
--- a/src/main/scala/net/psforever/objects/ShieldGeneratorDeployable.scala
+++ b/src/main/scala/net/psforever/objects/ShieldGeneratorDeployable.scala
@@ -2,8 +2,8 @@
package net.psforever.objects
import akka.actor.{Actor, ActorContext, Props}
-import net.psforever.objects.ce.{ComplexDeployable, Deployable, DeployableCategory}
-import net.psforever.objects.definition.{ComplexDeployableDefinition, SimpleDeployableDefinition}
+import net.psforever.objects.ce.{Deployable, DeployableBehavior, DeployableCategory}
+import net.psforever.objects.definition.DeployableDefinition
import net.psforever.objects.definition.converter.ShieldGeneratorConverter
import net.psforever.objects.equipment.{JammableBehavior, JammableUnit}
import net.psforever.objects.serverobject.damage.Damageable.Target
@@ -18,35 +18,40 @@ import net.psforever.services.Service
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
class ShieldGeneratorDeployable(cdef: ShieldGeneratorDefinition)
- extends ComplexDeployable(cdef)
+ extends Deployable(cdef)
with Hackable
with JammableUnit
-class ShieldGeneratorDefinition extends ComplexDeployableDefinition(240) {
+class ShieldGeneratorDefinition extends DeployableDefinition(240) {
Packet = new ShieldGeneratorConverter
DeployCategory = DeployableCategory.ShieldGenerators
- override def Initialize(obj: PlanetSideServerObject with Deployable, context: ActorContext) = {
+ override def Initialize(obj: Deployable, context: ActorContext) = {
obj.Actor =
context.actorOf(Props(classOf[ShieldGeneratorControl], obj), PlanetSideServerObject.UniqueActorName(obj))
}
-
- override def Uninitialize(obj: PlanetSideServerObject with Deployable, context: ActorContext) = {
- SimpleDeployableDefinition.SimpleUninitialize(obj, context)
- }
}
class ShieldGeneratorControl(gen: ShieldGeneratorDeployable)
extends Actor
+ with DeployableBehavior
with JammableBehavior
with DamageableEntity
with RepairableEntity {
- def JammableObject = gen
- def DamageableObject = gen
- def RepairableObject = gen
+ def DeployableObject = gen
+ def JammableObject = gen
+ def DamageableObject = gen
+ def RepairableObject = gen
+ deletionType = 1 //from DeployableBehavior
+
+ override def postStop(): Unit = {
+ super.postStop()
+ deployableBehaviorPostStop()
+ }
def receive: Receive =
- jammableBehavior
+ deployableBehavior
+ .orElse(jammableBehavior)
.orElse(takesDamage)
.orElse(canBeRepairedByNanoDispenser)
.orElse {
@@ -166,7 +171,7 @@ object ShieldGeneratorControl {
* @param target na
* @param attribution na
*/
- def DestructionAwareness(target: Damageable.Target with Deployable, attribution: PlanetSideGUID): Unit = {
+ def DestructionAwareness(target: Deployable, attribution: PlanetSideGUID): Unit = {
Deployables.AnnounceDestroyDeployable(target, None)
}
}
diff --git a/src/main/scala/net/psforever/objects/SpecialEmp.scala b/src/main/scala/net/psforever/objects/SpecialEmp.scala
index c0b8a1a2..17769771 100644
--- a/src/main/scala/net/psforever/objects/SpecialEmp.scala
+++ b/src/main/scala/net/psforever/objects/SpecialEmp.scala
@@ -134,11 +134,7 @@ object SpecialEmp {
zone: Zone,
obj: PlanetSideGameObject with FactionAffinity with Vitality,
properties: DamageWithPosition
- ): (List[PlanetSideServerObject with Vitality], List[PlanetSideGameObject with FactionAffinity with Vitality]) = {
- (
- zone.DeployableList
- .collect { case o: BoomerDeployable if !o.Destroyed && (o ne obj) => o },
- Nil
- )
+ ): List[PlanetSideServerObject with Vitality] = {
+ zone.DeployableList.collect { case o: BoomerDeployable if !o.Destroyed && (o ne obj) => o }
}
-}
\ No newline at end of file
+}
diff --git a/src/main/scala/net/psforever/objects/Telepad.scala b/src/main/scala/net/psforever/objects/Telepad.scala
index 956230e0..4ced4598 100644
--- a/src/main/scala/net/psforever/objects/Telepad.scala
+++ b/src/main/scala/net/psforever/objects/Telepad.scala
@@ -4,7 +4,9 @@ package net.psforever.objects
import net.psforever.objects.ce.TelepadLike
import net.psforever.objects.definition.ConstructionItemDefinition
-class Telepad(private val cdef: ConstructionItemDefinition) extends ConstructionItem(cdef) with TelepadLike
+class Telepad(private val cdef: ConstructionItemDefinition)
+ extends ConstructionItem(cdef)
+ with TelepadLike
object Telepad {
def apply(cdef: ConstructionItemDefinition): Telepad = {
diff --git a/src/main/scala/net/psforever/objects/TelepadDeployable.scala b/src/main/scala/net/psforever/objects/TelepadDeployable.scala
index 89517362..1a00dba9 100644
--- a/src/main/scala/net/psforever/objects/TelepadDeployable.scala
+++ b/src/main/scala/net/psforever/objects/TelepadDeployable.scala
@@ -1,7 +1,139 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects
-import net.psforever.objects.ce.{SimpleDeployable, TelepadLike}
-import net.psforever.objects.definition.SimpleDeployableDefinition
+import akka.actor.{Actor, ActorContext, ActorRef, Props}
+import net.psforever.objects.ce.{Deployable, DeployableBehavior, DeployedItem, TelepadLike}
+import net.psforever.objects.definition.DeployableDefinition
+import net.psforever.objects.serverobject.PlanetSideServerObject
+import net.psforever.objects.serverobject.damage.{Damageable, DamageableEntity}
+import net.psforever.objects.vehicles.UtilityType
+import net.psforever.objects.vehicles.Utility.InternalTelepad
+import net.psforever.objects.vital.SimpleResolutions
+import net.psforever.objects.vital.interaction.DamageResult
+import net.psforever.objects.zones.Zone
+import net.psforever.services.local.{LocalAction, LocalServiceMessage}
-class TelepadDeployable(ddef: SimpleDeployableDefinition) extends SimpleDeployable(ddef) with TelepadLike
+import scala.concurrent.duration._
+
+class TelepadDeployable(ddef: TelepadDeployableDefinition)
+ extends Deployable(ddef) with TelepadLike {
+ override def Definition: TelepadDeployableDefinition = ddef
+}
+
+class TelepadDeployableDefinition(objectId: Int) extends DeployableDefinition(objectId) {
+ Model = SimpleResolutions.calculate
+
+ var linkTime: FiniteDuration = 60.seconds
+
+ override def Initialize(obj: Deployable, context: ActorContext) = {
+ obj.Actor = context.actorOf(Props(classOf[TelepadDeployableControl], obj), PlanetSideServerObject.UniqueActorName(obj))
+ }
+}
+
+object TelepadDeployableDefinition {
+ def apply(dtype: DeployedItem.Value): TelepadDeployableDefinition = {
+ new TelepadDeployableDefinition(dtype.id)
+ }
+}
+
+class TelepadDeployableControl(tpad: TelepadDeployable)
+ extends Actor
+ with DeployableBehavior
+ with DamageableEntity {
+ def DeployableObject = tpad
+ def DamageableObject = tpad
+
+ override def postStop(): Unit = {
+ super.postStop()
+ deployableBehaviorPostStop()
+ TelepadControl.DestructionAwareness(tpad)
+ }
+
+ def receive: Receive =
+ deployableBehavior
+ .orElse(takesDamage)
+ .orElse {
+ case TelepadLike.Activate(tpad: TelepadDeployable)
+ if isConstructed.contains(true) =>
+ val zone = tpad.Zone
+ (zone.GUID(tpad.Router) match {
+ case Some(vehicle : Vehicle) => vehicle.Utility(UtilityType.internal_router_telepad_deployable)
+ case _ => None
+ }) match {
+ case Some(obj: InternalTelepad) =>
+ import scala.concurrent.ExecutionContext.Implicits.global
+ setup = context.system.scheduler.scheduleOnce(
+ tpad.Definition.linkTime,
+ obj.Actor,
+ TelepadLike.RequestLink(tpad)
+ )
+ case _ =>
+ deconstructDeployable(None)
+ tpad.OwnerName match {
+ case Some(owner) =>
+ TelepadControl.TelepadError(zone, owner, msg = "@Telepad_NoDeploy_RouterLost")
+ case None => ;
+ }
+ }
+
+ case TelepadLike.Activate(obj: InternalTelepad)
+ if isConstructed.contains(true) =>
+ if (obj.Telepad.contains(tpad.GUID) && tpad.Router.contains(obj.Owner.GUID)) {
+ tpad.Active = true
+ TelepadLike.LinkTelepad(tpad.Zone, tpad.GUID)
+ }
+
+ case TelepadLike.SeverLink(obj: InternalTelepad)
+ if isConstructed.contains(true) =>
+ if (tpad.Router.contains(obj.Owner.GUID)) {
+ tpad.Router = None
+ tpad.Active = false
+ tpad.Actor ! Deployable.Deconstruct()
+ }
+
+ case _ =>
+ }
+
+ override protected def DestructionAwareness(target: Damageable.Target, cause: DamageResult): Unit = {
+ super.DestructionAwareness(target, cause)
+ TelepadControl.DestructionAwareness(tpad)
+ Deployables.AnnounceDestroyDeployable(tpad, None)
+ }
+
+ override def startOwnerlessDecay(): Unit = {
+ //telepads do not decay when they become ownerless
+ //telepad decay is tied to their lifecycle with routers
+ tpad.Owner = None
+ tpad.OwnerName = None
+ }
+
+ override def finalizeDeployable(callback: ActorRef): Unit = {
+ super.finalizeDeployable(callback)
+ decay.cancel() //telepad does not decay if unowned; but, deconstruct if router link fails
+ self ! TelepadLike.Activate(tpad)
+ }
+
+ override def deconstructDeployable(time : Option[FiniteDuration]) : Unit = {
+ TelepadControl.DestructionAwareness(tpad)
+ super.deconstructDeployable(time)
+ }
+}
+
+object TelepadControl {
+ def DestructionAwareness(tpad: TelepadDeployable): Unit = {
+ if (tpad.Active) {
+ tpad.Active = false
+ (tpad.Zone.GUID(tpad.Router) match {
+ case Some(vehicle : Vehicle) => vehicle.Utility(UtilityType.internal_router_telepad_deployable)
+ case _ => None
+ }) match {
+ case Some(obj: InternalTelepad) => obj.Actor ! TelepadLike.SeverLink(tpad)
+ case _ => ;
+ }
+ }
+ }
+
+ def TelepadError(zone: Zone, channel: String, msg: String): Unit = {
+ zone.LocalEvents ! LocalServiceMessage(channel, LocalAction.RouterTelepadMessage(msg))
+ }
+}
diff --git a/src/main/scala/net/psforever/objects/TrapDeployable.scala b/src/main/scala/net/psforever/objects/TrapDeployable.scala
index 6a9ec388..f4203823 100644
--- a/src/main/scala/net/psforever/objects/TrapDeployable.scala
+++ b/src/main/scala/net/psforever/objects/TrapDeployable.scala
@@ -2,9 +2,9 @@
package net.psforever.objects
import akka.actor.{Actor, ActorContext, Props}
-import net.psforever.objects.ce.{ComplexDeployable, Deployable, DeployedItem}
+import net.psforever.objects.ce.{Deployable, DeployableBehavior, DeployedItem}
import net.psforever.objects.definition.converter.TRAPConverter
-import net.psforever.objects.definition.{ComplexDeployableDefinition, SimpleDeployableDefinition}
+import net.psforever.objects.definition.DeployableDefinition
import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.serverobject.damage.{Damageable, DamageableEntity}
import net.psforever.objects.serverobject.repair.RepairableEntity
@@ -12,19 +12,16 @@ import net.psforever.objects.vital.SimpleResolutions
import net.psforever.objects.vital.interaction.DamageResult
import net.psforever.objects.zones.Zone
-class TrapDeployable(cdef: TrapDeployableDefinition) extends ComplexDeployable(cdef)
+class TrapDeployable(cdef: TrapDeployableDefinition)
+ extends Deployable(cdef)
-class TrapDeployableDefinition(objectId: Int) extends ComplexDeployableDefinition(objectId) {
+class TrapDeployableDefinition(objectId: Int) extends DeployableDefinition(objectId) {
Model = SimpleResolutions.calculate
Packet = new TRAPConverter
- override def Initialize(obj: PlanetSideServerObject with Deployable, context: ActorContext) = {
+ override def Initialize(obj: Deployable, context: ActorContext) = {
obj.Actor = context.actorOf(Props(classOf[TrapDeployableControl], obj), PlanetSideServerObject.UniqueActorName(obj))
}
-
- override def Uninitialize(obj: PlanetSideServerObject with Deployable, context: ActorContext) = {
- SimpleDeployableDefinition.SimpleUninitialize(obj, context)
- }
}
object TrapDeployableDefinition {
@@ -33,12 +30,23 @@ object TrapDeployableDefinition {
}
}
-class TrapDeployableControl(trap: TrapDeployable) extends Actor with DamageableEntity with RepairableEntity {
+class TrapDeployableControl(trap: TrapDeployable)
+ extends Actor
+ with DeployableBehavior
+ with DamageableEntity
+ with RepairableEntity {
+ def DeployableObject = trap
def DamageableObject = trap
def RepairableObject = trap
+ override def postStop(): Unit = {
+ super.postStop()
+ deployableBehaviorPostStop()
+ }
+
def receive: Receive =
- takesDamage
+ deployableBehavior
+ .orElse(takesDamage)
.orElse(canBeRepairedByNanoDispenser)
.orElse {
case _ =>
diff --git a/src/main/scala/net/psforever/objects/TurretDeployable.scala b/src/main/scala/net/psforever/objects/TurretDeployable.scala
index 76f4eabb..b2616885 100644
--- a/src/main/scala/net/psforever/objects/TurretDeployable.scala
+++ b/src/main/scala/net/psforever/objects/TurretDeployable.scala
@@ -2,10 +2,11 @@
package net.psforever.objects
import akka.actor.{Actor, ActorContext, Props}
-import net.psforever.objects.ce.{ComplexDeployable, Deployable, DeployedItem}
-import net.psforever.objects.definition.{ComplexDeployableDefinition, SimpleDeployableDefinition}
+import net.psforever.objects.ce.{Deployable, DeployableBehavior, DeployedItem}
+import net.psforever.objects.definition.DeployableDefinition
import net.psforever.objects.definition.converter.SmallTurretConverter
import net.psforever.objects.equipment.{JammableMountedWeapons, JammableUnit}
+import net.psforever.objects.guid.GUIDTask
import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.serverobject.affinity.FactionAffinityBehavior
import net.psforever.objects.serverobject.damage.Damageable.Target
@@ -17,9 +18,12 @@ import net.psforever.objects.serverobject.turret.{TurretDefinition, WeaponTurret
import net.psforever.objects.vital.damage.DamageCalculations
import net.psforever.objects.vital.interaction.DamageResult
import net.psforever.objects.vital.{SimpleResolutions, StandardVehicleResistance}
+import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
+
+import scala.concurrent.duration.FiniteDuration
class TurretDeployable(tdef: TurretDeployableDefinition)
- extends ComplexDeployable(tdef)
+ extends Deployable(tdef)
with WeaponTurret
with JammableUnit
with Hackable {
@@ -29,7 +33,7 @@ class TurretDeployable(tdef: TurretDeployableDefinition)
}
class TurretDeployableDefinition(private val objectId: Int)
- extends ComplexDeployableDefinition(objectId)
+ extends DeployableDefinition(objectId)
with TurretDefinition {
Name = "turret_deployable"
Packet = new SmallTurretConverter
@@ -38,17 +42,13 @@ class TurretDeployableDefinition(private val objectId: Int)
Model = SimpleResolutions.calculate
//override to clarify inheritance conflict
- override def MaxHealth: Int = super[ComplexDeployableDefinition].MaxHealth
+ override def MaxHealth: Int = super[DeployableDefinition].MaxHealth
//override to clarify inheritance conflict
- override def MaxHealth_=(max: Int): Int = super[ComplexDeployableDefinition].MaxHealth_=(max)
+ override def MaxHealth_=(max: Int): Int = super[DeployableDefinition].MaxHealth_=(max)
- override def Initialize(obj: PlanetSideServerObject with Deployable, context: ActorContext) = {
+ override def Initialize(obj: Deployable, context: ActorContext) = {
obj.Actor = context.actorOf(Props(classOf[TurretControl], obj), PlanetSideServerObject.UniqueActorName(obj))
}
-
- override def Uninitialize(obj: PlanetSideServerObject with Deployable, context: ActorContext) = {
- SimpleDeployableDefinition.SimpleUninitialize(obj, context)
- }
}
object TurretDeployableDefinition {
@@ -61,11 +61,13 @@ object TurretDeployableDefinition {
class TurretControl(turret: TurretDeployable)
extends Actor
+ with DeployableBehavior
with FactionAffinityBehavior.Check
with JammableMountedWeapons //note: jammable status is reported as vehicle events, not local events
with MountableBehavior
with DamageableWeaponTurret
with RepairableWeaponTurret {
+ def DeployableObject = turret
def MountableObject = turret
def JammableObject = turret
def FactionObject = turret
@@ -74,11 +76,13 @@ class TurretControl(turret: TurretDeployable)
override def postStop(): Unit = {
super.postStop()
+ deployableBehaviorPostStop()
damageableWeaponTurretPostStop()
}
def receive: Receive =
- checkBehavior
+ deployableBehavior
+ .orElse(checkBehavior)
.orElse(jammableBehavior)
.orElse(mountBehavior)
.orElse(dismountBehavior)
@@ -99,4 +103,35 @@ class TurretControl(turret: TurretDeployable)
super.DestructionAwareness(target, cause)
Deployables.AnnounceDestroyDeployable(turret, None)
}
+
+ override def deconstructDeployable(time: Option[FiniteDuration]) : Unit = {
+ val zone = turret.Zone
+ val seats = turret.Seats.values
+ //either we have no seats or no one gets to sit
+ val retime = if (seats.count(_.isOccupied) > 0) {
+ //unlike with vehicles, it's possible to request deconstruction of one's own field turret while seated in it
+ val wasKickedByDriver = false
+ seats.foreach { seat =>
+ seat.occupant match {
+ case Some(tplayer) =>
+ seat.unmount(tplayer)
+ tplayer.VehicleSeated = None
+ zone.VehicleEvents ! VehicleServiceMessage(
+ zone.id,
+ VehicleAction.KickPassenger(tplayer.GUID, 4, wasKickedByDriver, turret.GUID)
+ )
+ case None => ;
+ }
+ }
+ Some(time.getOrElse(Deployable.cleanup) + Deployable.cleanup)
+ } else {
+ time
+ }
+ super.deconstructDeployable(retime)
+ }
+
+ override def unregisterDeployable(obj: Deployable): Unit = {
+ val zone = obj.Zone
+ zone.tasks ! GUIDTask.UnregisterDeployableTurret(turret)(zone.GUID)
+ }
}
diff --git a/src/main/scala/net/psforever/objects/Vehicles.scala b/src/main/scala/net/psforever/objects/Vehicles.scala
index c3cee443..eb9c659a 100644
--- a/src/main/scala/net/psforever/objects/Vehicles.scala
+++ b/src/main/scala/net/psforever/objects/Vehicles.scala
@@ -1,6 +1,7 @@
// Copyright (c) 2020 PSForever
package net.psforever.objects
+import net.psforever.objects.ce.TelepadLike
import net.psforever.objects.serverobject.CommonMessages
import net.psforever.objects.serverobject.deploy.Deployment
import net.psforever.objects.serverobject.transfer.TransferContainer
@@ -9,12 +10,12 @@ import net.psforever.objects.vehicles._
import net.psforever.objects.zones.Zone
import net.psforever.packet.game.TriggeredSound
import net.psforever.types.{DriveState, PlanetSideGUID, Vector3}
-import net.psforever.services.{RemoverActor, Service}
+import net.psforever.services.Service
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
import net.psforever.services.local.{LocalAction, LocalServiceMessage}
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
-import scala.concurrent.duration._
+//import scala.concurrent.duration._
object Vehicles {
private val log = org.log4s.getLogger("Vehicles")
@@ -309,7 +310,13 @@ object Vehicles {
// If AMS is deployed, swap it to the new faction
target.Definition match {
case GlobalDefinitions.router =>
- Vehicles.RemoveTelepads(target)
+ target.Utility(UtilityType.internal_router_telepad_deployable) match {
+ case Some(util: Utility.InternalTelepad) =>
+ //"power cycle"
+ util.Actor ! TelepadLike.Deactivate(util)
+ util.Actor ! TelepadLike.Activate(util)
+ case _ => ;
+ }
case GlobalDefinitions.ams if target.DeploymentState == DriveState.Deployed =>
zone.VehicleEvents ! VehicleServiceMessage.AMSDeploymentChange(zone)
case _ => ;
@@ -391,25 +398,6 @@ object Vehicles {
}
}
- def RemoveTelepads(vehicle: Vehicle): Unit = {
- val zone = vehicle.Zone
- (vehicle.Utility(UtilityType.internal_router_telepad_deployable) match {
- case Some(util: Utility.InternalTelepad) =>
- val telepad = util.Telepad
- util.Telepad = None
- zone.GUID(telepad)
- case _ =>
- None
- }) match {
- case Some(telepad: TelepadDeployable) =>
- log.debug(s"BeforeUnload: deconstructing telepad $telepad that was linked to router $vehicle ...")
- telepad.Active = false
- zone.LocalEvents ! LocalServiceMessage.Deployables(RemoverActor.ClearSpecific(List(telepad), zone))
- zone.LocalEvents ! LocalServiceMessage.Deployables(RemoverActor.AddTask(telepad, zone, Some(0 seconds)))
- case _ => ;
- }
- }
-
/**
* Find the position and angle at which an ejected player will be placed once outside of the shuttle.
* Mainly for use with the proper high altitude rapid transport (HART) shuttle and it's corresponding HART building.
diff --git a/src/main/scala/net/psforever/objects/avatar/DeployableToolbox.scala b/src/main/scala/net/psforever/objects/avatar/DeployableToolbox.scala
index 0683284a..91451b4e 100644
--- a/src/main/scala/net/psforever/objects/avatar/DeployableToolbox.scala
+++ b/src/main/scala/net/psforever/objects/avatar/DeployableToolbox.scala
@@ -1,9 +1,9 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.avatar
-import net.psforever.objects.PlanetSideGameObject
import net.psforever.objects.ce.{Deployable, DeployableCategory, DeployedItem}
import net.psforever.types.PlanetSideGUID
+
import scala.collection.mutable
/**
@@ -13,13 +13,18 @@ import scala.collection.mutable
* `CombatEngineering` and above certifications include permissions for different types of deployables,
* and one unique type of deployable is available through the `GroundSupport`
* and one that also requires `AdvancedHacking`.
- * (They are collectively called "ce" for that reason.)
+ * (They are collectively called "ce" for that reason.)
+ *
* Not only does the level of certification change the maximum number of deployables that can be managed by type
* but it also influences the maximum number of deployables that can be managed by category.
* Individual deployables are counted by type and category individually in special data structures
* to avoid having to probe the primary list of deployable references whenever a question of quantity is asked.
* As deployables are added and removed, and tracked certifications are added and removed,
* these structures are updated to reflect proper count.
+ * For example, the greatest number of spitfire turrets that can be placed is 15 (individual count)
+ * and the greatest number of shadow turrets and cerebus turrets that can be placed is 5 each (individual counts)
+ * but the maximum number of small turrets that can be placed overall is only 15 (categorical count).
+ * Spitfire turrets, shadow turrets, and cerebus turrets are all included in the category of small turrets.
*/
class DeployableToolbox {
@@ -148,6 +153,28 @@ class DeployableToolbox {
}
}
+ /**
+ * Manage the provided deployable, the maximum number of governable units be damned.
+ * It still needs to be a unique unit of a governable deployable type, however.
+ * @param obj the deployable
+ * @return `true`, if the deployable is added;
+ * `false`, otherwise
+ */
+ def AddOverLimit(obj: DeployableToolbox.AcceptableDeployable): Boolean = {
+ val category = obj.Definition.DeployCategory
+ val dCategory = categoryCounts(category)
+ val dType = deployableCounts(DeployableToolbox.UnifiedType(obj.Definition.Item))
+ val dList = deployableLists(category)
+ if (!dList.contains(obj)) {
+ dCategory.Current += 1
+ dType.Current += 1
+ dList += obj
+ true
+ } else {
+ false
+ }
+ }
+
/**
* Stop managing the provided deployable.
*
@@ -156,7 +183,7 @@ class DeployableToolbox {
* If the deployable is found to currently being managed by this toolbox, then it is properly removed.
* No changes should occur if the deployable is not properly removed.
* @param obj the deployable
- * @return `true`, if the deployable is added;
+ * @return `true`, if the deployable is removed;
* `false`, otherwise
*/
def Remove(obj: DeployableToolbox.AcceptableDeployable): Boolean = {
@@ -194,7 +221,7 @@ class DeployableToolbox {
*/
def DisplaceFirst(
obj: DeployableToolbox.AcceptableDeployable,
- rule: (Deployable) => Boolean
+ rule: Deployable => Boolean
): Option[DeployableToolbox.AcceptableDeployable] = {
val definition = obj.Definition
val category = definition.DeployCategory
@@ -298,7 +325,9 @@ class DeployableToolbox {
List((curr, dType.Current, max, dType.Max))
}
- def UpdateUI(): List[(Int, Int, Int, Int)] = DeployedItem.values flatMap UpdateUIElement toList
+ def UpdateUI(): List[(Int, Int, Int, Int)] = DeployedItem.values.flatMap { value: DeployedItem.Value =>
+ UpdateUIElement(value)
+ }.toList
def UpdateUI(entry: Certification): List[(Int, Int, Int, Int)] = {
import Certification._
@@ -395,7 +424,7 @@ object DeployableToolbox {
/**
* A `type` intended to properly define the minimum acceptable conditions for a `Deployable` object.
*/
- type AcceptableDeployable = PlanetSideGameObject with Deployable
+ type AcceptableDeployable = Deployable
/**
* An internal class to keep track of the quantity of deployables managed for a certain set of criteria.
diff --git a/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala b/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala
index d018b418..fd251119 100644
--- a/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala
+++ b/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala
@@ -3,8 +3,11 @@ package net.psforever.objects.avatar
import akka.actor.{Actor, ActorRef, Props, typed}
import net.psforever.actors.session.AvatarActor
+import net.psforever.login.WorldSession.{DropEquipmentFromInventory, HoldNewEquipmentUp, PutNewEquipmentInInventoryOrDrop, RemoveOldEquipmentFromInventory}
import net.psforever.objects.{Player, _}
import net.psforever.objects.ballistics.PlayerSource
+import net.psforever.objects.ce.Deployable
+import net.psforever.objects.definition.DeployAnimation
import net.psforever.objects.equipment._
import net.psforever.objects.guid.GUIDTask
import net.psforever.objects.inventory.{GridInventory, InventoryItem}
@@ -22,7 +25,7 @@ import net.psforever.objects.zones._
import net.psforever.packet.game._
import net.psforever.packet.game.objectcreate.ObjectCreateMessageParent
import net.psforever.types._
-import net.psforever.services.{RemoverActor, Service}
+import net.psforever.services.Service
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
import net.psforever.services.local.{LocalAction, LocalServiceMessage}
import net.psforever.objects.locker.LockerContainerControl
@@ -33,6 +36,7 @@ import net.psforever.objects.vital.environment.EnvironmentReason
import net.psforever.objects.vital.etc.{PainboxReason, SuicideReason}
import net.psforever.objects.vital.interaction.{DamageInteraction, DamageResult}
import net.psforever.services.hart.ShuttleState
+import net.psforever.packet.PlanetSideGamePacket
import scala.concurrent.duration._
@@ -44,8 +48,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
with AggravatedBehavior
with AuraEffectBehavior
with RespondsToZoneEnvironment {
-
- def JammableObject = player
+ def JammableObject = player
def DamageableObject = player
@@ -65,12 +68,12 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
SetInteraction(EnvironmentAttribute.Death, doInteractingWithDeath)
SetInteraction(EnvironmentAttribute.GantryDenialField, doInteractingWithGantryField)
SetInteractionStop(EnvironmentAttribute.Water, stopInteractingWithWater)
-
- private[this] val log = org.log4s.getLogger(player.Name)
+ private[this] val log = org.log4s.getLogger(player.Name)
private[this] val damageLog = org.log4s.getLogger(Damageable.LogChannel)
/** suffocating, or regaining breath? */
var submergedCondition: Option[OxygenState] = None
-
+ /** assistance for deployable construction, retention of the construction item */
+ var deployablePair: Option[(Deployable, ConstructionItem)] = None
/** control agency for the player's locker container (dedicated inventory slot #5) */
val lockerControlAgent: ActorRef = {
val locker = player.avatar.locker
@@ -110,20 +113,20 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
if item.Definition == GlobalDefinitions.medicalapplicator && player.isAlive =>
//heal
val originalHealth = player.Health
- val definition = player.Definition
+ val definition = player.Definition
if (
player.MaxHealth > 0 && originalHealth < player.MaxHealth &&
user.Faction == player.Faction &&
item.Magazine > 0 &&
Vector3.Distance(user.Position, player.Position) < definition.RepairDistance
) {
- val zone = player.Zone
+ val zone = player.Zone
val events = zone.AvatarEvents
- val uname = user.Name
- val guid = player.GUID
+ val uname = user.Name
+ val guid = player.GUID
if (!(player.isMoving || user.isMoving)) { //only allow stationary heals
val newHealth = player.Health = originalHealth + 10
- val magazine = item.Discharge()
+ val magazine = item.Discharge()
events ! AvatarServiceMessage(
uname,
AvatarAction.SendResponse(
@@ -173,17 +176,17 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
case CommonMessages.Use(user, Some(item: Tool)) if item.Definition == GlobalDefinitions.bank =>
val originalArmor = player.Armor
- val definition = player.Definition
+ val definition = player.Definition
if (
player.MaxArmor > 0 && originalArmor < player.MaxArmor &&
user.Faction == player.Faction &&
item.AmmoType == Ammo.armor_canister && item.Magazine > 0 &&
Vector3.Distance(user.Position, player.Position) < definition.RepairDistance
) {
- val zone = player.Zone
+ val zone = player.Zone
val events = zone.AvatarEvents
- val uname = user.Name
- val guid = player.GUID
+ val uname = user.Name
+ val guid = player.GUID
if (!(player.isMoving || user.isMoving)) { //only allow stationary repairs
val newArmor = player.Armor =
originalArmor + Repairable.applyLevelModifier(user, item, RepairToolValue(item)).toInt + definition.RepairMod
@@ -313,16 +316,16 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
case Terminal.InfantryLoadout(exosuit, subtype, holsters, inventory) =>
log.info(s"${player.Name} wants to change equipment loadout to their option #${msg.unk1 + 1}")
val fallbackSubtype = 0
- val fallbackSuit = ExoSuitType.Standard
- val originalSuit = player.ExoSuit
+ val fallbackSuit = ExoSuitType.Standard
+ val originalSuit = player.ExoSuit
val originalSubtype = Loadout.DetermineSubtype(player)
//sanitize exo-suit for change
- val dropPred = ContainableBehavior.DropPredicate(player)
- val oldHolsters = Players.clearHolsters(player.Holsters().iterator)
- val dropHolsters = oldHolsters.filter(dropPred)
- val oldInventory = player.Inventory.Clear()
+ val dropPred = ContainableBehavior.DropPredicate(player)
+ val oldHolsters = Players.clearHolsters(player.Holsters().iterator)
+ val dropHolsters = oldHolsters.filter(dropPred)
+ val oldInventory = player.Inventory.Clear()
val dropInventory = oldInventory.filter(dropPred)
- val toDeleteOrDrop: List[InventoryItem] = (player.FreeHand.Equipment match {
+ val toDeleteOrDrop : List[InventoryItem] = (player.FreeHand.Equipment match {
case Some(obj) =>
val out = InventoryItem(obj, -1)
player.FreeHand.Equipment = None
@@ -337,27 +340,27 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
//a loadout with a prohibited exo-suit type will result in the fallback exo-suit type
//imposed 5min delay on mechanized exo-suit switches
val (nextSuit, nextSubtype) =
- if (
- Players.CertificationToUseExoSuit(player, exosuit, subtype) &&
- (if (exosuit == ExoSuitType.MAX) {
- val weapon = GlobalDefinitions.MAXArms(subtype, player.Faction)
- player.avatar.purchaseCooldown(weapon) match {
- case Some(_) => false
- case None =>
- avatarActor ! AvatarActor.UpdatePurchaseTime(weapon)
- true
- }
- } else {
- true
- })
- ) {
- (exosuit, subtype)
+ if (
+ Players.CertificationToUseExoSuit(player, exosuit, subtype) &&
+ (if (exosuit == ExoSuitType.MAX) {
+ val weapon = GlobalDefinitions.MAXArms(subtype, player.Faction)
+ player.avatar.purchaseCooldown(weapon) match {
+ case Some(_) => false
+ case None =>
+ avatarActor ! AvatarActor.UpdatePurchaseTime(weapon)
+ true
+ }
} else {
- log.warn(
- s"${player.Name} no longer has permission to wear the exo-suit type $exosuit; will wear $fallbackSuit instead"
- )
- (fallbackSuit, fallbackSubtype)
- }
+ true
+ })
+ ) {
+ (exosuit, subtype)
+ } else {
+ log.warn(
+ s"${player.Name} no longer has permission to wear the exo-suit type $exosuit; will wear $fallbackSuit instead"
+ )
+ (fallbackSuit, fallbackSubtype)
+ }
//sanitize (incoming) inventory
//TODO equipment permissions; these loops may be expanded upon in future
val curatedHolsters = for {
@@ -412,6 +415,11 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
(finalHolsters, finalInventory)
}
(afterHolsters ++ afterInventory).foreach { entry => entry.obj.Faction = player.Faction }
+ afterHolsters.foreach {
+ case InventoryItem(citem: ConstructionItem, _) =>
+ Deployables.initializeConstructionAmmoMode(player.avatar.certifications, citem)
+ case _ => ;
+ }
toDeleteOrDrop.foreach { entry => entry.obj.Faction = PlanetSideEmpire.NEUTRAL }
//deactivate non-passive implants
avatarActor ! AvatarActor.DeactivateActiveImplants()
@@ -439,46 +447,13 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
}
case Zone.Ground.ItemOnGround(item, _, _) =>
- val name = player.Name
- val zone = player.Zone
- val avatarEvents = zone.AvatarEvents
- val localEvents = zone.LocalEvents
item match {
case trigger: BoomerTrigger =>
- //dropped the trigger, no longer own the boomer; make certain whole faction is aware of that
- (zone.GUID(trigger.Companion), zone.Players.find { _.name == name }) match {
- case (Some(boomer: BoomerDeployable), Some(avatar)) =>
- val guid = boomer.GUID
- val factionChannel = boomer.Faction.toString
- if (avatar.deployables.Remove(boomer)) {
- boomer.Faction = PlanetSideEmpire.NEUTRAL
- boomer.AssignOwnership(None)
- avatar.deployables.UpdateUIElement(boomer.Definition.Item).foreach {
- case (currElem, curr, maxElem, max) =>
- avatarEvents ! AvatarServiceMessage(
- name,
- AvatarAction.PlanetsideAttributeToAll(Service.defaultPlayerGUID, maxElem, max)
- )
- avatarEvents ! AvatarServiceMessage(
- name,
- AvatarAction.PlanetsideAttributeToAll(Service.defaultPlayerGUID, currElem, curr)
- )
- }
- localEvents ! LocalServiceMessage.Deployables(RemoverActor.AddTask(boomer, zone))
- localEvents ! LocalServiceMessage(
- factionChannel,
- LocalAction.DeployableMapIcon(
- Service.defaultPlayerGUID,
- DeploymentAction.Dismiss,
- DeployableInfo(guid, DeployableIcon.Boomer, boomer.Position, PlanetSideGUID(0))
- )
- )
- avatarEvents ! AvatarServiceMessage(
- factionChannel,
- AvatarAction.SetEmpire(Service.defaultPlayerGUID, guid, PlanetSideEmpire.NEUTRAL)
- )
- }
- case _ => ; //pointless trigger? or a trigger being deleted?
+ //drop the trigger, lose the boomer; make certain whole faction is aware of that
+ player.Zone.GUID(trigger.Companion) match {
+ case Some(obj: BoomerDeployable) =>
+ loseDeployableOwnership(obj)
+ case _ => ;
}
case _ => ;
}
@@ -491,14 +466,85 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
case Zone.Ground.CanNotPickupItem(_, item_guid, reason) =>
log.warn(s"${player.Name} failed to pick up an item ($item_guid) from the ground because $reason")
+ case Player.BuildDeployable(obj: TelepadDeployable, tool: Telepad) =>
+ obj.Router = tool.Router //necessary; forwards link to the router that prodcued the telepad
+ setupDeployable(obj, tool)
+
+ case Player.BuildDeployable(obj, tool) =>
+ setupDeployable(obj, tool)
+
+ case Zone.Deployable.IsBuilt(obj: BoomerDeployable) =>
+ deployablePair match {
+ case Some((deployable, tool)) if deployable eq obj =>
+ val zone = player.Zone
+ //boomers
+ val trigger = new BoomerTrigger
+ trigger.Companion = obj.GUID
+ obj.Trigger = trigger
+ //TODO sufficiently delete the tool
+ zone.AvatarEvents ! AvatarServiceMessage(zone.id, AvatarAction.ObjectDelete(player.GUID, tool.GUID))
+ zone.tasks ! GUIDTask.UnregisterEquipment(tool)(zone.GUID)
+ player.Find(tool) match {
+ case Some(index) if player.VisibleSlots.contains(index) =>
+ player.Slot(index).Equipment = None
+ zone.tasks ! HoldNewEquipmentUp(player)(trigger, index)
+ case Some(index) =>
+ player.Slot(index).Equipment = None
+ zone.tasks ! PutNewEquipmentInInventoryOrDrop(player)(trigger)
+ case None =>
+ //don't know where boomer trigger "should" go
+ zone.tasks ! PutNewEquipmentInInventoryOrDrop(player)(trigger)
+ }
+ Players.buildCooldownReset(zone, player.Name, obj)
+ case _ => ;
+ }
+ deployablePair = None
+
+ case Zone.Deployable.IsBuilt(obj: TelepadDeployable) =>
+ deployablePair match {
+ case Some((deployable, tool: Telepad)) if deployable eq obj =>
+ RemoveOldEquipmentFromInventory(player)(tool)
+ val zone = player.Zone
+ zone.GUID(obj.Router) match {
+ case Some(v: Vehicle)
+ if v.Definition == GlobalDefinitions.router => ;
+ case _ =>
+ player.Actor ! Player.LoseDeployable(obj)
+ TelepadControl.TelepadError(zone, player.Name, msg = "@Telepad_NoDeploy_RouterLost")
+ }
+ Players.buildCooldownReset(zone, player.Name, obj)
+ case _ => ;
+ }
+ deployablePair = None
+
+ case Zone.Deployable.IsBuilt(obj) =>
+ deployablePair match {
+ case Some((deployable, tool)) if deployable eq obj =>
+ Players.buildCooldownReset(player.Zone, player.Name, obj)
+ player.Find(tool) match {
+ case Some(index) =>
+ Players.commonDestroyConstructionItem(player, tool, index)
+ Players.findReplacementConstructionItem(player, tool, index)
+ case None =>
+ log.warn(s"${player.Name} should have destroyed a ${tool.Definition.Name} here, but could not find it")
+ }
+ case _ => ;
+ }
+ deployablePair = None
+
+ case Player.LoseDeployable(obj) =>
+ if (player.avatar.deployables.Remove(obj)) {
+ player.Zone.LocalEvents ! LocalServiceMessage(player.Name, LocalAction.DeployableUIFor(obj.Definition.Item))
+ }
+
case _ => ;
}
def setExoSuit(exosuit: ExoSuitType.Value, subtype: Int): Boolean = {
- var toDelete: List[InventoryItem] = Nil
- val originalSuit = player.ExoSuit
- val originalSubtype = Loadout.DetermineSubtype(player)
- val requestToChangeArmor = originalSuit != exosuit || originalSubtype != subtype
+ var toDelete : List[InventoryItem] = Nil
+ val originalSuit = player.ExoSuit
+ val originalSubtype = Loadout.DetermineSubtype(player)
+ val requestToChangeArmor = originalSuit != exosuit || originalSubtype != subtype
val allowedToChangeArmor = Players.CertificationToUseExoSuit(player, exosuit, subtype) &&
(if (exosuit == ExoSuitType.MAX) {
val weapon = GlobalDefinitions.MAXArms(subtype, player.Faction)
@@ -509,12 +555,13 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
avatarActor ! AvatarActor.UpdatePurchaseTime(weapon)
true
}
- } else {
+ }
+ else {
true
})
if (requestToChangeArmor && allowedToChangeArmor) {
log.info(s"${player.Name} wants to change to a different exo-suit - $exosuit")
- val beforeHolsters = Players.clearHolsters(player.Holsters().iterator)
+ val beforeHolsters = Players.clearHolsters(player.Holsters().iterator)
val beforeInventory = player.Inventory.Clear()
//change suit
val originalArmor = player.Armor
@@ -523,7 +570,8 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
val toArmor = if (originalSuit != exosuit || originalSubtype != subtype || originalArmor > toMaxArmor) {
player.History(HealFromExoSuitChange(PlayerSource(player), exosuit))
player.Armor = toMaxArmor
- } else {
+ }
+ else {
player.Armor = originalArmor
}
//ensure arm is down, even if it needs to go back up
@@ -534,7 +582,8 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
val (maxWeapons, normalWeapons) = beforeHolsters.partition(elem => elem.obj.Size == EquipmentSize.Max)
toDelete ++= maxWeapons
normalWeapons
- } else {
+ }
+ else {
beforeHolsters
}
//populate holsters
@@ -543,9 +592,11 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
normalHolsters,
Players.fillEmptyHolsters(List(player.Slot(4)).iterator, normalHolsters) ++ beforeInventory
)
- } else if (originalSuit == exosuit) { //note - this will rarely be the situation
+ }
+ else if (originalSuit == exosuit) { //note - this will rarely be the situation
(normalHolsters, Players.fillEmptyHolsters(player.Holsters().iterator, normalHolsters))
- } else {
+ }
+ else {
val (afterHolsters, toInventory) =
normalHolsters.partition(elem => elem.obj.Size == player.Slot(elem.start).Size)
afterHolsters.foreach({ elem => player.Slot(elem.start).Equipment = elem.obj })
@@ -558,7 +609,8 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
//put items back into inventory
val (stow, drop) = if (originalSuit == exosuit) {
(finalInventory, Nil)
- } else {
+ }
+ else {
val (a, b) = GridInventory.recoverInventory(finalInventory, player.Inventory)
(
a,
@@ -590,11 +642,64 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
)
)
true
- } else {
+ }
+ else {
false
}
}
+ def loseDeployableOwnership(obj: Deployable): Boolean = {
+ if (player.avatar.deployables.Remove(obj)) {
+ obj.Actor ! Deployable.Ownership(None)
+ player.Zone.LocalEvents ! LocalServiceMessage(player.Name, LocalAction.DeployableUIFor(obj.Definition.Item))
+ true
+ }
+ else {
+ false
+ }
+ }
+
+ def setupDeployable(obj: Deployable, tool: ConstructionItem): Unit = {
+ if (deployablePair.isEmpty) {
+ val zone = player.Zone
+ val deployables = player.avatar.deployables
+ if (deployables.Valid(obj) &&
+ !deployables.Contains(obj) &&
+ Players.deployableWithinBuildLimits(player, obj)) {
+ tool.Definition match {
+ case GlobalDefinitions.ace | /* animation handled in deployable lifecycle */
+ GlobalDefinitions.router_telepad => ; /* no special animation */
+ case GlobalDefinitions.advanced_ace
+ if obj.Definition.deployAnimation == DeployAnimation.Fdu =>
+ zone.AvatarEvents ! AvatarServiceMessage(zone.id, AvatarAction.PutDownFDU(player.GUID))
+ case _ =>
+ org.log4s.getLogger(name = "Deployables").warn(
+ s"not sure what kind of construction item to animate - ${tool.Definition.Name}"
+ )
+ }
+ deployablePair = Some((obj, tool))
+ obj.Faction = player.Faction
+ obj.AssignOwnership(player)
+ obj.Actor ! Zone.Deployable.Setup()
+ }
+ else {
+ log.warn(s"cannot build a ${obj.Definition.Name}")
+ DropEquipmentFromInventory(player)(tool, Some(obj.Position))
+ Players.buildCooldownReset(zone, player.Name, obj)
+ obj.Position = Vector3.Zero
+ obj.AssignOwnership(None)
+ zone.Deployables ! Zone.Deployable.Dismiss(obj)
+ }
+ } else {
+ log.warn(s"already building one deployable, so cannot build a ${obj.Definition.Name}")
+ obj.Position = Vector3.Zero
+ obj.AssignOwnership(None)
+ val zone = player.Zone
+ zone.Deployables ! Zone.Deployable.Dismiss(obj)
+ Players.buildCooldownReset(zone, player.Name, obj)
+ }
+ }
+
override protected def PerformDamage(
target: Target,
applyDamageTo: Output
@@ -998,7 +1103,31 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
val definition = item.Definition
val faction = obj.Faction
val toChannel = if (player.isBackpack) { self.toString } else { name }
+ val willBeVisible = obj.VisibleSlots.contains(slot)
item.Faction = faction
+ //handle specific types of items
+ item match {
+ case trigger: BoomerTrigger =>
+ //pick up the trigger, own the boomer; make certain whole faction is aware of that
+ zone.GUID(trigger.Companion) match {
+ case Some(obj: BoomerDeployable) =>
+ val deployables = player.avatar.deployables
+ if (deployables.Valid(obj)) {
+ Players.gainDeployableOwnership(player, obj, deployables.AddOverLimit)
+ }
+ case _ => ;
+ }
+
+ case citem: ConstructionItem
+ if willBeVisible =>
+ if (citem.AmmoTypeIndex > 0) {
+ //can not preserve ammo type in construction tool packets
+ citem.resetAmmoTypes()
+ }
+ Deployables.initializeConstructionAmmoMode(player.avatar.certifications, citem)
+
+ case _ => ;
+ }
events ! AvatarServiceMessage(
toChannel,
AvatarAction.SendResponse(
@@ -1011,56 +1140,9 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
)
)
)
- if (!player.isBackpack && obj.VisibleSlots.contains(slot)) {
+ if (!player.isBackpack && willBeVisible) {
events ! AvatarServiceMessage(zone.id, AvatarAction.EquipmentInHand(guid, guid, slot, item))
}
- //handle specific types of items
- item match {
- case trigger: BoomerTrigger =>
- //pick up the trigger, own the boomer; make certain whole faction is aware of that
- (zone.GUID(trigger.Companion), zone.Players.find { _.name == name }) match {
- case (Some(boomer: BoomerDeployable), Some(avatar))
- if !boomer.OwnerName.contains(name) || boomer.Faction != faction =>
- val bguid = boomer.GUID
- val faction = player.Faction
- val factionChannel = faction.toString
- if (avatar.deployables.Add(boomer)) {
- boomer.Faction = faction
- boomer.AssignOwnership(player)
- avatar.deployables.UpdateUIElement(boomer.Definition.Item).foreach {
- case (currElem, curr, maxElem, max) =>
- events ! AvatarServiceMessage(
- name,
- AvatarAction.PlanetsideAttributeToAll(Service.defaultPlayerGUID, maxElem, max)
- )
- events ! AvatarServiceMessage(
- name,
- AvatarAction.PlanetsideAttributeToAll(Service.defaultPlayerGUID, currElem, curr)
- )
- }
- zone.LocalEvents ! LocalServiceMessage.Deployables(RemoverActor.ClearSpecific(List(boomer), zone))
- events ! AvatarServiceMessage(
- factionChannel,
- AvatarAction.SetEmpire(Service.defaultPlayerGUID, bguid, faction)
- )
- zone.LocalEvents ! LocalServiceMessage(
- factionChannel,
- LocalAction.DeployableMapIcon(
- Service.defaultPlayerGUID,
- DeploymentAction.Build,
- DeployableInfo(
- bguid,
- DeployableIcon.Boomer,
- boomer.Position,
- boomer.Owner.getOrElse(PlanetSideGUID(0))
- )
- )
- )
- }
- case _ => ; //pointless trigger?
- }
- case _ => ;
- }
}
def SwapItemCallback(item: Equipment, fromSlot: Int): Unit = {
@@ -1240,4 +1322,8 @@ object PlayerControl {
case Aura.Fire => 8
case _ => 0
}
+
+ def sendResponse(zone: Zone, channel: String, msg: PlanetSideGamePacket): Unit = {
+ zone.AvatarEvents ! AvatarServiceMessage(channel, AvatarAction.SendResponse(Service.defaultPlayerGUID, msg))
+ }
}
diff --git a/src/main/scala/net/psforever/objects/ballistics/ComplexDeployableSource.scala b/src/main/scala/net/psforever/objects/ballistics/ComplexDeployableSource.scala
deleted file mode 100644
index 8ee73d56..00000000
--- a/src/main/scala/net/psforever/objects/ballistics/ComplexDeployableSource.scala
+++ /dev/null
@@ -1,55 +0,0 @@
-// Copyright (c) 2017 PSForever
-package net.psforever.objects.ballistics
-
-import net.psforever.objects.TurretDeployable
-import net.psforever.objects.ce.ComplexDeployable
-import net.psforever.objects.definition.{DeployableDefinition, ObjectDefinition}
-import net.psforever.objects.vital.resistance.ResistanceProfile
-import net.psforever.types.{PlanetSideEmpire, Vector3}
-
-final case class ComplexDeployableSource(
- obj_def: ObjectDefinition with DeployableDefinition,
- faction: PlanetSideEmpire.Value,
- health: Int,
- shields: Int,
- ownerName: String,
- position: Vector3,
- orientation: Vector3
-) extends SourceEntry {
- override def Name = SourceEntry.NameFormat(obj_def.Name)
- override def Faction = faction
- def Definition: ObjectDefinition with DeployableDefinition = obj_def
- def Health = health
- def Shields = shields
- def OwnerName = ownerName
- def Position = position
- def Orientation = orientation
- def Velocity = None
- def Modifiers = obj_def.asInstanceOf[ResistanceProfile]
-}
-
-object ComplexDeployableSource {
- def apply(obj: ComplexDeployable): ComplexDeployableSource = {
- ComplexDeployableSource(
- obj.Definition,
- obj.Faction,
- obj.Health,
- obj.Shields,
- obj.OwnerName.getOrElse(""),
- obj.Position,
- obj.Orientation
- )
- }
-
- def apply(obj: TurretDeployable): ComplexDeployableSource = {
- ComplexDeployableSource(
- obj.Definition,
- obj.Faction,
- obj.Health,
- obj.Shields,
- obj.OwnerName.getOrElse(""),
- obj.Position,
- obj.Orientation
- )
- }
-}
diff --git a/src/main/scala/net/psforever/objects/ballistics/DeployableSource.scala b/src/main/scala/net/psforever/objects/ballistics/DeployableSource.scala
index 8e2f5d64..26a6fcdc 100644
--- a/src/main/scala/net/psforever/objects/ballistics/DeployableSource.scala
+++ b/src/main/scala/net/psforever/objects/ballistics/DeployableSource.scala
@@ -1,7 +1,6 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.ballistics
-import net.psforever.objects.PlanetSideGameObject
import net.psforever.objects.ce.Deployable
import net.psforever.objects.definition.{DeployableDefinition, ObjectDefinition}
import net.psforever.objects.vital.resistance.ResistanceProfile
@@ -11,6 +10,7 @@ final case class DeployableSource(
obj_def: ObjectDefinition with DeployableDefinition,
faction: PlanetSideEmpire.Value,
health: Int,
+ shields: Int,
ownerName: String,
position: Vector3,
orientation: Vector3
@@ -19,6 +19,7 @@ final case class DeployableSource(
override def Faction = faction
def Definition: ObjectDefinition with DeployableDefinition = obj_def
def Health = health
+ def Shields = shields
def OwnerName = ownerName
def Position = position
def Orientation = orientation
@@ -27,11 +28,12 @@ final case class DeployableSource(
}
object DeployableSource {
- def apply(obj: PlanetSideGameObject with Deployable): DeployableSource = {
+ def apply(obj: Deployable): DeployableSource = {
DeployableSource(
obj.Definition,
obj.Faction,
obj.Health,
+ obj.Shields,
obj.OwnerName.getOrElse(""),
obj.Position,
obj.Orientation
diff --git a/src/main/scala/net/psforever/objects/ballistics/SourceEntry.scala b/src/main/scala/net/psforever/objects/ballistics/SourceEntry.scala
index eae56e98..820fe1ce 100644
--- a/src/main/scala/net/psforever/objects/ballistics/SourceEntry.scala
+++ b/src/main/scala/net/psforever/objects/ballistics/SourceEntry.scala
@@ -1,7 +1,7 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.ballistics
-import net.psforever.objects.ce.{ComplexDeployable, SimpleDeployable}
+import net.psforever.objects.ce.Deployable
import net.psforever.objects.definition.ObjectDefinition
import net.psforever.objects.{PlanetSideGameObject, Player, Vehicle}
import net.psforever.objects.entity.WorldEntity
@@ -32,11 +32,10 @@ object SourceEntry {
def apply(target: PlanetSideGameObject with FactionAffinity): SourceEntry = {
target match {
- case obj: Player => PlayerSource(obj)
- case obj: Vehicle => VehicleSource(obj)
- case obj: ComplexDeployable => ComplexDeployableSource(obj)
- case obj: SimpleDeployable => DeployableSource(obj)
- case _ => ObjectSource(target)
+ case obj: Player => PlayerSource(obj)
+ case obj: Vehicle => VehicleSource(obj)
+ case obj: Deployable => DeployableSource(obj)
+ case _ => ObjectSource(target)
}
}
diff --git a/src/main/scala/net/psforever/objects/ce/ComplexDeployable.scala b/src/main/scala/net/psforever/objects/ce/ComplexDeployable.scala
deleted file mode 100644
index 144925e5..00000000
--- a/src/main/scala/net/psforever/objects/ce/ComplexDeployable.scala
+++ /dev/null
@@ -1,22 +0,0 @@
-// Copyright (c) 2017 PSForever
-package net.psforever.objects.ce
-
-import net.psforever.objects.definition.ComplexDeployableDefinition
-import net.psforever.objects.serverobject.PlanetSideServerObject
-
-abstract class ComplexDeployable(cdef: ComplexDeployableDefinition) extends PlanetSideServerObject with Deployable {
- private var shields: Int = 0
-
- def Shields: Int = shields
-
- def Shields_=(toShields: Int): Int = {
- shields = math.min(math.max(0, toShields), MaxShields)
- Shields
- }
-
- def MaxShields: Int = {
- 0 //Definition.MaxShields
- }
-
- def Definition = cdef
-}
diff --git a/src/main/scala/net/psforever/objects/ce/Deployable.scala b/src/main/scala/net/psforever/objects/ce/Deployable.scala
index 844965e0..504450e0 100644
--- a/src/main/scala/net/psforever/objects/ce/Deployable.scala
+++ b/src/main/scala/net/psforever/objects/ce/Deployable.scala
@@ -3,6 +3,7 @@ package net.psforever.objects.ce
import net.psforever.objects._
import net.psforever.objects.definition.DeployableDefinition
+import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.vital.Vitality
import net.psforever.objects.vital.resolution.DamageResistanceModel
@@ -10,9 +11,25 @@ import net.psforever.objects.zones.ZoneAware
import net.psforever.packet.game.DeployableIcon
import net.psforever.types.PlanetSideEmpire
-trait Deployable extends FactionAffinity with Vitality with OwnableByPlayer with ZoneAware {
- this: PlanetSideGameObject =>
+trait BaseDeployable
+ extends PlanetSideServerObject
+ with FactionAffinity
+ with Vitality
+ with OwnableByPlayer
+ with ZoneAware {
private var faction: PlanetSideEmpire.Value = PlanetSideEmpire.NEUTRAL
+ private var shields: Int = 0
+
+ def Shields: Int = shields
+
+ def Shields_=(toShields: Int): Int = {
+ shields = math.min(math.max(0, toShields), MaxShields)
+ Shields
+ }
+
+ def MaxShields: Int = {
+ 0 //Definition.MaxShields
+ }
def MaxHealth: Int
@@ -28,7 +45,33 @@ trait Deployable extends FactionAffinity with Vitality with OwnableByPlayer with
def Definition: DeployableDefinition
}
+abstract class Deployable(cdef: DeployableDefinition)
+ extends BaseDeployable {
+ def Definition: DeployableDefinition = cdef
+}
+
object Deployable {
+ import scala.concurrent.duration._
+ final val decay: FiniteDuration = 3.minutes
+
+ final val cleanup: FiniteDuration = 2.seconds
+
+ final case class Deconstruct(time: Option[FiniteDuration] = None)
+
+ object Deconstruct {
+ def apply(): Deconstruct = Deconstruct(None)
+ }
+
+ /**
+ * Change a vehicle's internal ownership property to match that of the target player.
+ * @param player the person who will own the vehicle, or `None` if the vehicle will go unowned
+ */
+ final case class Ownership(player: Option[Player])
+
+ object Ownership {
+ def apply(player: Player): Ownership = Ownership(Some(player))
+ }
+
object Category {
def Of(item: DeployedItem.Value): DeployableCategory.Value = deployablesToCategories(item)
diff --git a/src/main/scala/net/psforever/objects/ce/DeployableBehavior.scala b/src/main/scala/net/psforever/objects/ce/DeployableBehavior.scala
new file mode 100644
index 00000000..c7c4d151
--- /dev/null
+++ b/src/main/scala/net/psforever/objects/ce/DeployableBehavior.scala
@@ -0,0 +1,316 @@
+// Copyright (c) 2021 PSForever
+package net.psforever.objects.ce
+
+import akka.actor.{Actor, ActorRef, Cancellable}
+import net.psforever.objects.guid.GUIDTask
+import net.psforever.objects._
+import net.psforever.objects.definition.DeployAnimation
+import net.psforever.objects.zones.Zone
+import net.psforever.packet.game._
+import net.psforever.services.Service
+import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
+import net.psforever.services.local.{LocalAction, LocalServiceMessage}
+import net.psforever.types.PlanetSideEmpire
+
+import scala.concurrent.duration._
+
+/**
+ * A `trait` mixin to manage the basic lifecycle of `Deployable` entities.
+ *
+ * Two parts of the deployable lifecycle are supported - building/deployment and dismissal/deconstruction.
+ * Furthermore, both parts of the lifecycle can also be broken down into two parts for the purposes of sequencing.
+ * The former part can be referred to as "preparation" which, at the least, queues the future part.
+ * This latter part can be referred to as "execution" where the the actual process takes place.
+ * Internal messaging protocol permits the lifecycle to transition.
+ * "Building" of the deployable starts when a `Setup` request is received during the appropriate window of opportunity
+ * and queues up a the formal construction event and its packets for a later period (usually a few seconds).
+ * After being constructed, the deployable can be deconstructed by receiving such a `Deconstruct` message.
+ * As deployables are capable of being owned by the player,
+ * in between two two states of being created and deconstructed,
+ * deployables may also recognize that their ownership has been changed and go through appropriate element shuffling.
+ * That recognition is much easier before having their construction finalized, however.
+ *
+ * Interaction with the major zone deployable management service is crucial.
+ * @see `OwnableByPlayer`
+ * @see `ZoneDeployableActor`
+ */
+trait DeployableBehavior {
+ _: Actor =>
+ def DeployableObject: Deployable
+
+ /** the timer for the construction process */
+ var setup: Cancellable = Default.Cancellable
+ /** the timer for the deconstruction process */
+ var decay: Cancellable = Default.Cancellable
+ /** used to manage the internal knowledge of the construction process;
+ * `None` means "never constructed",
+ * `Some(false)` means "deconstructed" or a state that is unresponsive to certain messaging input,
+ * `Some(true)` means "constructed" */
+ private var constructed: Option[Boolean] = None
+ /** a value that is utilized by the `ObjectDeleteMessage` packet, affecting animation */
+ var deletionType: Int = 2
+
+ def deployableBehaviorPostStop(): Unit = {
+ setup.cancel()
+ decay.cancel()
+ }
+
+ def isConstructed: Option[Boolean] = constructed
+
+ val deployableBehavior: Receive = {
+ case Zone.Deployable.Setup()
+ if constructed.isEmpty && setup.isCancelled =>
+ setupDeployable(sender())
+
+ case DeployableBehavior.Finalize(callback) =>
+ finalizeDeployable(callback)
+
+ case Deployable.Ownership(None)
+ if DeployableObject.Owner.nonEmpty =>
+ val obj = DeployableObject
+ if (constructed.contains(true)) {
+ loseOwnership(obj.Faction)
+ } else {
+ obj.Owner = None
+ }
+
+ case Deployable.Ownership(Some(player))
+ if !DeployableObject.Destroyed && DeployableObject.Owner.isEmpty =>
+ if (constructed.contains(true)) {
+ gainOwnership(player)
+ } else {
+ DeployableObject.AssignOwnership(player)
+ }
+
+ case Deployable.Deconstruct(time)
+ if constructed.contains(true) =>
+ deconstructDeployable(time)
+
+ case DeployableBehavior.FinalizeElimination() =>
+ dismissDeployable()
+
+ case Zone.Deployable.IsDismissed(obj)
+ if (obj eq DeployableObject) && (constructed.isEmpty || constructed.contains(false)) =>
+ unregisterDeployable(obj)
+ }
+
+ /**
+ * Losing ownership involves updating map screen UI, to remove management rights from the now-previous owner,
+ * and may involve concealing the deployable from the map screen for the entirety of the previous owner's faction.
+ * Displaying the deployable on the map screen of another faction may be required.
+ * @param toFaction the faction to which to set the deployable to be visualized on the map and in the game world;
+ * may also affect deployable operation
+ */
+ def loseOwnership(toFaction: PlanetSideEmpire.Value): Unit = {
+ val obj = DeployableObject
+ val guid = obj.GUID
+ val zone = obj.Zone
+ val localEvents = zone.LocalEvents
+ val originalFaction = obj.Faction
+ val changeFaction = originalFaction != toFaction
+ val info = DeployableInfo(guid, DeployableIcon.Boomer, obj.Position, Service.defaultPlayerGUID)
+ if (changeFaction) {
+ obj.Faction = toFaction
+ //visual tells in regards to ownership by faction
+ zone.AvatarEvents ! AvatarServiceMessage(
+ zone.id,
+ AvatarAction.SetEmpire(Service.defaultPlayerGUID, guid, toFaction)
+ )
+ //remove knowledge by the previous owner's faction
+ localEvents ! LocalServiceMessage(
+ originalFaction.toString,
+ LocalAction.DeployableMapIcon(Service.defaultPlayerGUID, DeploymentAction.Dismiss, info)
+ )
+ //display to the given faction
+ localEvents ! LocalServiceMessage(
+ toFaction.toString,
+ LocalAction.DeployableMapIcon(Service.defaultPlayerGUID, DeploymentAction.Build, info)
+ )
+ }
+ startOwnerlessDecay()
+ }
+
+ def startOwnerlessDecay(): Unit = {
+ val obj = DeployableObject
+ if (obj.Owner.nonEmpty && decay.isCancelled) {
+ //without an owner, this deployable should begin to decay and will deconstruct later
+ import scala.concurrent.ExecutionContext.Implicits.global
+ decay = context.system.scheduler.scheduleOnce(Deployable.decay, self, Deployable.Deconstruct())
+ }
+ obj.Owner = None //OwnerName should remain set
+ }
+
+ /**
+ * na
+ * @see `gainOwnership(Player, PlanetSideEmpire.Value)`
+ * @param player the player being given ownership of this deployable
+ */
+ def gainOwnership(player: Player): Unit = {
+ gainOwnership(player, player.Faction)
+ }
+
+ /**
+ * na
+ * @param player the player being given ownership of this deployable
+ * @param toFaction the faction to which the deployable is being assigned;
+ * usually matches the `player`'s own faction
+ */
+ def gainOwnership(player: Player, toFaction: PlanetSideEmpire.Value): Unit = {
+ val obj = DeployableObject
+ obj.AssignOwnership(player)
+ decay.cancel()
+
+ val guid = obj.GUID
+ val zone = obj.Zone
+ val originalFaction = obj.Faction
+ val info = DeployableInfo(guid, DeployableIcon.Boomer, obj.Position, obj.Owner.get)
+ if (originalFaction != toFaction) {
+ obj.Faction = toFaction
+ val localEvents = zone.LocalEvents
+ zone.AvatarEvents ! AvatarServiceMessage(
+ zone.id,
+ AvatarAction.SetEmpire(Service.defaultPlayerGUID, guid, toFaction)
+ )
+ localEvents ! LocalServiceMessage(
+ originalFaction.toString,
+ LocalAction.DeployableMapIcon(Service.defaultPlayerGUID, DeploymentAction.Dismiss, info)
+ )
+ localEvents ! LocalServiceMessage(
+ toFaction.toString,
+ LocalAction.DeployableMapIcon(Service.defaultPlayerGUID, DeploymentAction.Build, info)
+ )
+ }
+ }
+
+ /**
+ * The first stage of the deployable build process, to put the formal process in motion.
+ * Deployables, upon construction, may display an animation effect.
+ * Parameters are required to be passed onto the next stage of the build process and are not used here.
+ * @see `DeployableDefinition.deployAnimation`
+ * @see `DeployableDefinition.DeployTime`
+ * @see `LocalAction.TriggerEffectLocation`
+ * @param callback an `ActorRef` used for confirming the deployable's completion of the process
+ */
+ def setupDeployable(callback: ActorRef): Unit = {
+ val obj = DeployableObject
+ constructed = Some(false)
+ if (obj.Definition.deployAnimation == DeployAnimation.Standard) {
+ val zone = obj.Zone
+ zone.LocalEvents ! LocalServiceMessage(
+ zone.id,
+ LocalAction.TriggerEffectLocation(
+ obj.Owner.getOrElse(Service.defaultPlayerGUID),
+ "spawn_object_effect",
+ obj.Position,
+ obj.Orientation
+ )
+ )
+ }
+ import scala.concurrent.ExecutionContext.Implicits.global
+ setup = context.system.scheduler.scheduleOnce(
+ obj.Definition.DeployTime milliseconds,
+ self,
+ DeployableBehavior.Finalize(callback)
+ )
+ }
+
+ /**
+ * The second stage of the deployable build process, to complete the formal process.
+ * If no owner is assigned, the deployable must immediately begin suffering decay.
+ * Nothing dangerous happens if it does not begin to decay, but, because it is not under a player's management,
+ * the deployable will not properly transition to a decay state for another reason
+ * and can linger in the zone ownerless for as long as it is not destroyed.
+ * @see `AvatarAction.DeployItem`
+ * @see `DeploymentAction`
+ * @see `DeployableInfo`
+ * @see `LocalAction.DeployableMapIcon`
+ * @see `Zone.Deployable.IsBuilt`
+ * @param callback an `ActorRef` used for confirming the deployable's completion of the process
+ */
+ def finalizeDeployable(callback: ActorRef): Unit = {
+ setup.cancel()
+ constructed = Some(true)
+ val obj = DeployableObject
+ val zone = obj.Zone
+ val localEvents = zone.LocalEvents
+ val owner = obj.Owner.getOrElse(Service.defaultPlayerGUID)
+ obj.OwnerName match {
+ case Some(_) =>
+ case None =>
+ import scala.concurrent.ExecutionContext.Implicits.global
+ decay = context.system.scheduler.scheduleOnce(
+ Deployable.decay,
+ self,
+ Deployable.Deconstruct()
+ )
+ }
+ //zone build
+ zone.AvatarEvents ! AvatarServiceMessage(zone.id, AvatarAction.DeployItem(Service.defaultPlayerGUID, obj))
+ //zone map icon
+ localEvents ! LocalServiceMessage(
+ obj.Faction.toString,
+ LocalAction.DeployableMapIcon(
+ Service.defaultPlayerGUID,
+ DeploymentAction.Build, DeployableInfo(obj.GUID, Deployable.Icon(obj.Definition.Item), obj.Position, owner)
+ )
+ )
+ //local build management
+ callback ! Zone.Deployable.IsBuilt(obj)
+ }
+
+ /**
+ * The first stage of the deployable dismissal process, to put the formal process in motion.
+ * @param time an optional duration that overrides the deployable's usual duration
+ */
+ def deconstructDeployable(time: Option[FiniteDuration]): Unit = {
+ constructed = Some(false)
+ val duration = time.getOrElse(Deployable.cleanup)
+ import scala.concurrent.ExecutionContext.Implicits.global
+ setup.cancel()
+ decay.cancel()
+ decay = context.system.scheduler.scheduleOnce(duration, self, DeployableBehavior.FinalizeElimination())
+ }
+
+ /**
+ * The task for unregistering this deployable.
+ * Most deployables are monolithic entities, requiring only a single unique identifier.
+ * @param obj the deployable
+ */
+ def unregisterDeployable(obj: Deployable): Unit = {
+ val zone = obj.Zone
+ zone.tasks ! GUIDTask.UnregisterObjectTask(obj)(zone.GUID)
+ }
+
+ /**
+ * The second stage of the deployable build process, to complete the formal process.
+ * Queue up final deployable unregistering, separate from the zone's deployable governance,
+ * and instruct all clients in this zone that the deployable is to be deconstructed.
+ */
+ def dismissDeployable(): Unit = {
+ setup.cancel()
+ decay.cancel()
+ val obj = DeployableObject
+ val zone = obj.Zone
+ //there's no special meaning behind directing any replies from from zone governance straight back to zone governance
+ //this deployable control agency, however, will be expiring and can not be a recipient
+ zone.Deployables ! Zone.Deployable.Dismiss(obj)
+ zone.LocalEvents ! LocalServiceMessage(
+ zone.id,
+ LocalAction.EliminateDeployable(obj, obj.GUID, obj.Position, deletionType)
+ )
+ //when the deployable is being destroyed, certain functions will already have been invoked
+ //as destruction will instigate deconstruction, skip these invocations to avoid needless message passing
+ if (!obj.Destroyed) {
+ Deployables.AnnounceDestroyDeployable(obj)
+ }
+ obj.OwnerName = None
+ }
+}
+
+object DeployableBehavior {
+ /** internal message for progressing the build process */
+ private case class Finalize(callback: ActorRef)
+
+ /** internal message for progresisng the deconstruction process */
+ private case class FinalizeElimination()
+}
diff --git a/src/main/scala/net/psforever/objects/ce/SimpleDeployable.scala b/src/main/scala/net/psforever/objects/ce/SimpleDeployable.scala
deleted file mode 100644
index 03cb1453..00000000
--- a/src/main/scala/net/psforever/objects/ce/SimpleDeployable.scala
+++ /dev/null
@@ -1,11 +0,0 @@
-// Copyright (c) 2017 PSForever
-package net.psforever.objects.ce
-
-import net.psforever.objects.PlanetSideGameObject
-import net.psforever.objects.definition.SimpleDeployableDefinition
-
-abstract class SimpleDeployable(cdef: SimpleDeployableDefinition) extends PlanetSideGameObject with Deployable {
- Health = Definition.MaxHealth
-
- def Definition = cdef
-}
diff --git a/src/main/scala/net/psforever/objects/ce/TelepadLike.scala b/src/main/scala/net/psforever/objects/ce/TelepadLike.scala
index 6ad62550..9f1ad124 100644
--- a/src/main/scala/net/psforever/objects/ce/TelepadLike.scala
+++ b/src/main/scala/net/psforever/objects/ce/TelepadLike.scala
@@ -1,12 +1,15 @@
-// Copyright (c) 2017 PSForever
+// Copyright (c) 2018 PSForever
package net.psforever.objects.ce
-import akka.actor.ActorContext
-import net.psforever.objects.{Default, PlanetSideGameObject, TelepadDeployable, Vehicle}
+import akka.actor.{ActorContext, Cancellable}
+import net.psforever.objects.{Default, TelepadDeployable, Vehicle}
import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.serverobject.structures.Amenity
-import net.psforever.objects.vehicles.Utility
+import net.psforever.objects.vehicles.Utility.InternalTelepad
import net.psforever.objects.zones.Zone
+import net.psforever.packet.game.{GenericObjectActionMessage, ObjectCreateMessage, ObjectDeleteMessage}
+import net.psforever.packet.game.objectcreate.ObjectCreateMessageParent
+import net.psforever.services.local.{LocalAction, LocalServiceMessage}
import net.psforever.types.PlanetSideGUID
trait TelepadLike {
@@ -38,9 +41,13 @@ trait TelepadLike {
}
object TelepadLike {
- final case class Activate(obj: PlanetSideGameObject with TelepadLike)
+ final case class RequestLink(obj: TelepadDeployable)
- final case class Deactivate(obj: PlanetSideGameObject with TelepadLike)
+ final case class SeverLink(obj: PlanetSideServerObject with TelepadLike)
+
+ final case class Activate(obj: PlanetSideServerObject with TelepadLike)
+
+ final case class Deactivate(obj: PlanetSideServerObject with TelepadLike)
/**
* Assemble some logic for a provided object.
@@ -64,12 +71,12 @@ object TelepadLike {
* @param zone where the router is located
* @return the pair of units that compose the teleportation system
*/
- def AppraiseTeleportationSystem(router: Vehicle, zone: Zone): Option[(Utility.InternalTelepad, TelepadDeployable)] = {
+ def AppraiseTeleportationSystem(router: Vehicle, zone: Zone): Option[(InternalTelepad, TelepadDeployable)] = {
import net.psforever.objects.vehicles.UtilityType
import net.psforever.types.DriveState
router.Utility(UtilityType.internal_router_telepad_deployable) match {
//if the vehicle has an internal telepad, it is allowed to be a Router (that's a weird way of saying it)
- case Some(util: Utility.InternalTelepad) =>
+ case Some(util: InternalTelepad) =>
//check for a readied remote telepad
zone.GUID(util.Telepad) match {
case Some(telepad: TelepadDeployable) =>
@@ -86,6 +93,60 @@ object TelepadLike {
None
}
}
+
+ /**
+ * Create the mechanism that serves as one endpoint of the linked router teleportation system.
+ *
+ * Technically, the mechanism - an `InternalTelepad` object - is always made to exist
+ * due to how the Router vehicle object is encoded into an `ObjectCreateMessage` packet.
+ * Regardless, that internal mechanism is created anew each time the system links a new remote telepad.
+ * @param routerGUID the vehicle that houses one end of the teleportation system (the `internalTelepad`)
+ * @param obj the endpoint of the teleportation system housed by the router
+ */
+ def StartRouterInternalTelepad(zone: Zone, routerGUID: PlanetSideGUID, obj: InternalTelepad): Unit = {
+ val utilityGUID = obj.GUID
+ val udef = obj.Definition
+ val events = zone.LocalEvents
+ val zoneId = zone.id
+ /*
+ the following instantiation and configuration creates the internal Router component
+ normally dispatched while the Router is transitioned into its Deploying state
+ it is safe, however, to perform these actions at any time during and after the Deploying state
+ */
+ events ! LocalServiceMessage(
+ zoneId,
+ LocalAction.SendResponse(
+ ObjectCreateMessage(
+ udef.ObjectId,
+ utilityGUID,
+ ObjectCreateMessageParent(routerGUID, 2), //TODO stop assuming slot number
+ udef.Packet.ConstructorData(obj).get
+ )
+ )
+ )
+ events ! LocalServiceMessage(
+ zoneId,
+ LocalAction.SendResponse(GenericObjectActionMessage(utilityGUID, 27))
+ )
+ events ! LocalServiceMessage(
+ zoneId,
+ LocalAction.SendResponse(GenericObjectActionMessage(utilityGUID, 30))
+ )
+ LinkTelepad(zone, utilityGUID)
+ }
+
+ def LinkTelepad(zone: Zone, telepadGUID: PlanetSideGUID): Unit = {
+ val events = zone.LocalEvents
+ val zoneId = zone.id
+ events ! LocalServiceMessage(
+ zoneId,
+ LocalAction.SendResponse(GenericObjectActionMessage(telepadGUID, 27))
+ )
+ events ! LocalServiceMessage(
+ zoneId,
+ LocalAction.SendResponse(GenericObjectActionMessage(telepadGUID, 28))
+ )
+ }
}
/**
@@ -95,8 +156,49 @@ object TelepadLike {
* a placeholder like this is easy to reason around.
* @param obj an entity that extends `TelepadLike`
*/
-class TelepadControl(obj: TelepadLike) extends akka.actor.Actor {
+class TelepadControl(obj: InternalTelepad) extends akka.actor.Actor {
+ var setup: Cancellable = Default.Cancellable
+
def receive: akka.actor.Actor.Receive = {
+ case TelepadLike.Activate(o: InternalTelepad) if obj eq o =>
+ obj.Active = true
+
+ case TelepadLike.Deactivate(o: InternalTelepad) if obj eq o =>
+ obj.Active = false
+ val zone = obj.Zone
+ zone.GUID(obj.Telepad) match {
+ case Some(oldTpad: TelepadDeployable) if !obj.Active && !setup.isCancelled =>
+ oldTpad.Actor ! TelepadLike.SeverLink(obj)
+ case None => ;
+ }
+ obj.Telepad = None
+ zone.LocalEvents ! LocalServiceMessage(zone.id, LocalAction.SendResponse(ObjectDeleteMessage(obj.GUID, 0)))
+
+ case TelepadLike.RequestLink(tpad: TelepadDeployable) =>
+ val zone = obj.Zone
+ if (obj.Active) {
+ zone.GUID(obj.Telepad) match {
+ case Some(oldTpad: TelepadDeployable) if !obj.Active && !setup.isCancelled =>
+ oldTpad.Actor ! TelepadLike.SeverLink(obj)
+ case None => ;
+ }
+ obj.Telepad = tpad.GUID
+ //zone.LocalEvents ! LocalServiceMessage(zone.id, LocalAction.StartRouterInternalTelepad(obj.Owner.GUID, obj.GUID, obj))
+ TelepadLike.StartRouterInternalTelepad(zone, obj.Owner.GUID, obj)
+ tpad.Actor ! TelepadLike.Activate(obj)
+ } else {
+ val channel = obj.Owner.asInstanceOf[Vehicle].OwnerName.getOrElse("")
+ zone.LocalEvents ! LocalServiceMessage(channel, LocalAction.RouterTelepadMessage("@Teleport_NotDeployed"))
+ tpad.Actor ! TelepadLike.SeverLink(obj)
+ }
+
+ case TelepadLike.SeverLink(tpad: TelepadDeployable) =>
+ if (obj.Telepad.contains(tpad.GUID)) {
+ obj.Telepad = None
+ val zone = obj.Zone
+ zone.LocalEvents ! LocalServiceMessage(zone.id, LocalAction.SendResponse(ObjectDeleteMessage(obj.GUID, 0)))
+ }
+
case _ => ;
}
}
diff --git a/src/main/scala/net/psforever/objects/definition/SimpleDeployableDefinition.scala b/src/main/scala/net/psforever/objects/definition/DeployableDefinition.scala
similarity index 61%
rename from src/main/scala/net/psforever/objects/definition/SimpleDeployableDefinition.scala
rename to src/main/scala/net/psforever/objects/definition/DeployableDefinition.scala
index 89810d07..78ffb641 100644
--- a/src/main/scala/net/psforever/objects/definition/SimpleDeployableDefinition.scala
+++ b/src/main/scala/net/psforever/objects/definition/DeployableDefinition.scala
@@ -2,10 +2,9 @@
package net.psforever.objects.definition
import akka.actor.ActorContext
-import net.psforever.objects.{Default, PlanetSideGameObject}
+import net.psforever.objects.Default
import net.psforever.objects.ce.{Deployable, DeployableCategory, DeployedItem}
import net.psforever.objects.definition.converter.SmallDeployableConverter
-import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.vital.damage.DamageCalculations
import net.psforever.objects.vital.resistance.ResistanceProfileMutators
import net.psforever.objects.vital.resolution.DamageResistanceModel
@@ -13,9 +12,16 @@ import net.psforever.objects.vital.{NoResistanceSelection, VitalityDefinition}
import scala.concurrent.duration._
+object DeployAnimation extends Enumeration {
+ type Type = Value
+
+ val None, Standard, Fdu = Value
+}
+
trait BaseDeployableDefinition {
private var category: DeployableCategory.Value = DeployableCategory.Boomers
private var deployTime: Long = (1 second).toMillis //ms
+ var deployAnimation: DeployAnimation.Value = DeployAnimation.None
def Item: DeployedItem.Value
@@ -35,13 +41,12 @@ trait BaseDeployableDefinition {
DeployTime
}
- def Initialize(obj: PlanetSideGameObject with Deployable, context: ActorContext): Unit = {}
+ def Initialize(obj: Deployable, context: ActorContext): Unit = {}
- def Initialize(obj: PlanetSideServerObject with Deployable, context: ActorContext): Unit = {}
-
- def Uninitialize(obj: PlanetSideGameObject with Deployable, context: ActorContext): Unit = {}
-
- def Uninitialize(obj: PlanetSideServerObject with Deployable, context: ActorContext): Unit = {}
+ def Uninitialize(obj: Deployable, context: ActorContext): Unit = {
+ obj.Actor ! akka.actor.PoisonPill
+ obj.Actor = Default.Actor
+ }
}
abstract class DeployableDefinition(objectId: Int)
@@ -53,24 +58,7 @@ abstract class DeployableDefinition(objectId: Int)
private val item = DeployedItem(objectId) //let throw NoSuchElementException
DamageUsing = DamageCalculations.AgainstVehicle
ResistUsing = NoResistanceSelection
+ Packet = new SmallDeployableConverter
def Item: DeployedItem.Value = item
}
-
-class SimpleDeployableDefinition(objectId: Int) extends DeployableDefinition(objectId) {
- Packet = new SmallDeployableConverter
-}
-
-abstract class ComplexDeployableDefinition(objectId: Int) extends DeployableDefinition(objectId)
-
-object SimpleDeployableDefinition {
- def apply(item: DeployedItem.Value): SimpleDeployableDefinition =
- new SimpleDeployableDefinition(item.id)
-
- def SimpleUninitialize(obj: PlanetSideGameObject, context: ActorContext): Unit = {}
-
- def SimpleUninitialize(obj: PlanetSideServerObject, context: ActorContext): Unit = {
- context.stop(obj.Actor)
- obj.Actor = Default.Actor
- }
-}
diff --git a/src/main/scala/net/psforever/objects/definition/converter/SmallDeployableConverter.scala b/src/main/scala/net/psforever/objects/definition/converter/SmallDeployableConverter.scala
index 5ad1b408..264beb5b 100644
--- a/src/main/scala/net/psforever/objects/definition/converter/SmallDeployableConverter.scala
+++ b/src/main/scala/net/psforever/objects/definition/converter/SmallDeployableConverter.scala
@@ -2,15 +2,14 @@
package net.psforever.objects.definition.converter
import net.psforever.objects.ce.Deployable
-import net.psforever.objects.PlanetSideGameObject
import net.psforever.objects.equipment.JammableUnit
import net.psforever.packet.game.objectcreate._
import net.psforever.types.PlanetSideGUID
import scala.util.{Failure, Success, Try}
-class SmallDeployableConverter extends ObjectCreateConverter[PlanetSideGameObject with Deployable]() {
- override def ConstructorData(obj: PlanetSideGameObject with Deployable): Try[CommonFieldDataWithPlacement] = {
+class SmallDeployableConverter extends ObjectCreateConverter[Deployable]() {
+ override def ConstructorData(obj: Deployable): Try[CommonFieldDataWithPlacement] = {
Success(
CommonFieldDataWithPlacement(
PlacementData(obj.Position, obj.Orientation),
@@ -35,6 +34,6 @@ class SmallDeployableConverter extends ObjectCreateConverter[PlanetSideGameObjec
)
}
- override def DetailedConstructorData(obj: PlanetSideGameObject with Deployable): Try[CommonFieldDataWithPlacement] =
+ override def DetailedConstructorData(obj: Deployable): Try[CommonFieldDataWithPlacement] =
Failure(new Exception("converter should not be used to generate detailed small deployable data"))
}
diff --git a/src/main/scala/net/psforever/objects/definition/converter/VehicleConverter.scala b/src/main/scala/net/psforever/objects/definition/converter/VehicleConverter.scala
index 596bf5c8..8ee353db 100644
--- a/src/main/scala/net/psforever/objects/definition/converter/VehicleConverter.scala
+++ b/src/main/scala/net/psforever/objects/definition/converter/VehicleConverter.scala
@@ -1,8 +1,8 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.definition.converter
-import net.psforever.objects.equipment.Equipment
-import net.psforever.objects.Vehicle
+import net.psforever.objects.equipment.{Equipment, EquipmentSlot}
+import net.psforever.objects.{PlanetSideGameObject, Vehicle}
import net.psforever.packet.game.objectcreate._
import net.psforever.types.{DriveState, PlanetSideGUID}
@@ -86,7 +86,7 @@ class VehicleConverter extends ObjectCreateConverter[Vehicle]() {
private def MakeMountings(obj: Vehicle): List[InventoryItemData.InventoryItem] = {
obj.Weapons.collect {
- case (index, slot) if slot.Equipment.nonEmpty =>
+ case (index, slot: EquipmentSlot) if slot.Equipment.nonEmpty =>
val equip: Equipment = slot.Equipment.get
val equipDef = equip.Definition
InventoryItemData(equipDef.ObjectId, equip.GUID, index, equipDef.Packet.ConstructorData(equip).get)
@@ -98,8 +98,8 @@ class VehicleConverter extends ObjectCreateConverter[Vehicle]() {
.EquipmentUtilities(obj.Utilities)
.map({
case (index, utilContainer) =>
- val util = utilContainer()
- val utilDef = util.Definition
+ val util: PlanetSideGameObject = utilContainer()
+ val utilDef = util.Definition
InventoryItemData(utilDef.ObjectId, util.GUID, index, utilDef.Packet.ConstructorData(util).get)
})
.toList
diff --git a/src/main/scala/net/psforever/objects/equipment/FireModeSwitch.scala b/src/main/scala/net/psforever/objects/equipment/FireModeSwitch.scala
index b39cd262..32bc75d6 100644
--- a/src/main/scala/net/psforever/objects/equipment/FireModeSwitch.scala
+++ b/src/main/scala/net/psforever/objects/equipment/FireModeSwitch.scala
@@ -7,7 +7,19 @@ package net.psforever.objects.equipment
* All weapons and some support items have fire modes, though most only have one.
* The number of fire modes is visually indicated by the bubbles next to the icon of the `Equipment` in a holster slot.
* The specifics of how a fire mode affects the output is left to implementation and execution.
- * Contrast how `Tool`s deal with multiple types of ammunition.
+ * Contrast how `Tool`s deal with multiple types of ammunition.
+ *
+ * While most `Tools` - weapons and such - are known to have fire modes,
+ * `ConstructionItem` equipment that produce deployable entities in the game world also support fire modes.
+ * The mechanism is different, however, even while the user interactions work in a similar way.
+ * For most weapons, the fire mode is just a modification of how the projectiles behave or the weapon behaves.
+ * For example, the bounciness of the grenades or the number of shots fired by the Jackhammer.
+ * One has to change tool ammo types to actual swap out different ammunitions such as, most commonly,
+ * grey normal ammo for gold armor-piercing ammo.
+ * For deployable-constructing entities, fire mode switches between the categories of deployables that can be built
+ * and "changing ammunition" actually changes the subtype of deployable within that deployable category.
+ * For example, fire modes go from "Boomers" to "Mines"
+ * while ammo types for "Mines" goes from "HE mines" to "Disruptor Mines".
* @tparam Mode the type parameter representing the fire mode
*/
trait FireModeSwitch[Mode] {
diff --git a/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala b/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala
index 368fa062..7d255c7f 100644
--- a/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala
+++ b/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala
@@ -31,7 +31,6 @@ import net.psforever.packet.game.objectcreate.ObjectCreateMessageParent
import net.psforever.types._
import net.psforever.services.Service
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
-import net.psforever.services.local.{LocalAction, LocalServiceMessage}
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
import scala.concurrent.ExecutionContext.Implicits.global
@@ -605,17 +604,9 @@ class VehicleControl(vehicle: Vehicle)
state match {
case DriveState.Deploying =>
vehicle.Utility(UtilityType.internal_router_telepad_deployable) match {
- case Some(util: Utility.InternalTelepad) =>
- util.Active = true
- case _ =>
- //log.warn(s"DeploymentActivities: could not find internal telepad in router@${vehicle.GUID.guid} while $state")
+ case Some(util: Utility.InternalTelepad) => util.Actor ! TelepadLike.Activate(util)
+ case _ => ;
}
- case DriveState.Deployed =>
- //let the timer do all the work
- events ! LocalServiceMessage(
- zoneChannel,
- LocalAction.ToggleTeleportSystem(GUID0, vehicle, TelepadLike.AppraiseTeleportationSystem(vehicle, zone))
- )
case _ => ;
}
}
@@ -663,8 +654,10 @@ class VehicleControl(vehicle: Vehicle)
state match {
case DriveState.Undeploying =>
//deactivate internal router before trying to reset the system
- Vehicles.RemoveTelepads(vehicle)
- zone.LocalEvents ! LocalServiceMessage(zone.id, LocalAction.ToggleTeleportSystem(GUID0, vehicle, None))
+ vehicle.Utility(UtilityType.internal_router_telepad_deployable) match {
+ case Some(util: Utility.InternalTelepad) => util.Actor ! TelepadLike.Deactivate(util)
+ case _ => ;
+ }
case _ => ;
}
}
diff --git a/src/main/scala/net/psforever/objects/vital/Vitality.scala b/src/main/scala/net/psforever/objects/vital/Vitality.scala
index c31aa88d..6747727d 100644
--- a/src/main/scala/net/psforever/objects/vital/Vitality.scala
+++ b/src/main/scala/net/psforever/objects/vital/Vitality.scala
@@ -2,7 +2,6 @@
package net.psforever.objects.vital
import net.psforever.objects.vital.resolution.{DamageAndResistance, ResolutionCalculations}
-import net.psforever.objects.vital.interaction.DamageResult
/**
* A vital object can be hurt or damaged or healed or repaired (HDHR).
@@ -54,12 +53,4 @@ object Vitality {
* @param func a function literal
*/
final case class Damage(func: ResolutionCalculations.Output)
-
- final case class DamageOn(obj: Vitality, func: ResolutionCalculations.Output)
-
- /**
- * Report that a vitals object must be updated due to damage.
- * @param obj the vital object
- */
- final case class DamageResolution(obj: Vitality, cause: DamageResult)
}
diff --git a/src/main/scala/net/psforever/objects/vital/resolution/ResolutionCalculations.scala b/src/main/scala/net/psforever/objects/vital/resolution/ResolutionCalculations.scala
index c8e83fed..be9672e6 100644
--- a/src/main/scala/net/psforever/objects/vital/resolution/ResolutionCalculations.scala
+++ b/src/main/scala/net/psforever/objects/vital/resolution/ResolutionCalculations.scala
@@ -3,7 +3,7 @@ package net.psforever.objects.vital.resolution
import net.psforever.objects.{PlanetSideGameObject, Player, TurretDeployable, Vehicle}
import net.psforever.objects.ballistics.{PlayerSource, SourceEntry}
-import net.psforever.objects.ce.ComplexDeployable
+import net.psforever.objects.ce.Deployable
import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.serverobject.damage.Damageable
import net.psforever.objects.vital.base.DamageResolution
@@ -266,7 +266,7 @@ object ResolutionCalculations {
ce.Health -= damage
}
- case ce: ComplexDeployable if CanDamage(ce, damage, data) =>
+ case ce: Deployable if CanDamage(ce, damage, data) =>
if (ce.Shields > 0) {
if (damage > ce.Shields) {
ce.Health -= (damage - ce.Shields)
@@ -310,7 +310,7 @@ object ResolutionCalculations {
}
VehicleApplication(dam, data)(target)
- case _: ComplexDeployable =>
+ case _: Deployable =>
val dam : Int = damage match {
case a: Int => a
case _ => 0
diff --git a/src/main/scala/net/psforever/objects/zones/Zone.scala b/src/main/scala/net/psforever/objects/zones/Zone.scala
index 3feab123..4eb851d4 100644
--- a/src/main/scala/net/psforever/objects/zones/Zone.scala
+++ b/src/main/scala/net/psforever/objects/zones/Zone.scala
@@ -3,9 +3,9 @@ package net.psforever.objects.zones
import akka.actor.{ActorContext, ActorRef, Props}
import akka.routing.RandomPool
-import net.psforever.objects.ballistics.{Projectile, SourceEntry}
import net.psforever.objects.{PlanetSideGameObject, _}
-import net.psforever.objects.ce.{ComplexDeployable, Deployable, SimpleDeployable}
+import net.psforever.objects.ballistics.{Projectile, SourceEntry}
+import net.psforever.objects.ce.Deployable
import net.psforever.objects.entity.IdentifiableEntity
import net.psforever.objects.equipment.Equipment
import net.psforever.objects.guid.{NumberPoolHub, TaskResolver}
@@ -101,7 +101,7 @@ class Zone(val id: String, val map: ZoneMap, zoneNumber: Int) {
/**
*/
- private val constructions: ListBuffer[PlanetSideGameObject with Deployable] = ListBuffer()
+ private val constructions: ListBuffer[Deployable] = ListBuffer()
/**
*/
@@ -536,7 +536,7 @@ class Zone(val id: String, val map: ZoneMap, zoneNumber: Int) {
*/
def EquipmentOnGround: List[Equipment] = equipmentOnGround.toList
- def DeployableList: List[PlanetSideGameObject with Deployable] = constructions.toList
+ def DeployableList: List[Deployable] = constructions.toList
def Vehicles: List[Vehicle] = vehicles.toList
@@ -930,11 +930,14 @@ object Zone {
}
object Deployable {
- final case class Build(obj: PlanetSideGameObject with Deployable, withTool: ConstructionItem)
- final case class DeployableIsBuilt(obj: PlanetSideGameObject with Deployable, withTool: ConstructionItem)
+ final case class Build(obj: Deployable)
+ final case class BuildByOwner(obj: Deployable, owner: Player, withTool: ConstructionItem)
+ final case class Setup()
+ final case class IsBuilt(obj: Deployable)
+ final case class CanNotBeBuilt(obj: Deployable, withTool: ConstructionItem)
- final case class Dismiss(obj: PlanetSideGameObject with Deployable)
- final case class DeployableIsDismissed(obj: PlanetSideGameObject with Deployable)
+ final case class Dismiss(obj: Deployable)
+ final case class IsDismissed(obj: Deployable)
}
object Vehicle {
@@ -1109,7 +1112,7 @@ object Zone {
source: PlanetSideGameObject with FactionAffinity with Vitality,
createInteraction: (PlanetSideGameObject with FactionAffinity with Vitality, PlanetSideGameObject with FactionAffinity with Vitality) => DamageInteraction,
testTargetsFromZone: (PlanetSideGameObject, PlanetSideGameObject, Float) => Boolean = distanceCheck,
- acquireTargetsFromZone: (Zone, PlanetSideGameObject with FactionAffinity with Vitality, DamageWithPosition) => (List[PlanetSideServerObject with Vitality], List[PlanetSideGameObject with FactionAffinity with Vitality]) = findAllTargets
+ acquireTargetsFromZone: (Zone, PlanetSideGameObject with FactionAffinity with Vitality, DamageWithPosition) => List[PlanetSideServerObject with Vitality] = findAllTargets
): List[PlanetSideServerObject] = {
source.Definition.innateDamage match {
case Some(damage) =>
@@ -1145,20 +1148,16 @@ object Zone {
properties: DamageWithPosition,
createInteraction: (PlanetSideGameObject with FactionAffinity with Vitality, PlanetSideGameObject with FactionAffinity with Vitality) => DamageInteraction,
testTargetsFromZone: (PlanetSideGameObject, PlanetSideGameObject, Float) => Boolean,
- acquireTargetsFromZone: (Zone, PlanetSideGameObject with FactionAffinity with Vitality, DamageWithPosition) => (List[PlanetSideServerObject with Vitality], List[PlanetSideGameObject with FactionAffinity with Vitality])
+ acquireTargetsFromZone: (Zone, PlanetSideGameObject with FactionAffinity with Vitality, DamageWithPosition) => List[PlanetSideServerObject with Vitality]
): List[PlanetSideServerObject] = {
//collect targets that can be damaged
- val (pssos, psgos) = acquireTargetsFromZone(zone, source, properties)
+ val pssos = acquireTargetsFromZone(zone, source, properties)
val radius = properties.DamageRadius * properties.DamageRadius
//restrict to targets according to the detection plan
val allAffectedTargets = pssos.filter { target => testTargetsFromZone(source, target, radius) }
//inform remaining targets that they have suffered damage
allAffectedTargets
.foreach { target => target.Actor ! Vitality.Damage(createInteraction(source, target).calculate()) }
- //important note - these are not returned as targets that were affected
- psgos
- .filter { target => testTargetsFromZone(source, target, radius) }
- .foreach { target => zone.LocalEvents ! Vitality.DamageOn(target, createInteraction(source, target).calculate()) }
allAffectedTargets
}
@@ -1172,18 +1171,16 @@ object Zone {
* @see `Zone.DeployableList`
* @see `Zone.LivePlayers`
* @see `Zone.Vehicles`
- * @param zone the zone in which to search
+ * @param zone the zone in which the explosion should occur
* @param source a game entity that is treated as the origin and is excluded from results
* @param damagePropertiesBySource information about the effect/damage
- * @return two lists of objects with different characteristics;
- * the first list is `PlanetSideServerObject` entities with `Vitality`;
- * the second list is `PlanetSideGameObject` entities with both `Vitality` and `FactionAffinity`
+ * @return a list of affected entities
*/
def findAllTargets(
zone: Zone,
source: PlanetSideGameObject with Vitality,
damagePropertiesBySource: DamageWithPosition
- ): (List[PlanetSideServerObject with Vitality], List[PlanetSideGameObject with FactionAffinity with Vitality]) = {
+ ): List[PlanetSideServerObject with Vitality] = {
val sourcePosition = source.Position
val sourcePositionXY = sourcePosition.xy
val radius = damagePropertiesBySource.DamageRadius * damagePropertiesBySource.DamageRadius
@@ -1193,16 +1190,7 @@ object Zone {
//vehicles
val vehicleTargets = zone.Vehicles.filterNot { v => v.Destroyed || v.MountedIn.nonEmpty }
//deployables
- val (simpleDeployableTargets, complexDeployableTargets) =
- zone.DeployableList
- .filterNot { _.Destroyed }
- .foldRight((List.empty[SimpleDeployable], List.empty[ComplexDeployable])) { case (f, (simp, comp)) =>
- f match {
- case o: SimpleDeployable => (simp :+ o, comp)
- case o: ComplexDeployable => (simp, comp :+ o)
- case _ => (simp, comp)
- }
- }
+ val deployableTargets = zone.DeployableList.filterNot { _.Destroyed }
//amenities
val soiTargets = source match {
case o: Amenity =>
@@ -1217,12 +1205,9 @@ object Zone {
.flatMap { _.Amenities }
.filter { _.Definition.Damageable }
}
- (
- (playerTargets ++ vehicleTargets ++ complexDeployableTargets ++ soiTargets)
- .filter { target => target ne source },
- simpleDeployableTargets
- .filter { target => target ne source }
- )
+
+ //restrict to targets according to the detection plan
+ (playerTargets ++ vehicleTargets ++ deployableTargets ++ soiTargets).filter { target => target ne source }
}
/**
diff --git a/src/main/scala/net/psforever/objects/zones/ZoneDeployableActor.scala b/src/main/scala/net/psforever/objects/zones/ZoneDeployableActor.scala
index 8192c7e2..e946d0fc 100644
--- a/src/main/scala/net/psforever/objects/zones/ZoneDeployableActor.scala
+++ b/src/main/scala/net/psforever/objects/zones/ZoneDeployableActor.scala
@@ -2,9 +2,8 @@
package net.psforever.objects.zones
import akka.actor.Actor
+import net.psforever.objects.Player
import net.psforever.objects.ce.Deployable
-import net.psforever.objects.serverobject.PlanetSideServerObject
-import net.psforever.objects.PlanetSideGameObject
import scala.annotation.tailrec
import scala.collection.mutable.ListBuffer
@@ -13,46 +12,51 @@ import scala.collection.mutable.ListBuffer
* na
* @param zone the `Zone` object
*/
-class ZoneDeployableActor(zone: Zone, deployableList: ListBuffer[PlanetSideGameObject with Deployable]) extends Actor {
+class ZoneDeployableActor(zone: Zone, deployableList: ListBuffer[Deployable]) extends Actor {
import ZoneDeployableActor._
private[this] val log = org.log4s.getLogger
def receive: Receive = {
- case Zone.Deployable.Build(obj, tool) =>
+ case Zone.Deployable.Build(obj) =>
if (DeployableBuild(obj, deployableList)) {
- obj match {
- case o: PlanetSideServerObject =>
- obj.Definition.Initialize(o, context)
- case _ =>
- obj.Definition.Initialize(obj, context)
- }
obj.Zone = zone
- sender() ! Zone.Deployable.DeployableIsBuilt(obj, tool)
+ obj.Definition.Initialize(obj, context)
+ obj.Actor ! Zone.Deployable.Setup()
} else {
- log.warn(s"failed to build deployable ${obj} ${tool}")
+ log.warn(s"failed to build a ${obj.Definition.Name}")
+ sender() ! Zone.Deployable.IsDismissed(obj)
+ }
+
+ case Zone.Deployable.BuildByOwner(obj, owner, tool) =>
+ if (DeployableBuild(obj, deployableList)) {
+ obj.Zone = zone
+ obj.Definition.Initialize(obj, context)
+ owner.Actor ! Player.BuildDeployable(obj, tool)
+ } else {
+ log.warn(s"failed to build a ${obj.Definition.Name} belonging to ${obj.OwnerName.getOrElse("no one")}")
+ sender() ! Zone.Deployable.IsDismissed(obj)
}
case Zone.Deployable.Dismiss(obj) =>
if (DeployableDismiss(obj, deployableList)) {
- obj match {
- case o: PlanetSideServerObject =>
- obj.Definition.Uninitialize(o, context)
- case _ =>
- obj.Definition.Uninitialize(obj, context)
- }
- sender() ! Zone.Deployable.DeployableIsDismissed(obj)
+ obj.Actor ! Zone.Deployable.IsDismissed(obj)
+ obj.Definition.Uninitialize(obj, context)
}
+ case Zone.Deployable.IsBuilt(_) => ;
+
+ case Zone.Deployable.IsDismissed(_) => ;
+
case _ => ;
}
}
object ZoneDeployableActor {
def DeployableBuild(
- obj: PlanetSideGameObject with Deployable,
- deployableList: ListBuffer[PlanetSideGameObject with Deployable]
+ obj: Deployable,
+ deployableList: ListBuffer[Deployable]
): Boolean = {
deployableList.find(d => d == obj) match {
case Some(_) =>
@@ -64,8 +68,8 @@ object ZoneDeployableActor {
}
def DeployableDismiss(
- obj: PlanetSideGameObject with Deployable,
- deployableList: ListBuffer[PlanetSideGameObject with Deployable]
+ obj: Deployable,
+ deployableList: ListBuffer[Deployable]
): Boolean = {
recursiveFindDeployable(deployableList.iterator, obj) match {
case None =>
@@ -77,8 +81,8 @@ object ZoneDeployableActor {
}
@tailrec final def recursiveFindDeployable(
- iter: Iterator[PlanetSideGameObject with Deployable],
- target: PlanetSideGameObject with Deployable,
+ iter: Iterator[Deployable],
+ target: Deployable,
index: Int = 0
): Option[Int] = {
if (!iter.hasNext) {
diff --git a/src/main/scala/net/psforever/packet/control/Unknown30.scala b/src/main/scala/net/psforever/packet/control/Unknown30.scala
index a7ed734b..16839108 100644
--- a/src/main/scala/net/psforever/packet/control/Unknown30.scala
+++ b/src/main/scala/net/psforever/packet/control/Unknown30.scala
@@ -2,7 +2,6 @@ package net.psforever.packet.control
import net.psforever.packet.{ControlPacketOpcode, Marshallable, PlanetSideControlPacket}
import scodec.Codec
-import scodec.bits._
import scodec.codecs._
final case class Unknown30(clientNonce: Long) extends PlanetSideControlPacket {
@@ -12,7 +11,5 @@ final case class Unknown30(clientNonce: Long) extends PlanetSideControlPacket {
}
object Unknown30 extends Marshallable[Unknown30] {
- implicit val codec: Codec[Unknown30] = (
- ("client_nonce" | uint32L)
- ).as[Unknown30]
+ implicit val codec: Codec[Unknown30] = ("client_nonce" | uint32L).as[Unknown30]
}
diff --git a/src/main/scala/net/psforever/packet/game/objectcreate/DetailedConstructionToolData.scala b/src/main/scala/net/psforever/packet/game/objectcreate/DetailedConstructionToolData.scala
index 7413d8df..f376f5d3 100644
--- a/src/main/scala/net/psforever/packet/game/objectcreate/DetailedConstructionToolData.scala
+++ b/src/main/scala/net/psforever/packet/game/objectcreate/DetailedConstructionToolData.scala
@@ -7,9 +7,17 @@ import scodec.{Attempt, Codec, Err}
import shapeless.{::, HNil}
/**
- * `DetailedACEData` - `data.faction` is faction affinity, `data.unk1` is `true`
- * `DetailedBoomerTriggerData` - `data.faction` can be `NEUTRAL`, `data.unk1` is `true`
- * `DetailedTelepadData` - `data.faction` can be `NEUTRAL`, `data.jammered` is the router's GUID
+ * A representation of the construction item portion of `ObjectCreateDetailedMessage` packet data.
+ * This creates what is known as a construction tool item (or, `ConstructionItem`)
+ * which is a game world object that is manipulated by the player
+ * to construct other game world objects which are known as combat engineering deployables (or, just `Deployable`s).
+ * None of the information about the `Deployable` objects are maintained here and
+ * are instead implicit to the type of `ConstructionItem`.
+ * That aspect of the entity is adjusted through fire modes and ammunition types
+ * much like conventional weaponry (`Tool`s), though the initial fire mode can be indicated.
+ * @see `ConstructionItem`
+ * @see `Deployable`
+ * @see `FireModeSwitch`
*/
final case class DetailedConstructionToolData(data: CommonFieldData, mode: Int) extends ConstructorData {
override def bitsize: Long = 28L + data.bitsize
@@ -20,7 +28,7 @@ object DetailedConstructionToolData extends Marshallable[DetailedConstructionToo
implicit val codec: Codec[DetailedConstructionToolData] = (
("data" | CommonFieldData.codec(false)) ::
- uint8 ::
+ uint8 :: //n > 1 produces a stack of construction items (tends to crash the client)
("mode" | uint16) ::
uint2 ::
uint2
diff --git a/src/main/scala/net/psforever/services/avatar/AvatarService.scala b/src/main/scala/net/psforever/services/avatar/AvatarService.scala
index 589fb6ee..3863a806 100644
--- a/src/main/scala/net/psforever/services/avatar/AvatarService.scala
+++ b/src/main/scala/net/psforever/services/avatar/AvatarService.scala
@@ -280,7 +280,7 @@ class AvatarService(zone: Zone) extends Actor {
)
case AvatarAction.PutDownFDU(player_guid) =>
AvatarEvents.publish(
- AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.PutDownFDU(player_guid))
+ AvatarServiceResponse(s"/$forChannel/Avatar", Service.defaultPlayerGUID, AvatarResponse.PutDownFDU(player_guid))
)
case AvatarAction.Release(player, _, time) =>
undertaker forward RemoverActor.AddTask(player, zone, time)
diff --git a/src/main/scala/net/psforever/services/avatar/AvatarServiceMessage.scala b/src/main/scala/net/psforever/services/avatar/AvatarServiceMessage.scala
index e475a068..c36d7f56 100644
--- a/src/main/scala/net/psforever/services/avatar/AvatarServiceMessage.scala
+++ b/src/main/scala/net/psforever/services/avatar/AvatarServiceMessage.scala
@@ -1,7 +1,7 @@
// Copyright (c) 2017 PSForever
package net.psforever.services.avatar
-import net.psforever.objects.{PlanetSideGameObject, Player}
+import net.psforever.objects.Player
import net.psforever.objects.ballistics.{Projectile, SourceEntry}
import net.psforever.objects.ce.Deployable
import net.psforever.objects.equipment.Equipment
@@ -40,7 +40,7 @@ object AvatarAction {
final case class ConcealPlayer(player_guid: PlanetSideGUID) extends Action
final case class EnvironmentalDamage(player_guid: PlanetSideGUID, source_guid: PlanetSideGUID, amount: Int)
extends Action
- final case class DeployItem(player_guid: PlanetSideGUID, item: PlanetSideGameObject with Deployable) extends Action
+ final case class DeployItem(player_guid: PlanetSideGUID, item: Deployable) extends Action
final case class DeactivateImplantSlot(player_guid: PlanetSideGUID, slot: Int) extends Action
final case class ActivateImplantSlot(player_guid: PlanetSideGUID, slot: Int) extends Action
final case class Destroy(victim: PlanetSideGUID, killer: PlanetSideGUID, weapon: PlanetSideGUID, pos: Vector3)
diff --git a/src/main/scala/net/psforever/services/galaxy/GalaxyService.scala b/src/main/scala/net/psforever/services/galaxy/GalaxyService.scala
index 2f2eeb07..188c6526 100644
--- a/src/main/scala/net/psforever/services/galaxy/GalaxyService.scala
+++ b/src/main/scala/net/psforever/services/galaxy/GalaxyService.scala
@@ -3,7 +3,7 @@ package net.psforever.services.galaxy
import akka.actor.Actor
import net.psforever.objects.zones.Zone
-import net.psforever.packet.game.{BuildingInfoUpdateMessage, FlagInfo}
+import net.psforever.packet.game.BuildingInfoUpdateMessage
import net.psforever.services.{GenericEventBus, Service}
class GalaxyService extends Actor {
diff --git a/src/main/scala/net/psforever/services/galaxy/GalaxyServiceMessage.scala b/src/main/scala/net/psforever/services/galaxy/GalaxyServiceMessage.scala
index 6f369bdb..d18c2437 100644
--- a/src/main/scala/net/psforever/services/galaxy/GalaxyServiceMessage.scala
+++ b/src/main/scala/net/psforever/services/galaxy/GalaxyServiceMessage.scala
@@ -3,7 +3,7 @@ package net.psforever.services.galaxy
import net.psforever.objects.Vehicle
import net.psforever.objects.vehicles.VehicleManifest
-import net.psforever.packet.game.{BuildingInfoUpdateMessage, CaptureFlagUpdateMessage, FlagInfo}
+import net.psforever.packet.game.{BuildingInfoUpdateMessage, CaptureFlagUpdateMessage}
import net.psforever.types.PlanetSideGUID
final case class GalaxyServiceMessage(forChannel: String, actionMessage: GalaxyAction.Action)
diff --git a/src/main/scala/net/psforever/services/galaxy/GalaxyServiceResponse.scala b/src/main/scala/net/psforever/services/galaxy/GalaxyServiceResponse.scala
index ceebb9d4..6a2c7f04 100644
--- a/src/main/scala/net/psforever/services/galaxy/GalaxyServiceResponse.scala
+++ b/src/main/scala/net/psforever/services/galaxy/GalaxyServiceResponse.scala
@@ -4,7 +4,7 @@ package net.psforever.services.galaxy
import net.psforever.objects.Vehicle
import net.psforever.objects.vehicles.VehicleManifest
import net.psforever.objects.zones.HotSpotInfo
-import net.psforever.packet.game.{BuildingInfoUpdateMessage, CaptureFlagUpdateMessage, FlagInfo}
+import net.psforever.packet.game.{BuildingInfoUpdateMessage, CaptureFlagUpdateMessage}
import net.psforever.types.PlanetSideGUID
import net.psforever.services.GenericEventBusMsg
diff --git a/src/main/scala/net/psforever/services/local/LocalService.scala b/src/main/scala/net/psforever/services/local/LocalService.scala
index 8214bfe1..4c9b5606 100644
--- a/src/main/scala/net/psforever/services/local/LocalService.scala
+++ b/src/main/scala/net/psforever/services/local/LocalService.scala
@@ -1,33 +1,29 @@
// Copyright (c) 2017 PSForever
package net.psforever.services.local
-import akka.actor.{Actor, ActorRef, Props}
-import net.psforever.objects._
-import net.psforever.objects.ce.Deployable
+import akka.actor.{Actor, Props}
import net.psforever.objects.serverobject.terminals.Terminal
-import net.psforever.objects.vehicles.{Utility, UtilityType}
-import net.psforever.objects.vital.Vitality
import net.psforever.objects.zones.Zone
import net.psforever.packet.game.{TriggeredEffect, TriggeredEffectLocation}
-import net.psforever.services.local.support.{CaptureFlagManager, _}
+import net.psforever.services.local.support.CaptureFlagManager
+import net.psforever.types.PlanetSideGUID
+import net.psforever.services.local.support._
+import net.psforever.services.{GenericEventBus, Service}
import net.psforever.services.support.SupportActor
-import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
-import net.psforever.services.{GenericEventBus, RemoverActor, Service}
-import net.psforever.types.{PlanetSideGUID, Vector3}
-
-import scala.concurrent.duration.{Duration, _}
class LocalService(zone: Zone) extends Actor {
- private val doorCloser = context.actorOf(Props[DoorCloseActor](), s"${zone.id}-local-door-closer")
- private val hackClearer = context.actorOf(Props[HackClearActor](), s"${zone.id}-local-hack-clearer")
- private val hackCapturer =
- context.actorOf(Props(classOf[HackCaptureActor], zone.tasks), s"${zone.id}-local-hack-capturer")
- private val captureFlagManager =
- context.actorOf(Props(classOf[CaptureFlagManager], zone.tasks, zone), s"${zone.id}-local-capture-flag-manager")
- private val engineer =
- context.actorOf(Props(classOf[DeployableRemover], zone.tasks), s"${zone.id}-deployable-remover-agent")
- private val teleportDeployment: ActorRef =
- context.actorOf(Props[RouterTelepadActivation](), s"${zone.id}-telepad-activate-agent")
+ private val doorCloser = context.actorOf(
+ Props[DoorCloseActor](), s"${zone.id}-local-door-closer"
+ )
+ private val hackClearer = context.actorOf(
+ Props[HackClearActor](), s"${zone.id}-local-hack-clearer"
+ )
+ private val hackCapturer = context.actorOf(
+ Props(classOf[HackCaptureActor], zone.tasks), s"${zone.id}-local-hack-capturer"
+ )
+ private val captureFlagManager = context.actorOf(
+ Props(classOf[CaptureFlagManager], zone.tasks, zone), s"${zone.id}-local-capture-flag-manager"
+ )
private[this] val log = org.log4s.getLogger
val LocalEvents = new GenericEventBus[LocalServiceResponse]
@@ -49,14 +45,6 @@ class LocalService(zone: Zone) extends Actor {
case LocalServiceMessage(forChannel, action) =>
action match {
- case LocalAction.AlertDestroyDeployable(_, obj) =>
- LocalEvents.publish(
- LocalServiceResponse(
- s"/$forChannel/Local",
- Service.defaultPlayerGUID,
- LocalResponse.AlertDestroyDeployable(obj)
- )
- )
case LocalAction.DeployableMapIcon(player_guid, behavior, deployInfo) =>
LocalEvents.publish(
LocalServiceResponse(
@@ -65,6 +53,14 @@ class LocalService(zone: Zone) extends Actor {
LocalResponse.DeployableMapIcon(behavior, deployInfo)
)
)
+ case LocalAction.DeployableUIFor(item) =>
+ LocalEvents.publish(
+ LocalServiceResponse(
+ s"/$forChannel/Local",
+ Service.defaultPlayerGUID,
+ LocalResponse.DeployableUIFor(item)
+ )
+ )
case LocalAction.Detonate(guid, obj) =>
LocalEvents.publish(
LocalServiceResponse(s"/$forChannel/Local", Service.defaultPlayerGUID, LocalResponse.Detonate(guid, obj))
@@ -84,6 +80,14 @@ class LocalService(zone: Zone) extends Actor {
LocalEvents.publish(
LocalServiceResponse(s"/$forChannel/Local", Service.defaultPlayerGUID, LocalResponse.DoorCloses(door_guid))
)
+ case LocalAction.EliminateDeployable(obj, guid, pos, effect) =>
+ LocalEvents.publish(
+ LocalServiceResponse(
+ s"/$forChannel/Local",
+ Service.defaultPlayerGUID,
+ LocalResponse.EliminateDeployable(obj, guid, pos, effect)
+ )
+ )
case LocalAction.HackClear(player_guid, target, unk1, unk2) =>
LocalEvents.publish(
LocalServiceResponse(
@@ -144,7 +148,6 @@ class LocalService(zone: Zone) extends Actor {
LocalResponse.SendPlanetsideAttributeMessage(target_guid, attribute_number, attribute_value)
)
)
-
case LocalAction.SendGenericObjectActionMessage(player_guid, target_guid, action_number) =>
LocalEvents.publish(
LocalServiceResponse(
@@ -171,7 +174,14 @@ class LocalService(zone: Zone) extends Actor {
LocalResponse.SendGenericActionMessage(action_number)
)
)
-
+ case LocalAction.RouterTelepadMessage(msg) =>
+ LocalEvents.publish(
+ LocalServiceResponse(
+ s"/$forChannel/Local",
+ Service.defaultPlayerGUID,
+ LocalResponse.RouterTelepadMessage(msg)
+ )
+ )
case LocalAction.RouterTelepadTransport(player_guid, passenger_guid, src_guid, dest_guid) =>
LocalEvents.publish(
LocalServiceResponse(
@@ -224,6 +234,14 @@ class LocalService(zone: Zone) extends Actor {
LocalResponse.ShuttleState(guid, pos, orient, state)
)
)
+ case LocalAction.StartRouterInternalTelepad(router_guid, obj_guid, obj) =>
+ LocalEvents.publish(
+ LocalServiceResponse(
+ s"/$forChannel/Local",
+ Service.defaultPlayerGUID,
+ LocalResponse.StartRouterInternalTelepad(router_guid, obj_guid, obj)
+ )
+ )
case LocalAction.ToggleTeleportSystem(player_guid, router, system_plan) =>
LocalEvents.publish(
LocalServiceResponse(
@@ -288,13 +306,6 @@ class LocalService(zone: Zone) extends Actor {
//response from HackClearActor
case HackClearActor.SendHackMessageHackCleared(target_guid, _, unk1, unk2) =>
log.info(s"Clearing hack for $target_guid")
- LocalEvents.publish(
- LocalServiceResponse(
- s"/${zone.id}/Local",
- Service.defaultPlayerGUID,
- LocalResponse.SendHackMessageHackCleared(target_guid, unk1, unk2)
- )
- )
//message from ProximityTerminalControl
case Terminal.StartProximityEffect(terminal) =>
@@ -314,123 +325,6 @@ class LocalService(zone: Zone) extends Actor {
)
)
- //message to Engineer
- case LocalServiceMessage.Deployables(msg) =>
- engineer forward msg
-
- //message(s) from Engineer
- case msg @ DeployableRemover.EliminateDeployable(obj: TurretDeployable, guid, pos, _) =>
- val seats = obj.Seats.values
- if (seats.count(_.isOccupied) > 0) {
- val wasKickedByDriver = false //TODO yeah, I don't know
- seats.foreach(seat => {
- seat.occupant match {
- case Some(tplayer) =>
- seat.unmount(tplayer)
- tplayer.VehicleSeated = None
- zone.VehicleEvents ! VehicleServiceMessage(
- zone.id,
- VehicleAction.KickPassenger(tplayer.GUID, 4, wasKickedByDriver, obj.GUID)
- )
- case None => ;
- }
- })
- import scala.concurrent.ExecutionContext.Implicits.global
- context.system.scheduler.scheduleOnce(Duration.create(2, "seconds"), self, msg)
- } else {
- EliminateDeployable(obj, guid, pos)
- }
-
- case DeployableRemover.EliminateDeployable(obj: BoomerDeployable, guid, pos, _) =>
- EliminateDeployable(obj, guid, pos)
- obj.Trigger match {
- case Some(trigger) =>
- log.warn(s"LocalService: deconstructing boomer in ${zone.id}, but trigger@${trigger.GUID.guid} still exists")
- case _ => ;
- }
-
- case DeployableRemover.EliminateDeployable(obj: TelepadDeployable, guid, pos, _) =>
- obj.Active = false
- //ClearSpecific will also remove objects that do not have GUID's; we may not have a GUID at this time
- teleportDeployment ! SupportActor.ClearSpecific(List(obj), zone)
- EliminateDeployable(obj, guid, pos)
-
- case DeployableRemover.EliminateDeployable(obj, guid, pos, _) =>
- EliminateDeployable(obj, guid, pos)
-
- case DeployableRemover.DeleteTrigger(trigger_guid, _) =>
- LocalEvents.publish(
- LocalServiceResponse(
- s"/${zone.id}/Local",
- Service.defaultPlayerGUID,
- LocalResponse.ObjectDelete(trigger_guid, 0)
- )
- )
-
- //message to RouterTelepadActivation
- case LocalServiceMessage.Telepads(msg) =>
- teleportDeployment forward msg
-
- //from RouterTelepadActivation
- case RouterTelepadActivation.ActivateTeleportSystem(telepad, _) =>
- val remoteTelepad = telepad.asInstanceOf[TelepadDeployable]
- remoteTelepad.Active = true
- zone.GUID(remoteTelepad.Router) match {
- case Some(router: Vehicle) =>
- router.Utility(UtilityType.internal_router_telepad_deployable) match {
- case Some(internalTelepad: Utility.InternalTelepad) =>
- //get rid of previous linked remote telepad (if any)
- zone.GUID(internalTelepad.Telepad) match {
- case Some(old: TelepadDeployable) =>
- log.trace(
- s"ActivateTeleportSystem: old remote telepad@${old.GUID.guid} linked to internal@${internalTelepad.GUID.guid} will be deconstructed"
- )
- old.Active = false
- engineer ! SupportActor.ClearSpecific(List(old), zone)
- engineer ! RemoverActor.AddTask(old, zone, Some(0 seconds))
- case _ => ;
- }
- internalTelepad.Telepad = remoteTelepad.GUID
- if (internalTelepad.Active) {
- log.trace(
- s"ActivateTeleportSystem: fully deployed router@${router.GUID.guid} in ${zone.id} will link internal@${internalTelepad.GUID.guid} and remote@${remoteTelepad.GUID.guid}"
- )
- LocalEvents.publish(
- LocalServiceResponse(
- s"/${zone.id}/Local",
- Service.defaultPlayerGUID,
- LocalResponse.ToggleTeleportSystem(router, Some((internalTelepad, remoteTelepad)))
- )
- )
- } else {
- remoteTelepad.OwnerName match {
- case Some(name) =>
- LocalEvents.publish(
- LocalServiceResponse(
- s"/$name/Local",
- Service.defaultPlayerGUID,
- LocalResponse.RouterTelepadMessage("@Teleport_NotDeployed")
- )
- )
- case None => ;
- }
- }
- case _ =>
- log.error(s"ActivateTeleportSystem: vehicle@${router.GUID.guid} in ${zone.id} is not a router?")
- RouterTelepadError(remoteTelepad, "@Telepad_NoDeploy_RouterLost")
- }
- case Some(o) =>
- log.error(s"ActivateTeleportSystem: ${o.Definition.Name}@${o.GUID.guid} in ${zone.id} is not a router")
- RouterTelepadError(remoteTelepad, "@Telepad_NoDeploy_RouterLost")
- case None =>
- RouterTelepadError(remoteTelepad, "@Telepad_NoDeploy_RouterLost")
- }
-
- //synchronized damage calculations
- case Vitality.DamageOn(target: PlanetSideGameObject with Deployable, damage_func) =>
- val cause = damage_func(target)
- sender() ! Vitality.DamageResolution(target, cause)
-
// Forward all CaptureFlagManager messages
case msg @ (CaptureFlagManager.SpawnCaptureFlag(_, _, _) | CaptureFlagManager.PickupFlag(_, _) |
CaptureFlagManager.DropFlag(_) | CaptureFlagManager.Captured(_) | CaptureFlagManager.Lost(_, _) |
@@ -440,53 +334,4 @@ class LocalService(zone: Zone) extends Actor {
case msg =>
log.warn(s"Unhandled message $msg from ${sender()}")
}
-
- /**
- * na
- * @param telepad na
- * @param msg na
- */
- def RouterTelepadError(telepad: TelepadDeployable, msg: String): Unit = {
- telepad.OwnerName match {
- case Some(name) =>
- LocalEvents.publish(
- LocalServiceResponse(s"/$name/Local", Service.defaultPlayerGUID, LocalResponse.RouterTelepadMessage(msg))
- )
- case None => ;
- }
- engineer ! SupportActor.ClearSpecific(List(telepad), zone)
- engineer ! RemoverActor.AddTask(telepad, zone, Some(0 seconds))
- }
-
- /**
- * Common behavior for distributing information about a deployable's destruction or deconstruction.
- *
- * The primary distribution task instructs all clients to eliminate the target deployable.
- * This is a cosmetic exercise as the deployable should already be unregistered from its zone and
- * functionally removed from its zone's list of deployable objects by external operations.
- * The other distribution is a targeted message sent to the former owner of the deployable
- * if he still exists on the server
- * to clean up any leftover ownership-specific knowledge about the deployable.
- * @see `DeployableRemover`
- * @param obj the deployable object
- * @param guid the deployable objects globally unique identifier;
- * may be a former identifier
- * @param position the deployable's position
- */
- def EliminateDeployable(obj: PlanetSideGameObject with Deployable, guid: PlanetSideGUID, position: Vector3): Unit = {
- LocalEvents.publish(
- LocalServiceResponse(
- s"/${zone.id}/Local",
- Service.defaultPlayerGUID,
- LocalResponse.EliminateDeployable(obj, guid, position)
- )
- )
- obj.OwnerName match {
- case Some(name) =>
- LocalEvents.publish(
- LocalServiceResponse(s"/$name/Local", Service.defaultPlayerGUID, LocalResponse.AlertDestroyDeployable(obj))
- )
- case None => ;
- }
- }
}
diff --git a/src/main/scala/net/psforever/services/local/LocalServiceMessage.scala b/src/main/scala/net/psforever/services/local/LocalServiceMessage.scala
index 0eb988fb..db5a7ab0 100644
--- a/src/main/scala/net/psforever/services/local/LocalServiceMessage.scala
+++ b/src/main/scala/net/psforever/services/local/LocalServiceMessage.scala
@@ -1,7 +1,7 @@
// Copyright (c) 2017 PSForever
package net.psforever.services.local
-import net.psforever.objects.ce.Deployable
+import net.psforever.objects.ce.{Deployable, DeployedItem}
import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.serverobject.doors.Door
import net.psforever.objects.serverobject.hackable.Hackable
@@ -22,24 +22,27 @@ final case class LocalServiceMessage(forChannel: String, actionMessage: LocalAct
object LocalServiceMessage {
final case class Deployables(msg: Any)
-
- final case class Telepads(msg: Any)
}
object LocalAction {
trait Action
- final case class AlertDestroyDeployable(player_guid: PlanetSideGUID, obj: PlanetSideGameObject with Deployable)
- extends Action
final case class DeployableMapIcon(
- player_guid: PlanetSideGUID,
- behavior: DeploymentAction.Value,
- deployInfo: DeployableInfo
- ) extends Action
+ player_guid: PlanetSideGUID,
+ behavior: DeploymentAction.Value,
+ deployInfo: DeployableInfo
+ ) extends Action
+ final case class DeployableUIFor(obj: DeployedItem.Value) extends Action
final case class Detonate(guid: PlanetSideGUID, obj: PlanetSideGameObject) extends Action
final case class DoorOpens(player_guid: PlanetSideGUID, continent: Zone, door: Door) extends Action
final case class DoorCloses(player_guid: PlanetSideGUID, door_guid: PlanetSideGUID) extends Action
final case class DoorSlamsShut(door: Door) extends Action
+ final case class EliminateDeployable(
+ obj: Deployable,
+ object_guid: PlanetSideGUID,
+ pos: Vector3,
+ deletionEffect: Int
+ ) extends Action
final case class HackClear(player_guid: PlanetSideGUID, target: PlanetSideServerObject, unk1: Long, unk2: Long = 8L)
extends Action
final case class HackTemporarily(
@@ -66,7 +69,6 @@ object LocalAction {
attribute_number: PlanetsideAttributeEnum,
attribute_value: Long
) extends Action
-
final case class SendGenericObjectActionMessage(
player_guid: PlanetSideGUID,
target: PlanetSideGUID,
@@ -82,7 +84,7 @@ object LocalAction {
player_guid: PlanetSideGUID,
action_number: GenericActionEnum
) extends Action
-
+ final case class RouterTelepadMessage(msg: String) extends Action
final case class RouterTelepadTransport(
player_guid: PlanetSideGUID,
passenger_guid: PlanetSideGUID,
@@ -99,6 +101,11 @@ object LocalAction {
) extends Action
final case class ShuttleEvent(ev: OrbitalShuttleEvent) extends Action
final case class ShuttleState(guid: PlanetSideGUID, pos: Vector3, orientation: Vector3, state: Int) extends Action
+ final case class StartRouterInternalTelepad(
+ router_guid: PlanetSideGUID,
+ obj_guid: PlanetSideGUID,
+ obj: Utility.InternalTelepad
+ ) extends Action
final case class ToggleTeleportSystem(
player_guid: PlanetSideGUID,
router: Vehicle,
diff --git a/src/main/scala/net/psforever/services/local/LocalServiceResponse.scala b/src/main/scala/net/psforever/services/local/LocalServiceResponse.scala
index 48626712..2aadb269 100644
--- a/src/main/scala/net/psforever/services/local/LocalServiceResponse.scala
+++ b/src/main/scala/net/psforever/services/local/LocalServiceResponse.scala
@@ -1,13 +1,11 @@
// Copyright (c) 2017 PSForever
package net.psforever.services.local
-import net.psforever.objects.{PlanetSideGameObject, TelepadDeployable, Vehicle}
-import net.psforever.objects.ce.Deployable
import net.psforever.objects.serverobject.llu.CaptureFlag
-import net.psforever.objects.serverobject.structures.{AmenityOwner, Building}
+import net.psforever.objects.{PlanetSideGameObject, TelepadDeployable, Vehicle}
+import net.psforever.objects.ce.{Deployable, DeployedItem}
import net.psforever.objects.serverobject.terminals.{ProximityUnit, Terminal}
import net.psforever.objects.vehicles.Utility
-import net.psforever.packet.PlanetSideGamePacket
import net.psforever.packet.game.GenericActionEnum.GenericActionEnum
import net.psforever.packet.game.GenericObjectActionEnum.GenericObjectActionEnum
import net.psforever.packet.game.PlanetsideAttributeEnum.PlanetsideAttributeEnum
@@ -26,17 +24,18 @@ final case class LocalServiceResponse(
object LocalResponse {
trait Response
- final case class AlertDestroyDeployable(obj: PlanetSideGameObject with Deployable) extends Response
final case class DeployableMapIcon(action: DeploymentAction.Value, deployInfo: DeployableInfo) extends Response
+ final case class DeployableUIFor(obj: DeployedItem.Value) extends Response
final case class Detonate(guid: PlanetSideGUID, obj: PlanetSideGameObject) extends Response
final case class DoorOpens(door_guid: PlanetSideGUID) extends Response
final case class DoorCloses(door_guid: PlanetSideGUID) extends Response
final case class EliminateDeployable(
- obj: PlanetSideGameObject with Deployable,
- object_guid: PlanetSideGUID,
- pos: Vector3
- ) extends Response
- final case class SendHackMessageHackCleared(target_guid: PlanetSideGUID, unk1: Long, unk2: Long) extends Response
+ obj: Deployable,
+ object_guid: PlanetSideGUID,
+ pos: Vector3,
+ deletionEffect: Int
+ ) extends Response
+ final case class SendHackMessageHackCleared(target_guid: PlanetSideGUID, unk1: Long, unk2: Long) extends Response
final case class HackObject(target_guid: PlanetSideGUID, unk1: Long, unk2: Long) extends Response
final case class SendPacket(packet: PlanetSideGamePacket) extends Response
@@ -70,6 +69,11 @@ object LocalResponse {
) extends Response
final case class ShuttleEvent(ev: OrbitalShuttleEvent) extends Response
final case class ShuttleState(guid: PlanetSideGUID, pos: Vector3, orientation: Vector3, state: Int) extends Response
+ final case class StartRouterInternalTelepad(
+ router_guid: PlanetSideGUID,
+ obj_guid: PlanetSideGUID,
+ obj: Utility.InternalTelepad
+ ) extends Response
final case class ToggleTeleportSystem(
router: Vehicle,
systemPlan: Option[(Utility.InternalTelepad, TelepadDeployable)]
diff --git a/src/main/scala/net/psforever/services/local/support/DeployableRemover.scala b/src/main/scala/net/psforever/services/local/support/DeployableRemover.scala
deleted file mode 100644
index 0d8f1e65..00000000
--- a/src/main/scala/net/psforever/services/local/support/DeployableRemover.scala
+++ /dev/null
@@ -1,87 +0,0 @@
-// Copyright (c) 2017 PSForever
-package net.psforever.services.local.support
-
-import akka.actor.ActorRef
-import net.psforever.objects.ce.Deployable
-import net.psforever.objects.guid.{GUIDTask, TaskResolver}
-import net.psforever.objects.zones.Zone
-import net.psforever.objects.{BoomerDeployable, PlanetSideGameObject, TurretDeployable}
-import net.psforever.types.{PlanetSideGUID, Vector3}
-import net.psforever.services.RemoverActor
-
-import scala.concurrent.duration._
-
-class DeployableRemover(taskResolver: ActorRef) extends RemoverActor(taskResolver) {
- final val FirstStandardDuration: FiniteDuration = 3 minutes
-
- final val SecondStandardDuration: FiniteDuration = 2 seconds
-
- def InclusionTest(entry: RemoverActor.Entry): Boolean = entry.obj.isInstanceOf[Deployable]
-
- def InitialJob(entry: RemoverActor.Entry): Unit = {}
-
- def FirstJob(entry: RemoverActor.Entry): Unit = {
- val obj = entry.obj
- obj match {
- case boomer: BoomerDeployable =>
- FirstJobBoomer(boomer, entry)
- case _ => ;
- }
- entry.zone.Deployables ! Zone.Deployable.Dismiss(obj.asInstanceOf[PlanetSideGameObject with Deployable])
- }
-
- def FirstJobBoomer(obj: BoomerDeployable, entry: RemoverActor.Entry): Unit = {
- obj.Trigger match {
- case Some(trigger) =>
- if (trigger.HasGUID) {
- val guid = trigger.GUID
- Zone.EquipmentIs.Where(trigger, guid, entry.zone) match {
- case Some(Zone.EquipmentIs.InContainer(container, index)) =>
- container.Slot(index).Equipment = None
- case Some(Zone.EquipmentIs.OnGround()) =>
- entry.zone.Ground ! Zone.Ground.RemoveItem(guid)
- case _ => ;
- }
- context.parent ! DeployableRemover.DeleteTrigger(guid, entry.zone)
- }
- case None => ;
- }
- }
-
- override def SecondJob(entry: RemoverActor.Entry): Unit = {
- val obj = entry.obj.asInstanceOf[PlanetSideGameObject with Deployable]
- trace(s"Deleting a ${obj.Definition.Name} deployable")
- context.parent ! DeployableRemover.EliminateDeployable(obj, obj.GUID, obj.Position, entry.zone)
- super.SecondJob(entry)
- }
-
- def ClearanceTest(entry: RemoverActor.Entry): Boolean = !entry.zone.DeployableList.contains(entry.obj)
-
- def DeletionTask(entry: RemoverActor.Entry): TaskResolver.GiveTask = {
- entry.obj match {
- case turret: TurretDeployable =>
- GUIDTask.UnregisterDeployableTurret(turret)(entry.zone.GUID)
- case boomer: BoomerDeployable =>
- boomer.Trigger match {
- case Some(trigger) =>
- boomer.Trigger = None
- boomer.Zone.tasks ! GUIDTask.UnregisterObjectTask(trigger)(entry.zone.GUID)
- case None => ;
- }
- GUIDTask.UnregisterObjectTask(boomer)(entry.zone.GUID)
- case obj =>
- GUIDTask.UnregisterObjectTask(obj)(entry.zone.GUID)
- }
- }
-}
-
-object DeployableRemover {
- final case class EliminateDeployable(
- obj: PlanetSideGameObject with Deployable,
- guid: PlanetSideGUID,
- position: Vector3,
- zone: Zone
- )
-
- final case class DeleteTrigger(trigger_guid: PlanetSideGUID, zone: Zone)
-}
diff --git a/src/main/scala/net/psforever/services/local/support/RouterTelepadActivation.scala b/src/main/scala/net/psforever/services/local/support/RouterTelepadActivation.scala
index decf9407..e070da9d 100644
--- a/src/main/scala/net/psforever/services/local/support/RouterTelepadActivation.scala
+++ b/src/main/scala/net/psforever/services/local/support/RouterTelepadActivation.scala
@@ -1,143 +1,13 @@
// Copyright (c) 2017 PSForever
package net.psforever.services.local.support
-import akka.actor.Cancellable
import net.psforever.objects.zones.Zone
import net.psforever.objects._
-import net.psforever.services.support.{SimilarityComparator, SupportActor}
import scala.concurrent.duration._
-class RouterTelepadActivation extends SupportActor[RouterTelepadActivation.Entry] {
- var activationTask: Cancellable = Default.Cancellable
- var telepadList: List[RouterTelepadActivation.Entry] = List()
- val sameEntryComparator = new SimilarityComparator[RouterTelepadActivation.Entry]() {
- def Test(entry1: RouterTelepadActivation.Entry, entry2: RouterTelepadActivation.Entry): Boolean = {
- (entry1.obj eq entry2.obj) && (entry1.zone eq entry2.zone) && entry1.obj.GUID == entry2.obj.GUID
- }
- }
- val firstStandardTime: FiniteDuration = 60 seconds
-
- def InclusionTest(entry: RouterTelepadActivation.Entry): Boolean = {
- val obj = entry.obj
- obj.isInstanceOf[TelepadDeployable] && !obj.asInstanceOf[TelepadDeployable].Active
- }
-
- def receive: Receive =
- entryManagementBehaviors
- .orElse {
- case RouterTelepadActivation.AddTask(obj, zone, duration) =>
- val entry = RouterTelepadActivation.Entry(obj, zone, duration.getOrElse(firstStandardTime).toNanos)
- if (InclusionTest(entry) && !telepadList.exists(test => sameEntryComparator.Test(test, entry))) {
- if (entry.duration == 0) {
- //skip the queue altogether
- ActivationTask(entry)
- } else if (telepadList.isEmpty) {
- //we were the only entry so the event must be started from scratch
- telepadList = List(entry)
- trace(s"an activation task has been added: $entry")
- RetimeFirstTask()
- } else {
- //unknown number of entries; append, sort, then re-time tasking
- val oldHead = telepadList.head
- if (!telepadList.exists(test => sameEntryComparator.Test(test, entry))) {
- telepadList = (telepadList :+ entry).sortBy(entry => entry.time + entry.duration)
- trace(s"an activation task has been added: $entry")
- if (oldHead != telepadList.head) {
- RetimeFirstTask()
- }
- } else {
- trace(s"$obj is already queued")
- }
- }
- } else {
- trace(s"$obj either does not qualify for this behavior or is already queued")
- }
-
- //private messages from self to self
- case RouterTelepadActivation.TryActivate() =>
- activationTask.cancel()
- val now: Long = System.nanoTime
- val (in, out) = telepadList.partition(entry => { now - entry.time >= entry.duration })
- telepadList = out
- in.foreach { ActivationTask }
- RetimeFirstTask()
- trace(s"router activation task has found ${in.size} items to process")
-
- case _ => ;
- }
-
- /**
- * Common function to reset the first task's delayed execution.
- * Cancels the scheduled timer and will only restart the timer if there is at least one entry in the first pool.
- * @param now the time (in nanoseconds);
- * defaults to the current time (in nanoseconds)
- */
- def RetimeFirstTask(now: Long = System.nanoTime): Unit = {
- activationTask.cancel()
- if (telepadList.nonEmpty) {
- val short_timeout: FiniteDuration =
- math.max(1, telepadList.head.duration - (now - telepadList.head.time)) nanoseconds
- import scala.concurrent.ExecutionContext.Implicits.global
- activationTask = context.system.scheduler.scheduleOnce(short_timeout, self, RouterTelepadActivation.TryActivate())
- }
- }
-
- def HurrySpecific(targets: List[PlanetSideGameObject], zone: Zone): Unit = {
- PartitionTargetsFromList(telepadList, targets.map { RouterTelepadActivation.Entry(_, zone, 0) }, zone) match {
- case (Nil, _) =>
- debug(s"no tasks matching the targets $targets have been hurried")
- case (in, out) =>
- debug(s"the following tasks have been hurried: $in")
- telepadList = out
- if (out.nonEmpty) {
- RetimeFirstTask()
- }
- in.foreach { ActivationTask }
- }
- }
-
- def HurryAll(): Unit = {
- trace("all tasks have been hurried")
- activationTask.cancel()
- telepadList.foreach {
- ActivationTask
- }
- telepadList = Nil
- }
-
- def ClearSpecific(targets: List[PlanetSideGameObject], zone: Zone): Unit = {
- PartitionTargetsFromList(telepadList, targets.map { RouterTelepadActivation.Entry(_, zone, 0) }, zone) match {
- case (Nil, _) =>
- debug(s"no tasks matching the targets $targets have been cleared")
- case (in, out) =>
- debug(s"the following tasks have been cleared: $in")
- telepadList = out //.sortBy(entry => entry.time + entry.duration)
- if (out.nonEmpty) {
- RetimeFirstTask()
- }
- }
- }
-
- def ClearAll(): Unit = {
- trace("all tasks have been cleared")
- activationTask.cancel()
- telepadList = Nil
- }
-
- def ActivationTask(entry: SupportActor.Entry): Unit = {
- entry.obj.asInstanceOf[TelepadDeployable].Active = true
- context.parent ! RouterTelepadActivation.ActivateTeleportSystem(entry.obj, entry.zone)
- }
-}
-
object RouterTelepadActivation {
- final case class Entry(_obj: PlanetSideGameObject, _zone: Zone, _duration: Long)
- extends SupportActor.Entry(_obj, _zone, _duration)
-
final case class AddTask(obj: PlanetSideGameObject, zone: Zone, duration: Option[FiniteDuration] = None)
- final case class TryActivate()
-
final case class ActivateTeleportSystem(telepad: PlanetSideGameObject, zone: Zone)
}
diff --git a/src/main/scala/net/psforever/zones/Zones.scala b/src/main/scala/net/psforever/zones/Zones.scala
index 53a9ce57..7134cd53 100644
--- a/src/main/scala/net/psforever/zones/Zones.scala
+++ b/src/main/scala/net/psforever/zones/Zones.scala
@@ -785,8 +785,6 @@ object Zones {
60 seconds
case _: DeployableSource =>
60 seconds
- case _: ComplexDeployableSource =>
- 60 seconds
case _ =>
0 seconds
}
diff --git a/src/test/scala/objects/DeployableBehaviorTest.scala b/src/test/scala/objects/DeployableBehaviorTest.scala
new file mode 100644
index 00000000..1cce70a7
--- /dev/null
+++ b/src/test/scala/objects/DeployableBehaviorTest.scala
@@ -0,0 +1,353 @@
+// Copyright (c) 2021 PSForever
+package objects
+
+import akka.actor.{ActorRef, Props}
+import akka.testkit.TestProbe
+import base.{ActorTest, FreedContextActorTest}
+import net.psforever.objects.avatar.{Avatar, Certification, PlayerControl}
+import net.psforever.objects.{ConstructionItem, Deployables, GlobalDefinitions, Player}
+import net.psforever.objects.ce.{Deployable, DeployedItem}
+import net.psforever.objects.guid.{NumberPoolHub, TaskResolver}
+import net.psforever.objects.guid.source.MaxNumberSource
+import net.psforever.objects.zones.{Zone, ZoneDeployableActor, ZoneMap}
+import net.psforever.packet.game._
+import net.psforever.services.Service
+import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
+import net.psforever.services.local.{LocalAction, LocalServiceMessage}
+import net.psforever.types._
+
+import scala.collection.mutable.ListBuffer
+import scala.concurrent.duration._
+
+class DeployableBehaviorSetupTest extends ActorTest {
+ val eventsProbe = new TestProbe(system)
+ val jmine = Deployables.Make(DeployedItem.jammer_mine)() //guid=1
+ val deployableList = new ListBuffer()
+ val guid = new NumberPoolHub(new MaxNumberSource(max = 5))
+ val zone = new Zone(id = "test", new ZoneMap(name = "test"), zoneNumber = 0) {
+ private val deployables = system.actorOf(Props(classOf[ZoneDeployableActor], this, deployableList), name = "test-zone-deployables")
+
+ override def SetupNumberPools(): Unit = {}
+ GUID(guid)
+ override def AvatarEvents: ActorRef = eventsProbe.ref
+ override def LocalEvents: ActorRef = eventsProbe.ref
+ override def Deployables: ActorRef = deployables
+ }
+ guid.register(jmine, number = 1)
+ jmine.Faction = PlanetSideEmpire.TR
+ jmine.Position = Vector3(1,2,3)
+ jmine.Orientation = Vector3(4,5,6)
+
+ "DeployableBehavior" should {
+ "perform self-setup" in {
+ assert(deployableList.isEmpty, "self-setup test - deployable list is not empty")
+ zone.Deployables ! Zone.Deployable.Build(jmine)
+
+ val eventsMsgs = eventsProbe.receiveN(3, 10.seconds)
+ eventsMsgs.head match {
+ case LocalServiceMessage(
+ "test",
+ LocalAction.TriggerEffectLocation(PlanetSideGUID(0), "spawn_object_effect", Vector3(1,2,3), Vector3(4,5,6))
+ ) => ;
+ case _ => assert(false, "self-setup test - no spawn fx")
+ }
+ eventsMsgs(1) match {
+ case AvatarServiceMessage("test", AvatarAction.DeployItem(PlanetSideGUID(0), obj)) =>
+ assert(obj eq jmine, "self-setup test - not same mine")
+ case _ =>
+ assert( false, "self-setup test - wrong deploy message")
+ }
+ eventsMsgs(2) match {
+ case LocalServiceMessage(
+ "TR",
+ LocalAction.DeployableMapIcon(
+ PlanetSideGUID(0),
+ DeploymentAction.Build,
+ DeployableInfo(PlanetSideGUID(1), DeployableIcon.DisruptorMine, Vector3(1,2,3), PlanetSideGUID(0))
+ )
+ ) => ;
+ case _ => assert(false, "self-setup test - no icon or wrong icon")
+ }
+ assert(deployableList.contains(jmine), "self-setup test - deployable not appended to list")
+ }
+ }
+}
+
+class DeployableBehaviorSetupOwnedP1Test extends ActorTest {
+ val eventsProbe = new TestProbe(system)
+ val avatar = Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)
+ val player = Player(avatar) //guid=3
+ val jmine = Deployables.Make(DeployedItem.jammer_mine)() //guid=1
+ val citem = new ConstructionItem(GlobalDefinitions.ace) //guid = 2
+ val deployableList = new ListBuffer()
+ val guid = new NumberPoolHub(new MaxNumberSource(max = 5))
+ val zone = new Zone(id = "test", new ZoneMap(name = "test"), zoneNumber = 0) {
+ private val deployables = system.actorOf(Props(classOf[ZoneDeployableActor], this, deployableList), name = "test-zone-deployables")
+
+ override def SetupNumberPools(): Unit = {}
+ GUID(guid)
+ override def AvatarEvents: ActorRef = eventsProbe.ref
+ override def LocalEvents: ActorRef = eventsProbe.ref
+ override def Deployables: ActorRef = deployables
+ override def Players = List(avatar)
+ override def LivePlayers = List(player)
+ }
+ guid.register(jmine, number = 1)
+ guid.register(citem, number = 2)
+ guid.register(player, number = 3)
+ jmine.Faction = PlanetSideEmpire.TR
+ jmine.Position = Vector3(1,2,3)
+ jmine.Orientation = Vector3(4,5,6)
+ jmine.AssignOwnership(player)
+
+ "DeployableBehavior" should {
+ "receive setup functions after asking owner" in {
+ val playerProbe = new TestProbe(system)
+ player.Actor = playerProbe.ref
+ assert(deployableList.isEmpty, "owned setup test, 1 - deployable list is not empty")
+ zone.Deployables ! Zone.Deployable.BuildByOwner(jmine, player, citem)
+
+ playerProbe.receiveOne(200.milliseconds) match {
+ case Player.BuildDeployable(a, b) =>
+ assert((a eq jmine) && (b eq citem), "owned setup test, 1 - process echoing wrong mine or wrong construction item")
+ case _ =>
+ assert(false, "owned setup test, 1 - not echoing messages to owner")
+ }
+ }
+ }
+}
+
+class DeployableBehaviorSetupOwnedP2Test extends FreedContextActorTest {
+ val eventsProbe = new TestProbe(system)
+ val avatar = Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)
+ val player = Player(avatar) //guid=3
+ val jmine = Deployables.Make(DeployedItem.jammer_mine)() //guid=1
+ val citem = new ConstructionItem(GlobalDefinitions.ace) //guid = 2
+ val deployableList = new ListBuffer()
+ val guid = new NumberPoolHub(new MaxNumberSource(max = 5))
+ val zone = new Zone(id = "test", new ZoneMap(name = "test"), zoneNumber = 0) {
+ private val deployables = system.actorOf(Props(classOf[ZoneDeployableActor], this, deployableList), name = "test-zone-deployables")
+
+ override def SetupNumberPools(): Unit = {}
+ GUID(guid)
+ override def AvatarEvents: ActorRef = eventsProbe.ref
+ override def LocalEvents: ActorRef = eventsProbe.ref
+ override def Deployables: ActorRef = deployables
+ override def Players = List(avatar)
+ override def LivePlayers = List(player)
+ override def tasks: ActorRef = eventsProbe.ref
+ }
+ guid.register(jmine, number = 1)
+ guid.register(citem, number = 2)
+ guid.register(player, number = 3)
+ guid.register(avatar.locker, number = 4)
+ jmine.Faction = PlanetSideEmpire.TR
+ jmine.Position = Vector3(1,2,3)
+ jmine.Orientation = Vector3(4,5,6)
+ jmine.AssignOwnership(player)
+ avatar.deployables.UpdateMaxCounts(Set(Certification.CombatEngineering, Certification.AssaultEngineering))
+ player.Zone = zone
+ player.Slot(slot = 0).Equipment = citem
+ player.Actor = system.actorOf(Props(classOf[PlayerControl], player, null), name = "deployable-test-player-control")
+
+ "DeployableBehavior" should {
+ "perform setup functions after asking owner" in {
+ assert(player.Slot(slot = 0).Equipment.contains(citem), "owned setup test, 2 - player hand is empty")
+ assert(deployableList.isEmpty, "owned setup test, 2 - deployable list is not empty")
+ assert(!avatar.deployables.Contains(jmine), "owned setup test, 2 - avatar already owns deployable")
+ zone.Deployables ! Zone.Deployable.BuildByOwner(jmine, player, citem)
+ //assert(false, "test needs to be fixed")
+ val eventsMsgs = eventsProbe.receiveN(8, 10.seconds)
+ eventsMsgs.head match {
+ case AvatarServiceMessage(
+ "TestCharacter1",
+ AvatarAction.SendResponse(
+ Service.defaultPlayerGUID,
+ ObjectDeployedMessage(0, "jammer_mine", DeployOutcome.Success, 1, 20)
+ )
+ ) => ;
+ case _ =>
+ assert(false, "owned setup test, 2 - did not receive build confirmation")
+ }
+ eventsMsgs(1) match {
+ case LocalServiceMessage("TestCharacter1", LocalAction.DeployableUIFor(DeployedItem.jammer_mine)) => ;
+ case _ => assert(false, "owned setup test, 2 - did not receive ui update")
+ }
+ eventsMsgs(2) match {
+ case LocalServiceMessage(
+ "test",
+ LocalAction.TriggerEffectLocation(PlanetSideGUID(3), "spawn_object_effect", Vector3(1,2,3), Vector3(4,5,6))
+ ) => ;
+ case _ => assert(false, "owned setup test, 2 - no spawn fx")
+ }
+ eventsMsgs(3) match {
+ case AvatarServiceMessage("test", AvatarAction.DeployItem(PlanetSideGUID(0), obj)) =>
+ assert(obj eq jmine, "owned setup test, 2 - not same mine")
+ case _ =>
+ assert( false, "owned setup test, 2 - wrong deploy message")
+ }
+ //the message order can be jumbled from here-on
+ eventsMsgs(4) match {
+ case LocalServiceMessage(
+ "TR",
+ LocalAction.DeployableMapIcon(
+ PlanetSideGUID(0),
+ DeploymentAction.Build,
+ DeployableInfo(PlanetSideGUID(1), DeployableIcon.DisruptorMine, Vector3(1,2,3), PlanetSideGUID(3))
+ )
+ ) => ;
+ case _ =>
+ assert(false, "owned setup test, 2 - no icon or wrong icon")
+ }
+ eventsMsgs(5) match {
+ case AvatarServiceMessage(
+ "TestCharacter1",
+ AvatarAction.SendResponse(Service.defaultPlayerGUID, GenericObjectActionMessage(PlanetSideGUID(1), 21))
+ ) => ;
+ case _ =>
+ assert(false, "owned setup test, 2 - build action not reset (GOAM21)")
+ }
+ eventsMsgs(6) match {
+ case AvatarServiceMessage("test", AvatarAction.ObjectDelete(Service.defaultPlayerGUID, PlanetSideGUID(2), 0)) => ;
+ case _ =>
+ 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(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")
+ }
+ }
+}
+
+class DeployableBehaviorDeconstructTest extends ActorTest {
+ val eventsProbe = new TestProbe(system)
+ val jmine = Deployables.Make(DeployedItem.jammer_mine)() //guid = 1
+ val deployableList = new ListBuffer()
+ val guid = new NumberPoolHub(new MaxNumberSource(max = 5))
+ val zone = new Zone(id = "test", new ZoneMap(name = "test"), zoneNumber = 0) {
+ private val deployables = system.actorOf(Props(classOf[ZoneDeployableActor], this, deployableList), name = "test-zone-deployables")
+
+ override def SetupNumberPools(): Unit = {}
+ GUID(guid)
+ override def AvatarEvents: ActorRef = eventsProbe.ref
+ override def LocalEvents: ActorRef = eventsProbe.ref
+ override def tasks: ActorRef = eventsProbe.ref
+ override def Deployables: ActorRef = deployables
+ }
+ guid.register(jmine, number = 1)
+ jmine.Faction = PlanetSideEmpire.TR
+ jmine.Position = Vector3(1,2,3)
+ jmine.Orientation = Vector3(4,5,6)
+
+ "DeployableBehavior" should {
+ "deconstruct, by self" in {
+ zone.Deployables ! Zone.Deployable.Build(jmine)
+ eventsProbe.receiveN(3, 10.seconds) //all of the messages from the construction (see other testing)
+ assert(deployableList.contains(jmine), "deconstruct test - deployable not appended to list")
+
+ jmine.Actor ! Deployable.Deconstruct()
+ val eventsMsgs = eventsProbe.receiveN(3, 10.seconds)
+ eventsMsgs.head match {
+ case LocalServiceMessage("test", LocalAction.EliminateDeployable(`jmine`, PlanetSideGUID(1), Vector3(1,2,3), 2)) => ;
+ case _ => assert(false, "deconstruct test - not eliminating deployable")
+ }
+ eventsMsgs(1) match {
+ case LocalServiceMessage(
+ "TR",
+ LocalAction.DeployableMapIcon(
+ PlanetSideGUID(0),
+ DeploymentAction.Dismiss,
+ DeployableInfo(PlanetSideGUID(1), DeployableIcon.DisruptorMine, Vector3(1, 2, 3), PlanetSideGUID(0))
+ )
+ ) => ;
+ 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")
+ }
+ }
+}
+
+class DeployableBehaviorDeconstructOwnedTest extends FreedContextActorTest {
+ val eventsProbe = new TestProbe(system)
+ val avatar = Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)
+ val player = Player(avatar) //guid=3
+ val jmine = Deployables.Make(DeployedItem.jammer_mine)() //guid=1
+ val citem = new ConstructionItem(GlobalDefinitions.ace) //guid = 2
+ val deployableList = new ListBuffer()
+ val guid = new NumberPoolHub(new MaxNumberSource(max = 5))
+ val zone = new Zone(id = "test", new ZoneMap(name = "test"), zoneNumber = 0) {
+ private val deployables = system.actorOf(Props(classOf[ZoneDeployableActor], this, deployableList), name = "test-zone-deployables")
+
+ override def SetupNumberPools(): Unit = {}
+ GUID(guid)
+ override def AvatarEvents: ActorRef = eventsProbe.ref
+ override def LocalEvents: ActorRef = eventsProbe.ref
+ override def Deployables: ActorRef = deployables
+ override def Players = List(avatar)
+ override def LivePlayers = List(player)
+ override def tasks: ActorRef = eventsProbe.ref
+ }
+ guid.register(jmine, number = 1)
+ guid.register(citem, number = 2)
+ guid.register(player, number = 3)
+ guid.register(avatar.locker, number = 4)
+ jmine.Faction = PlanetSideEmpire.TR
+ jmine.Position = Vector3(1,2,3)
+ jmine.Orientation = Vector3(4,5,6)
+ jmine.AssignOwnership(player)
+ avatar.deployables.UpdateMaxCounts(Set(Certification.CombatEngineering, Certification.AssaultEngineering))
+ player.Zone = zone
+ player.Slot(slot = 0).Equipment = citem
+ player.Actor = system.actorOf(Props(classOf[PlayerControl], player, null), name = "deployable-test-player-control")
+
+ "DeployableBehavior" should {
+ "deconstruct and alert owner" in {
+ zone.Deployables ! Zone.Deployable.BuildByOwner(jmine, player, citem)
+ eventsProbe.receiveN(8, 10.seconds)
+ 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")
+
+ jmine.Actor ! Deployable.Deconstruct()
+ val eventsMsgs = eventsProbe.receiveN(4, 10.seconds)
+ eventsMsgs.head match {
+ case LocalServiceMessage("test", LocalAction.EliminateDeployable(`jmine`, PlanetSideGUID(1), Vector3(1,2,3), 2)) => ;
+ case _ => assert(false, "owned deconstruct test - not eliminating deployable")
+ }
+ eventsMsgs(1) match {
+ case LocalServiceMessage("TestCharacter1", LocalAction.DeployableUIFor(DeployedItem.jammer_mine)) => ;
+ case _ => assert(false, "")
+ }
+ eventsMsgs(2) match {
+ case LocalServiceMessage(
+ "TR",
+ LocalAction.DeployableMapIcon(
+ PlanetSideGUID(0),
+ DeploymentAction.Dismiss,
+ DeployableInfo(PlanetSideGUID(1), DeployableIcon.DisruptorMine, Vector3(1, 2, 3), PlanetSideGUID(0))
+ )
+ ) => ;
+ 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(!avatar.deployables.Contains(jmine), "owned deconstruct test - avatar still owns deployable")
+ }
+ }
+}
+
+object DeployableBehaviorTest {
+ //...
+}
diff --git a/src/test/scala/objects/DeployableTest.scala b/src/test/scala/objects/DeployableTest.scala
index f8eca34b..e85471f8 100644
--- a/src/test/scala/objects/DeployableTest.scala
+++ b/src/test/scala/objects/DeployableTest.scala
@@ -1,7 +1,7 @@
// Copyright (c) 2017 PSForever
package objects
-import akka.actor.{Actor, Props}
+import akka.actor.{Actor, ActorRef, Props}
import akka.testkit.TestProbe
import base.ActorTest
import net.psforever.objects.ballistics._
@@ -10,20 +10,20 @@ import net.psforever.objects.guid.NumberPoolHub
import net.psforever.objects.guid.source.MaxNumberSource
import net.psforever.objects.serverobject.mount.{MountInfo, Mountable}
import net.psforever.objects.vital.Vitality
-import net.psforever.objects.zones.{Zone, ZoneMap}
+import net.psforever.objects.zones.{Zone, ZoneDeployableActor, ZoneMap}
import net.psforever.objects.{TurretDeployable, _}
import net.psforever.packet.game.{DeployableIcon, DeployableInfo, DeploymentAction}
import net.psforever.types._
import org.specs2.mutable.Specification
-import net.psforever.services.{RemoverActor, Service}
+import net.psforever.services.Service
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
import net.psforever.services.local.{LocalAction, LocalServiceMessage}
-import net.psforever.services.support.SupportActor
import net.psforever.objects.avatar.Avatar
import net.psforever.objects.vital.base.DamageResolution
import net.psforever.objects.vital.interaction.DamageInteraction
import net.psforever.objects.vital.projectile.ProjectileReason
+import scala.collection.mutable.ListBuffer
import scala.concurrent.duration._
class DeployableTest extends Specification {
@@ -307,25 +307,30 @@ class ShieldGeneratorDeployableTest extends Specification {
class ExplosiveDeployableJammerTest extends ActorTest {
val guid = new NumberPoolHub(new MaxNumberSource(10))
- val zone = new Zone("test", new ZoneMap("test"), 0) {
- override def SetupNumberPools() = {}
- GUID(guid)
- }
- val activityProbe = TestProbe()
- val avatarProbe = TestProbe()
- val localProbe = TestProbe()
- zone.Activity = activityProbe.ref
- zone.AvatarEvents = avatarProbe.ref
- zone.LocalEvents = localProbe.ref
+ val eventsProbe = new TestProbe(system)
val j_mine = Deployables.Make(DeployedItem.jammer_mine)().asInstanceOf[ExplosiveDeployable] //guid=1
- val player1 =
- Player(Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)) //guid=3
- player1.Spawn()
- val player2 =
- Player(Avatar(0, "TestCharacter2", PlanetSideEmpire.NC, CharacterSex.Male, 0, CharacterVoice.Mute)) //guid=4
- player2.Spawn()
+ val avatar1 = Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)
+ val player1 = Player(avatar1) //guid=3
+ val avatar2 = Avatar(0, "TestCharacter2", PlanetSideEmpire.NC, CharacterSex.Male, 0, CharacterVoice.Mute)
+ val player2 = Player(avatar2) //guid=4
val weapon = Tool(GlobalDefinitions.jammer_grenade) //guid=5
+ val deployableList = new ListBuffer()
+ val zone = new Zone("test", new ZoneMap("test"), 0) {
+ private val deployables = system.actorOf(Props(classOf[ZoneDeployableActor], this, deployableList), name = "test-zone-deployables")
+
+ override def SetupNumberPools() = {}
+ GUID(guid)
+ override def Activity: ActorRef = eventsProbe.ref
+ override def AvatarEvents: ActorRef = eventsProbe.ref
+ override def LocalEvents: ActorRef = eventsProbe.ref
+ override def Deployables: ActorRef = deployables
+ override def Players = List(avatar1, avatar2)
+ override def LivePlayers = List(player1, player2)
+ override def tasks: ActorRef = eventsProbe.ref
+ }
+ player1.Spawn()
+ player2.Spawn()
guid.register(j_mine, 1)
guid.register(player1, 3)
guid.register(player2, 4)
@@ -355,51 +360,22 @@ class ExplosiveDeployableJammerTest extends ActorTest {
assert(!j_mine.Destroyed)
j_mine.Actor ! Vitality.Damage(applyDamageToJ)
- val msg_local = localProbe.receiveN(4, 200 milliseconds)
- val msg_avatar = avatarProbe.receiveOne(200 milliseconds)
- activityProbe.expectNoMessage(200 milliseconds)
- assert(
- msg_local.head match {
- case LocalServiceMessage("TestCharacter2", LocalAction.AlertDestroyDeployable(PlanetSideGUID(0), target)) =>
- target eq j_mine
- case _ => false
- }
- )
- assert(
- msg_local(1) match {
- case LocalServiceMessage(
- "NC",
- LocalAction.DeployableMapIcon(
- PlanetSideGUID(0),
- DeploymentAction.Dismiss,
- DeployableInfo(PlanetSideGUID(1), DeployableIcon.DisruptorMine, _, PlanetSideGUID(0))
- )
- ) =>
- true
- case _ => false
- }
- )
- assert(
- msg_local(2) match {
- case LocalServiceMessage.Deployables(SupportActor.ClearSpecific(List(target), _zone)) =>
- (j_mine eq target) && (_zone eq zone)
- case _ => false
- }
- )
- assert(
- msg_local(3) match {
- case LocalServiceMessage.Deployables(RemoverActor.AddTask(target, _zone, _)) =>
- (target eq j_mine) && (_zone eq zone)
- case _ => false
- }
- )
- assert(
- msg_avatar match {
- case AvatarServiceMessage("test", AvatarAction.Destroy(PlanetSideGUID(1), _, Service.defaultPlayerGUID, _)) =>
- true
- case _ => false
- }
- )
+ val eventMsgs = eventsProbe.receiveN(2, 200 milliseconds)
+ eventMsgs.head match {
+ case LocalServiceMessage(
+ "NC",
+ LocalAction.DeployableMapIcon(
+ ValidPlanetSideGUID(0),
+ DeploymentAction.Dismiss,
+ DeployableInfo(ValidPlanetSideGUID(1), DeployableIcon.DisruptorMine, Vector3.Zero, ValidPlanetSideGUID(0))
+ )
+ ) => ;
+ case _ => assert(false, "")
+ }
+ eventMsgs(1) match {
+ case AvatarServiceMessage("test", AvatarAction.Destroy(PlanetSideGUID(1), _, Service.defaultPlayerGUID, _)) => ;
+ case _ => assert(false, "")
+ }
assert(j_mine.Destroyed)
}
}
@@ -407,25 +383,36 @@ class ExplosiveDeployableJammerTest extends ActorTest {
class ExplosiveDeployableJammerExplodeTest extends ActorTest {
val guid = new NumberPoolHub(new MaxNumberSource(10))
- val zone = new Zone("test", new ZoneMap("test"), 0) {
- override def SetupNumberPools() = {}
- GUID(guid)
- }
- val activityProbe = TestProbe()
- val avatarProbe = TestProbe()
- val localProbe = TestProbe()
- zone.Activity = activityProbe.ref
- zone.AvatarEvents = avatarProbe.ref
- zone.LocalEvents = localProbe.ref
+ val eventsProbe = new TestProbe(system)
+ val player1Probe = new TestProbe(system)
+ val player2Probe = new TestProbe(system)
val h_mine = Deployables.Make(DeployedItem.he_mine)().asInstanceOf[ExplosiveDeployable] //guid=2
- val player1 =
- Player(Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)) //guid=3
- player1.Spawn()
- val player2 =
- Player(Avatar(0, "TestCharacter2", PlanetSideEmpire.NC, CharacterSex.Male, 0, CharacterVoice.Mute)) //guid=4
- player2.Spawn()
+ val avatar1 = Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)
+ val player1 = Player(avatar1) //guid=3
+ val avatar2 = Avatar(0, "TestCharacter2", PlanetSideEmpire.NC, CharacterSex.Male, 0, CharacterVoice.Mute)
+ val player2 = Player(avatar2) //guid=4
val weapon = Tool(GlobalDefinitions.jammer_grenade) //guid=5
+ val deployableList = new ListBuffer()
+ val zone = new Zone("test", new ZoneMap("test"), 0) {
+ private val deployables = system.actorOf(Props(classOf[ZoneDeployableActor], this, deployableList), name = "test-zone-deployables")
+
+ override def SetupNumberPools() = {}
+ GUID(guid)
+ override def Activity: ActorRef = eventsProbe.ref
+ override def AvatarEvents: ActorRef = eventsProbe.ref
+ override def LocalEvents: ActorRef = eventsProbe.ref
+ override def Deployables: ActorRef = deployables
+ override def Players = List(avatar1, avatar2)
+ override def LivePlayers = List(player1, player2)
+ override def tasks: ActorRef = eventsProbe.ref
+ }
+ player1.Spawn()
+ player1.Actor = player1Probe.ref
+ avatar2.deployables.AddOverLimit(h_mine) //cram it down your throat
+ player2.Spawn()
+ player2.Position = Vector3(10,0,0)
+ player2.Actor = player2Probe.ref
guid.register(h_mine, 2)
guid.register(player1, 3)
guid.register(player2, 4)
@@ -451,66 +438,50 @@ class ExplosiveDeployableJammerExplodeTest extends ActorTest {
"ExplosiveDeployable" should {
"handle being jammered appropriately (detonation)" in {
+ assert(avatar2.deployables.Contains(h_mine))
assert(!h_mine.Destroyed)
h_mine.Actor ! Vitality.Damage(applyDamageToH)
- val msg_local = localProbe.receiveN(5, 200 milliseconds)
- val msg_avatar = avatarProbe.receiveOne(200 milliseconds)
- val msg_activity = activityProbe.receiveOne(200 milliseconds)
- assert(
- msg_local.head match {
- case LocalServiceMessage("test", LocalAction.Detonate(PlanetSideGUID(2), target)) => target eq h_mine
- case _ => false
- }
- )
- assert(
- msg_local(1) match {
- case LocalServiceMessage("TestCharacter2", LocalAction.AlertDestroyDeployable(PlanetSideGUID(0), target)) =>
- target eq h_mine
- case _ => false
- }
- )
- assert(
- msg_local(2) match {
- case LocalServiceMessage(
- "NC",
- LocalAction.DeployableMapIcon(
- PlanetSideGUID(0),
- DeploymentAction.Dismiss,
- DeployableInfo(PlanetSideGUID(2), DeployableIcon.HEMine, _, PlanetSideGUID(0))
- )
- ) =>
- true
- case _ => false
- }
- )
- assert(
- msg_local(3) match {
- case LocalServiceMessage.Deployables(SupportActor.ClearSpecific(List(target), _zone)) =>
- (h_mine eq target) && (_zone eq zone)
- case _ => false
- }
- )
- assert(
- msg_local(4) match {
- case LocalServiceMessage.Deployables(RemoverActor.AddTask(target, _zone, _)) =>
- (target eq h_mine) && (_zone eq zone)
- case _ => false
- }
- )
- assert(
- msg_avatar match {
- case AvatarServiceMessage("test", AvatarAction.Destroy(PlanetSideGUID(2), _, Service.defaultPlayerGUID, _)) =>
- true
- case _ => false
- }
- )
- assert(
- msg_activity match {
- case Zone.HotSpot.Conflict(target, attacker, _) => (target.Definition eq h_mine.Definition) && (attacker eq pSource)
- case _ => false
- }
- )
+ val eventMsgs = eventsProbe.receiveN(5, 200 milliseconds)
+ val p1Msgs = player1Probe.receiveN(1, 200 milliseconds)
+ player2Probe.expectNoMessage(200 milliseconds)
+ eventMsgs.head match {
+ case Zone.HotSpot.Conflict(target, attacker, _)
+ if (target.Definition eq h_mine.Definition) && (attacker eq pSource) => ;
+ case _ => assert(false, "")
+ }
+ eventMsgs(1) match {
+ case LocalServiceMessage("test", LocalAction.Detonate(PlanetSideGUID(2), target))
+ if target eq h_mine => ;
+ case _ => assert(false, "")
+ }
+ eventMsgs(2) match {
+ case LocalServiceMessage("TestCharacter2", LocalAction.DeployableUIFor(DeployedItem.he_mine)) => ;
+ case _ => assert(false, "")
+ }
+ eventMsgs(3) match {
+ case LocalServiceMessage(
+ "NC",
+ LocalAction.DeployableMapIcon(
+ PlanetSideGUID(0),
+ DeploymentAction.Dismiss,
+ DeployableInfo(PlanetSideGUID(2), DeployableIcon.HEMine, _, PlanetSideGUID(0))
+ )
+ ) => ;
+ case _ => assert(false, "")
+ }
+ eventMsgs(4) match {
+ case AvatarServiceMessage(
+ "test",
+ AvatarAction.Destroy(PlanetSideGUID(2), PlanetSideGUID(3), Service.defaultPlayerGUID, Vector3.Zero)
+ ) => ;
+ case _ => assert(false, "")
+ }
+ p1Msgs.head match {
+ case Vitality.Damage(_) => ;
+ case _ => assert(false, "")
+ }
+ assert(!avatar2.deployables.Contains(h_mine))
assert(h_mine.Destroyed)
}
}
@@ -518,25 +489,36 @@ class ExplosiveDeployableJammerExplodeTest extends ActorTest {
class ExplosiveDeployableDestructionTest extends ActorTest {
val guid = new NumberPoolHub(new MaxNumberSource(10))
- val zone = new Zone("test", new ZoneMap("test"), 0) {
- override def SetupNumberPools() = {}
- GUID(guid)
- }
- val activityProbe = TestProbe()
- val avatarProbe = TestProbe()
- val localProbe = TestProbe()
- zone.Activity = activityProbe.ref
- zone.AvatarEvents = avatarProbe.ref
- zone.LocalEvents = localProbe.ref
+ val eventsProbe = new TestProbe(system)
+ val player1Probe = new TestProbe(system)
+ val player2Probe = new TestProbe(system)
val h_mine = Deployables.Make(DeployedItem.he_mine)().asInstanceOf[ExplosiveDeployable] //guid=2
- val player1 =
- Player(Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)) //guid=3
- player1.Spawn()
- val player2 =
- Player(Avatar(0, "TestCharacter2", PlanetSideEmpire.NC, CharacterSex.Male, 0, CharacterVoice.Mute)) //guid=4
- player2.Spawn()
+ val avatar1 = Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)
+ val player1 = Player(avatar1) //guid=3
+ val avatar2 = Avatar(0, "TestCharacter2", PlanetSideEmpire.NC, CharacterSex.Male, 0, CharacterVoice.Mute)
+ val player2 = Player(avatar2) //guid=4
val weapon = Tool(GlobalDefinitions.suppressor) //guid=5
+ val deployableList = new ListBuffer()
+ val zone = new Zone("test", new ZoneMap("test"), 0) {
+ private val deployables = system.actorOf(Props(classOf[ZoneDeployableActor], this, deployableList), name = "test-zone-deployables")
+
+ override def SetupNumberPools() = {}
+ GUID(guid)
+ override def Activity: ActorRef = eventsProbe.ref
+ override def AvatarEvents: ActorRef = eventsProbe.ref
+ override def LocalEvents: ActorRef = eventsProbe.ref
+ override def Deployables: ActorRef = deployables
+ override def Players = List(avatar1, avatar2)
+ override def LivePlayers = List(player1, player2)
+ override def tasks: ActorRef = eventsProbe.ref
+ }
+ player1.Spawn()
+ player1.Actor = player1Probe.ref
+ avatar2.deployables.AddOverLimit(h_mine) //cram it down your throat
+ player2.Spawn()
+ player2.Position = Vector3(10,0,0)
+ player2.Actor = player2Probe.ref
guid.register(h_mine, 2)
guid.register(player1, 3)
guid.register(player2, 4)
@@ -561,6 +543,10 @@ class ExplosiveDeployableDestructionTest extends ActorTest {
)
val applyDamageTo = resolved.calculate()
+ val activityProbe = TestProbe()
+ val avatarProbe = TestProbe()
+ val localProbe = TestProbe()
+
"ExplosiveDeployable" should {
"handle being destroyed" in {
h_mine.Health = h_mine.Definition.DamageDestroysAt + 1
@@ -568,58 +554,35 @@ class ExplosiveDeployableDestructionTest extends ActorTest {
assert(!h_mine.Destroyed)
h_mine.Actor ! Vitality.Damage(applyDamageTo)
- val msg_local = localProbe.receiveN(5, 200 milliseconds)
- val msg_avatar = avatarProbe.receiveOne(200 milliseconds)
- activityProbe.expectNoMessage(200 milliseconds)
- assert(
- msg_local.head match {
- case LocalServiceMessage("TestCharacter2", LocalAction.AlertDestroyDeployable(PlanetSideGUID(0), target)) =>
- target eq h_mine
- case _ => false
- }
- )
- assert(
- msg_local(1) match {
- case LocalServiceMessage(
- "NC",
- LocalAction.DeployableMapIcon(
- PlanetSideGUID(0),
- DeploymentAction.Dismiss,
- DeployableInfo(PlanetSideGUID(2), DeployableIcon.HEMine, _, PlanetSideGUID(0))
- )
- ) =>
- true
- case _ => false
- }
- )
- assert(
- msg_local(2) match {
- case LocalServiceMessage.Deployables(SupportActor.ClearSpecific(List(target), _zone)) =>
- (h_mine eq target) && (_zone eq zone)
- case _ => false
- }
- )
- assert(
- msg_local(3) match {
- case LocalServiceMessage.Deployables(RemoverActor.AddTask(target, _zone, _)) =>
- (target eq h_mine) && (_zone eq zone)
- case _ => false
- }
- )
- assert(
- msg_local(4) match {
- case LocalServiceMessage("test", LocalAction.TriggerEffect(_, "detonate_damaged_mine", PlanetSideGUID(2))) =>
- true
- case _ => false
- }
- )
- assert(
- msg_avatar match {
- case AvatarServiceMessage("test", AvatarAction.Destroy(PlanetSideGUID(2), _, Service.defaultPlayerGUID, _)) =>
- true
- case _ => false
- }
- )
+ val eventMsgs = eventsProbe.receiveN(4, 200 milliseconds)
+ player1Probe.expectNoMessage(200 milliseconds)
+ player2Probe.expectNoMessage(200 milliseconds)
+ eventMsgs.head match {
+ case LocalServiceMessage("TestCharacter2", LocalAction.DeployableUIFor(DeployedItem.he_mine)) => ;
+ case _ => assert(false, "")
+ }
+ eventMsgs(1) match {
+ case LocalServiceMessage(
+ "NC",
+ LocalAction.DeployableMapIcon(
+ PlanetSideGUID(0),
+ DeploymentAction.Dismiss,
+ DeployableInfo(PlanetSideGUID(2), DeployableIcon.HEMine, _, PlanetSideGUID(0))
+ )
+ ) => ;
+ case _ => assert(false, "")
+ }
+ eventMsgs(2) match {
+ case AvatarServiceMessage(
+ "test",
+ AvatarAction.Destroy(PlanetSideGUID(2), PlanetSideGUID(3), Service.defaultPlayerGUID, Vector3.Zero)
+ ) => ;
+ case _ => assert(false, "")
+ }
+ eventMsgs(3) match {
+ case LocalServiceMessage("test", LocalAction.TriggerEffect(_, "detonate_damaged_mine", PlanetSideGUID(2))) => ;
+ case _ => assert(false, "")
+ }
assert(h_mine.Health <= h_mine.Definition.DamageDestroysAt)
assert(h_mine.Destroyed)
}
diff --git a/src/test/scala/objects/EquipmentTest.scala b/src/test/scala/objects/EquipmentTest.scala
index 8c8bdf0c..9cb82776 100644
--- a/src/test/scala/objects/EquipmentTest.scala
+++ b/src/test/scala/objects/EquipmentTest.scala
@@ -383,7 +383,7 @@ class EquipmentTest extends Specification {
obj.AmmoType mustEqual DeployedItem.he_mine
}
- "when switching fire modes, ammo mode resets to the first entry" in {
+ "when switching fire modes, ammo mode should be maintained" in {
val obj: ConstructionItem = ConstructionItem(GlobalDefinitions.ace)
obj.NextFireMode
obj.AmmoType mustEqual DeployedItem.he_mine
@@ -392,8 +392,8 @@ class EquipmentTest extends Specification {
obj.NextFireMode //spitfire_turret
obj.NextFireMode //motionalarmsensor
obj.NextFireMode //boomer
- obj.NextFireMode
- obj.AmmoType mustEqual DeployedItem.he_mine
+ obj.NextFireMode //?
+ obj.AmmoType mustEqual DeployedItem.jammer_mine
}
"qualify certifications that must be met before ammo types may be used" in {
diff --git a/src/test/scala/objects/TelepadRouterTest.scala b/src/test/scala/objects/TelepadRouterTest.scala
new file mode 100644
index 00000000..683f5e37
--- /dev/null
+++ b/src/test/scala/objects/TelepadRouterTest.scala
@@ -0,0 +1,333 @@
+// Copyright (c) 2021 PSForever
+package objects
+
+import akka.actor.{ActorRef, Props}
+import akka.testkit.TestProbe
+import base.ActorTest
+import net.psforever.objects._
+import net.psforever.objects.ce.{DeployableCategory, DeployedItem, TelepadLike}
+import net.psforever.objects.guid.NumberPoolHub
+import net.psforever.objects.guid.source.MaxNumberSource
+import net.psforever.objects.serverobject.deploy.Deployment
+import net.psforever.objects.vehicles.{Utility, UtilityType, VehicleControl}
+import net.psforever.objects.zones.{Zone, ZoneDeployableActor, ZoneMap}
+import net.psforever.packet.game.objectcreate.ObjectCreateMessageParent
+import net.psforever.packet.game._
+import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
+import net.psforever.services.local.{LocalAction, LocalServiceMessage}
+import net.psforever.types.{DriveState, PlanetSideGUID, Vector3}
+
+import scala.collection.mutable.ListBuffer
+import scala.concurrent.duration._
+
+class TelepadDeployableNoRouterTest extends ActorTest {
+ val eventsProbe = new TestProbe(system)
+ val telepad = Deployables.Make(DeployedItem.router_telepad_deployable)() //guid=1
+ val deployableList = new ListBuffer()
+ val guid = new NumberPoolHub(new MaxNumberSource(max = 5))
+ val zone = new Zone(id = "test", new ZoneMap(name = "test"), zoneNumber = 0) {
+ private val deployables = system.actorOf(Props(classOf[ZoneDeployableActor], this, deployableList), name = "test-zone-deployables")
+
+ override def SetupNumberPools(): Unit = {}
+ GUID(guid)
+ override def AvatarEvents: ActorRef = eventsProbe.ref
+ override def LocalEvents: ActorRef = eventsProbe.ref
+ override def Deployables: ActorRef = deployables
+ }
+ guid.register(telepad, number = 1)
+
+ "TelepadDeployable" should {
+ "fail to activate without a router" in {
+ assert(deployableList.isEmpty, "no-router telepad deployable test - deployable list is not empty")
+ zone.Deployables ! Zone.Deployable.Build(telepad)
+
+ val eventsMsgs = eventsProbe.receiveN(4, 10.seconds)
+ eventsMsgs.head match {
+ case AvatarServiceMessage("test", AvatarAction.DeployItem(PlanetSideGUID(0), obj)) =>
+ assert(obj eq telepad, "no-router telepad deployable testt - not same telepad")
+ case _ =>
+ assert( false, "no-router telepad deployable test - wrong deploy message")
+ }
+ eventsMsgs(1) match {
+ case LocalServiceMessage(
+ "NEUTRAL",
+ LocalAction.DeployableMapIcon(
+ PlanetSideGUID(0),
+ DeploymentAction.Build,
+ DeployableInfo(PlanetSideGUID(1), DeployableIcon.RouterTelepad, Vector3.Zero, PlanetSideGUID(0))
+ )
+ ) => ;
+ case _ => assert(false, "no-router telepad deployable test - no icon or wrong icon")
+ }
+ eventsMsgs(2) match {
+ case LocalServiceMessage("test", LocalAction.EliminateDeployable(`telepad`, PlanetSideGUID(1), Vector3.Zero, 2)) => ;
+ case _ => assert(false, "no-router telepad deployable test - not eliminating deployable")
+ }
+ eventsMsgs(3) match {
+ case LocalServiceMessage(
+ "NEUTRAL",
+ LocalAction.DeployableMapIcon(
+ PlanetSideGUID(0),
+ DeploymentAction.Dismiss,
+ DeployableInfo(PlanetSideGUID(1), DeployableIcon.RouterTelepad, Vector3.Zero, PlanetSideGUID(0))
+ )
+ ) => ;
+ case _ => assert(false, "no-router telepad deployable test - no icon or wrong icon cleared")
+ }
+ assert(deployableList.isEmpty, "no-router telepad deployable test - deployable is being tracked")
+ }
+ }
+}
+
+class TelepadDeployableNoActivationTest extends ActorTest {
+ val eventsProbe = new TestProbe(system)
+ val routerProbe = new TestProbe(system)
+ val telepad = Deployables.Make(DeployedItem.router_telepad_deployable)() //guid=1
+ val router = Vehicle(GlobalDefinitions.router) //guid=2
+ val internal = router.Utility(UtilityType.internal_router_telepad_deployable).get //guid=3
+ val deployableList = new ListBuffer()
+ val guid = new NumberPoolHub(new MaxNumberSource(max = 5))
+ val zone = new Zone(id = "test", new ZoneMap(name = "test"), zoneNumber = 0) {
+ private val deployables = system.actorOf(Props(classOf[ZoneDeployableActor], this, deployableList), name = "test-zone-deployables")
+
+ override def SetupNumberPools(): Unit = {}
+ GUID(guid)
+ override def AvatarEvents: ActorRef = eventsProbe.ref
+ override def LocalEvents: ActorRef = eventsProbe.ref
+ override def Deployables: ActorRef = deployables
+ override def Vehicles: List[Vehicle] = List(router)
+ }
+ guid.register(telepad, number = 1)
+ guid.register(router, number = 2)
+ guid.register(internal, number = 3)
+ router.Actor = eventsProbe.ref
+ internal.Actor = routerProbe.ref
+
+ "TelepadDeployable" should {
+ "fail to activate without a connected router" in {
+ assert(deployableList.isEmpty, "no-activate telepad deployable test - deployable list is not empty")
+ zone.Deployables ! Zone.Deployable.Build(telepad)
+
+ val eventsMsgs = eventsProbe.receiveN(4, 10.seconds)
+ eventsMsgs.head match {
+ case AvatarServiceMessage("test", AvatarAction.DeployItem(PlanetSideGUID(0), obj)) =>
+ assert(obj eq telepad, "no-activate telepad deployable testt - not same telepad")
+ case _ =>
+ assert( false, "no-activate telepad deployable test - wrong deploy message")
+ }
+ eventsMsgs(1) match {
+ case LocalServiceMessage(
+ "NEUTRAL",
+ LocalAction.DeployableMapIcon(
+ PlanetSideGUID(0),
+ DeploymentAction.Build,
+ DeployableInfo(PlanetSideGUID(1), DeployableIcon.RouterTelepad, Vector3.Zero, PlanetSideGUID(0))
+ )
+ ) => ;
+ case _ =>
+ assert( false, "no-activate telepad deployable test - no icon or wrong icon")
+ }
+ eventsMsgs(2) match {
+ case LocalServiceMessage("test", LocalAction.EliminateDeployable(`telepad`, PlanetSideGUID(1), Vector3.Zero, 2)) => ;
+ case _ => assert(false, "no-activate telepad deployable test - not eliminating deployable")
+ }
+ eventsMsgs(3) match {
+ case LocalServiceMessage(
+ "NEUTRAL",
+ LocalAction.DeployableMapIcon(
+ PlanetSideGUID(0),
+ DeploymentAction.Dismiss,
+ DeployableInfo(PlanetSideGUID(1), DeployableIcon.RouterTelepad, Vector3.Zero, PlanetSideGUID(0))
+ )
+ ) => ;
+ case _ => assert(false, "no-activate telepad deployable test - no icon or wrong icon")
+ }
+ routerProbe.expectNoMessage(100.millisecond)
+ assert(deployableList.isEmpty, "no-activate telepad deployable test - deployable is being tracked")
+ }
+ }
+}
+
+class TelepadDeployableAttemptTest extends ActorTest {
+ val eventsProbe = new TestProbe(system)
+ val routerProbe = new TestProbe(system)
+ val telepad = new TelepadDeployable(TelepadRouterTest.router_telepad_deployable) //guid=1
+ val router = Vehicle(GlobalDefinitions.router) //guid=2
+ val internal = router.Utility(UtilityType.internal_router_telepad_deployable).get //guid=3
+ val deployableList = new ListBuffer()
+ val guid = new NumberPoolHub(new MaxNumberSource(max = 5))
+ val zone = new Zone(id = "test", new ZoneMap(name = "test"), zoneNumber = 0) {
+ private val deployables = system.actorOf(Props(classOf[ZoneDeployableActor], this, deployableList), name = "test-zone-deployables")
+
+ override def SetupNumberPools(): Unit = {}
+ GUID(guid)
+ override def AvatarEvents: ActorRef = eventsProbe.ref
+ override def LocalEvents: ActorRef = eventsProbe.ref
+ override def Deployables: ActorRef = deployables
+ override def Vehicles: List[Vehicle] = List(router)
+ }
+ guid.register(telepad, number = 1)
+ guid.register(router, number = 2)
+ guid.register(internal, number = 3)
+ router.Actor = eventsProbe.ref
+ internal.Actor = routerProbe.ref
+ telepad.Router = PlanetSideGUID(2) //artificial
+
+ "TelepadDeployable" should {
+ "attempt to link with a connected router" in {
+ assert(deployableList.isEmpty, "link attempt telepad deployable test - deployable list is not empty")
+ zone.Deployables ! Zone.Deployable.Build(telepad)
+
+ val eventsMsgs = eventsProbe.receiveN(2, 10.seconds)
+ val routerMsgs = routerProbe.receiveN(1, 10.seconds)
+ eventsMsgs.head match {
+ case AvatarServiceMessage("test", AvatarAction.DeployItem(PlanetSideGUID(0), obj)) =>
+ assert(obj eq telepad, "link attempt telepad deployable testt - not same telepad")
+ case _ =>
+ assert( false, "link attempt telepad deployable test - wrong deploy message")
+ }
+ eventsMsgs(1) match {
+ case LocalServiceMessage(
+ "NEUTRAL",
+ LocalAction.DeployableMapIcon(
+ PlanetSideGUID(0),
+ DeploymentAction.Build,
+ DeployableInfo(PlanetSideGUID(1), DeployableIcon.RouterTelepad, Vector3.Zero, PlanetSideGUID(0))
+ )
+ ) => ;
+ case _ => assert(false, "link attempt telepad deployable test - no icon or wrong icon")
+ }
+ routerMsgs.head match {
+ case TelepadLike.RequestLink(tpad) if tpad eq telepad => ;
+ case _ => assert(false, "link attempt telepad deployable test - did not try to link")
+ }
+ assert(deployableList.contains(telepad), "link attempt telepad deployable test - deployable list is not empty")
+ }
+ }
+}
+
+class TelepadDeployableResponseFromRouterTest extends ActorTest {
+ val eventsProbe = new TestProbe(system)
+ val telepad = new TelepadDeployable(TelepadRouterTest.router_telepad_deployable) //guid=1
+ val router = Vehicle(GlobalDefinitions.router) //guid=2
+ val internal = router
+ .Utility(UtilityType.internal_router_telepad_deployable)
+ .get
+ .asInstanceOf[Utility.InternalTelepad] //guid=3
+ val deployableList = new ListBuffer()
+ val guid = new NumberPoolHub(new MaxNumberSource(max = 5))
+ val zone = new Zone(id = "test", new ZoneMap(name = "test"), zoneNumber = 0) {
+ private val deployables = system.actorOf(Props(classOf[ZoneDeployableActor], this, deployableList), name = "test-zone-deployables")
+
+ override def SetupNumberPools(): Unit = {}
+ GUID(guid)
+ override def AvatarEvents: ActorRef = eventsProbe.ref
+ override def LocalEvents: ActorRef = eventsProbe.ref
+ override def VehicleEvents: ActorRef = eventsProbe.ref
+ override def Deployables: ActorRef = deployables
+ override def Vehicles: List[Vehicle] = List(router)
+ }
+ guid.register(telepad, number = 1)
+ guid.register(router, number = 2)
+ guid.register(internal, number = 3)
+ guid.register(router.Utility(UtilityType.teleportpad_terminal).get, number = 4) //necessary
+ router.Zone = zone
+ router.Actor = system.actorOf(Props(classOf[VehicleControl], router), "test-router")
+ telepad.Router = PlanetSideGUID(2) //artificial
+
+ "TelepadDeployable" should {
+ "link with a connected router" in {
+ assert(!telepad.Active, "link to router test - telepad active earlier than intended (1)")
+ assert(!internal.Active, "link to router test - router internals active earlier than intended")
+ router.Actor.tell(Deployment.TryDeploy(DriveState.Deploying), new TestProbe(system).ref)
+ eventsProbe.receiveN(10, 10.seconds) //flush all messages related to deployment
+ assert(!telepad.Active, "link to router test - telepad active earlier than intended (2)")
+ assert(internal.Active, "link to router test - router internals active not active when expected")
+
+ assert(deployableList.isEmpty, "link to router test - deployable list is not empty")
+ zone.Deployables ! Zone.Deployable.Build(telepad)
+
+ val eventsMsgs = eventsProbe.receiveN(9, 10.seconds)
+ eventsMsgs.head match {
+ case AvatarServiceMessage("test", AvatarAction.DeployItem(PlanetSideGUID(0), obj)) =>
+ assert(obj eq telepad, "link to router test - not same telepad")
+ case _ =>
+ assert( false, "link to router test - wrong deploy message")
+ }
+ eventsMsgs(1) match {
+ case LocalServiceMessage(
+ "NEUTRAL",
+ LocalAction.DeployableMapIcon(
+ PlanetSideGUID(0),
+ DeploymentAction.Build,
+ DeployableInfo(PlanetSideGUID(1), DeployableIcon.RouterTelepad, Vector3.Zero, PlanetSideGUID(0))
+ )
+ ) => ;
+ case _ => assert(false, "link to router test - no icon or wrong icon")
+ }
+ eventsMsgs(2) match {
+ case LocalServiceMessage(
+ "test",
+ LocalAction.SendResponse(
+ ObjectCreateMessage(_, 744, PlanetSideGUID(3), Some(ObjectCreateMessageParent(PlanetSideGUID(2), 2)), _)
+ )
+ ) => ;
+ case _ => assert(false, "link to router test - did not create the internal router telepad (1)")
+ }
+ eventsMsgs(3) match {
+ case LocalServiceMessage(
+ "test",
+ LocalAction.SendResponse(GenericObjectActionMessage(PlanetSideGUID(3), 27))
+ ) => ;
+ case _ => assert(false, "link to router test - did not create the internal router telepad (2)")
+ }
+ eventsMsgs(4) match {
+ case LocalServiceMessage(
+ "test",
+ LocalAction.SendResponse(GenericObjectActionMessage(PlanetSideGUID(3), 30))
+ ) => ;
+ case _ => assert(false, "link to router test - did not create the internal router telepad (3)")
+ }
+ eventsMsgs(5) match {
+ case LocalServiceMessage(
+ "test",
+ LocalAction.SendResponse(GenericObjectActionMessage(PlanetSideGUID(3), 27))
+ ) => ;
+ case _ => assert(false, "link to router test - did not link the internal telepad (1)")
+ }
+ eventsMsgs(6) match {
+ case LocalServiceMessage(
+ "test",
+ LocalAction.SendResponse(GenericObjectActionMessage(PlanetSideGUID(3), 28))
+ ) => ;
+ case _ => assert(false, "link to router test - did not link the internal telepad (2)")
+ }
+ eventsMsgs(7) match {
+ case LocalServiceMessage(
+ "test",
+ LocalAction.SendResponse(GenericObjectActionMessage(PlanetSideGUID(1), 27))
+ ) => ;
+ case _ => assert(false, "link to router test - did not link the telepad (1)")
+ }
+ eventsMsgs(8) match {
+ case LocalServiceMessage(
+ "test",
+ LocalAction.SendResponse(GenericObjectActionMessage(PlanetSideGUID(1), 28))
+ ) => ;
+ case _ => assert(false, "link to router test - did not link the telepad (2)")
+ }
+ assert(telepad.Active, "link to router test - telepad not active when expected")
+ assert(internal.Active, "link to router test - router internals active not active when expected (2)")
+ assert(deployableList.contains(telepad), "link to router test - deployable list is not empty")
+ }
+ }
+}
+
+object TelepadRouterTest {
+ val router_telepad_deployable = new TelepadDeployableDefinition(DeployedItem.router_telepad_deployable.id) {
+ Name = "test_telepad_dep"
+ DeployTime = Duration.create(1, "ms")
+ DeployCategory = DeployableCategory.Telepads
+ linkTime = 1.second
+ }
+}
diff --git a/src/test/scala/service/LocalServiceTest.scala b/src/test/scala/service/LocalServiceTest.scala
index fe03619c..774cc2d4 100644
--- a/src/test/scala/service/LocalServiceTest.scala
+++ b/src/test/scala/service/LocalServiceTest.scala
@@ -78,20 +78,6 @@ class LocalService5Test extends ActorTest {
}
}
-class AlertDestroyDeployableTest extends ActorTest {
- ServiceManager.boot(system)
- val obj = new SensorDeployable(GlobalDefinitions.motionalarmsensor)
-
- "LocalService" should {
- "pass AlertDestroyDeployable" in {
- val service = system.actorOf(Props(classOf[LocalService], Zone.Nowhere), "l_service")
- service ! Service.Join("test")
- service ! LocalServiceMessage("test", LocalAction.AlertDestroyDeployable(PlanetSideGUID(10), obj))
- expectMsg(LocalServiceResponse("/test/Local", PlanetSideGUID(0), LocalResponse.AlertDestroyDeployable(obj)))
- }
- }
-}
-
class DeployableMapIconTest extends ActorTest {
ServiceManager.boot(system)
diff --git a/src/test/scala/service/RouterTelepadActivationTest.scala b/src/test/scala/service/RouterTelepadActivationTest.scala
deleted file mode 100644
index e5b21f4a..00000000
--- a/src/test/scala/service/RouterTelepadActivationTest.scala
+++ /dev/null
@@ -1,175 +0,0 @@
-// Copyright (c) 2017 PSForever
-package service
-
-import akka.actor.Props
-import base.ActorTest
-import net.psforever.objects._
-import net.psforever.objects.zones.Zone
-import net.psforever.types.PlanetSideGUID
-import net.psforever.services.local.support.RouterTelepadActivation
-import net.psforever.services.support.SupportActor
-
-import scala.concurrent.duration._
-
-class RouterTelepadActivationTest extends ActorTest {
- "RouterTelepadActivation" should {
- "construct" in {
- system.actorOf(Props[RouterTelepadActivation](), "activation-test-actor")
- }
- }
-}
-
-class RouterTelepadActivationSimpleTest extends ActorTest {
- "RouterTelepadActivation" should {
- "handle a task" in {
- val telepad = new TelepadDeployable(GlobalDefinitions.router_telepad_deployable)
- telepad.GUID = PlanetSideGUID(1)
- val obj = system.actorOf(
- Props(classOf[ActorTest.SupportActorInterface], Props[RouterTelepadActivation](), self),
- "activation-test-actor"
- )
-
- obj ! RouterTelepadActivation.AddTask(telepad, Zone.Nowhere, Some(2 seconds))
- expectMsg(3 seconds, RouterTelepadActivation.ActivateTeleportSystem(telepad, Zone.Nowhere))
- }
- }
-}
-
-class RouterTelepadActivationComplexTest extends ActorTest {
- "RouterTelepadActivation" should {
- "handle multiple tasks" in {
- val telepad1 = new TelepadDeployable(GlobalDefinitions.router_telepad_deployable)
- telepad1.GUID = PlanetSideGUID(1)
- val telepad2 = new TelepadDeployable(GlobalDefinitions.router_telepad_deployable)
- telepad2.GUID = PlanetSideGUID(2)
- val telepad3 = new TelepadDeployable(GlobalDefinitions.router_telepad_deployable)
- telepad3.GUID = PlanetSideGUID(3)
- val obj = system.actorOf(
- Props(classOf[ActorTest.SupportActorInterface], Props[RouterTelepadActivation](), self),
- "activation-test-actor"
- )
-
- obj ! RouterTelepadActivation.AddTask(telepad1, Zone.Nowhere, Some(2 seconds))
- obj ! RouterTelepadActivation.AddTask(telepad2, Zone.Nowhere, Some(3 seconds))
- obj ! RouterTelepadActivation.AddTask(telepad3, Zone.Nowhere, Some(1 seconds))
- val msgs = receiveN(3, 5 seconds) //organized by duration
- assert(msgs.head.isInstanceOf[RouterTelepadActivation.ActivateTeleportSystem])
- assert(msgs.head.asInstanceOf[RouterTelepadActivation.ActivateTeleportSystem].telepad == telepad3)
- assert(msgs(1).isInstanceOf[RouterTelepadActivation.ActivateTeleportSystem])
- assert(msgs(1).asInstanceOf[RouterTelepadActivation.ActivateTeleportSystem].telepad == telepad1)
- assert(msgs(2).isInstanceOf[RouterTelepadActivation.ActivateTeleportSystem])
- assert(msgs(2).asInstanceOf[RouterTelepadActivation.ActivateTeleportSystem].telepad == telepad2)
- }
- }
-}
-
-class RouterTelepadActivationHurryTest extends ActorTest {
- "RouterTelepadActivation" should {
- "hurry specific tasks" in {
- val telepad1 = new TelepadDeployable(GlobalDefinitions.router_telepad_deployable)
- telepad1.GUID = PlanetSideGUID(1)
- val telepad2 = new TelepadDeployable(GlobalDefinitions.router_telepad_deployable)
- telepad2.GUID = PlanetSideGUID(2)
- val telepad3 = new TelepadDeployable(GlobalDefinitions.router_telepad_deployable)
- telepad3.GUID = PlanetSideGUID(3)
- val obj = system.actorOf(
- Props(classOf[ActorTest.SupportActorInterface], Props[RouterTelepadActivation](), self),
- "activation-test-actor"
- )
-
- obj ! RouterTelepadActivation.AddTask(telepad1, Zone.Nowhere, Some(2 seconds))
- obj ! RouterTelepadActivation.AddTask(telepad2, Zone.Nowhere, Some(2 seconds))
- obj ! RouterTelepadActivation.AddTask(telepad3, Zone.Nowhere, Some(2 seconds))
- obj ! SupportActor.HurrySpecific(List(telepad1, telepad2), Zone.Nowhere)
- val msgs = receiveN(2, 1 seconds)
- assert(msgs.head.isInstanceOf[RouterTelepadActivation.ActivateTeleportSystem])
- assert(msgs.head.asInstanceOf[RouterTelepadActivation.ActivateTeleportSystem].telepad == telepad1)
- assert(msgs(1).isInstanceOf[RouterTelepadActivation.ActivateTeleportSystem])
- assert(msgs(1).asInstanceOf[RouterTelepadActivation.ActivateTeleportSystem].telepad == telepad2)
- val last = receiveOne(3 seconds)
- assert(last.isInstanceOf[RouterTelepadActivation.ActivateTeleportSystem])
- assert(last.asInstanceOf[RouterTelepadActivation.ActivateTeleportSystem].telepad == telepad3)
- }
- }
-}
-
-class RouterTelepadActivationHurryAllTest extends ActorTest {
- "RouterTelepadActivation" should {
- "hurry all tasks" in {
- val telepad1 = new TelepadDeployable(GlobalDefinitions.router_telepad_deployable)
- telepad1.GUID = PlanetSideGUID(1)
- val telepad2 = new TelepadDeployable(GlobalDefinitions.router_telepad_deployable)
- telepad2.GUID = PlanetSideGUID(2)
- val telepad3 = new TelepadDeployable(GlobalDefinitions.router_telepad_deployable)
- telepad3.GUID = PlanetSideGUID(3)
- val obj = system.actorOf(
- Props(classOf[ActorTest.SupportActorInterface], Props[RouterTelepadActivation](), self),
- "activation-test-actor"
- )
-
- obj ! RouterTelepadActivation.AddTask(telepad1, Zone.Nowhere, Some(7 seconds))
- obj ! RouterTelepadActivation.AddTask(telepad2, Zone.Nowhere, Some(5 seconds))
- obj ! RouterTelepadActivation.AddTask(telepad3, Zone.Nowhere, Some(6 seconds))
- obj ! SupportActor.HurryAll()
- val msgs =
- receiveN(
- 3,
- 4 seconds
- ) //organized by duration; note: all messages received before the earliest task should be performed
- assert(msgs.head.isInstanceOf[RouterTelepadActivation.ActivateTeleportSystem])
- assert(msgs.head.asInstanceOf[RouterTelepadActivation.ActivateTeleportSystem].telepad == telepad2)
- assert(msgs(1).isInstanceOf[RouterTelepadActivation.ActivateTeleportSystem])
- assert(msgs(1).asInstanceOf[RouterTelepadActivation.ActivateTeleportSystem].telepad == telepad3)
- assert(msgs(2).isInstanceOf[RouterTelepadActivation.ActivateTeleportSystem])
- assert(msgs(2).asInstanceOf[RouterTelepadActivation.ActivateTeleportSystem].telepad == telepad1)
- }
- }
-}
-
-class RouterTelepadActivationClearTest extends ActorTest {
- "RouterTelepadActivation" should {
- "clear specific tasks" in {
- val telepad1 = new TelepadDeployable(GlobalDefinitions.router_telepad_deployable)
- telepad1.GUID = PlanetSideGUID(1)
- val telepad2 = new TelepadDeployable(GlobalDefinitions.router_telepad_deployable)
- telepad2.GUID = PlanetSideGUID(2)
- val telepad3 = new TelepadDeployable(GlobalDefinitions.router_telepad_deployable)
- telepad3.GUID = PlanetSideGUID(3)
- val obj = system.actorOf(
- Props(classOf[ActorTest.SupportActorInterface], Props[RouterTelepadActivation](), self),
- "activation-test-actor"
- )
-
- obj ! RouterTelepadActivation.AddTask(telepad1, Zone.Nowhere, Some(2 seconds))
- obj ! RouterTelepadActivation.AddTask(telepad2, Zone.Nowhere, Some(2 seconds))
- obj ! RouterTelepadActivation.AddTask(telepad3, Zone.Nowhere, Some(2 seconds))
- obj ! SupportActor.ClearSpecific(List(telepad1, telepad2), Zone.Nowhere)
- val msgs = receiveN(1, 3 seconds) //should only receive telepad3
- assert(msgs.head.isInstanceOf[RouterTelepadActivation.ActivateTeleportSystem])
- assert(msgs.head.asInstanceOf[RouterTelepadActivation.ActivateTeleportSystem].telepad == telepad3)
- }
- }
-}
-
-class RouterTelepadActivationClearAllTest extends ActorTest {
- "RouterTelepadActivation" should {
- "clear all tasks" in {
- val telepad1 = new TelepadDeployable(GlobalDefinitions.router_telepad_deployable)
- telepad1.GUID = PlanetSideGUID(1)
- val telepad2 = new TelepadDeployable(GlobalDefinitions.router_telepad_deployable)
- telepad2.GUID = PlanetSideGUID(2)
- val telepad3 = new TelepadDeployable(GlobalDefinitions.router_telepad_deployable)
- telepad3.GUID = PlanetSideGUID(3)
- val obj = system.actorOf(
- Props(classOf[ActorTest.SupportActorInterface], Props[RouterTelepadActivation](), self),
- "activation-test-actor"
- )
-
- obj ! RouterTelepadActivation.AddTask(telepad1, Zone.Nowhere, Some(2 seconds))
- obj ! RouterTelepadActivation.AddTask(telepad2, Zone.Nowhere, Some(2 seconds))
- obj ! RouterTelepadActivation.AddTask(telepad3, Zone.Nowhere, Some(2 seconds))
- obj ! SupportActor.ClearAll()
- expectNoMessage(4 seconds)
- }
- }
-}