mirror of
https://github.com/2revoemag/PSF-BotServer.git
synced 2026-01-19 18:14:44 +00:00
Deployable Behaviors (#840)
* unifying the split code pathways that separated telepads from other deloyables; in other words, no more SimpleDeployables and ComplexDeployables, just Deployables * moved some aspects of the build logic into a deployable control mixin; aspects governing the deplpoyable toolbox have been transferred into the player control agency * moving aspects of teleportation system establishment and decomposition into specialized Telepad control agencies * retiring deployable disposal code path that required a dedicated remover; each deployable now handles its own removal, and some do special things when being removed; process still has some rough edges and tests are probably thoroughly broken * additional modifications to support boomers and telepads; consolidation of code for deployable acknowledgement by owner and during failure conditions; tests for behavior * retooled a significant portion of the build sequence and deconstruct sequence to: eliminate duplicate messages, give the player more input to and control over the process, remove undue responsibility thrust on SessionActor * messaging issue where player did not re-raise hand after exchanging a used construction tool for a new construction tool * modification to deconstruct path to make certain deplayble is unregistered last; ridding requirement of AlertDestroyDeployable; fixing test * create paths for unowned deployable building and (standard) owned deployable building; corrected activation and connection between telepad deployable and internal roouter telepad; wrote tests for connection between telepad deployable and internal telepad * modifiying the conditions of a deployable construction item being moved into a visible player slot such that the construction item's initial output is valid given the player's current certifications * by forcing the fire mode to revert briefly before the ammo type updates, the construction item can be made to remain consistent between fire mode shifts * construction tools now keep track of fire mode ammo types for a period of time, allowing one mode's last setting to be retained * greatly delayed rebase with master * minor changes; test correction (?) * router is go?
This commit is contained in:
parent
7b4f955cbf
commit
2f9c4a7cf2
File diff suppressed because it is too large
Load diff
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 => ;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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 _ => ;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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.<br>
|
||||
* <br>
|
||||
* 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."<br>
|
||||
* <br>
|
||||
* 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.<br>
|
||||
* <br>
|
||||
* 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.<br>
|
||||
* <br>
|
||||
* 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`<br>
|
||||
* `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"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 _ =>
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.)<br>
|
||||
* <br>
|
||||
* 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.<br>
|
||||
* <br>
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
316
src/main/scala/net/psforever/objects/ce/DeployableBehavior.scala
Normal file
316
src/main/scala/net/psforever/objects/ce/DeployableBehavior.scala
Normal file
|
|
@ -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.<br>
|
||||
* <br>
|
||||
* 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.<br>
|
||||
* <br>
|
||||
* 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()
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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.<br>
|
||||
* <br>
|
||||
* 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 _ => ;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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"))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.<br>
|
||||
* <br>
|
||||
* 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] {
|
||||
|
|
|
|||
|
|
@ -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 _ => ;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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.<br>
|
||||
* <br>
|
||||
* 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 => ;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -785,8 +785,6 @@ object Zones {
|
|||
60 seconds
|
||||
case _: DeployableSource =>
|
||||
60 seconds
|
||||
case _: ComplexDeployableSource =>
|
||||
60 seconds
|
||||
case _ =>
|
||||
0 seconds
|
||||
}
|
||||
|
|
|
|||
353
src/test/scala/objects/DeployableBehaviorTest.scala
Normal file
353
src/test/scala/objects/DeployableBehaviorTest.scala
Normal file
|
|
@ -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 {
|
||||
//...
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
333
src/test/scala/objects/TelepadRouterTest.scala
Normal file
333
src/test/scala/objects/TelepadRouterTest.scala
Normal file
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue