diff --git a/src/main/scala/net/psforever/actors/session/SessionActor.scala b/src/main/scala/net/psforever/actors/session/SessionActor.scala
index 9063e81e6..7fbd9a205 100644
--- a/src/main/scala/net/psforever/actors/session/SessionActor.scala
+++ b/src/main/scala/net/psforever/actors/session/SessionActor.scala
@@ -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
diff --git a/src/main/scala/net/psforever/actors/zone/ZoneActor.scala b/src/main/scala/net/psforever/actors/zone/ZoneActor.scala
index 3dec97166..149fb0e48 100644
--- a/src/main/scala/net/psforever/actors/zone/ZoneActor.scala
+++ b/src/main/scala/net/psforever/actors/zone/ZoneActor.scala
@@ -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)
diff --git a/src/main/scala/net/psforever/objects/zones/InteractsWithZone.scala b/src/main/scala/net/psforever/objects/zones/InteractsWithZone.scala
index 7a059d59b..35ecc8925 100644
--- a/src/main/scala/net/psforever/objects/zones/InteractsWithZone.scala
+++ b/src/main/scala/net/psforever/objects/zones/InteractsWithZone.scala
@@ -53,6 +53,7 @@ trait InteractsWithZone
def doInteractions(): Unit = {
val sector = getInteractionSector()
+ //println(sector.environmentList.map { _.attribute }.mkString(" "))
interactions.foreach { _.interaction(sector, target = this) }
}
diff --git a/src/main/scala/net/psforever/objects/zones/MapInfo.scala b/src/main/scala/net/psforever/objects/zones/MapInfo.scala
index 6bfe16974..c5c19dff7 100644
--- a/src/main/scala/net/psforever/objects/zones/MapInfo.scala
+++ b/src/main/scala/net/psforever/objects/zones/MapInfo.scala
@@ -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
+ }
}
diff --git a/src/main/scala/net/psforever/objects/zones/blockmap/BlockMap.scala b/src/main/scala/net/psforever/objects/zones/blockmap/BlockMap.scala
index 2204a971f..dd8f19e92 100644
--- a/src/main/scala/net/psforever/objects/zones/blockmap/BlockMap.scala
+++ b/src/main/scala/net/psforever/objects/zones/blockmap/BlockMap.scala
@@ -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)
}
}
}
diff --git a/src/main/scala/net/psforever/objects/zones/blockmap/BlockMapEntity.scala b/src/main/scala/net/psforever/objects/zones/blockmap/BlockMapEntity.scala
index 88dd9f97a..ef33dfcf3 100644
--- a/src/main/scala/net/psforever/objects/zones/blockmap/BlockMapEntity.scala
+++ b/src/main/scala/net/psforever/objects/zones/blockmap/BlockMapEntity.scala
@@ -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
diff --git a/src/main/scala/net/psforever/objects/zones/blockmap/Sector.scala b/src/main/scala/net/psforever/objects/zones/blockmap/Sector.scala
index 838783e82..1a605e33b 100644
--- a/src/main/scala/net/psforever/objects/zones/blockmap/Sector.scala
+++ b/src/main/scala/net/psforever/objects/zones/blockmap/Sector.scala
@@ -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,
diff --git a/src/main/scala/net/psforever/packet/GamePacketOpcode.scala b/src/main/scala/net/psforever/packet/GamePacketOpcode.scala
index 7cbbb3e04..407658b15 100644
--- a/src/main/scala/net/psforever/packet/GamePacketOpcode.scala
+++ b/src/main/scala/net/psforever/packet/GamePacketOpcode.scala
@@ -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
diff --git a/src/main/scala/net/psforever/packet/game/AvatarAwardMessage.scala b/src/main/scala/net/psforever/packet/game/AvatarAwardMessage.scala
index 60660dba5..102fe7d83 100644
--- a/src/main/scala/net/psforever/packet/game/AvatarAwardMessage.scala
+++ b/src/main/scala/net/psforever/packet/game/AvatarAwardMessage.scala
@@ -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)
diff --git a/src/main/scala/net/psforever/packet/game/OffshoreVehicleMessage.scala b/src/main/scala/net/psforever/packet/game/OffshoreVehicleMessage.scala
new file mode 100644
index 000000000..7b525154b
--- /dev/null
+++ b/src/main/scala/net/psforever/packet/game/OffshoreVehicleMessage.scala
@@ -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.
+ * Do not dispatch this packet if the player is not seated in a vehicle or else the client will crash.
+ *
+ * Messages follow:
+ * 1) `WARNING: Power Link is weakening. Proceed back to the continent.`
+ * 2) `DANGER: Power Link is Dangerously Low. Turn back to the continent immediately!`
+ * 3) `Power Link lost.`
+ * 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]
+}
diff --git a/src/main/scala/net/psforever/types/Vector3.scala b/src/main/scala/net/psforever/types/Vector3.scala
index 6c604d257..fc2c4cab1 100644
--- a/src/main/scala/net/psforever/types/Vector3.scala
+++ b/src/main/scala/net/psforever/types/Vector3.scala
@@ -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.