From 5787c14a297132e1e328b61ae21ea89cad5bbfb9 Mon Sep 17 00:00:00 2001 From: Fate-JH Date: Sat, 7 May 2022 00:39:28 -0400 Subject: [PATCH] warning fields and kill fields added to the perimeter surrounding most zone continents (#992) --- .../actors/session/SessionActor.scala | 2 +- .../net/psforever/actors/zone/ZoneActor.scala | 2 +- .../objects/zones/InteractsWithZone.scala | 1 + .../net/psforever/objects/zones/MapInfo.scala | 395 ++++++++++++------ .../objects/zones/blockmap/BlockMap.scala | 130 ++++-- .../zones/blockmap/BlockMapEntity.scala | 14 +- .../objects/zones/blockmap/Sector.scala | 63 ++- .../psforever/packet/GamePacketOpcode.scala | 2 +- .../packet/game/AvatarAwardMessage.scala | 21 +- .../packet/game/OffshoreVehicleMessage.scala | 42 ++ .../scala/net/psforever/types/Vector3.scala | 11 + 11 files changed, 495 insertions(+), 188 deletions(-) create mode 100644 src/main/scala/net/psforever/packet/game/OffshoreVehicleMessage.scala 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.