Painbox v2 (#417)

* Add rotation to building constructor and map files

* Change non-radius based painfields to use a bounding box check based on nearby amenities (disabled in caves)

* Reduce pain field damage to 5
This commit is contained in:
Mazo 2020-05-04 05:26:26 +01:00 committed by GitHub
parent f9ba930007
commit a7cfcd3af7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 1105 additions and 1033 deletions

View file

@ -1,47 +1,95 @@
package net.psforever.objects.serverobject.painbox
import akka.actor.{Actor, Cancellable}
import net.psforever.objects.DefaultCancellable
import net.psforever.objects.serverobject.doors.Door
import net.psforever.objects.serverobject.structures.Building
import net.psforever.objects.{DefaultCancellable, GlobalDefinitions}
import net.psforever.types.{PlanetSideEmpire, Vector3}
import services.avatar.{AvatarAction, AvatarServiceMessage}
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._
class PainboxControl(painbox: Painbox) extends Actor {
private[this] val log = org.log4s.getLogger(s"Painbox")
var painboxTick: Cancellable = DefaultCancellable.obj
var nearestDoor : Option[Door] = None
var nearestDoor: Option[Door] = None
var bBoxMinCorner = Vector3.Zero
var bBoxMaxCorner = Vector3.Zero
var bBoxMidPoint = Vector3.Zero
def receive : Receive = {
def receive: Receive = {
case "startup" =>
if(painbox.Definition.HasNearestDoorDependency) {
if (painbox.Definition.HasNearestDoorDependency) {
(painbox.Owner match {
case obj : Building =>
case obj: Building =>
obj.Amenities
.collect { case door : Door => door }
.collect { case door: Door => door }
.sortBy(door => Vector3.DistanceSquared(painbox.Position, door.Position))
.headOption
case _ =>
None
}) match {
case door @ Some(_) =>
case door@Some(_) =>
nearestDoor = door
context.become(Stopped)
case _ =>
log.error(s"object #${painbox.GUID.guid} can not find a door that it needed")
log.error(s"Painbox ${painbox.GUID} on ${painbox.Owner.Continent} - ${painbox.Position} can not find a door that it is dependent on")
}
}
else {
context.become(Stopped)
}
else {
if (painbox.Definition.Radius == 0f) {
if (painbox.Owner.Continent.matches("c[0-9]")) {
// todo: handle non-radius painboxes in caverns properly
log.warn(s"Skipping initialization of ${painbox.GUID} on ${painbox.Owner.Continent} - ${painbox.Position}")
} else {
painbox.Owner match {
case obj: Building =>
val planarRange = 16.5f
val aboveRange = 5
val belowRange = 5
// Find amenities within the specified range
val nearbyAmenities = obj.Amenities
.filter(amenity =>
amenity.Position != Vector3.Zero
&& (amenity.Definition == GlobalDefinitions.mb_locker
|| amenity.Definition == GlobalDefinitions.respawn_tube
|| amenity.Definition == GlobalDefinitions.spawn_terminal
|| amenity.Definition == GlobalDefinitions.order_terminal
|| amenity.Definition == GlobalDefinitions.door)
&& amenity.Position.x > painbox.Position.x - planarRange && amenity.Position.x < painbox.Position.x + planarRange
&& amenity.Position.y > painbox.Position.y - planarRange && amenity.Position.y < painbox.Position.y + planarRange
&& amenity.Position.z > painbox.Position.z - belowRange && amenity.Position.z < painbox.Position.z + aboveRange
)
// Calculate bounding box of amenities
bBoxMinCorner = Vector3(
nearbyAmenities.minBy(amenity => amenity.Position.x).Position.x,
nearbyAmenities.minBy(amenity => amenity.Position.y).Position.y,
nearbyAmenities.minBy(x => x.Position.z).Position.z
)
bBoxMaxCorner = Vector3(
nearbyAmenities.maxBy(amenity => amenity.Position.x).Position.x,
nearbyAmenities.maxBy(amenity => amenity.Position.y).Position.y,
painbox.Position.z
)
bBoxMidPoint = Vector3(
(bBoxMinCorner.x + bBoxMaxCorner.x) / 2,
(bBoxMinCorner.y + bBoxMaxCorner.y) / 2,
(bBoxMinCorner.z + bBoxMaxCorner.z) / 2
)
case _ => None
}
}
context.become(Stopped)
}
}
case _ => ;
}
def Running : Receive = {
def Running: Receive = {
case Painbox.Stop() =>
context.become(Stopped)
painboxTick.cancel
@ -54,23 +102,47 @@ class PainboxControl(painbox: Painbox) extends Actor {
val guid = painbox.GUID
val owner = painbox.Owner.asInstanceOf[Building]
val faction = owner.Faction
if(faction != PlanetSideEmpire.NEUTRAL && (nearestDoor match { case Some(door) => door.Open.nonEmpty; case _ => true })) {
if (faction != PlanetSideEmpire.NEUTRAL && (nearestDoor match {
case Some(door) => door.Open.nonEmpty;
case _ => true
})) {
val events = painbox.Zone.AvatarEvents
val damage = painbox.Definition.Damage
val radius = painbox.Definition.Radius * painbox.Definition.Radius
val position = painbox.Position
owner.PlayersInSOI
.collect { case p if p.Faction != faction
&& p.Health > 0
&& Vector3.DistanceSquared(p.Position, position) < radius =>
events ! AvatarServiceMessage(p.Name, AvatarAction.EnvironmentalDamage(p.GUID, guid, damage))
}
if (painbox.Definition.Radius != 0f) {
// Spherical pain field
owner.PlayersInSOI
.collect { case p if p.Faction != faction
&& p.Health > 0
&& Vector3.DistanceSquared(p.Position, position) < radius =>
events ! AvatarServiceMessage(p.Name, AvatarAction.EnvironmentalDamage(p.GUID, guid, damage))
}
} else {
// Bounding box pain field
owner.PlayersInSOI
.collect { case p if p.Faction != faction
&& p.Health > 0 =>
/*
This may be cpu intensive with a large number of players in SOI. Further performance tweaking may be required
The bounding box is calculated aligned to the world XY axis, instead of rotating the painbox corners to match the base rotation
we instead rotate the player's current coordinates to match the base rotation, which allows for much simplified checking of if the player is
within the bounding box
*/
val playerRot = Vector3.PlanarRotateAroundPoint(p.Position, bBoxMidPoint, painbox.Owner.Orientation.z.toRadians)
if (bBoxMinCorner.x <= playerRot.x && playerRot.x <= bBoxMaxCorner.x && bBoxMinCorner.y <= playerRot.y && playerRot.y <= bBoxMaxCorner.y
&& playerRot.z >= bBoxMinCorner.z && playerRot.z <= bBoxMaxCorner.z) {
events ! AvatarServiceMessage(p.Name, AvatarAction.EnvironmentalDamage(p.GUID, guid, damage))
}
}
}
}
case _ => ;
}
def Stopped : Receive = {
def Stopped: Receive = {
case Painbox.Start() =>
context.become(Running)
painboxTick.cancel

View file

@ -6,7 +6,7 @@ import net.psforever.types.Vector3
class PainboxDefinition(objectId : Int) extends AmenityDefinition(objectId) {
private var alwaysOn : Boolean = true
private var radius : Float = 0f
private var damage : Int = 10
private var damage : Int = 5
private var sphereOffset = Vector3(0f, 0f, -0.4f)
private var hasNearestDoorDependency = false
@ -14,11 +14,9 @@ class PainboxDefinition(objectId : Int) extends AmenityDefinition(objectId) {
case 622 =>
Name = "painbox"
alwaysOn = false
radius = 10f // Guess - not in game_objects.adb.lst - probably not radius based but it will have to do for the moment
damage = 0
case 623 =>
Name = "painbox_continuous"
radius = 10f // Guess - not in game_objects.adb.lst - probably not radius based but it will have to do for the moment
case 624 =>
Name = "painbox_door_radius"
alwaysOn = false

View file

@ -281,10 +281,11 @@ object Building {
new Building(name, guid, map_id, zone, buildingType, GlobalDefinitions.building)
}
def Structure(buildingType : StructureType.Value, location : Vector3, definition: BuildingDefinition)(name : String, guid : Int, map_id : Int, zone : Zone, context : ActorContext) : Building = {
def Structure(buildingType : StructureType.Value, location : Vector3, rotation : Vector3, definition: BuildingDefinition)(name : String, guid : Int, map_id : Int, zone : Zone, context : ActorContext) : Building = {
import akka.actor.Props
val obj = new Building(name, guid, map_id, zone, buildingType, definition)
obj.Position = location
obj.Orientation = rotation
obj.Actor = context.actorOf(Props(classOf[BuildingControl], obj), s"$map_id-$buildingType-building")
obj
}

View file

@ -326,4 +326,11 @@ object Vector3 {
z
)
}
def PlanarRotateAroundPoint(point: Vector3, center: Vector3, radians : Float) : Vector3 = {
val x = (Math.cos(radians) * (point.x - center.x) - Math.sin(radians) * (point.y - center.y) + center.x).toFloat
val y = (Math.sin(radians) * (point.x - center.x) + Math.cos(radians) * (point.y - center.y) + center.y).toFloat
Vector3(x, y, point.z)
}
}