mirror of
https://github.com/2revoemag/PSF-BotServer.git
synced 2026-07-14 07:55:07 +00:00
Not Really a Door Opener (#1063)
* the medical applicator will not long open doors from a distance unless we want it to do that * fixing tests
This commit is contained in:
parent
f448cad13f
commit
5b0203850d
8 changed files with 222 additions and 127 deletions
|
|
@ -177,6 +177,8 @@ game {
|
||||||
variable = 30
|
variable = 30
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
doors-can-be-opened-by-med-app-from-this-distance = 5.05
|
||||||
}
|
}
|
||||||
|
|
||||||
anti-cheat {
|
anti-cheat {
|
||||||
|
|
|
||||||
|
|
@ -540,7 +540,7 @@ class SessionData(
|
||||||
}
|
}
|
||||||
validObject(pkt.object_guid, decorator = "UseItem") match {
|
validObject(pkt.object_guid, decorator = "UseItem") match {
|
||||||
case Some(door: Door) =>
|
case Some(door: Door) =>
|
||||||
handleUseDoor(door)
|
handleUseDoor(door, equipment)
|
||||||
case Some(resourceSilo: ResourceSilo) =>
|
case Some(resourceSilo: ResourceSilo) =>
|
||||||
handleUseResourceSilo(resourceSilo, equipment)
|
handleUseResourceSilo(resourceSilo, equipment)
|
||||||
case Some(panel: IFFLock) =>
|
case Some(panel: IFFLock) =>
|
||||||
|
|
@ -1245,8 +1245,17 @@ class SessionData(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private def handleUseDoor(door: Door): Unit = {
|
private def handleUseDoor(door: Door, equipment: Option[Equipment]): Unit = {
|
||||||
door.Actor ! CommonMessages.Use(player)
|
equipment match {
|
||||||
|
case Some(tool: Tool) if tool.Definition == GlobalDefinitions.medicalapplicator =>
|
||||||
|
val distance: Float = math.max(
|
||||||
|
Config.app.game.doorsCanBeOpenedByMedAppFromThisDistance,
|
||||||
|
door.Definition.initialOpeningDistance
|
||||||
|
)
|
||||||
|
door.Actor ! CommonMessages.Use(player, Some(distance))
|
||||||
|
case _ =>
|
||||||
|
door.Actor ! CommonMessages.Use(player)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private def handleUseResourceSilo(resourceSilo: ResourceSilo, equipment: Option[Equipment]): Unit = {
|
private def handleUseResourceSilo(resourceSilo: ResourceSilo, equipment: Option[Equipment]): Unit = {
|
||||||
|
|
|
||||||
81
src/main/scala/net/psforever/objects/Doors.scala
Normal file
81
src/main/scala/net/psforever/objects/Doors.scala
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
// Copyright (c) 2023 PSForever
|
||||||
|
package net.psforever.objects
|
||||||
|
|
||||||
|
import net.psforever.objects.serverobject.doors.Door
|
||||||
|
import net.psforever.types.Vector3
|
||||||
|
|
||||||
|
object Doors {
|
||||||
|
/**
|
||||||
|
* If a player was considered as holding the door open,
|
||||||
|
* check that if that player is still in the same zone as a door
|
||||||
|
* and is still standing within a certain distance of that door.
|
||||||
|
* If the target player no longer qualifies to hold the door open,
|
||||||
|
* search for a new player to hold the door open.
|
||||||
|
* If the target player did not initially exist (the door was closed),
|
||||||
|
* do not search any further.
|
||||||
|
* @param door door that is installed somewhere
|
||||||
|
* @param maximumDistance permissible square distance between the player and the door
|
||||||
|
* @return optional player;
|
||||||
|
* `None` if the no player can trigger the door
|
||||||
|
*/
|
||||||
|
def testForTargetHoldingDoorOpen(
|
||||||
|
door: Door,
|
||||||
|
maximumDistance: Float
|
||||||
|
): Option[Player] = {
|
||||||
|
door.Open
|
||||||
|
.flatMap {
|
||||||
|
testForSpecificTargetHoldingDoorOpen(_, door, maximumDistance * maximumDistance)
|
||||||
|
.orElse { testForAnyTargetHoldingDoorOpen(door, maximumDistance) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check that a player is in the same zone as a door
|
||||||
|
* and is standing within a certain distance of that door.
|
||||||
|
* @see `Vector3.MagnitudeSquared`
|
||||||
|
* @param player player who is standing somewhere
|
||||||
|
* @param door door that is installed somewhere
|
||||||
|
* @param maximumDistance permissible square distance between the player and the door
|
||||||
|
* before one can not influence the other
|
||||||
|
* @return optional player (same as parameter);
|
||||||
|
* `None` if the parameter player can not trigger the door
|
||||||
|
*/
|
||||||
|
def testForSpecificTargetHoldingDoorOpen(
|
||||||
|
player: Player,
|
||||||
|
door: Door,
|
||||||
|
maximumDistance: Float
|
||||||
|
): Option[Player] = {
|
||||||
|
if (player.Zone == door.Zone && Vector3.MagnitudeSquared(door.Position - player.Position) <= maximumDistance) {
|
||||||
|
Some(player)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find a player, any player, that can hold the door open.
|
||||||
|
* Prop it open with a dead body if no one is available.
|
||||||
|
* @param door door that is installed somewhere
|
||||||
|
* @param maximumDistance permissible square distance between a player and the door
|
||||||
|
* before one can not influence the other
|
||||||
|
* @return optional player;
|
||||||
|
* `None` if no player can trigger the door
|
||||||
|
*/
|
||||||
|
private def testForAnyTargetHoldingDoorOpen(
|
||||||
|
door: Door,
|
||||||
|
maximumDistance: Float
|
||||||
|
): Option[Player] = {
|
||||||
|
//search for nearby players and nearby former players who would block open the door
|
||||||
|
val zone = door.Zone
|
||||||
|
val maximumDistanceSq = maximumDistance * maximumDistance
|
||||||
|
val bmap = zone.blockMap.sector(door.Position, maximumDistance)
|
||||||
|
(bmap.livePlayerList ++ bmap.corpseList)
|
||||||
|
.find { testForSpecificTargetHoldingDoorOpen(_, door, maximumDistanceSq).nonEmpty } match {
|
||||||
|
case out @ Some(newOpener) =>
|
||||||
|
door.Open = newOpener //another player is near the door, keep it open
|
||||||
|
out
|
||||||
|
case _ =>
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
// Copyright (c) 2017 PSForever
|
// Copyright (c) 2017 PSForever
|
||||||
package net.psforever.objects.serverobject.doors
|
package net.psforever.objects.serverobject.doors
|
||||||
|
|
||||||
import net.psforever.objects.Player
|
import akka.actor.ActorRef
|
||||||
|
import net.psforever.objects.{Default, Doors, Player}
|
||||||
import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject}
|
import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject}
|
||||||
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
|
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
|
||||||
import net.psforever.objects.serverobject.locks.IFFLock
|
import net.psforever.objects.serverobject.locks.IFFLock
|
||||||
|
|
@ -17,8 +18,9 @@ class DoorControl(door: Door)
|
||||||
extends PoweredAmenityControl
|
extends PoweredAmenityControl
|
||||||
with FactionAffinityBehavior.Check {
|
with FactionAffinityBehavior.Check {
|
||||||
def FactionObject: FactionAffinity = door
|
def FactionObject: FactionAffinity = door
|
||||||
var isLocked: Boolean = false
|
|
||||||
var lockingMechanism: Door.LockingMechanismLogic = DoorControl.alwaysOpen
|
private var isLocked: Boolean = false
|
||||||
|
private var lockingMechanism: Door.LockingMechanismLogic = DoorControl.alwaysOpen
|
||||||
|
|
||||||
def commonBehavior: Receive = checkBehavior
|
def commonBehavior: Receive = checkBehavior
|
||||||
.orElse {
|
.orElse {
|
||||||
|
|
@ -40,15 +42,16 @@ class DoorControl(door: Door)
|
||||||
def poweredStateLogic: Receive =
|
def poweredStateLogic: Receive =
|
||||||
commonBehavior
|
commonBehavior
|
||||||
.orElse {
|
.orElse {
|
||||||
|
case CommonMessages.Use(player, Some(customDistance: Float)) =>
|
||||||
|
testToOpenDoor(player, door, customDistance, sender())
|
||||||
|
|
||||||
case CommonMessages.Use(player, _) =>
|
case CommonMessages.Use(player, _) =>
|
||||||
if (lockingMechanism(player, door) && !isLocked) {
|
testToOpenDoor(player, door, door.Definition.initialOpeningDistance, sender())
|
||||||
openDoor(player)
|
|
||||||
}
|
|
||||||
|
|
||||||
case IFFLock.DoorOpenResponse(target: Player) if !isLocked =>
|
case IFFLock.DoorOpenResponse(target: Player) if !isLocked =>
|
||||||
openDoor(target)
|
DoorControl.openDoor(target, door)
|
||||||
|
|
||||||
case _ => ;
|
case _ => ()
|
||||||
}
|
}
|
||||||
|
|
||||||
def unpoweredStateLogic: Receive = {
|
def unpoweredStateLogic: Receive = {
|
||||||
|
|
@ -56,30 +59,33 @@ class DoorControl(door: Door)
|
||||||
.orElse {
|
.orElse {
|
||||||
case CommonMessages.Use(player, _) if !isLocked =>
|
case CommonMessages.Use(player, _) if !isLocked =>
|
||||||
//without power, the door opens freely
|
//without power, the door opens freely
|
||||||
openDoor(player)
|
DoorControl.openDoor(player, door)
|
||||||
|
|
||||||
case _ => ;
|
case _ => ()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def openDoor(player: Player): Unit = {
|
/**
|
||||||
val zone = door.Zone
|
* If the player is close enough to the door,
|
||||||
val doorGUID = door.GUID
|
* the locking mechanism allows for the door to open,
|
||||||
if (!door.isOpen) {
|
* and the door is not bolted shut (locked),
|
||||||
//global open
|
* then tell the door that it should open.
|
||||||
door.Open = player
|
* @param player player who is standing somewhere
|
||||||
zone.LocalEvents ! LocalServiceMessage(
|
* @param door door that is installed somewhere
|
||||||
zone.id,
|
* @param maximumDistance permissible square distance between the player and the door
|
||||||
LocalAction.DoorOpens(Service.defaultPlayerGUID, zone, door)
|
* @param replyTo the player's session message reference
|
||||||
)
|
*/
|
||||||
}
|
private def testToOpenDoor(
|
||||||
else {
|
player: Player,
|
||||||
//the door should already open, but the requesting player does not see it as open
|
door: Door,
|
||||||
sender() ! LocalServiceResponse(
|
maximumDistance: Float,
|
||||||
player.Name,
|
replyTo: ActorRef
|
||||||
Service.defaultPlayerGUID,
|
): Unit = {
|
||||||
LocalResponse.DoorOpens(doorGUID)
|
if (
|
||||||
)
|
Doors.testForSpecificTargetHoldingDoorOpen(player, door, maximumDistance * maximumDistance).contains(player) &&
|
||||||
|
lockingMechanism(player, door) && !isLocked
|
||||||
|
) {
|
||||||
|
DoorControl.openDoor(player, door, replyTo)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -89,5 +95,33 @@ class DoorControl(door: Door)
|
||||||
}
|
}
|
||||||
|
|
||||||
object DoorControl {
|
object DoorControl {
|
||||||
|
//noinspection ScalaUnusedSymbol
|
||||||
def alwaysOpen(obj: PlanetSideServerObject, door: Door): Boolean = true
|
def alwaysOpen(obj: PlanetSideServerObject, door: Door): Boolean = true
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the door is not open, open this door, propped open by the given player.
|
||||||
|
* If the door is considered open, ensure the door is proper visible as open to the player.
|
||||||
|
* @param player the player
|
||||||
|
* @param door the door
|
||||||
|
* @param replyTo the player's session message reference
|
||||||
|
*/
|
||||||
|
private def openDoor(player: Player, door: Door, replyTo: ActorRef = Default.Actor): Unit = {
|
||||||
|
val zone = door.Zone
|
||||||
|
val doorGUID = door.GUID
|
||||||
|
if (!door.isOpen) {
|
||||||
|
//global open
|
||||||
|
door.Open = player
|
||||||
|
zone.LocalEvents ! LocalServiceMessage(
|
||||||
|
zone.id,
|
||||||
|
LocalAction.DoorOpens(Service.defaultPlayerGUID, zone, door)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
//the door should already open, but the requesting player does not see it as open
|
||||||
|
replyTo ! LocalServiceResponse(
|
||||||
|
player.Name,
|
||||||
|
Service.defaultPlayerGUID,
|
||||||
|
LocalResponse.DoorOpens(doorGUID)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,4 +9,9 @@ import net.psforever.objects.serverobject.structures.AmenityDefinition
|
||||||
class DoorDefinition(objectId: Int)
|
class DoorDefinition(objectId: Int)
|
||||||
extends AmenityDefinition(objectId) {
|
extends AmenityDefinition(objectId) {
|
||||||
Name = "door"
|
Name = "door"
|
||||||
|
/** range wherein the door may first be opened
|
||||||
|
* (note: intentionally inflated as the initial check on the client occurs further than expected) */
|
||||||
|
var initialOpeningDistance: Float = 7.5f
|
||||||
|
/** range within which the door must detect a target player to remain open */
|
||||||
|
var continuousOpenDistance: Float = 5.05f
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,10 @@
|
||||||
package net.psforever.services.local.support
|
package net.psforever.services.local.support
|
||||||
|
|
||||||
import akka.actor.{Actor, Cancellable}
|
import akka.actor.{Actor, Cancellable}
|
||||||
import net.psforever.objects.{Default, Player}
|
import net.psforever.objects.{Default, Doors}
|
||||||
import net.psforever.objects.serverobject.doors.Door
|
import net.psforever.objects.serverobject.doors.Door
|
||||||
import net.psforever.objects.serverobject.structures.Building
|
|
||||||
import net.psforever.objects.zones.Zone
|
import net.psforever.objects.zones.Zone
|
||||||
import net.psforever.types.{PlanetSideGUID, Vector3}
|
import net.psforever.types.PlanetSideGUID
|
||||||
|
|
||||||
import scala.annotation.tailrec
|
import scala.annotation.tailrec
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
|
|
@ -17,7 +16,6 @@ import scala.concurrent.duration._
|
||||||
* @see `LocalService`
|
* @see `LocalService`
|
||||||
*/
|
*/
|
||||||
class DoorCloseActor() extends Actor {
|
class DoorCloseActor() extends Actor {
|
||||||
|
|
||||||
/** The periodic `Executor` that checks for doors to be closed */
|
/** The periodic `Executor` that checks for doors to be closed */
|
||||||
private var doorCloserTrigger: Cancellable = Default.Cancellable
|
private var doorCloserTrigger: Cancellable = Default.Cancellable
|
||||||
|
|
||||||
|
|
@ -36,43 +34,11 @@ class DoorCloseActor() extends Actor {
|
||||||
|
|
||||||
case DoorCloseActor.TryCloseDoors() =>
|
case DoorCloseActor.TryCloseDoors() =>
|
||||||
doorCloserTrigger.cancel()
|
doorCloserTrigger.cancel()
|
||||||
val now: Long = System.nanoTime
|
val now: Long = System.currentTimeMillis()
|
||||||
val (doorsToClose1, doorsLeftOpen1) = PartitionEntries(openDoors, now)
|
val (doorsToClose1, doorsLeftOpen1) = PartitionEntries(openDoors, now)
|
||||||
val (doorsToClose2, doorsLeftOpen2) = doorsToClose1.partition(entry => {
|
val (doorsToClose2, doorsLeftOpen2) = doorsToClose1.partition { entry =>
|
||||||
entry.door.Open match {
|
Doors.testForTargetHoldingDoorOpen(entry.door, entry.door.Definition.continuousOpenDistance).isEmpty
|
||||||
case Some(player) =>
|
}
|
||||||
// If the player that opened the door is far enough away, or they're dead,
|
|
||||||
var openerIsGone = Vector3.MagnitudeSquared(entry.door.Position - player.Position) > 25.5 || !player.isAlive
|
|
||||||
|
|
||||||
if (openerIsGone) {
|
|
||||||
// Check nobody else is nearby to hold the door opens
|
|
||||||
val playersToCheck: List[Player] =
|
|
||||||
if (
|
|
||||||
entry.door.Owner
|
|
||||||
.isInstanceOf[Building] && entry.door.Owner.asInstanceOf[Building].Definition.SOIRadius > 0
|
|
||||||
) {
|
|
||||||
entry.door.Owner.asInstanceOf[Building].PlayersInSOI
|
|
||||||
} else {
|
|
||||||
entry.zone.LivePlayers
|
|
||||||
}
|
|
||||||
|
|
||||||
playersToCheck
|
|
||||||
.filter(x => x.isAlive && Vector3.MagnitudeSquared(entry.door.Position - x.Position) < 25.5)
|
|
||||||
.headOption match {
|
|
||||||
case Some(newOpener) =>
|
|
||||||
// Another player is near the door, keep it open
|
|
||||||
entry.door.Open = newOpener
|
|
||||||
openerIsGone = false
|
|
||||||
case _ => ;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
openerIsGone
|
|
||||||
case None =>
|
|
||||||
// Door should not be open. Mark it to be closed.
|
|
||||||
true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
openDoors = (
|
openDoors = (
|
||||||
doorsLeftOpen1 ++
|
doorsLeftOpen1 ++
|
||||||
doorsLeftOpen2.map(entry => DoorCloseActor.DoorEntry(entry.door, entry.zone, now))
|
doorsLeftOpen2.map(entry => DoorCloseActor.DoorEntry(entry.door, entry.zone, now))
|
||||||
|
|
@ -89,7 +55,7 @@ class DoorCloseActor() extends Actor {
|
||||||
doorCloserTrigger = context.system.scheduler.scheduleOnce(short_timeout, self, DoorCloseActor.TryCloseDoors())
|
doorCloserTrigger = context.system.scheduler.scheduleOnce(short_timeout, self, DoorCloseActor.TryCloseDoors())
|
||||||
}
|
}
|
||||||
|
|
||||||
case _ => ;
|
case _ => ()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -144,9 +110,9 @@ class DoorCloseActor() extends Actor {
|
||||||
object DoorCloseActor {
|
object DoorCloseActor {
|
||||||
|
|
||||||
/** The wait before an open door closes; as a Long for calculation simplicity */
|
/** The wait before an open door closes; as a Long for calculation simplicity */
|
||||||
private final val timeout_time: Long = 5000000000L //nanoseconds (5s)
|
private final val timeout_time: Long = 5000L //milliseconds (5s)
|
||||||
/** The wait before an open door closes; as a `FiniteDuration` for `Executor` simplicity */
|
/** The wait before an open door closes; as a `FiniteDuration` for `Executor` simplicity */
|
||||||
private final val timeout: FiniteDuration = timeout_time nanoseconds
|
private final val timeout: FiniteDuration = timeout_time milliseconds
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Message that carries information about a door that has been opened.
|
* Message that carries information about a door that has been opened.
|
||||||
|
|
@ -155,7 +121,7 @@ object DoorCloseActor {
|
||||||
* @param time when the door was opened
|
* @param time when the door was opened
|
||||||
* @see `DoorEntry`
|
* @see `DoorEntry`
|
||||||
*/
|
*/
|
||||||
final case class DoorIsOpen(door: Door, zone: Zone, time: Long = System.nanoTime())
|
final case class DoorIsOpen(door: Door, zone: Zone, time: Long = System.currentTimeMillis())
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Message that carries information about a door that needs to close.
|
* Message that carries information about a door that needs to close.
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ object Config {
|
||||||
viaNonEmptyStringOpt[A](
|
viaNonEmptyStringOpt[A](
|
||||||
v =>
|
v =>
|
||||||
e.values.toList.collectFirst {
|
e.values.toList.collectFirst {
|
||||||
case e if e.toString.toLowerCase == v.toLowerCase => e.asInstanceOf[A]
|
case e if e.toString.toLowerCase == v.toLowerCase => e
|
||||||
},
|
},
|
||||||
_.toString
|
_.toString
|
||||||
)
|
)
|
||||||
|
|
@ -62,25 +62,23 @@ object Config {
|
||||||
// Raw config object - prefer app when possible
|
// Raw config object - prefer app when possible
|
||||||
lazy val config: TypesafeConfig = source.config() match {
|
lazy val config: TypesafeConfig = source.config() match {
|
||||||
case Right(config) => config
|
case Right(config) => config
|
||||||
case Left(failures) => {
|
case Left(failures) =>
|
||||||
logger.error("Loading config failed")
|
logger.error("Loading config failed")
|
||||||
failures.toList.foreach { failure =>
|
failures.toList.foreach { failure =>
|
||||||
logger.error(failure.toString)
|
logger.error(failure.toString)
|
||||||
}
|
}
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Typed config object
|
// Typed config object
|
||||||
lazy val app: AppConfig = source.load[AppConfig] match {
|
lazy val app: AppConfig = source.load[AppConfig] match {
|
||||||
case Right(config) => config
|
case Right(config) => config
|
||||||
case Left(failures) => {
|
case Left(failures) =>
|
||||||
logger.error("Loading config failed")
|
logger.error("Loading config failed")
|
||||||
failures.toList.foreach { failure =>
|
failures.toList.foreach { failure =>
|
||||||
logger.error(failure.toString)
|
logger.error(failure.toString)
|
||||||
}
|
}
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -159,7 +157,8 @@ case class GameConfig(
|
||||||
baseCertifications: Seq[Certification],
|
baseCertifications: Seq[Certification],
|
||||||
warpGates: WarpGateConfig,
|
warpGates: WarpGateConfig,
|
||||||
cavernRotation: CavernRotationConfig,
|
cavernRotation: CavernRotationConfig,
|
||||||
savedMsg: SavedMessageEvents
|
savedMsg: SavedMessageEvents,
|
||||||
|
doorsCanBeOpenedByMedAppFromThisDistance: Float
|
||||||
)
|
)
|
||||||
|
|
||||||
case class NewAvatar(
|
case class NewAvatar(
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
// Copyright (c) 2017 PSForever
|
// Copyright (c) 2017 PSForever
|
||||||
package objects
|
package objects
|
||||||
|
|
||||||
import akka.actor.{ActorSystem, Props}
|
import akka.actor.{ActorRef, ActorSystem, Props}
|
||||||
import akka.testkit.TestProbe
|
import akka.testkit.TestProbe
|
||||||
import base.ActorTest
|
import base.ActorTest
|
||||||
import net.psforever.objects.avatar.Avatar
|
import net.psforever.objects.avatar.Avatar
|
||||||
|
|
@ -12,15 +12,14 @@ import net.psforever.objects.{Default, GlobalDefinitions, Player}
|
||||||
import net.psforever.objects.serverobject.doors.{Door, DoorControl}
|
import net.psforever.objects.serverobject.doors.{Door, DoorControl}
|
||||||
import net.psforever.objects.serverobject.structures.{Building, StructureType}
|
import net.psforever.objects.serverobject.structures.{Building, StructureType}
|
||||||
import net.psforever.objects.zones.{Zone, ZoneMap}
|
import net.psforever.objects.zones.{Zone, ZoneMap}
|
||||||
import net.psforever.packet.game.UseItemMessage
|
import net.psforever.services.local.{LocalAction, LocalResponse, LocalServiceMessage, LocalServiceResponse}
|
||||||
import net.psforever.services.local.{LocalAction, LocalServiceMessage}
|
|
||||||
import net.psforever.types._
|
import net.psforever.types._
|
||||||
import org.specs2.mutable.Specification
|
import org.specs2.mutable.Specification
|
||||||
|
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
|
|
||||||
class DoorTest extends Specification {
|
class DoorTest extends Specification {
|
||||||
val player = Player(Avatar(0, "test", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute))
|
private val player: Player = Player(Avatar(0, "test", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute))
|
||||||
|
|
||||||
"Door" should {
|
"Door" should {
|
||||||
"construct" in {
|
"construct" in {
|
||||||
|
|
@ -49,19 +48,6 @@ class DoorTest extends Specification {
|
||||||
}
|
}
|
||||||
|
|
||||||
"be opened and closed (2; toggle)" in {
|
"be opened and closed (2; toggle)" in {
|
||||||
val msg = UseItemMessage(
|
|
||||||
PlanetSideGUID(6585),
|
|
||||||
PlanetSideGUID(0),
|
|
||||||
PlanetSideGUID(372),
|
|
||||||
4294967295L,
|
|
||||||
false,
|
|
||||||
Vector3(5.0f, 0.0f, 0.0f),
|
|
||||||
Vector3(0.0f, 0.0f, 0.0f),
|
|
||||||
11,
|
|
||||||
25,
|
|
||||||
0,
|
|
||||||
364
|
|
||||||
)
|
|
||||||
val door = Door(GlobalDefinitions.door)
|
val door = Door(GlobalDefinitions.door)
|
||||||
door.Open.isEmpty mustEqual true
|
door.Open.isEmpty mustEqual true
|
||||||
door.Open = player
|
door.Open = player
|
||||||
|
|
@ -74,7 +60,7 @@ class DoorTest extends Specification {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class DoorControl1Test extends ActorTest {
|
class DoorControlConstructTest extends ActorTest {
|
||||||
"DoorControl" should {
|
"DoorControl" should {
|
||||||
"construct" in {
|
"construct" in {
|
||||||
val door = Door(GlobalDefinitions.door)
|
val door = Door(GlobalDefinitions.door)
|
||||||
|
|
@ -84,28 +70,11 @@ class DoorControl1Test extends ActorTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class DoorControl2Test extends ActorTest {
|
class DoorControlOpenTest extends ActorTest {
|
||||||
"DoorControl" should {
|
"DoorControl" should {
|
||||||
"open on use" in {
|
"open on use" in {
|
||||||
val (player, door) = DoorControlTest.SetUpAgents(PlanetSideEmpire.TR)
|
val (player, door, probe) = DoorControlTest.SetUpAgents(PlanetSideEmpire.TR)
|
||||||
val probe = new TestProbe(system)
|
door.Actor ! CommonMessages.Use(player)
|
||||||
door.Zone.LocalEvents = probe.ref
|
|
||||||
val msg = UseItemMessage(
|
|
||||||
PlanetSideGUID(1),
|
|
||||||
PlanetSideGUID(0),
|
|
||||||
PlanetSideGUID(2),
|
|
||||||
0L,
|
|
||||||
false,
|
|
||||||
Vector3(0f, 0f, 0f),
|
|
||||||
Vector3(0f, 0f, 0f),
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0L
|
|
||||||
) //faked
|
|
||||||
assert(door.Open.isEmpty)
|
|
||||||
|
|
||||||
door.Actor ! CommonMessages.Use(player, Some(msg))
|
|
||||||
val reply = probe.receiveOne(1000 milliseconds)
|
val reply = probe.receiveOne(1000 milliseconds)
|
||||||
assert(reply match {
|
assert(reply match {
|
||||||
case LocalServiceMessage("test", LocalAction.DoorOpens(PlanetSideGUID(0), _, d)) => d eq door
|
case LocalServiceMessage("test", LocalAction.DoorOpens(PlanetSideGUID(0), _, d)) => d eq door
|
||||||
|
|
@ -116,26 +85,55 @@ class DoorControl2Test extends ActorTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class DoorControl3Test extends ActorTest {
|
class DoorControlTooFarTest extends ActorTest {
|
||||||
"DoorControl" should {
|
"DoorControl" should {
|
||||||
"do nothing if given garbage" in {
|
"do not open if the player is too far away" in {
|
||||||
val (_, door) = DoorControlTest.SetUpAgents(PlanetSideEmpire.TR)
|
val (player, door, probe) = DoorControlTest.SetUpAgents(PlanetSideEmpire.TR)
|
||||||
|
player.Position = Vector3(10,0,0)
|
||||||
|
door.Actor ! CommonMessages.Use(player)
|
||||||
|
probe.expectNoMessage(Duration.create(500, "ms"))
|
||||||
|
assert(door.Open.isEmpty)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DoorControlAlreadyOpenTest extends ActorTest {
|
||||||
|
"DoorControl" should {
|
||||||
|
"is already open" in {
|
||||||
|
val (player, door, probe) = DoorControlTest.SetUpAgents(PlanetSideEmpire.TR)
|
||||||
|
door.Open = player //door thinks it is open
|
||||||
|
door.Actor.tell(CommonMessages.Use(player), probe.ref)
|
||||||
|
val reply = probe.receiveOne(1000 milliseconds)
|
||||||
|
assert(reply match {
|
||||||
|
case LocalServiceResponse("test", _, LocalResponse.DoorOpens(guid)) => guid == door.GUID
|
||||||
|
case _ => false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DoorControlGarbageDataTest extends ActorTest {
|
||||||
|
"DoorControl" should {
|
||||||
|
"do nothing if given garbage data" in {
|
||||||
|
val (_, door, probe) = DoorControlTest.SetUpAgents(PlanetSideEmpire.TR)
|
||||||
assert(door.Open.isEmpty)
|
assert(door.Open.isEmpty)
|
||||||
|
|
||||||
door.Actor ! "trash"
|
door.Actor ! "trash"
|
||||||
expectNoMessage(Duration.create(500, "ms"))
|
probe.expectNoMessage(Duration.create(500, "ms"))
|
||||||
assert(door.Open.isEmpty)
|
assert(door.Open.isEmpty)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object DoorControlTest {
|
object DoorControlTest {
|
||||||
def SetUpAgents(faction: PlanetSideEmpire.Value)(implicit system: ActorSystem): (Player, Door) = {
|
def SetUpAgents(faction: PlanetSideEmpire.Value)(implicit system: ActorSystem): (Player, Door, TestProbe) = {
|
||||||
|
val eventsProbe = new TestProbe(system)
|
||||||
val door = Door(GlobalDefinitions.door)
|
val door = Door(GlobalDefinitions.door)
|
||||||
val guid = new NumberPoolHub(new MaxNumberSource(5))
|
val guid = new NumberPoolHub(new MaxNumberSource(5))
|
||||||
val zone = new Zone("test", new ZoneMap("test"), 0) {
|
val zone = new Zone(id = "test", new ZoneMap(name = "test"), zoneNumber = 0) {
|
||||||
override def SetupNumberPools() = {}
|
override def SetupNumberPools(): Unit = {}
|
||||||
GUID(guid)
|
GUID(guid)
|
||||||
|
override def LocalEvents: ActorRef = eventsProbe.ref
|
||||||
}
|
}
|
||||||
guid.register(door, 1)
|
guid.register(door, 1)
|
||||||
door.Actor = system.actorOf(Props(classOf[DoorControl], door), "door")
|
door.Actor = system.actorOf(Props(classOf[DoorControl], door), "door")
|
||||||
|
|
@ -149,7 +147,8 @@ object DoorControlTest {
|
||||||
)
|
)
|
||||||
door.Owner.Faction = faction
|
door.Owner.Faction = faction
|
||||||
val player = Player(Avatar(0, "test", faction, CharacterSex.Male, 0, CharacterVoice.Mute))
|
val player = Player(Avatar(0, "test", faction, CharacterSex.Male, 0, CharacterVoice.Mute))
|
||||||
|
player.Zone = zone
|
||||||
guid.register(player, 2)
|
guid.register(player, 2)
|
||||||
(player, door)
|
(player, door, eventsProbe)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue