warning fields and kill fields added to the perimeter surrounding most zone continents (#992)

This commit is contained in:
Fate-JH 2022-05-07 00:39:28 -04:00 committed by GitHub
parent 71b8c011c9
commit 5787c14a29
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 495 additions and 188 deletions

View file

@ -9498,7 +9498,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
def updateBlockMap(target: BlockMapEntity, zone: Zone, newCoords: Vector3): Unit = {
target.blockMapEntry match {
case Some(entry) =>
if (BlockMap.findSectorIndices(continent.blockMap, newCoords, entry.range).toSet.equals(entry.sectors)) {
if (BlockMap.findSectorIndices(continent.blockMap, newCoords, entry.rangeX, entry.rangeY).toSet.equals(entry.sectors)) {
target.updateBlockMapEntry(newCoords) //soft update
} else {
zone.actor ! ZoneActor.UpdateBlockMap(target, newCoords) //hard update

View file

@ -137,7 +137,7 @@ class ZoneActor(context: ActorContext[ZoneActor.Command], zone: Zone)
zone.blockMap.addTo(target, toPosition)
case UpdateBlockMap(target, toPosition) =>
target.updateBlockMapEntry(toPosition)
zone.blockMap.move(target, toPosition)
case RemoveFromBlockMap(target) =>
zone.blockMap.removeFrom(target)

View file

@ -53,6 +53,7 @@ trait InteractsWithZone
def doInteractions(): Unit = {
val sector = getInteractionSector()
//println(sector.environmentList.map { _.attribute }.mkString(" "))
interactions.foreach { _.interaction(sector, target = this) }
}

View file

@ -1,9 +1,13 @@
package net.psforever.objects.zones
import enumeratum.values.{StringEnum, StringEnumEntry}
import net.psforever.objects.PlanetSideGameObject
import net.psforever.objects.{PlanetSideGameObject, Player, Vehicle}
import net.psforever.objects.serverobject.environment._
import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID, Vector3}
import net.psforever.packet.game.{ChatMsg, OffshoreVehicleMessage}
import net.psforever.services.Service
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
import net.psforever.types.{ChatMessageType, PlanetSideEmpire, PlanetSideGUID, Vector3}
sealed abstract class MapInfo(
val value: String,
@ -27,7 +31,15 @@ case object MapInfo extends StringEnum[MapInfo] {
Pool(EnvironmentAttribute.Water, 43.515625f, 4805.5f, 4324.3984f, 4727.867f, 4280.2188f), //north of hapi
Pool(EnvironmentAttribute.Water, 43.0625f, 3313.1094f, 4746.4844f, 3259.4219f, 4691.2266f), //east of thoth
Pool(EnvironmentAttribute.Water, 43.51f, 1917.1016f, 4086.8984f, 1893.4844f, 4038.2734f) //between horus and amun
) ++ MapEnvironment.zoneMapEdgeKillPlane(MapScale.Dim8192, 100, 400, 400, 100, 200, 600, 600, 600)
) ++ MapEnvironment.zoneMapEdgeKillPlane(
MapScale.Dim8192,
(100, 400, 400, 100),
List(
(133, 450, 450, 200, 3),
(166, 500, 500, 400, 2),
(200, 600, 600, 600, 1)
)
)
)
case object Map02
@ -55,7 +67,15 @@ case object MapInfo extends StringEnum[MapInfo] {
Pool(EnvironmentAttribute.Water, 11, southNaum, eastNaum, 0, westNaum) //south of naum
//TODO voltan Killplane
//TODO naum Killplane
) ++ MapEnvironment.zoneMapEdgeKillPlane(MapScale.Dim8192, 400, 400, 200, 400, 600, 600, 400, 600)
) ++ MapEnvironment.zoneMapEdgeKillPlane(
MapScale.Dim8192,
(400, 400, 200, 400),
List(
(450, 450, 250, 450, 3),
(500, 500, 300, 500, 2),
(600, 600, 400, 600, 1)
)
)
}
)
@ -80,7 +100,15 @@ case object MapInfo extends StringEnum[MapInfo] {
Pool(EnvironmentAttribute.Water, 36.921875f, 3162.1094f, 1689.5703f, 3085.7422f, 1612.7734f), //north of nzame
Pool(EnvironmentAttribute.Water, 36.390625f, 4143.797f, 4872.3906f, 4021.9766f, 4798.578f), //south of gunuku
Pool(EnvironmentAttribute.Water, 35.71875f, 2591.336f, 1752.5938f, 2512.7578f, 1663.1172f) //south of nzame
) ++ MapEnvironment.zoneMapEdgeKillPlane(MapScale.Dim8192, 200, 100, 100, 100, 400, 200, 200, 200)
) ++ MapEnvironment.zoneMapEdgeKillPlane(
MapScale.Dim8192,
(200, 100, 100, 100),
List(
(250, 133, 133, 133, 3),
(300, 166, 166, 166, 2),
(400, 200, 200, 200, 1)
)
)
)
case object Map04
@ -89,7 +117,15 @@ case object MapInfo extends StringEnum[MapInfo] {
checksum = 2455050867L,
scale = MapScale.Dim8192,
environment = List(SeaLevel(EnvironmentAttribute.Water, 19.984375f)) ++
MapEnvironment.zoneMapEdgeKillPlane(MapScale.Dim8192, 200, 10, 10, 10, 400, 200, 200, 200)
MapEnvironment.zoneMapEdgeKillPlane(
MapScale.Dim8192,
(200, 10, 10, 10),
List(
(250, 60, 60, 60, 3),
(300, 110, 110, 110, 2),
(400, 200, 200, 200, 1)
)
)
)
case object Map05
@ -112,7 +148,15 @@ case object MapInfo extends StringEnum[MapInfo] {
Pool(EnvironmentAttribute.Water, 41.765625f, 2073.914f, 4982.5938f, 1995.4688f, 4899.086f), //L15-M16
Pool(EnvironmentAttribute.Water, 41.3125f, 3761.1484f, 2616.75f, 3627.4297f, 2505.1328f), //G11, south
Pool(EnvironmentAttribute.Water, 40.421875f, 4058.8281f, 2791.6562f, 3985.1016f, 2685.3672f) //G11, north
) ++ MapEnvironment.zoneMapEdgeKillPlane(MapScale.Dim8192, 400, 10, 200, 400, 600, 100, 400, 600)
) ++ MapEnvironment.zoneMapEdgeKillPlane(
MapScale.Dim8192,
(400, 10, 200, 400),
List(
(450, 25, 250, 450, 3),
(500, 50, 300, 500, 2),
(600, 100, 400, 600, 1)
)
)
)
case object Map06
@ -124,7 +168,15 @@ case object MapInfo extends StringEnum[MapInfo] {
SeaLevel(EnvironmentAttribute.Water, 10.03125f),
Pool(EnvironmentAttribute.Water, 213.03125f, 3116.7266f, 4724.414f, 2685.8281f, 4363.461f), //east side of southwest of tootega
Pool(EnvironmentAttribute.Water, 213.03125f, 2994.2969f, 4363.461f, 2685.8281f, 4187.4375f), //west side of southwest of tootega
) ++ MapEnvironment.zoneMapEdgeKillPlane(MapScale.Dim8192, 400, 400, 400, 400, 600, 600, 600, 600)
) ++ MapEnvironment.zoneMapEdgeKillPlane(
MapScale.Dim8192,
(400, 400, 400, 400),
List(
(450, 450, 450, 450, 3),
(500, 500, 500, 500, 2),
(600, 600, 600, 600, 1)
)
)
)
case object Map07
@ -132,8 +184,15 @@ case object MapInfo extends StringEnum[MapInfo] {
value = "map07",
checksum = 1564014762L,
scale = MapScale.Dim8192,
environment = List(SeaLevel(EnvironmentAttribute.Water, 29.984375f)) ++
MapEnvironment.zoneMapEdgeKillPlane(MapScale.Dim8192, 10, 10, 10, 10, 200, 200, 200, 200)
environment = List(SeaLevel(EnvironmentAttribute.Water, 29.984375f)) ++ MapEnvironment.zoneMapEdgeKillPlane(
MapScale.Dim8192,
(10, 10, 10, 10),
List(
(50, 50, 50, 50, 3),
(100, 100, 100, 100, 2),
(200, 200, 200, 200, 1)
)
)
)
case object Map08
@ -141,8 +200,15 @@ case object MapInfo extends StringEnum[MapInfo] {
value = "map08",
checksum = 0L,
scale = MapScale.Dim8192,
environment = List(SeaLevel(EnvironmentAttribute.Water, 26.078125f)) ++
MapEnvironment.zoneMapEdgeKillPlane(MapScale.Dim8192, 200, 200, 200, 200, 400, 400, 400, 400)
environment = List(SeaLevel(EnvironmentAttribute.Water, 26.078125f)) ++ MapEnvironment.zoneMapEdgeKillPlane(
MapScale.Dim8192,
(200, 200, 200, 200),
List(
(250, 250, 250, 250, 3),
(300, 300, 300, 300, 2),
(400, 400, 400, 400, 1)
)
)
)
case object Map09
@ -160,7 +226,15 @@ case object MapInfo extends StringEnum[MapInfo] {
Pool(EnvironmentAttribute.Lava, DeepSurface(187.57812f, 4288.1484f, 4589.0703f, 3996.3125f, 4355.6406f)), //lower central lava pool
Pool(EnvironmentAttribute.Lava, DeepSurface(181.45312f, 4635.1953f, 4579.3516f, 4406.3438f, 4303.828f)), //upper central lava pool
Pool(EnvironmentAttribute.Lava, DeepSurface(176.64062f, 4274.8125f, 4969.9688f, 4101.7734f, 4766.3594f)) //east lava pool
) ++ MapEnvironment.zoneMapEdgeKillPlane(MapScale.Dim8192, 200, 200, 200, 200, 400, 400, 400, 400)
) ++ MapEnvironment.zoneMapEdgeKillPlane(
MapScale.Dim8192,
(200, 200, 200, 200),
List(
(250, 250, 250, 250, 3),
(300, 300, 300, 300, 2),
(400, 400, 400, 400, 1)
)
)
)
case object Map10
@ -168,8 +242,15 @@ case object MapInfo extends StringEnum[MapInfo] {
value = "map10",
checksum = 230810349L,
scale = MapScale.Dim8192,
environment = List(SeaLevel(EnvironmentAttribute.Water, 28)) ++
MapEnvironment.zoneMapEdgeKillPlane(MapScale.Dim8192, 200, 200, 200, 200, 400, 400, 400, 400)
environment = List(SeaLevel(EnvironmentAttribute.Water, 28)) ++ MapEnvironment.zoneMapEdgeKillPlane(
MapScale.Dim8192,
(200, 200, 200, 200),
List(
(250, 250, 250, 250, 3),
(300, 300, 300, 300, 2),
(400, 400, 400, 400, 1)
)
)
)
case object Map11
@ -186,8 +267,15 @@ case object MapInfo extends StringEnum[MapInfo] {
Pool(EnvironmentAttribute.Water, 34.96875f, 5899.367f, 3235.5781f, 5573.8516f, 2865.7812f), //northeast of hart c campus
Pool(EnvironmentAttribute.Water, 34.328125f, 3880.7422f, 5261.508f, 3780.9219f, 5166.953f), //east of hart a campus
Pool(EnvironmentAttribute.Water, 31.03125f, 4849.797f, 2415.4297f, 4731.8594f, 2252.1484f) //south of hart c campus
) ++ MapEnvironment.map11Environment ++
MapEnvironment.zoneMapEdgeKillPlane(MapScale.Dim8192, 200, 400, 400, 200, 600, 800, 800, 600)
) ++ MapEnvironment.map11Environment ++ MapEnvironment.zoneMapEdgeKillPlane(
MapScale.Dim8192,
(200, 400, 400, 200),
List(
(300, 500, 500, 300, 3),
(400, 600, 600, 400, 2),
(600, 800, 800, 600, 1)
)
)
)
case object Map12
@ -363,11 +451,34 @@ object MapEnvironment {
hartGantryDenialFields(PlanetSideGUID(787), Vector3(3688, 2808, 90.85312f), vsHartMountPoints) ++
hartGantryDenialFields(PlanetSideGUID(788), Vector3(5610, 4238, 103.228859f), vsHartMountPoints)
/** common map edge kill planes */
final val dim1024MapEdgeKillPlanes: List[PieceOfEnvironment] = zoneMapEdgeKillPlane(MapScale.Dim1024)
final val dim2560MapEdgeKillPlanes: List[PieceOfEnvironment] = zoneMapEdgeKillPlane(MapScale.Dim2560)
final val dim4096MapEdgeKillPlanes: List[PieceOfEnvironment] = zoneMapEdgeKillPlane(MapScale.Dim4096)
final val dim8192MapEdgeKillPlanes: List[PieceOfEnvironment] = zoneMapEdgeKillPlane(MapScale.Dim8192)
/** common map edge kill planes; may be defunct eventually */
final val dim1024MapEdgeKillPlanes: List[PieceOfEnvironment] = MapEnvironment.zoneMapEdgeKillPlane(
MapScale.Dim1024,
(102, 102, 102, 102),
List(
(125, 125, 125, 125, 3),
(156, 156, 156, 156, 2),
(204, 204, 204, 204, 1)
)
)
final val dim4096MapEdgeKillPlanes: List[PieceOfEnvironment] = MapEnvironment.zoneMapEdgeKillPlane(
MapScale.Dim4096,
(204, 204, 204, 204),
List(
(255, 255, 255, 255, 3),
(306, 306, 306, 306, 2),
(408, 408, 408, 408, 1)
)
)
final val dim8192MapEdgeKillPlanes: List[PieceOfEnvironment] = MapEnvironment.zoneMapEdgeKillPlane(
MapScale.Dim8192,
(400, 400, 400, 400),
List(
(500, 500, 500, 500, 3),
(600, 600, 600, 600, 2),
(800, 800, 800, 800, 1)
)
)
/**
* Generate eight environmental representations that serve to eject players
@ -444,116 +555,160 @@ object MapEnvironment {
}
/**
* Generate the bounded fields on the egde of the zone maps
* Generate the bounded fields on the edge of the zone maps
* that kill players and vehicles the moment those game entities enter the region
* to disallow players from reaching and traversing the edge of the map.
* Bounded regions that warn players against going too far are also generated.
* @param scale the scale of the map, indicating an outer perimeter
* @return a list of environmental representations
*/
def zoneMapEdgeKillPlane(scale: MapScale): List[PieceOfEnvironment] = {
val killBoundingW = scale.width / 20f
val killBoundingH = scale.height / 20f
val warnBoundingW = killBoundingW * 2f
val warnBoundingH = killBoundingH * 2f
zoneMapEdgeKillPlane(
scale,
killBoundingH, killBoundingW, killBoundingH, killBoundingW,
warnBoundingH, warnBoundingW, warnBoundingH, warnBoundingW
)
}
/**
* Generate the bounded regions along the edges of the zone maps
* that kill players and vehicles the moment those game entities enter the region
* to disallow players from reaching and traversing the edge of the map.
* Warn players who are getting too close to the kill regions that they should be cautious and turn back.
* @param scale the scale of the map, indicating an outer perimeter
* @param killN distance of the kill field from the top edge of the zone map
* @param killE distance of the kill field from the right edge of the zone map
* @param killS distance of the kill field from the bottom edge of the zone map
* @param killW distance of the kill field from the left edge of the zone map
* @param warnN distance of the warning region from the top edge of the zone map to the kill field
* @param warnE distance of the warning region from the right edge of the zone map to the kill field
* @param warnS distance of the warning region from the bottom edge of the zone map to the kill field
* @param warnW distance of the warning region from the left edge of the zone map to the kill field
* @param killField the region defined as an "absolute death barrier" to any player or vehicle that enters it
* @param warnFields consecutive inset perimeters
* that indicate the bounded regions of warning before the `killField` region
* @return a list of environmental representations
*/
def zoneMapEdgeKillPlane(
scale: MapScale,
killN: Float,
killE: Float,
killS: Float,
killW: Float,
warnN: Float,
warnE: Float,
warnS: Float,
warnW: Float
killField: (Float,Float,Float,Float),
warnFields: List[(Float,Float,Float,Float,Int)]
): List[PieceOfEnvironment] = {
assert(killN < warnN, "north side warn region closer to map edge than kill region")
assert(killE < warnE, "east side warn region closer to map edge than kill region")
assert(killS < warnS, "south side warn region closer to map edge than kill region")
assert(killW < warnW, "west side warn region closer to map edge than kill region")
import net.psforever.objects.serverobject.environment.EnvironmentAttribute
val height = scale.height
val width = scale.width
val heightKillN = height - killN
val heightWarnN = height - warnN
val widthKillE = width - killE
val widthWarnE = width - warnE
List(
/*warnings*/
GeneralMovementField(warnCloseToEdgeOfMap(direction = "NW"), DeepSquare(1024, heightKillN, warnW, heightWarnN, killW)), //NW
GeneralMovementField(warnCloseToEdgeOfMap(direction = "N"), DeepSquare(1024, heightKillN, widthWarnE, heightWarnN, warnW)), //N
GeneralMovementField(warnCloseToEdgeOfMap(direction = "NE"), DeepSquare(1024, heightKillN, widthKillE, heightWarnN, widthWarnE)), //NE
GeneralMovementField(warnCloseToEdgeOfMap(direction = "E"), DeepSquare(1024, heightWarnN, widthKillE, warnS, widthWarnE)), //E
GeneralMovementField(warnCloseToEdgeOfMap(direction = "SE"), DeepSquare(1024, warnS, widthKillE, killS, widthWarnE)), //SE
GeneralMovementField(warnCloseToEdgeOfMap(direction = "S"), DeepSquare(1024, warnS, widthWarnE, killS, warnW)), //S
GeneralMovementField(warnCloseToEdgeOfMap(direction = "SW"), DeepSquare(1024, warnS, warnW, killS, killW)), //SW
GeneralMovementField(warnCloseToEdgeOfMap(direction = "W"), DeepSquare(1024, heightWarnN, warnW, warnS, killW)), //W
/*kill fields*/
Pool(EnvironmentAttribute.Death, 1024, height, width, heightKillN, 0), //N
Pool(EnvironmentAttribute.Death, 1024, height, width, 0, widthKillE), //E
Pool(EnvironmentAttribute.Death, 1024, killS, width, 0, 0), //S
Pool(EnvironmentAttribute.Death, 1024, height, killW, 0, 0) //W
val (kbn, kbe, kbs, kbw) = killField
val killFields = List(
Pool(EnvironmentAttribute.Death, 1024, height, width, height - kbn, 0), //N
Pool(EnvironmentAttribute.Death, 1024, height, width, 0, width - kbe), //E
Pool(EnvironmentAttribute.Death, 1024, kbs, width, 0, 0), //S
Pool(EnvironmentAttribute.Death, 1024, height, kbw, 0, 0) //W
)
Nil
}
private def warnCloseToEdgeOfMap(direction: String)(obj: PlanetSideGameObject): Unit = {
import net.psforever.objects.{Player, Vehicle}
import net.psforever.packet.game.ChatMsg
import net.psforever.services.Service
import net.psforever.services.avatar.{AvatarServiceMessage, AvatarAction}
import net.psforever.services.vehicle.{VehicleServiceMessage, VehicleAction}
import net.psforever.types.ChatMessageType
val msg = s"Do not travel any further $direction from the battlefield or you"
obj match {
case p: Player =>
val zone = p.Zone
val punishment = if (p.Faction == PlanetSideEmpire.VS) {
"r ongoing research venture will be defunded."
} else if (p.Faction == PlanetSideEmpire.NC) {
"r social credits will be liquidated."
} else {
" will be executed for desertion."
}
zone.AvatarEvents ! AvatarServiceMessage(
p.Name,
AvatarAction.SendResponseTargeted(
Service.defaultPlayerGUID,
ChatMsg(ChatMessageType.CMT_QUIT, false, "", s"$msg$punishment", None)
)
if (warnFields.nonEmpty) {
val msgs = 0 +: warnFields.map(_._5)
val mns = kbn +: warnFields.map (_._1)
val mes = kbe +: warnFields.map (_._2)
val mss = kbs +: warnFields.map (_._3)
val mws = kbw +: warnFields.map (_._4)
val warningFields = msgs.indices.drop(1).flatMap { index =>
val old = index - 1
val thisMsg = msgs(index)
List(
GeneralMovementField(
warnCloseToEdgeOfMap(List(Vector3(0,1,0),Vector3(-1,0,0)), thisMsg),
DeepSquare(1024, height - mns(old), mws(index), height - mns(index), mws(old))
), //NW
GeneralMovementField(
warnCloseToEdgeOfMap(List(Vector3(0,1,0)), thisMsg),
DeepSquare(1024, height - mns(old), width - mes(index), height - mns(index), mws(index))
), //N
GeneralMovementField(
warnCloseToEdgeOfMap(List(Vector3(0,1,0),Vector3(1,0,0)), thisMsg),
DeepSquare(1024, height - mns(old), width - mes(old), height - mns(index), width - mes(index))
), //NE
GeneralMovementField(
warnCloseToEdgeOfMap(List(Vector3(1,0,0)), thisMsg),
DeepSquare(1024, height - mns(index), width - mes(old), mss(index), width - mes(index))
), //E
GeneralMovementField(
warnCloseToEdgeOfMap(List(Vector3(0,-1,0),Vector3(1,0,0)), thisMsg),
DeepSquare(1024, mss(index), width - mes(old), mss(old), width - mes(index))
), //SE
GeneralMovementField(
warnCloseToEdgeOfMap(List(Vector3(0,-1,0)), thisMsg),
DeepSquare(1024, mss(index), width - mes(index), mss(old), mws(index))
), //S
GeneralMovementField(
warnCloseToEdgeOfMap(List(Vector3(0,-1,0),Vector3(-1,0,0)), thisMsg),
DeepSquare(1024, mss(index), mws(index), mss(old), mws(old))
), //SW
GeneralMovementField(
warnCloseToEdgeOfMap(List(Vector3(-1,0,0)), thisMsg),
DeepSquare(1024, height - mns(index), mws(index), mss(index), mws(old))
) //W
)
case v: Vehicle =>
val zone = v.Zone
zone.VehicleEvents ! VehicleServiceMessage(
v.Actor.toString(),
VehicleAction.SendResponse(
Service.defaultPlayerGUID,
ChatMsg(ChatMessageType.CMT_QUIT, false, "",s"${msg}r ${v.Definition.Name} will be destroyed.", None)
)
)
case _ => ;
}
(warningFields ++ killFields).toList
} else {
killFields
}
}
/**
* Upon entering a bounded warning region,
* determine if entering from a dangerous angle,
* and dispatch a warning message if so.
* A "dangerous angle" means any projected motion that would inevitably lead to a kill region.
* @param inDirectionOf directions to validate parallel movement triggering
* @param msg the message index belonging to an `OffshoreVehicleMessage` packet
*/
private def warnCloseToEdgeOfMap(inDirectionOf: List[Vector3], msg: Int)(obj: PlanetSideGameObject): Unit = {
val trespass: String = {
val direction = Vector3.Unit(obj.Velocity.getOrElse(Vector3.Zero))
inDirectionOf
.filter { test => Vector3.ScalarProjection(direction, test) > 0.1f }
.flatMap { directionToString }
.mkString
}
if (trespass.nonEmpty) {
obj match {
case p : Player =>
//if the player is moving independent of any vehicle towards a kill region, give them a flavorful message
val punishment = if (p.Faction == PlanetSideEmpire.VS) {
"r ongoing research venture will be defunded."
} else if (p.Faction == PlanetSideEmpire.NC) {
"r social credits will be liquidated."
} else if (p.Faction == PlanetSideEmpire.TR) {
" will be executed for desertion."
} else {
" will be executed for treason." //TODO for bops, eventually
}
val warning = s"Do not travel any further $trespass of the battlefield or you$punishment"
p.Zone.AvatarEvents ! AvatarServiceMessage(
p.Name,
AvatarAction.SendResponseTargeted(
Service.defaultPlayerGUID,
ChatMsg(ChatMessageType.CMT_QUIT, false, "", warning, None)
)
)
case _ => ;
}
obj match {
case v: Vehicle =>
v.Zone.VehicleEvents ! VehicleServiceMessage(
v.Actor.toString(),
VehicleAction.SendResponse(
Service.defaultPlayerGUID,
OffshoreVehicleMessage(v.Seats(0).occupant.get.GUID, v.GUID, msg)
)
)
case _ => ;
}
}
}
/**
* Transform a directional `Vector3` entity that satisfies cardinal map directions
* into one or two letters that indicate the same map direction
* @param direction the raw direction
* @return the clarified direction
*/
private def directionToString(direction: Vector3): String = {
val ns = {
val dir = Vector3.ScalarProjection(direction, Vector3(0,1,0))
if (dir > 0) {
"N"
} else if (dir < 0) {
"S"
} else {
""
}
}
val ew = {
val dir = Vector3.ScalarProjection(direction, Vector3(1,0,0))
if (dir > 0) {
"E"
} else if (dir < 0) {
"W"
} else {
""
}
}
ns ++ ew
}
}

View file

@ -100,7 +100,8 @@ class BlockMap(fullMapWidth: Int, fullMapHeight: Int, desiredSpanSize: Int) {
* @return a conglomerate sector which lists all of the entities in the allocated sector(s)
*/
def addTo(target: BlockMapEntity, toPosition: Vector3): SectorPopulation = {
addTo(target, toPosition, BlockMap.rangeFromEntity(target))
val (y,x) = BlockMap.rangeFromEntity(target)
addTo(target, toPosition, x, y)
}
/**
@ -125,11 +126,25 @@ class BlockMap(fullMapWidth: Int, fullMapHeight: Int, desiredSpanSize: Int) {
* @return a conglomerate sector which lists all of the entities in the allocated sector(s)
*/
def addTo(target: BlockMapEntity, toPosition: Vector3, range: Float): SectorPopulation = {
val to = BlockMap.findSectorIndices(blockMap = this, toPosition, range)
addTo(target, toPosition, range, range)
}
/**
* Allocate this entity into appropriate sectors on the blockmap
* using the provided game world coordinates and the provided axis range.
* @see `BlockMap.findSectorIndices`
* @param target the entity
* @param toPosition the game world coordinates that indicate the central sector
* @param rangeX the distance from the central sector along the major x-axis
* @param rangeY the distance from the central sector along the major y-axis
* @return a conglomerate sector which lists all of the entities in the allocated sector(s)
*/
def addTo(target: BlockMapEntity, toPosition: Vector3, rangeX: Float, rangeY: Float): SectorPopulation = {
val to = BlockMap.findSectorIndices(blockMap = this, toPosition, rangeX, rangeY)
val toSectors = to.toSet.map { blocks }
toSectors.foreach { block => block.addTo(target) }
target.blockMapEntry = Some(BlockMapEntry(toPosition, range, to.toSet))
BlockMap.quickToSectorGroup(range, toSectors)
target.blockMapEntry = Some(BlockMapEntry(toPosition, rangeX, rangeY, to.toSet))
BlockMap.quickToSectorGroup(rangeX, rangeY, toSectors)
}
/**
@ -140,7 +155,7 @@ class BlockMap(fullMapWidth: Int, fullMapHeight: Int, desiredSpanSize: Int) {
*/
def removeFrom(target: BlockMapEntity): SectorPopulation = {
target.blockMapEntry match {
case Some(entry) => actuallyRemoveFrom(target, entry.coords, entry.range)
case Some(entry) => actuallyRemoveFrom(target, entry.coords, entry.rangeX, entry.rangeY)
case None => SectorGroup(Nil)
}
}
@ -192,16 +207,17 @@ class BlockMap(fullMapWidth: Int, fullMapHeight: Int, desiredSpanSize: Int) {
* Really.
* @param target the entity
* @param fromPosition the game world coordinates that indicate the central sector
* @param range the distance from the central sector along the major axes
* @param rangeX the distance from the central sector along the major x-axis
* @param rangeY the distance from the central sector along the major y-axis
* @return a conglomerate sector which lists all of the entities in the allocated sector(s)
*/
private def actuallyRemoveFrom(target: BlockMapEntity, fromPosition: Vector3, range: Float): SectorPopulation = {
private def actuallyRemoveFrom(target: BlockMapEntity, fromPosition: Vector3, rangeX: Float, rangeY: Float): SectorPopulation = {
target.blockMapEntry match {
case Some(entry) =>
target.blockMapEntry = None
val from = entry.sectors.map { blocks }
from.foreach { block => block.removeFrom(target) }
BlockMap.quickToSectorGroup(range, from)
BlockMap.quickToSectorGroup(rangeX, rangeY, from)
case None =>
SectorGroup(Nil)
}
@ -215,7 +231,7 @@ class BlockMap(fullMapWidth: Int, fullMapHeight: Int, desiredSpanSize: Int) {
*/
def move(target: BlockMapEntity): SectorPopulation = {
target.blockMapEntry match {
case Some(entry) => move(target, target.Position, entry.coords, entry.range)
case Some(entry) => move(target, target.Position, entry.coords, entry.rangeX, entry.rangeY)
case None => SectorGroup(Nil)
}
}
@ -229,7 +245,7 @@ class BlockMap(fullMapWidth: Int, fullMapHeight: Int, desiredSpanSize: Int) {
*/
def move(target: BlockMapEntity, toPosition: Vector3): SectorPopulation = {
target.blockMapEntry match {
case Some(entry) => move(target, toPosition, entry.coords, entry.range)
case Some(entry) => move(target, toPosition, entry.coords, entry.rangeX, entry.rangeY)
case _ => SectorGroup(Nil)
}
}
@ -255,14 +271,27 @@ class BlockMap(fullMapWidth: Int, fullMapHeight: Int, desiredSpanSize: Int) {
* @return a conglomerate sector which lists all of the entities in the allocated sector(s)
*/
def move(target: BlockMapEntity, toPosition: Vector3, fromPosition: Vector3, range: Float): SectorPopulation = {
move(target, toPosition, fromPosition, range, range)
}
/**
* Move an entity on the blockmap structure and update the prerequisite internal information.
* @param target the entity
* @param toPosition the next location of the entity in world coordinates
* @param fromPosition the current location of the entity in world coordinates
* @param rangeX the distance from the location along the major x-axis
* @param rangeY the distance from the location along the major y-axis
* @return a conglomerate sector which lists all of the entities in the allocated sector(s)
*/
def move(target: BlockMapEntity, toPosition: Vector3, fromPosition: Vector3, rangeX: Float, rangeY: Float): SectorPopulation = {
target.blockMapEntry match {
case Some(entry) =>
val from = entry.sectors
val to = BlockMap.findSectorIndices(blockMap = this, toPosition, range).toSet
val to = BlockMap.findSectorIndices(blockMap = this, toPosition, rangeX, rangeY).toSet
to.diff(from).foreach { index => blocks(index).addTo(target) }
from.diff(to).foreach { index => blocks(index).removeFrom(target) }
target.blockMapEntry = Some(BlockMapEntry(toPosition, range, to))
BlockMap.quickToSectorGroup(range, to.map { blocks })
target.blockMapEntry = Some(BlockMapEntry(toPosition, rangeX, rangeY, to))
BlockMap.quickToSectorGroup(rangeX, rangeY, to.map { blocks })
case None =>
SectorGroup(Nil)
}
@ -290,7 +319,21 @@ object BlockMap {
* @return the indices of the sectors in the blockmap structure
*/
def findSectorIndices(blockMap: BlockMap, p: Vector3, range: Float): Iterable[Int] = {
findSectorIndices(blockMap.spanSize, blockMap.blocksInRow, blockMap.blocks.size, p, range)
findSectorIndices(blockMap, p, range, range)
}
/**
* The blockmap is mapped to a coordinate range in two directions,
* so find the indices of the sectors that correspond to the region
* defined by the range around a coordinate position.
* @param blockMap the blockmap structure
* @param p the coordinate position
* @param rangeX a rectangular range aigned with the lateral x-axis extending from a coordinate position
* @param rangeY a rectangular range aigned with the lateral y-axis extending from a coordinate position
* @return the indices of the sectors in the blockmap structure
*/
def findSectorIndices(blockMap: BlockMap, p: Vector3, rangeX: Float, rangeY: Float): Iterable[Int] = {
findSectorIndices(blockMap.spanSize, blockMap.blocksInRow, blockMap.blocks.size, p, rangeX, rangeY)
}
/**
@ -301,10 +344,18 @@ object BlockMap {
* @param blocksInRow the number of sectors across the width (in a row) of the blockmap
* @param blocksTotal the number of sectors in the blockmap
* @param p the coordinate position
* @param range a rectangular range aigned with lateral axes extending from a coordinate position
* @param rangeX a rectangular range aigned with a lateral x-axis extending from a coordinate position
* @param rangeY a rectangular range aigned with a lateral y-axis extending from a coordinate position
* @return the indices of the sectors in the blockmap structure
*/
private def findSectorIndices(spanSize: Int, blocksInRow: Int, blocksTotal: Int, p: Vector3, range: Float): Iterable[Int] = {
private def findSectorIndices(
spanSize: Int,
blocksInRow: Int,
blocksTotal: Int,
p: Vector3,
rangeX: Float,
rangeY: Float
): Iterable[Int] = {
val corners = {
/*
find the corners of a rectangular region extending in all cardinal directions from the position;
@ -327,10 +378,10 @@ object BlockMap {
[----][----][----][----] [----][----][----]
*/
val blocksInColumn = blocksTotal / blocksInRow
val lowx = math.max(0, p.x - range)
val highx = math.min(p.x + range, (blocksInRow * spanSize - 1).toFloat)
val lowy = math.max(0, p.y - range)
val highy = math.min(p.y + range, (blocksInColumn * spanSize - 1).toFloat)
val lowx = math.max(0, p.x - rangeX)
val highx = math.min(p.x + rangeX, (blocksInRow * spanSize - 1).toFloat)
val lowy = math.max(0, p.y - rangeY)
val highy = math.min(p.y + rangeY, (blocksInColumn * spanSize - 1).toFloat)
Seq( (lowx, lowy), (highx, lowy), (lowx, highy), (highx, highy) )
}.map { case (x, y) =>
(y / spanSize).toInt * blocksInRow + (x / spanSize).toInt
@ -350,34 +401,37 @@ object BlockMap {
/**
* Calculate the range expressed by a certain entity that can be allocated into a sector on the blockmap.
* Entities have different ways of expressing these ranges.
* @param target the entity
* @param defaultRadius a default radius, if no specific case is discovered;
* @param target the entity
* @param defaultX a default range for the x-axis, if no specific case is discovered;
* if no default case, the default-default case is a single unit (`1.0f`)
* @param defaultY a default range for the y-axis, if no specific case is discovered;
* if no default case, the default-default case is a single unit (`1.0f`)
* @return the distance from a central position along the major axes
* @return the distance from a central position along the major axes (y-axis, then x-axis)
*/
def rangeFromEntity(target: BlockMapEntity, defaultRadius: Option[Float] = None): Float = {
def rangeFromEntity(target: BlockMapEntity, defaultX: Option[Float] = None, defaultY: Option[Float] = None): (Float, Float) = {
target match {
case b: Building =>
//use the building's sphere of influence
b.Definition.SOIRadius.toFloat// * 0.5f
(b.Definition.SOIRadius.toFloat, b.Definition.SOIRadius.toFloat)
case o: PlanetSideGameObject =>
//use the server geometry
val pos = target.Position
val v = o.Definition.Geometry(o)
math.sqrt(math.max(
val out = math.sqrt(math.max(
Vector3.DistanceSquared(pos, v.pointOnOutside(Vector3(1,0,0)).asVector3),
Vector3.DistanceSquared(pos, v.pointOnOutside(Vector3(0,1,0)).asVector3)
)).toFloat
(out, out)
case e: PieceOfEnvironment =>
//use the bounds (like server geometry, but is alawys a rectangle on the XY-plane)
//use the bounds (like server geometry, but is always a rectangle on the XY-plane)
val bounds = e.collision.bounding
math.max(bounds.top - bounds.base, bounds.right - bounds.left) * 0.5f
((bounds.top - bounds.base) * 0.5f, (bounds.right - bounds.left) * 0.5f)
case _ =>
//default and default-default
defaultRadius.getOrElse(1.0f)
(defaultX.getOrElse(1.0f), defaultY.getOrElse(1.0f))
}
}
@ -403,10 +457,24 @@ object BlockMap {
* @return a conglomerate sector which lists all of the entities in the allocated sector(s)
*/
def quickToSectorGroup(range: Float, to: Iterable[Sector]): SectorPopulation = {
quickToSectorGroup(range, range, to)
}
/**
* If only one sector, just return that sector.
* If a group of sectors, organize them into a single referential sector.
* @param rangeX a custom range value for the x-axis
* @param rangeY a custom range value for the y-axis
* @param to all allocated sectors
* @return a conglomerate sector which lists all of the entities in the allocated sector(s)
*/
def quickToSectorGroup(rangeX: Float, rangeY: Float, to: Iterable[Sector]): SectorPopulation = {
if (to.size == 1) {
SectorGroup(range, to.head)
SectorGroup(rangeX, rangeY, to.head)
} else {
SectorGroup(range, to)
SectorGroup(rangeX, rangeY, to)
}
}
}

View file

@ -5,7 +5,7 @@ import net.psforever.objects.entity.WorldEntity
import net.psforever.objects.zones.Zone
import net.psforever.types.Vector3
sealed case class BlockMapEntry(coords: Vector3, range: Float, sectors: Set[Int])
sealed case class BlockMapEntry(coords: Vector3, rangeX: Float, rangeY: Float, sectors: Set[Int])
/**
* An game object that can be represented on a blockmap.
@ -64,6 +64,16 @@ trait BlockMapEntity
}
object BlockMapEntity {
/**
* Overloaded constructor that uses a single range to construct a block map entry.
* @param coords the absolute game world coordinates
* @param range the distance outwards from the game world coordinates along the major axes
* @param sectors the indices of sectors on the blockmap
* @return a `BlockMapEntry` entity
*/
def apply(coords: Vector3, range: Float, sectors: Set[Int]): BlockMapEntry =
BlockMapEntry(coords, range, range, sectors)
/**
* The entity is currently excluded from being represented on a blockmap structure.
* There is no need to update.
@ -87,7 +97,7 @@ object BlockMapEntity {
private def updateBlockMap(target: BlockMapEntity, newCoords: Vector3): Boolean = {
target.blockMapEntry match {
case Some(oldEntry) =>
target.blockMapEntry = Some(BlockMapEntry(newCoords, oldEntry.range, oldEntry.sectors))
target.blockMapEntry = Some(BlockMapEntry(newCoords, oldEntry.rangeX, oldEntry.rangeY, oldEntry.sectors))
true
case None =>
false

View file

@ -14,7 +14,9 @@ import scala.collection.mutable.ListBuffer
* The collections of entities in a sector conglomerate.
*/
trait SectorPopulation {
def range: Float
def rangeX: Float
def rangeY: Float
def livePlayerList: List[Player]
@ -153,7 +155,9 @@ class Sector(val longitude: Int, val latitude: Int, val span: Int)
(a: Projectile, b: Projectile) => a.id == b.id
)
def range: Float = span.toFloat
def rangeX: Float = span.toFloat
def rangeY: Float = span.toFloat
def livePlayerList : List[Player] = livePlayers.list
@ -248,7 +252,8 @@ class Sector(val longitude: Int, val latitude: Int, val span: Int)
* @param environmentList fields that represent the game world environment
*/
class SectorGroup(
val range: Float,
val rangeX: Float,
val rangeY: Float,
val livePlayerList: List[Player],
val corpseList: List[Player],
val vehicleList: List[Vehicle],
@ -269,18 +274,7 @@ object SectorGroup {
* @return a `SectorGroup` object
*/
def apply(sector: Sector): SectorGroup = {
new SectorGroup(
sector.range,
sector.livePlayerList,
sector.corpseList,
sector.vehicleList,
sector.equipmentOnGroundList,
sector.deployableList,
sector.buildingList,
sector.amenityList,
sector.environmentList,
sector.projectileList
)
SectorGroup(sector.rangeX, sector.rangeY, sector)
}
/**
@ -291,8 +285,21 @@ object SectorGroup {
* @return a `SectorGroup` object
*/
def apply(range: Float, sector: Sector): SectorGroup = {
SectorGroup(range, range, sector)
}
/**
* Overloaded constructor that takes a single sector
* and transfers the lists of entities into a single conglomeration of the sector populations.
* @param rangeX a custom range value for the x-axis
* @param rangeY a custom range value for the y-axis
* @param sector the sector to be counted
* @return a `SectorGroup` object
*/
def apply(rangeX: Float, rangeY: Float, sector: Sector): SectorGroup = {
new SectorGroup(
range,
rangeX,
rangeY,
sector.livePlayerList,
sector.corpseList,
sector.vehicleList,
@ -315,9 +322,9 @@ object SectorGroup {
if (sectors.isEmpty) {
SectorGroup(range = 0, sectors = Nil)
} else if (sectors.size == 1) {
SectorGroup(sectors.head.range, sectors)
SectorGroup(sectors.head.rangeX, sectors.head.rangeY, sectors)
} else {
SectorGroup(sectors.maxBy { _.range }.range, sectors)
SectorGroup(sectors.maxBy { _.rangeX }.rangeX, sectors.maxBy { _.rangeY }.rangeY, sectors)
}
}
@ -329,12 +336,25 @@ object SectorGroup {
* @return a `SectorGroup` object
*/
def apply(range: Float, sectors: Iterable[Sector]): SectorGroup = {
SectorGroup(range, range, sectors)
}
/**
* Overloaded constructor that takes a group of sectors
* and condenses all of the lists of entities into a single conglomeration of the sector populations.
* @param rangeX a custom range value for the x-axis
* @param rangeY a custom range value for the y-axis
* @param sectors the series of sectors to be counted
* @return a `SectorGroup` object
*/
def apply(rangeX: Float, rangeY: Float, sectors: Iterable[Sector]): SectorGroup = {
if (sectors.isEmpty) {
new SectorGroup(range, Nil, Nil, Nil, Nil, Nil, Nil, Nil, Nil, Nil)
new SectorGroup(rangeX, rangeY, Nil, Nil, Nil, Nil, Nil, Nil, Nil, Nil, Nil)
} else if (sectors.size == 1) {
val sector = sectors.head
new SectorGroup(
range,
rangeX,
rangeY,
sector.livePlayerList,
sector.corpseList,
sector.vehicleList,
@ -347,7 +367,8 @@ object SectorGroup {
)
} else {
new SectorGroup(
range,
rangeX,
rangeY,
sectors.flatMap { _.livePlayerList }.toList.distinct,
sectors.flatMap { _.corpseList }.toList.distinct,
sectors.flatMap { _.vehicleList }.toList.distinct,

View file

@ -460,7 +460,7 @@ object GamePacketOpcode extends Enumeration {
case 0x82 => noDecoder(TriggerBotAction)
case 0x83 => game.SquadWaypointRequest.decode
case 0x84 => game.SquadWaypointEvent.decode
case 0x85 => noDecoder(OffshoreVehicleMessage)
case 0x85 => game.OffshoreVehicleMessage.decode
case 0x86 => game.ObjectDeployedMessage.decode
case 0x87 => noDecoder(ObjectDeployedCountMessage)
// 0x88

View file

@ -82,36 +82,36 @@ object AvatarAwardMessage extends Marshallable[AvatarAwardMessage] {
private val qualification_codec: Codec[AwardOption] = {
uint32L.hlist
}.xmap[AwardOption](
}.xmap[AwardQualificationProgress](
{
case a :: HNil => AwardQualificationProgress(a)
},
{
case AwardQualificationProgress(a) => a :: HNil
}
)
).asInstanceOf[Codec[AwardOption]]
private val completion_codec: Codec[AwardOption] = {
uint32L.hlist
}.xmap[AwardOption](
}.xmap[AwardCompletion](
{
case a :: HNil => AwardCompletion(a)
},
{
case AwardCompletion(a) => a :: HNil
}
)
).asInstanceOf[Codec[AwardOption]]
private val progress_codec: Codec[AwardOption] = {
uint32L :: uint32L
}.xmap[AwardOption](
}.xmap[AwardProgress](
{
case a :: b :: HNil => AwardProgress(a, b)
},
{
case AwardProgress(a, b) => a :: b :: HNil
}
)
).asInstanceOf[Codec[AwardOption]]
implicit val codec: Codec[AvatarAwardMessage] = (
("merit_commendation" | MeritCommendation.codec) ::
@ -122,8 +122,8 @@ object AvatarAwardMessage extends Marshallable[AvatarAwardMessage] {
case Right(d) => d
},
{
case d: AwardProgress => Left(d)
case d: AwardQualificationProgress => Right(d)
case d: AwardProgress => Left(d)
case d: AwardOption => Right(d)
}
),
completion_codec
@ -133,9 +133,8 @@ object AvatarAwardMessage extends Marshallable[AvatarAwardMessage] {
case Right(d) => d
},
{
case d: AwardProgress => Left(d)
case d: AwardQualificationProgress => Left(d)
case d: AwardCompletion => Right(d)
case d: AwardCompletion => Right(d)
case d: AwardOption => Left(d)
}
)) ::
("unk" | uint8L)

View file

@ -0,0 +1,42 @@
// Copyright (c) 2022 PSForever
package net.psforever.packet.game
import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket}
import net.psforever.types.PlanetSideGUID
import scodec.Codec
import scodec.codecs._
/**
* Dispatched from the server to indicate that the player is traveling too far from the primary battlefield
* and needs to return.
* At first the intention is to warn.
* After the warning, the intent is to dispose.<br>
* Do not dispatch this packet if the player is not seated in a vehicle or else the client will crash.<br>
* <br>
* Messages follow:<br>
* 1) `WARNING: Power Link is weakening. Proceed back to the continent.`<br>
* 2) `DANGER: Power Link is Dangerously Low. Turn back to the continent immediately!`<br>
* 3) `Power Link lost.`<br>
* Upon reception of #3, the player will lose control of their vehicle and it may explode depending on the vehicle.
* The "Power Link" that is mentioned is a hand-wave.
* @param player_guid na
* @param vehicle_guid na
* @param msg the number indexes of the message to be displayed by the client
*/
final case class OffshoreVehicleMessage(
player_guid: PlanetSideGUID,
vehicle_guid: PlanetSideGUID,
msg: Int
) extends PlanetSideGamePacket {
type Packet = OffshoreVehicleMessage
def opcode = GamePacketOpcode.OffshoreVehicleMessage
def encode = OffshoreVehicleMessage.encode(this)
}
object OffshoreVehicleMessage extends Marshallable[OffshoreVehicleMessage] {
implicit val codec : Codec[OffshoreVehicleMessage] = (
("player_guid" | PlanetSideGUID.codec) ::
("vehicle_guid" | PlanetSideGUID.codec) ::
("msg" | uint2L)
).as[OffshoreVehicleMessage]
}

View file

@ -51,6 +51,17 @@ final case class Vector3(x: Float, y: Float, z: Float) {
Vector3(x / scalar, y / scalar, z / scalar)
}
/**
* Operator for multiplication of vector elements.
* This applies a scaling to each element of one vector by the same element of the other.
* The application of this overload is "vector ** v".
* @param v the per-element scalars as a `Vector3` object
* @return a new `Vector3` object
*/
def **(v: Vector3): Vector3 = {
Vector3(x * v.x, y * v.y, z * v.z)
}
/**
* Operator for returning the ground-planar coordinates
* and ignoring the perpendicular distance from the world floor.