Merge pull request #214 from Fate-JH/ams-workaround

AMS Workaround
This commit is contained in:
Fate-JH 2018-05-21 11:30:18 -04:00 committed by GitHub
commit fdf05337fd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 587 additions and 94 deletions

View file

@ -373,10 +373,9 @@ object Zone {
/** /**
* Message that returns a discovered spawn point to a request source. * Message that returns a discovered spawn point to a request source.
* @param zone_id the zone's text identifier * @param zone_id the zone's text identifier
* @param building the `Building` in which the spawnpoint is located
* @param spawn_tube the spawn point holding object * @param spawn_tube the spawn point holding object
*/ */
final case class SpawnPoint(zone_id : String, building : Building, spawn_tube : SpawnTube) final case class SpawnPoint(zone_id : String, spawn_tube : SpawnTube)
/** /**
* Message that informs a request source that a spawn point could not be discovered with the previous criteria. * Message that informs a request source that a spawn point could not be discovered with the previous criteria.
* @param zone_number this zone's numeric identifier * @param zone_number this zone's numeric identifier

View file

@ -4,9 +4,11 @@ package net.psforever.objects.zones
import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicInteger
import akka.actor.Actor import akka.actor.Actor
import net.psforever.objects.PlanetSideGameObject import net.psforever.objects.{GlobalDefinitions, PlanetSideGameObject}
import net.psforever.objects.serverobject.structures.StructureType import net.psforever.objects.serverobject.structures.StructureType
import net.psforever.types.Vector3 import net.psforever.objects.serverobject.tube.SpawnTube
import net.psforever.objects.vehicles.UtilityType
import net.psforever.types.{DriveState, Vector3}
import org.log4s.Logger import org.log4s.Logger
/** /**
@ -64,30 +66,61 @@ class ZoneActor(zone : Zone) extends Actor {
//own //own
case Zone.Lattice.RequestSpawnPoint(zone_number, player, spawn_group) => case Zone.Lattice.RequestSpawnPoint(zone_number, player, spawn_group) =>
if(zone_number == zone.Number) { if(zone_number == zone.Number) {
val buildingTypeSet = if(spawn_group == 6) {
Set(StructureType.Tower)
}
else if(spawn_group == 7) {
Set(StructureType.Facility, StructureType.Building)
}
else {
Set.empty[StructureType.Value]
}
val playerPosition = player.Position.xy val playerPosition = player.Position.xy
zone.SpawnGroups() (
.filter({ case((building, _)) => if(spawn_group == 2) {
building.Faction == player.Faction && buildingTypeSet.contains(building.BuildingType) //ams
}) zone.Vehicles
.toSeq .filter(veh =>
.sortBy({ case ((building, _)) => veh.DeploymentState == DriveState.Deployed &&
Vector3.DistanceSquared(playerPosition, building.Position.xy) veh.Definition == GlobalDefinitions.ams &&
}) veh.Faction == player.Faction
.headOption match { )
case Some((building, List(tube))) => .sortBy(veh => Vector3.DistanceSquared(playerPosition, veh.Position.xy))
sender ! Zone.Lattice.SpawnPoint(zone.Id, building, tube) .flatMap(veh => veh.Utilities.values.filter(util => util.UtilType == UtilityType.ams_respawn_tube))
.headOption match {
case None =>
None
case Some(util) =>
Some(List(util().asInstanceOf[SpawnTube]))
}
}
else {
//facilities, towers, and buildings
val buildingTypeSet = if(spawn_group == 0) {
Set(StructureType.Facility, StructureType.Tower, StructureType.Building)
}
else if(spawn_group == 6) {
Set(StructureType.Tower)
}
else if(spawn_group == 7) {
Set(StructureType.Facility, StructureType.Building)
}
else {
Set.empty[StructureType.Value]
}
zone.SpawnGroups()
.filter({ case ((building, _)) =>
building.Faction == player.Faction &&
buildingTypeSet.contains(building.BuildingType)
})
.toSeq
.sortBy({ case ((building, _)) =>
Vector3.DistanceSquared(playerPosition, building.Position.xy)
})
.headOption match {
case None | Some((_, Nil)) =>
None
case Some((_, tubes)) =>
Some(tubes)
}
}
) match {
case Some(List(tube)) =>
sender ! Zone.Lattice.SpawnPoint(zone.Id, tube)
case Some((building, tubes)) => case Some(tubes) =>
sender ! Zone.Lattice.SpawnPoint(zone.Id, building, scala.util.Random.shuffle(tubes).head) sender ! Zone.Lattice.SpawnPoint(zone.Id, scala.util.Random.shuffle(tubes).head)
case None => case None =>
sender ! Zone.Lattice.NoValidSpawnPoint(zone_number, Some(spawn_group)) sender ! Zone.Lattice.NoValidSpawnPoint(zone_number, Some(spawn_group))
@ -143,6 +176,68 @@ class ZoneActor(zone : Zone) extends Actor {
} }
object ZoneActor { object ZoneActor {
// import net.psforever.types.PlanetSideEmpire
// import net.psforever.objects.Vehicle
// import net.psforever.objects.serverobject.structures.Building
// def AllSpawnGroup(zone : Zone, targetPosition : Vector3, targetFaction : PlanetSideEmpire.Value) : Option[List[SpawnTube]] = {
// ClosestOwnedSpawnTube(AmsSpawnGroup(zone) ++ BuildingSpawnGroup(zone, 0), targetPosition, targetFaction)
// }
//
// def AmsSpawnGroup(vehicles : List[Vehicle]) : Iterable[(Vector3, PlanetSideEmpire.Value, Iterable[SpawnTube])] = {
// vehicles
// .filter(veh => veh.DeploymentState == DriveState.Deployed && veh.Definition == GlobalDefinitions.ams)
// .map(veh =>
// (veh.Position, veh.Faction,
// veh.Utilities
// .values
// .filter(util => util.UtilType == UtilityType.ams_respawn_tube)
// .map { _().asInstanceOf[SpawnTube] }
// )
// )
// }
//
// def AmsSpawnGroup(zone : Zone, spawn_group : Int = 2) : Iterable[(Vector3, PlanetSideEmpire.Value, Iterable[SpawnTube])] = {
// if(spawn_group == 2) {
// AmsSpawnGroup(zone.Vehicles)
// }
// else {
// Nil
// }
// }
//
// def BuildingSpawnGroup(spawnGroups : Map[Building, List[SpawnTube]]) : Iterable[(Vector3, PlanetSideEmpire.Value, Iterable[SpawnTube])] = {
// spawnGroups
// .map({ case ((building, tubes)) => (building.Position.xy, building.Faction, tubes) })
// }
//
// def BuildingSpawnGroup(zone : Zone, spawn_group : Int) : Iterable[(Vector3, PlanetSideEmpire.Value, Iterable[SpawnTube])] = {
// val buildingTypeSet = if(spawn_group == 0) {
// Set(StructureType.Facility, StructureType.Tower, StructureType.Building)
// }
// else if(spawn_group == 6) {
// Set(StructureType.Tower)
// }
// else if(spawn_group == 7) {
// Set(StructureType.Facility, StructureType.Building)
// }
// else {
// Set.empty[StructureType.Value]
// }
// BuildingSpawnGroup(
// zone.SpawnGroups().filter({ case((building, _)) => buildingTypeSet.contains(building.BuildingType) })
// )
// }
//
// def ClosestOwnedSpawnTube(tubes : Iterable[(Vector3, PlanetSideEmpire.Value, Iterable[SpawnTube])], targetPosition : Vector3, targetFaction : PlanetSideEmpire.Value) : Option[List[SpawnTube]] = {
// tubes
// .toSeq
// .filter({ case (_, faction, _) => faction == targetFaction })
// .sortBy({ case (pos, _, _) => Vector3.DistanceSquared(pos, targetPosition) })
// .take(1)
// .map({ case (_, _, tubes : List[SpawnTube]) => tubes })
// .headOption
// }
/** /**
* Recover an object from a collection and perform any number of validating tests upon it. * Recover an object from a collection and perform any number of validating tests upon it.
* If the object fails any tests, log an error. * If the object fails any tests, log an error.

View file

@ -475,7 +475,7 @@ object GamePacketOpcode extends Enumeration {
case 0x81 => game.DestroyDisplayMessage.decode case 0x81 => game.DestroyDisplayMessage.decode
case 0x82 => noDecoder(TriggerBotAction) case 0x82 => noDecoder(TriggerBotAction)
case 0x83 => noDecoder(SquadWaypointRequest) case 0x83 => noDecoder(SquadWaypointRequest)
case 0x84 => noDecoder(SquadWaypointEvent) case 0x84 => game.SquadWaypointEvent.decode
case 0x85 => noDecoder(OffshoreVehicleMessage) case 0x85 => noDecoder(OffshoreVehicleMessage)
case 0x86 => game.ObjectDeployedMessage.decode case 0x86 => game.ObjectDeployedMessage.decode
case 0x87 => noDecoder(ObjectDeployedCountMessage) case 0x87 => noDecoder(ObjectDeployedCountMessage)
@ -568,7 +568,7 @@ object GamePacketOpcode extends Enumeration {
// OPCODES 0xd0-df // OPCODES 0xd0-df
case 0xd0 => noDecoder(UnknownMessage208) case 0xd0 => noDecoder(UnknownMessage208)
case 0xd1 => game.DisplayedAwardMessage.decode case 0xd1 => game.DisplayedAwardMessage.decode
case 0xd2 => noDecoder(RespawnAMSInfoMessage) case 0xd2 => game.RespawnAMSInfoMessage.decode
case 0xd3 => noDecoder(ComponentDamageMessage) case 0xd3 => noDecoder(ComponentDamageMessage)
case 0xd4 => noDecoder(GenericObjectActionAtPositionMessage) case 0xd4 => noDecoder(GenericObjectActionAtPositionMessage)
case 0xd5 => game.PropertyOverrideMessage.decode case 0xd5 => game.PropertyOverrideMessage.decode

View file

@ -7,6 +7,8 @@ import scodec.{Attempt, Codec, Err}
import scodec.codecs._ import scodec.codecs._
import shapeless.{::, HNil} import shapeless.{::, HNil}
import scala.annotation.tailrec
/** /**
* A `Codec` for the actions that each layer of the diagram performs. * A `Codec` for the actions that each layer of the diagram performs.
* `Style`, `Vertex`, `Action5`, `DrawString`, and `Action7` have additional `DiagramStroke` input data. * `Style`, `Vertex`, `Action5`, `DrawString`, and `Action7` have additional `DiagramStroke` input data.
@ -152,7 +154,7 @@ final case class BattleDiagramAction(action : DiagramActionCode.Value,
*/ */
final case class BattleplanMessage(char_id : Long, final case class BattleplanMessage(char_id : Long,
player_name : String, player_name : String,
zone_id : PlanetSideGUID, zone_id : Int,
diagrams : List[BattleDiagramAction]) diagrams : List[BattleDiagramAction])
extends PlanetSideGamePacket { extends PlanetSideGamePacket {
type Packet = BattleplanMessage type Packet = BattleplanMessage
@ -365,11 +367,11 @@ object BattleplanMessage extends Marshallable[BattleplanMessage] {
* @param list a `List` of extracted `BattleDiagrams`; * @param list a `List` of extracted `BattleDiagrams`;
* technically, the output * technically, the output
*/ */
private def rollDiagramLayers(element : Option[BattleDiagramChain], list : ListBuffer[BattleDiagramAction]) : Unit = { @tailrec private def rollDiagramLayers(element : Option[BattleDiagramChain], list : ListBuffer[BattleDiagramAction]) : Unit = {
if(element.isEmpty) if(element.nonEmpty) {
return list += element.get.diagram
list += element.get.diagram rollDiagramLayers(element.get.next, list)
rollDiagramLayers(element.get.next, list) //tail call optimization }
} }
/** /**
@ -380,17 +382,20 @@ object BattleplanMessage extends Marshallable[BattleplanMessage] {
* technically, the output * technically, the output
* @return a linked list of `BattleDiagramChain` objects * @return a linked list of `BattleDiagramChain` objects
*/ */
private def unrollDiagramLayers(revIter : Iterator[BattleDiagramAction], layers : Option[BattleDiagramChain] = None) : Option[BattleDiagramChain] = { @tailrec private def unrollDiagramLayers(revIter : Iterator[BattleDiagramAction], layers : Option[BattleDiagramChain] = None) : Option[BattleDiagramChain] = {
if(!revIter.hasNext) if(!revIter.hasNext) {
return layers layers
val elem : BattleDiagramAction = revIter.next }
unrollDiagramLayers(revIter, Some(BattleDiagramChain(elem, layers))) //tail call optimization else {
val elem : BattleDiagramAction = revIter.next
unrollDiagramLayers(revIter, Some(BattleDiagramChain(elem, layers)))
}
} }
implicit val codec : Codec[BattleplanMessage] = ( implicit val codec : Codec[BattleplanMessage] = (
("char_id" | uint32L) :: ("char_id" | uint32L) ::
("player_name" | PacketHelpers.encodedWideString) :: ("player_name" | PacketHelpers.encodedWideString) ::
("zone_id" | PlanetSideGUID.codec) :: ("zone_id" | uint16L) ::
(uint8L >>:~ { count => (uint8L >>:~ { count =>
conditional(count > 0, "diagrams" | parse_diagrams_codec(count)).hlist conditional(count > 0, "diagrams" | parse_diagrams_codec(count)).hlist
}) })

View file

@ -0,0 +1,75 @@
// Copyright (c) 2017 PSForever
package net.psforever.packet.game
import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
import net.psforever.types.Vector3
import scodec.{Attempt, Codec, Err}
import scodec.codecs._
import shapeless.{::, HNil}
final case class RespawnInfo(unk1 : List[Vector3],
unk2 : List[Boolean])
final case class RespawnAMSInfoMessage(unk1 : PlanetSideGUID,
unk2 : Boolean,
unk3 : Option[RespawnInfo])
extends PlanetSideGamePacket {
type Packet = RespawnAMSInfoMessage
def opcode = GamePacketOpcode.RespawnAMSInfoMessage
def encode = RespawnAMSInfoMessage.encode(this)
}
object RespawnAMSInfoMessage extends Marshallable[RespawnAMSInfoMessage] {
def apply(u1 : PlanetSideGUID, u2 : Boolean) : RespawnAMSInfoMessage = {
RespawnAMSInfoMessage(u1, u2, None)
}
def apply(u1 : PlanetSideGUID, u2 : Boolean, u3 : RespawnInfo) : RespawnAMSInfoMessage = {
RespawnAMSInfoMessage(u1, u2, Some(u3))
}
private val info_codec : Codec[RespawnInfo] = (
uint(6) >>:~ { size => //max 63
("unk1" | PacketHelpers.listOfNSized(size, Vector3.codec_pos)) ::
("unk2" | PacketHelpers.listOfNSized(size, bool))
}).exmap[RespawnInfo] ({
case _ :: a :: b :: HNil =>
Attempt.Successful(RespawnInfo(a, b))
},
{
case RespawnInfo(a, b) =>
val alen = a.length
if(alen != b.length) {
Attempt.Failure(Err(s"respawn info lists must match in length - $alen vs ${b.length}"))
}
else if(alen > 63) {
Attempt.Failure(Err(s"respawn info lists too long - $alen > 63"))
}
else {
Attempt.Successful(alen :: a :: b :: HNil)
}
}
)
/*
technically, the order of reading should be 16u + 1u + 7u which is byte-aligned
the 7u, however, is divided into a subsequent 1u + 6u reading
if that second 1u is true, the 6u doesn't matter and doesn't need to be read when not necessary
*/
implicit val codec : Codec[RespawnAMSInfoMessage] = (
("unk1" | PlanetSideGUID.codec) ::
("unk2" | bool) ::
(bool >>:~ { test =>
conditional(!test, "unk3" | info_codec).hlist
})
).xmap[RespawnAMSInfoMessage] (
{
case u1 :: u2 :: _ :: u3 :: HNil =>
RespawnAMSInfoMessage(u1, u2, u3)
},
{
case RespawnAMSInfoMessage(u1, u2, u3) =>
u1 :: u2 :: u3.isDefined :: u3 :: HNil
}
)
}

View file

@ -10,7 +10,8 @@ import scodec.codecs._
* @param unk1 when defined, na; * @param unk1 when defined, na;
* non-zero when selecting the sanctuary option from a non-sanctuary continent deployment map * non-zero when selecting the sanctuary option from a non-sanctuary continent deployment map
* @param unk2 when defined, indicates type of spawn point by destination; * @param unk2 when defined, indicates type of spawn point by destination;
* 0 is unknown (may refer to all available spawns regardless of last position); * 0 is nothing;
* 2 is ams;
* 6 is towers; * 6 is towers;
* 7 is facilities * 7 is facilities
* @param unk3 na * @param unk3 na

View file

@ -0,0 +1,78 @@
// Copyright (c) 2017 PSForever
package net.psforever.packet.game
import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket}
import net.psforever.types.Vector3
import scodec.{Attempt, Codec, Err}
import scodec.codecs._
import shapeless.{::, HNil}
final case class WaypointEvent(unk1 : Int,
pos : Vector3,
unk2 : Int)
final case class SquadWaypointEvent(unk1 : Int,
unk2 : Int,
unk3 : Long,
unk4 : Int,
unk5 : Option[Long],
unk6 : Option[WaypointEvent])
extends PlanetSideGamePacket {
type Packet = SquadWaypointEvent
def opcode = GamePacketOpcode.SquadWaypointEvent
def encode = SquadWaypointEvent.encode(this)
}
object SquadWaypointEvent extends Marshallable[SquadWaypointEvent] {
def apply(unk1 : Int, unk2 : Int, unk3 : Long, unk4 : Int, unk_a : Long) : SquadWaypointEvent =
SquadWaypointEvent(unk1, unk2, unk3, unk4, Some(unk_a), None)
def apply(unk1 : Int, unk2 : Int, unk3 : Long, unk4 : Int, unk_a : Int, pos : Vector3, unk_b : Int) : SquadWaypointEvent =
SquadWaypointEvent(unk1, unk2, unk3, unk4, None, Some(WaypointEvent(unk_a, pos, unk_b)))
def apply(unk1 : Int, unk2 : Int, unk3 : Long, unk4 : Int) : SquadWaypointEvent =
SquadWaypointEvent(unk1, unk2, unk3, unk4, None, None)
private val waypoint_codec : Codec[WaypointEvent] = (
("unk1" | uint16L) ::
("pos" | Vector3.codec_pos) ::
("unk2" | uint(3))
).as[WaypointEvent]
implicit val codec : Codec[SquadWaypointEvent] = (
("unk1" | uint2) >>:~ { unk1 =>
("unk2" | uint16L) ::
("unk3" | uint32L) ::
("unk4" | uint8L) ::
("unk5" | conditional(unk1 == 1, uint32L)) ::
("unk6" | conditional(unk1 == 0, waypoint_codec))
}
).exmap[SquadWaypointEvent] (
{
case 0 :: a :: b :: c :: None :: Some(d) :: HNil =>
Attempt.Successful(SquadWaypointEvent(0, a, b, c, None, Some(d)))
case 1 :: a :: b :: c :: Some(d) :: None :: HNil =>
Attempt.Successful(SquadWaypointEvent(1, a, b, c, Some(d), None))
case a :: b :: c :: d :: None :: None :: HNil =>
Attempt.Successful(SquadWaypointEvent(a, b, c, d, None, None))
case n :: _ :: _ :: _ :: _ :: _ :: HNil =>
Attempt.Failure(Err(s"unexpected format for unk1 - $n"))
},
{
case SquadWaypointEvent(0, a, b, c, None, Some(d)) =>
Attempt.Successful(0 :: a :: b :: c :: None :: Some(d) :: HNil)
case SquadWaypointEvent(1, a, b, c, Some(d), None) =>
Attempt.Successful(1 :: a :: b :: c :: Some(d) :: None :: HNil)
case SquadWaypointEvent(a, b, c, d, None, None) =>
Attempt.Successful(a :: b :: c :: d :: None :: None :: HNil)
case SquadWaypointEvent(n, _, _, _, _, _) =>
Attempt.Failure(Err(s"unexpected format for unk1 - $n"))
}
)
}

View file

@ -19,7 +19,7 @@ class BattleplanMessageTest extends Specification {
case BattleplanMessage(char_id, player_name, zone_id, diagrams) => case BattleplanMessage(char_id, player_name, zone_id, diagrams) =>
char_id mustEqual 41490746 char_id mustEqual 41490746
player_name mustEqual "YetAnotherFailureAlt" player_name mustEqual "YetAnotherFailureAlt"
zone_id mustEqual PlanetSideGUID(0) zone_id mustEqual 0
diagrams.size mustEqual 1 diagrams.size mustEqual 1
//0 //0
diagrams.head.action mustEqual DiagramActionCode.StartDrawing diagrams.head.action mustEqual DiagramActionCode.StartDrawing
@ -34,7 +34,7 @@ class BattleplanMessageTest extends Specification {
case BattleplanMessage(char_id, player_name, zone_id, diagrams) => case BattleplanMessage(char_id, player_name, zone_id, diagrams) =>
char_id mustEqual 41490746 char_id mustEqual 41490746
player_name mustEqual "YetAnotherFailureAlt" player_name mustEqual "YetAnotherFailureAlt"
zone_id mustEqual PlanetSideGUID(0) zone_id mustEqual 0
diagrams.size mustEqual 1 diagrams.size mustEqual 1
//0 //0
diagrams.head.action mustEqual DiagramActionCode.StopDrawing diagrams.head.action mustEqual DiagramActionCode.StopDrawing
@ -49,7 +49,7 @@ class BattleplanMessageTest extends Specification {
case BattleplanMessage(char_id, player_name, zone_id, diagrams) => case BattleplanMessage(char_id, player_name, zone_id, diagrams) =>
char_id mustEqual 41378949 char_id mustEqual 41378949
player_name mustEqual "Outstabulous" player_name mustEqual "Outstabulous"
zone_id mustEqual PlanetSideGUID(10) zone_id mustEqual 10
diagrams.size mustEqual 32 diagrams.size mustEqual 32
//0 //0
diagrams.head.action mustEqual DiagramActionCode.Vertex diagrams.head.action mustEqual DiagramActionCode.Vertex
@ -191,7 +191,7 @@ class BattleplanMessageTest extends Specification {
case BattleplanMessage(char_id, player_name, zone_id, diagrams) => case BattleplanMessage(char_id, player_name, zone_id, diagrams) =>
char_id mustEqual 41378949 char_id mustEqual 41378949
player_name mustEqual "Outstabulous" player_name mustEqual "Outstabulous"
zone_id mustEqual PlanetSideGUID(10) zone_id mustEqual 10
diagrams.size mustEqual 3 diagrams.size mustEqual 3
//0 //0
diagrams.head.action mustEqual DiagramActionCode.Style diagrams.head.action mustEqual DiagramActionCode.Style
@ -217,7 +217,7 @@ class BattleplanMessageTest extends Specification {
case BattleplanMessage(char_id, player_name, zone_id, diagrams) => case BattleplanMessage(char_id, player_name, zone_id, diagrams) =>
char_id mustEqual 41378949 char_id mustEqual 41378949
player_name mustEqual "Outstabulous" player_name mustEqual "Outstabulous"
zone_id mustEqual PlanetSideGUID(10) zone_id mustEqual 10
diagrams.size mustEqual 1 diagrams.size mustEqual 1
//0 //0
diagrams.head.action mustEqual DiagramActionCode.DrawString diagrams.head.action mustEqual DiagramActionCode.DrawString
@ -237,7 +237,7 @@ class BattleplanMessageTest extends Specification {
val msg = BattleplanMessage( val msg = BattleplanMessage(
41490746, 41490746,
"YetAnotherFailureAlt", "YetAnotherFailureAlt",
PlanetSideGUID(0), 0,
BattleDiagramAction(DiagramActionCode.StartDrawing) :: BattleDiagramAction(DiagramActionCode.StartDrawing) ::
Nil Nil
) )
@ -250,7 +250,7 @@ class BattleplanMessageTest extends Specification {
val msg = BattleplanMessage( val msg = BattleplanMessage(
41490746, 41490746,
"YetAnotherFailureAlt", "YetAnotherFailureAlt",
PlanetSideGUID(0), 0,
BattleDiagramAction(DiagramActionCode.StopDrawing) :: BattleDiagramAction(DiagramActionCode.StopDrawing) ::
Nil Nil
) )
@ -263,7 +263,7 @@ class BattleplanMessageTest extends Specification {
val msg = BattleplanMessage( val msg = BattleplanMessage(
41378949, 41378949,
"Outstabulous", "Outstabulous",
PlanetSideGUID(10), 10,
BattleDiagramAction.vertex(7512.0f, 6312.0f) :: BattleDiagramAction.vertex(7512.0f, 6312.0f) ::
BattleDiagramAction.vertex(7512.0f, 6328.0f) :: BattleDiagramAction.vertex(7512.0f, 6328.0f) ::
BattleDiagramAction.vertex(7512.0f, 6344.0f) :: BattleDiagramAction.vertex(7512.0f, 6344.0f) ::
@ -307,7 +307,7 @@ class BattleplanMessageTest extends Specification {
val msg = BattleplanMessage( val msg = BattleplanMessage(
41378949, 41378949,
"Outstabulous", "Outstabulous",
PlanetSideGUID(10), 10,
BattleDiagramAction.style(3.0f, 2) :: BattleDiagramAction.style(3.0f, 2) ::
BattleDiagramAction.vertex(7512.0f, 6328.0f) :: BattleDiagramAction.vertex(7512.0f, 6328.0f) ::
BattleDiagramAction.vertex(7512.0f, 6344.0f) :: BattleDiagramAction.vertex(7512.0f, 6344.0f) ::
@ -322,7 +322,7 @@ class BattleplanMessageTest extends Specification {
val msg = BattleplanMessage( val msg = BattleplanMessage(
41378949, 41378949,
"Outstabulous", "Outstabulous",
PlanetSideGUID(10), 10,
BattleDiagramAction.drawString(7512.0f, 6312.0f, 2, 0, "Hello Auraxis!") :: Nil BattleDiagramAction.drawString(7512.0f, 6312.0f, 2, 0, "Hello Auraxis!") :: Nil
) )
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector val pkt = PacketCoding.EncodePacket(msg).require.toByteVector

View file

@ -0,0 +1,99 @@
// Copyright (c) 2017 PSForever
package game
import org.specs2.mutable._
import net.psforever.packet._
import net.psforever.packet.game.{SquadWaypointEvent, WaypointEvent}
import net.psforever.types.Vector3
import scodec.bits._
class SquadWaypointEventTest extends Specification {
val string_1 = hex"84 82c025d9b6c04000"
val string_2 = hex"84 8280000000000100"
val string_3 = hex"84 00c03f1e5e808042803f3018f316800008"
val string_4 = hex"84 40c03f1e5e80804100000000" //fabricated example
"decode (1)" in {
PacketCoding.DecodePacket(string_1).require match {
case SquadWaypointEvent(unk1, unk2, unk3, unk4, unk5, unk6) =>
unk1 mustEqual 2
unk2 mustEqual 11
unk3 mustEqual 31155863L
unk4 mustEqual 0
unk5 mustEqual None
unk6 mustEqual None
case _ =>
ko
}
}
"decode (2)" in {
PacketCoding.DecodePacket(string_2).require match {
case SquadWaypointEvent(unk1, unk2, unk3, unk4, unk5, unk6) =>
unk1 mustEqual 2
unk2 mustEqual 10
unk3 mustEqual 0L
unk4 mustEqual 4
unk5 mustEqual None
unk6 mustEqual None
case _ =>
ko
}
}
"decode (3)" in {
PacketCoding.DecodePacket(string_3).require match {
case SquadWaypointEvent(unk1, unk2, unk3, unk4, unk5, unk6) =>
unk1 mustEqual 0
unk2 mustEqual 3
unk3 mustEqual 41581052L
unk4 mustEqual 1
unk5 mustEqual None
unk6 mustEqual Some(WaypointEvent(10, Vector3(3457.9688f, 5514.4688f, 0.0f), 1))
case _ =>
ko
}
}
"decode (4)" in {
PacketCoding.DecodePacket(string_4).require match {
case SquadWaypointEvent(unk1, unk2, unk3, unk4, unk5, unk6) =>
unk1 mustEqual 1
unk2 mustEqual 3
unk3 mustEqual 41581052L
unk4 mustEqual 1
unk5 mustEqual Some(4L)
unk6 mustEqual None
case _ =>
ko
}
}
"encode (1)" in {
val msg = SquadWaypointEvent(2, 11, 31155863L, 0)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string_1
}
"encode (2)" in {
val msg = SquadWaypointEvent(2, 10, 0L, 4)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string_2
}
"encode (3)" in {
val msg = SquadWaypointEvent(0, 3, 41581052L, 1, 10, Vector3(3457.9688f, 5514.4688f, 0.0f), 1)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string_3
}
"encode (4)" in {
val msg = SquadWaypointEvent(1, 3, 41581052L, 1, 4L)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string_4
}
}

View file

@ -194,7 +194,6 @@ class ZoneActorTest extends ActorTest {
val reply1 = receiveOne(Duration.create(200, "ms")) val reply1 = receiveOne(Duration.create(200, "ms"))
assert(reply1.isInstanceOf[Zone.Lattice.SpawnPoint]) assert(reply1.isInstanceOf[Zone.Lattice.SpawnPoint])
assert(reply1.asInstanceOf[Zone.Lattice.SpawnPoint].zone_id == "test") assert(reply1.asInstanceOf[Zone.Lattice.SpawnPoint].zone_id == "test")
assert(reply1.asInstanceOf[Zone.Lattice.SpawnPoint].building == bldg1)
assert(reply1.asInstanceOf[Zone.Lattice.SpawnPoint].spawn_tube.Owner == bldg1) assert(reply1.asInstanceOf[Zone.Lattice.SpawnPoint].spawn_tube.Owner == bldg1)
player.Position = Vector3(3,3,3) //closer to bldg3 player.Position = Vector3(3,3,3) //closer to bldg3
@ -202,7 +201,6 @@ class ZoneActorTest extends ActorTest {
val reply3 = receiveOne(Duration.create(200, "ms")) val reply3 = receiveOne(Duration.create(200, "ms"))
assert(reply3.isInstanceOf[Zone.Lattice.SpawnPoint]) assert(reply3.isInstanceOf[Zone.Lattice.SpawnPoint])
assert(reply3.asInstanceOf[Zone.Lattice.SpawnPoint].zone_id == "test") assert(reply3.asInstanceOf[Zone.Lattice.SpawnPoint].zone_id == "test")
assert(reply3.asInstanceOf[Zone.Lattice.SpawnPoint].building == bldg3)
assert(reply3.asInstanceOf[Zone.Lattice.SpawnPoint].spawn_tube.Owner == bldg3) assert(reply3.asInstanceOf[Zone.Lattice.SpawnPoint].spawn_tube.Owner == bldg3)
} }

View file

@ -4,7 +4,7 @@ import java.util.concurrent.atomic.AtomicInteger
import akka.actor.{Actor, ActorRef, Cancellable, MDCContextAware} import akka.actor.{Actor, ActorRef, Cancellable, MDCContextAware}
import net.psforever.packet._ import net.psforever.packet._
import net.psforever.packet.control._ import net.psforever.packet.control._
import net.psforever.packet.game._ import net.psforever.packet.game.{BattleDiagramAction, _}
import scodec.Attempt.{Failure, Successful} import scodec.Attempt.{Failure, Successful}
import scodec.bits._ import scodec.bits._
import org.log4s.MDC import org.log4s.MDC
@ -76,6 +76,8 @@ class WorldSessionActor extends Actor with MDCContextAware {
var deadState : DeadState.Value = DeadState.Dead var deadState : DeadState.Value = DeadState.Dead
var whenUsedLastKit : Long = 0 var whenUsedLastKit : Long = 0
var amsSpawnPoint : Option[SpawnTube] = None
var clientKeepAlive : Cancellable = DefaultCancellable.obj var clientKeepAlive : Cancellable = DefaultCancellable.obj
var progressBarUpdate : Cancellable = DefaultCancellable.obj var progressBarUpdate : Cancellable = DefaultCancellable.obj
var reviveTimer : Cancellable = DefaultCancellable.obj var reviveTimer : Cancellable = DefaultCancellable.obj
@ -546,16 +548,43 @@ class WorldSessionActor extends Actor with MDCContextAware {
} }
} }
case VehicleResponse.UpdateAmsSpawnPoint(list) =>
if(player.isBackpack) {
//dismiss old ams spawn point
ClearCurrentAmsSpawnPoint()
//draw new ams spawn point
list
.filter(tube => tube.Faction == player.Faction)
.sortBy(tube => Vector3.DistanceSquared(tube.Position, player.Position))
.headOption match {
case Some(tube) =>
sendResponse(
BattleplanMessage(41378949, "ams", continent.Number, List(BattleDiagramAction(DiagramActionCode.StartDrawing)))
)
sendResponse(
BattleplanMessage(41378949, "ams", continent.Number, List(BattleDiagramAction.drawString(tube.Position.x, tube.Position.y, 3, 0, "AMS")))
)
amsSpawnPoint = Some(tube)
case None => ;
}
}
case _ => ; case _ => ;
} }
case Deployment.CanDeploy(obj, state) => case Deployment.CanDeploy(obj, state) =>
val vehicle_guid = obj.GUID val vehicle_guid = obj.GUID
if(state == DriveState.Deploying) { //TODO remove this arbitrary allowance angle when no longer helpful
if(obj.Orientation.x > 30 && obj.Orientation.x < 330) {
obj.DeploymentState = DriveState.Mobile
CanNotChangeDeployment(obj, state, "ground too steep")
}
else if(state == DriveState.Deploying) {
log.info(s"DeployRequest: $obj transitioning to deploy state") log.info(s"DeployRequest: $obj transitioning to deploy state")
obj.Velocity = Some(Vector3.Zero) //no velocity obj.Velocity = Some(Vector3.Zero) //no velocity
sendResponse(DeployRequestMessage(player.GUID, vehicle_guid, state, 0, false, Vector3.Zero)) sendResponse(DeployRequestMessage(player.GUID, vehicle_guid, state, 0, false, Vector3.Zero))
vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.DeployRequest(player.GUID, vehicle_guid, state, 0, false, Vector3.Zero)) vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.DeployRequest(player.GUID, vehicle_guid, state, 0, false, Vector3.Zero))
DeploymentActivities(obj)
import scala.concurrent.duration._ import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.ExecutionContext.Implicits.global
context.system.scheduler.scheduleOnce(obj.DeployTime milliseconds, obj.Actor, Deployment.TryDeploy(DriveState.Deployed)) context.system.scheduler.scheduleOnce(obj.DeployTime milliseconds, obj.Actor, Deployment.TryDeploy(DriveState.Deployed))
@ -577,6 +606,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
log.info(s"DeployRequest: $obj transitioning to undeploy state") log.info(s"DeployRequest: $obj transitioning to undeploy state")
sendResponse(DeployRequestMessage(player.GUID, vehicle_guid, state, 0, false, Vector3.Zero)) sendResponse(DeployRequestMessage(player.GUID, vehicle_guid, state, 0, false, Vector3.Zero))
vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.DeployRequest(player.GUID, vehicle_guid, state, 0, false, Vector3.Zero)) vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.DeployRequest(player.GUID, vehicle_guid, state, 0, false, Vector3.Zero))
DeploymentActivities(obj)
import scala.concurrent.duration._ import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.ExecutionContext.Implicits.global
context.system.scheduler.scheduleOnce(obj.UndeployTime milliseconds, obj.Actor, Deployment.TryUndeploy(DriveState.Mobile)) context.system.scheduler.scheduleOnce(obj.UndeployTime milliseconds, obj.Actor, Deployment.TryUndeploy(DriveState.Mobile))
@ -585,6 +615,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
log.info(s"DeployRequest: $obj is Mobile") log.info(s"DeployRequest: $obj is Mobile")
sendResponse(DeployRequestMessage(player.GUID, vehicle_guid, state, 0, false, Vector3.Zero)) sendResponse(DeployRequestMessage(player.GUID, vehicle_guid, state, 0, false, Vector3.Zero))
vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.DeployRequest(player.GUID, vehicle_guid, state, 0, false, Vector3.Zero)) vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.DeployRequest(player.GUID, vehicle_guid, state, 0, false, Vector3.Zero))
DeploymentActivities(obj)
//... //...
} }
else { else {
@ -1247,10 +1278,41 @@ class WorldSessionActor extends Actor with MDCContextAware {
case Zone.Population.PlayerAlreadySpawned(zone, tplayer) => case Zone.Population.PlayerAlreadySpawned(zone, tplayer) =>
log.warn(s"$tplayer is already spawned on zone ${zone.Id}; a clerical error?") log.warn(s"$tplayer is already spawned on zone ${zone.Id}; a clerical error?")
case Zone.Lattice.SpawnPoint(zone_id, building, spawn_tube) => case Zone.Lattice.SpawnPoint(zone_id, spawn_tube) =>
log.info(s"Zone.Lattice.SpawnPoint: spawn point on $zone_id in ${building.Id} @ ${spawn_tube.GUID.guid} selected") var pos = spawn_tube.Position
var ori = spawn_tube.Orientation
spawn_tube.Owner match {
case building : Building =>
log.info(s"Zone.Lattice.SpawnPoint: spawn point on $zone_id in building ${building.Id} selected")
case vehicle : Vehicle =>
//TODO replace this bad math with good math or no math
//position the player alongside either of the AMS's terminals, facing away from it
val side = if(System.currentTimeMillis() % 2 == 0) 1 else -1 //right | left
val z = spawn_tube.Orientation.z
val zrot = (z + 90) % 360
val x = spawn_tube.Orientation.x
val xsin = 3 * side * math.abs(math.sin(math.toRadians(x))).toFloat + 0.5f //sin because 0-degrees is up
val zrad = math.toRadians(zrot)
pos = pos + (Vector3(math.sin(zrad).toFloat, math.cos(zrad).toFloat, 0) * (3 * side)) //x=sin, y=cos because compass-0 is East, not North
ori = if(side == 1) {
Vector3(0, 0, zrot)
}
else {
Vector3(0, 0, (z - 90) % 360)
}
pos = if(x >= 330) { //leaning to the left
pos + Vector3(0, 0, xsin)
}
else {
pos - Vector3(0, 0, xsin)
}
log.info(s"Zone.Lattice.SpawnPoint: spawn point on $zone_id at ams ${vehicle.GUID.guid} selected")
case owner =>
log.warn(s"Zone.Lattice.SpawnPoint: spawn point on $zone_id at ${spawn_tube.Position} has unexpected owner $owner")
}
respawnTimer.cancel respawnTimer.cancel
reviveTimer.cancel reviveTimer.cancel
ClearCurrentAmsSpawnPoint()
val sameZone = zone_id == continent.Id val sameZone = zone_id == continent.Id
val backpack = player.isBackpack val backpack = player.isBackpack
val respawnTime : Long = if(sameZone) { 10 } else { 0 } //s val respawnTime : Long = if(sameZone) { 10 } else { 0 } //s
@ -1267,8 +1329,8 @@ class WorldSessionActor extends Actor with MDCContextAware {
player //player is deconstructing self player //player is deconstructing self
} }
tplayer.Position = spawn_tube.Position tplayer.Position = pos
tplayer.Orientation = spawn_tube.Orientation tplayer.Orientation = ori
val (target, msg) : (ActorRef, Any) = if(sameZone) { val (target, msg) : (ActorRef, Any) = if(sameZone) {
if(backpack) { if(backpack) {
//respawning from unregistered player //respawning from unregistered player
@ -1305,7 +1367,13 @@ class WorldSessionActor extends Actor with MDCContextAware {
case Zone.Lattice.NoValidSpawnPoint(zone_number, Some(spawn_group)) => case Zone.Lattice.NoValidSpawnPoint(zone_number, Some(spawn_group)) =>
log.warn(s"Zone.Lattice.SpawnPoint: zone $zone_number has no available ${player.Faction} targets in spawn group $spawn_group") log.warn(s"Zone.Lattice.SpawnPoint: zone $zone_number has no available ${player.Faction} targets in spawn group $spawn_group")
reviveTimer.cancel reviveTimer.cancel
RequestSanctuaryZoneSpawn(player, zone_number) if(spawn_group == 2) {
sendResponse(ChatMsg(ChatMessageType.CMT_OPEN, false, "", "No friendly AMS is deployed in this region.", None))
galaxy ! Zone.Lattice.RequestSpawnPoint(zone_number, player, 0)
}
else {
RequestSanctuaryZoneSpawn(player, zone_number)
}
case InterstellarCluster.ClientInitializationComplete() => case InterstellarCluster.ClientInitializationComplete() =>
StopBundlingPackets() StopBundlingPackets()
@ -1486,7 +1554,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
//TODO begin temp player character auto-loading; remove later //TODO begin temp player character auto-loading; remove later
import net.psforever.objects.GlobalDefinitions._ import net.psforever.objects.GlobalDefinitions._
import net.psforever.types.CertificationType._ import net.psforever.types.CertificationType._
avatar = Avatar("TestCharacter"+sessionId.toString, PlanetSideEmpire.VS, CharacterGender.Female, 41, 1) avatar = Avatar("TestCharacter" + sessionId.toString, PlanetSideEmpire.VS, CharacterGender.Female, 41, 1)
avatar.Certifications += StandardAssault avatar.Certifications += StandardAssault
avatar.Certifications += MediumAssault avatar.Certifications += MediumAssault
avatar.Certifications += StandardExoSuit avatar.Certifications += StandardExoSuit
@ -1539,6 +1607,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.ExecutionContext.Implicits.global
clientKeepAlive.cancel clientKeepAlive.cancel
clientKeepAlive = context.system.scheduler.schedule(0 seconds, 500 milliseconds, self, PokeClient()) clientKeepAlive = context.system.scheduler.schedule(0 seconds, 500 milliseconds, self, PokeClient())
log.warn(PacketCoding.DecodePacket(hex"d2327e7b8a972b95113881003710").toString)
case msg @ CharacterCreateRequestMessage(name, head, voice, gender, empire) => case msg @ CharacterCreateRequestMessage(name, head, voice, gender, empire) =>
log.info("Handling " + msg) log.info("Handling " + msg)
@ -1571,7 +1640,6 @@ class WorldSessionActor extends Actor with MDCContextAware {
vehicleService ! Service.Join(continent.Id) vehicleService ! Service.Join(continent.Id)
configZone(continent) configZone(continent)
sendResponse(TimeOfDayMessage(1191182336)) sendResponse(TimeOfDayMessage(1191182336))
//custom //custom
sendResponse(ContinentalLockUpdateMessage(13, PlanetSideEmpire.VS)) // "The VS have captured the VS Sanctuary." sendResponse(ContinentalLockUpdateMessage(13, PlanetSideEmpire.VS)) // "The VS have captured the VS Sanctuary."
sendResponse(ReplicationStreamMessage(5, Some(6), Vector(SquadListing()))) //clear squad list sendResponse(ReplicationStreamMessage(5, Some(6), Vector(SquadListing()))) //clear squad list
@ -1597,7 +1665,9 @@ class WorldSessionActor extends Actor with MDCContextAware {
} }
}) })
//load corpses in zone //load corpses in zone
continent.Corpses.foreach { TurnPlayerIntoCorpse } continent.Corpses.foreach {
TurnPlayerIntoCorpse
}
//load active vehicles in zone //load active vehicles in zone
continent.Vehicles.foreach(vehicle => { continent.Vehicles.foreach(vehicle => {
val definition = vehicle.Definition val definition = vehicle.Definition
@ -1615,7 +1685,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
ReloadVehicleAccessPermissions(vehicle) ReloadVehicleAccessPermissions(vehicle)
}) })
//implant terminals //implant terminals
continent.Map.TerminalToInterface.foreach({ case((terminal_guid, interface_guid)) => continent.Map.TerminalToInterface.foreach({ case ((terminal_guid, interface_guid)) =>
val parent_guid = PlanetSideGUID(terminal_guid) val parent_guid = PlanetSideGUID(terminal_guid)
continent.GUID(interface_guid) match { continent.GUID(interface_guid) match {
case Some(obj : Terminal) => case Some(obj : Terminal) =>
@ -1633,7 +1703,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
//seat terminal occupants //seat terminal occupants
continent.GUID(terminal_guid) match { continent.GUID(terminal_guid) match {
case Some(obj : Mountable) => case Some(obj : Mountable) =>
obj.MountPoints.foreach({ case((_, seat_num)) => obj.MountPoints.foreach({ case ((_, seat_num)) =>
obj.Seat(seat_num).get.Occupant match { obj.Seat(seat_num).get.Occupant match {
case Some(tplayer) => case Some(tplayer) =>
if(tplayer.HasGUID) { if(tplayer.HasGUID) {
@ -1656,7 +1726,6 @@ class WorldSessionActor extends Actor with MDCContextAware {
player.FacingYawUpper = yaw_upper player.FacingYawUpper = yaw_upper
player.Crouching = is_crouching player.Crouching = is_crouching
player.Jumping = is_jumping player.Jumping = is_jumping
if(vel.isDefined && usingMedicalTerminal.isDefined) { if(vel.isDefined && usingMedicalTerminal.isDefined) {
StopUsingProximityUnit(continent.GUID(usingMedicalTerminal.get).get.asInstanceOf[ProximityTerminal]) StopUsingProximityUnit(continent.GUID(usingMedicalTerminal.get).get.asInstanceOf[ProximityTerminal])
} }
@ -1692,16 +1761,17 @@ class WorldSessionActor extends Actor with MDCContextAware {
log.warn(s"ChildObjectState: player $player's controllable agent not available in scope") log.warn(s"ChildObjectState: player $player's controllable agent not available in scope")
} }
case None => case None =>
//TODO status condition of "playing getting out of vehicle to allow for late packets without warning //TODO status condition of "playing getting out of vehicle to allow for late packets without warning
//log.warn(s"ChildObjectState: player $player not related to anything with a controllable agent") //log.warn(s"ChildObjectState: player $player not related to anything with a controllable agent")
} }
//log.info("ChildObjectState: " + msg) //log.info("ChildObjectState: " + msg)
case msg @ VehicleStateMessage(vehicle_guid, unk1, pos, ang, vel, unk5, unk6, unk7, wheels, unk9, unkA) => case msg @ VehicleStateMessage(vehicle_guid, unk1, pos, ang, vel, unk5, unk6, unk7, wheels, unk9, unkA) =>
continent.GUID(vehicle_guid) match { continent.GUID(vehicle_guid) match {
case Some(obj : Vehicle) => case Some(obj : Vehicle) =>
val seat = obj.Seat(0).get val seat = obj.Seat(0).get
if(seat.Occupant.contains(player)) { //we're driving the vehicle if(seat.Occupant.contains(player)) {
//we're driving the vehicle
player.Position = pos //convenient player.Position = pos //convenient
if(seat.ControlledWeapon.isEmpty) { if(seat.ControlledWeapon.isEmpty) {
player.Orientation = Vector3(0f, 0f, ang.z) //convenient player.Orientation = Vector3(0f, 0f, ang.z) //convenient
@ -1711,17 +1781,17 @@ class WorldSessionActor extends Actor with MDCContextAware {
obj.Velocity = vel obj.Velocity = vel
vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.VehicleState(player.GUID, vehicle_guid, unk1, pos, ang, vel, unk5, unk6, unk7, wheels, unk9, unkA)) vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.VehicleState(player.GUID, vehicle_guid, unk1, pos, ang, vel, unk5, unk6, unk7, wheels, unk9, unkA))
} }
//TODO placing a "not driving" warning here may trigger as we are disembarking the vehicle //TODO placing a "not driving" warning here may trigger as we are disembarking the vehicle
case _ => case _ =>
log.warn(s"VehicleState: no vehicle $vehicle_guid found in zone") log.warn(s"VehicleState: no vehicle $vehicle_guid found in zone")
} }
//log.info(s"VehicleState: $msg") //log.info(s"VehicleState: $msg")
case msg @ VehicleSubStateMessage(vehicle_guid, player_guid, vehicle_pos, vehicle_ang, vel, unk1, unk2) => case msg @ VehicleSubStateMessage(vehicle_guid, player_guid, vehicle_pos, vehicle_ang, vel, unk1, unk2) =>
//log.info(s"VehicleSubState: $vehicle_guid, $player_guid, $vehicle_pos, $vehicle_ang, $vel, $unk1, $unk2") //log.info(s"VehicleSubState: $vehicle_guid, $player_guid, $vehicle_pos, $vehicle_ang, $vel, $unk1, $unk2")
case msg @ ProjectileStateMessage(projectile_guid, shot_pos, shot_vector, unk1, unk2, unk3, unk4, time_alive) => case msg @ ProjectileStateMessage(projectile_guid, shot_pos, shot_vector, unk1, unk2, unk3, unk4, time_alive) =>
//log.info("ProjectileState: " + msg) //log.info("ProjectileState: " + msg)
case msg @ ReleaseAvatarRequestMessage() => case msg @ ReleaseAvatarRequestMessage() =>
log.info(s"ReleaseAvatarRequest: ${player.GUID} on ${continent.Id} has released") log.info(s"ReleaseAvatarRequest: ${player.GUID} on ${continent.Id} has released")
@ -1730,6 +1800,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
deadState = DeadState.Release deadState = DeadState.Release
sendResponse(AvatarDeadStateMessage(DeadState.Release, 0, 0, player.Position, player.Faction, true)) sendResponse(AvatarDeadStateMessage(DeadState.Release, 0, 0, player.Position, player.Faction, true))
continent.Population ! Zone.Population.Release(avatar) continent.Population ! Zone.Population.Release(avatar)
vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.UpdateAmsSpawnPoint(continent))
player.VehicleSeated match { player.VehicleSeated match {
case None => case None =>
FriskCorpse(player) FriskCorpse(player)
@ -1738,7 +1809,8 @@ class WorldSessionActor extends Actor with MDCContextAware {
continent.Population ! Zone.Corpse.Add(player) //TODO move back out of this match case when changing below issue continent.Population ! Zone.Corpse.Add(player) //TODO move back out of this match case when changing below issue
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.Release(player, continent)) avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.Release(player, continent))
} }
else { //no items in inventory; leave no corpse else {
//no items in inventory; leave no corpse
val player_guid = player.GUID val player_guid = player.GUID
sendResponse(ObjectDeleteMessage(player_guid, 0)) sendResponse(ObjectDeleteMessage(player_guid, 0))
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ObjectDelete(player_guid, player_guid, 0)) avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ObjectDelete(player_guid, player_guid, 0))
@ -1769,20 +1841,22 @@ class WorldSessionActor extends Actor with MDCContextAware {
log.info("SetChatFilters: " + msg) log.info("SetChatFilters: " + msg)
case msg @ ChatMsg(messagetype, has_wide_contents, recipient, contents, note_contents) => case msg @ ChatMsg(messagetype, has_wide_contents, recipient, contents, note_contents) =>
var makeReply : Boolean = true
var echoContents : String = contents var echoContents : String = contents
val trimContents = contents.trim
//TODO messy on/off strings may work //TODO messy on/off strings may work
if(messagetype == ChatMessageType.CMT_FLY) { if(messagetype == ChatMessageType.CMT_FLY) {
if(contents.trim.equals("on")) { if(trimContents.equals("on")) {
flying = true flying = true
} }
else if(contents.trim.equals("off")) { else if(trimContents.equals("off")) {
flying = false flying = false
} }
} }
else if(messagetype == ChatMessageType.CMT_SPEED) { else if(messagetype == ChatMessageType.CMT_SPEED) {
speed = { speed = {
try { try {
contents.trim.toFloat trimContents.toFloat
} }
catch { catch {
case _ : Exception => case _ : Exception =>
@ -1792,7 +1866,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
} }
} }
else if(messagetype == ChatMessageType.CMT_TOGGLESPECTATORMODE) { else if(messagetype == ChatMessageType.CMT_TOGGLESPECTATORMODE) {
if(contents.trim.equals("on")) { if(trimContents.equals("on")) {
spectator = true spectator = true
} }
else if(contents.trim.equals("off")) { else if(contents.trim.equals("off")) {
@ -1826,18 +1900,19 @@ class WorldSessionActor extends Actor with MDCContextAware {
case (false, _) => ; case (false, _) => ;
} }
// TODO: Prevents log spam, but should be handled correctly // TODO: Prevents log spam, but should be handled correctly
if (messagetype != ChatMessageType.CMT_TOGGLE_GM) { if(messagetype != ChatMessageType.CMT_TOGGLE_GM) {
log.info("Chat: " + msg) log.info("Chat: " + msg)
} }
else {
makeReply = false
}
if(messagetype == ChatMessageType.CMT_SUICIDE) { if(messagetype == ChatMessageType.CMT_SUICIDE) {
if(player.isAlive && deadState != DeadState.Release) { if(player.isAlive && deadState != DeadState.Release) {
KillPlayer(player) KillPlayer(player)
} }
} }
if(messagetype == ChatMessageType.CMT_DESTROY) { if(messagetype == ChatMessageType.CMT_DESTROY) {
val guid = contents.toInt val guid = contents.toInt
continent.Map.TerminalToSpawnPad.get(guid) match { continent.Map.TerminalToSpawnPad.get(guid) match {
@ -1847,25 +1922,30 @@ class WorldSessionActor extends Actor with MDCContextAware {
self ! PacketCoding.CreateGamePacket(0, RequestDestroyMessage(PlanetSideGUID(guid))) self ! PacketCoding.CreateGamePacket(0, RequestDestroyMessage(PlanetSideGUID(guid)))
} }
} }
if(messagetype == ChatMessageType.CMT_VOICE) {
if (messagetype == ChatMessageType.CMT_VOICE) {
sendResponse(ChatMsg(ChatMessageType.CMT_VOICE, false, player.Name, contents, None)) sendResponse(ChatMsg(ChatMessageType.CMT_VOICE, false, player.Name, contents, None))
} }
// TODO: handle this appropriately // TODO: handle this appropriately
if(messagetype == ChatMessageType.CMT_QUIT) { if(messagetype == ChatMessageType.CMT_QUIT) {
sendResponse(DropCryptoSession()) sendResponse(DropCryptoSession())
sendResponse(DropSession(sessionId, "user quit")) sendResponse(DropSession(sessionId, "user quit"))
} }
//dev hack; consider bang-commands to complement slash-commands in future
if(contents.trim.equals("!loc")) { //dev hack; consider bang-commands to complement slash-commands in future if(trimContents.equals("!loc")) {
echoContents = s"zone=${continent.Id} pos=${player.Position.x},${player.Position.y},${player.Position.z}; ori=${player.Orientation.x},${player.Orientation.y},${player.Orientation.z}" echoContents = s"zone=${continent.Id} pos=${player.Position.x},${player.Position.y},${player.Position.z}; ori=${player.Orientation.x},${player.Orientation.y},${player.Orientation.z}"
log.info(echoContents) log.info(echoContents)
} }
else if(trimContents.equals("!ams")) {
makeReply = false
if(player.isBackpack) { //player is on deployment screen (either dead or deconstructed)
galaxy ! Zone.Lattice.RequestSpawnPoint(continent.Number, player, 2)
}
}
// TODO: Depending on messagetype, may need to prepend sender's name to contents with proper spacing // TODO: Depending on messagetype, may need to prepend sender's name to contents with proper spacing
// TODO: Just replays the packet straight back to sender; actually needs to be routed to recipients! // TODO: Just replays the packet straight back to sender; actually needs to be routed to recipients!
sendResponse(ChatMsg(messagetype, has_wide_contents, recipient, echoContents, note_contents)) if(makeReply) {
sendResponse(ChatMsg(messagetype, has_wide_contents, recipient, echoContents, note_contents))
}
case msg @ VoiceHostRequest(unk, PlanetSideGUID(player_guid), data) => case msg @ VoiceHostRequest(unk, PlanetSideGUID(player_guid), data) =>
log.info("Player "+player_guid+" requested in-game voice chat.") log.info("Player "+player_guid+" requested in-game voice chat.")
@ -3989,7 +4069,19 @@ class WorldSessionActor extends Actor with MDCContextAware {
obj match { obj match {
case vehicle : Vehicle => case vehicle : Vehicle =>
ReloadVehicleAccessPermissions(vehicle) //TODO we should not have to do this imho ReloadVehicleAccessPermissions(vehicle) //TODO we should not have to do this imho
sendResponse(PlanetsideAttributeMessage(obj.GUID, 81, 1)) //
if(obj.Definition == GlobalDefinitions.ams) {
obj.DeploymentState match {
case DriveState.Deployed =>
vehicleService ! VehicleServiceMessage.AMSDeploymentChange(continent)
sendResponse(PlanetsideAttributeMessage(obj.GUID, 81, 1))
case DriveState.Undeploying =>
vehicleService ! VehicleServiceMessage.AMSDeploymentChange(continent)
sendResponse(PlanetsideAttributeMessage(obj.GUID, 81, 0))
case DriveState.Mobile | DriveState.State7 =>
case _ => ;
}
}
case _ => ; case _ => ;
} }
} }
@ -4013,6 +4105,17 @@ class WorldSessionActor extends Actor with MDCContextAware {
log.error(s"DeployRequest: $obj can not transition to $state - $reason$mobileShift") log.error(s"DeployRequest: $obj can not transition to $state - $reason$mobileShift")
} }
def ClearCurrentAmsSpawnPoint() : Unit = {
amsSpawnPoint match {
case Some(_) =>
sendResponse(
BattleplanMessage(41378949, "ams", continent.Number, List(BattleDiagramAction(DiagramActionCode.StopDrawing)))
)
amsSpawnPoint = None
case None => ;
}
}
/** /**
* For a given continental structure, determine the method of generating server-join client configuration packets. * For a given continental structure, determine the method of generating server-join client configuration packets.
* @param continentNumber the zone id * @param continentNumber the zone id

View file

@ -25,4 +25,6 @@ object VehicleAction {
final case class UnloadVehicle(player_guid : PlanetSideGUID, continent : Zone, vehicle : Vehicle) extends Action final case class UnloadVehicle(player_guid : PlanetSideGUID, continent : Zone, vehicle : Vehicle) extends Action
final case class UnstowEquipment(player_guid : PlanetSideGUID, item_guid : PlanetSideGUID) extends Action final case class UnstowEquipment(player_guid : PlanetSideGUID, item_guid : PlanetSideGUID) extends Action
final case class VehicleState(player_guid : PlanetSideGUID, vehicle_guid : PlanetSideGUID, unk1 : Int, pos : Vector3, ang : Vector3, vel : Option[Vector3], unk2 : Option[Int], unk3 : Int, unk4 : Int, wheel_direction : Int, unk5 : Boolean, unk6 : Boolean) extends Action final case class VehicleState(player_guid : PlanetSideGUID, vehicle_guid : PlanetSideGUID, unk1 : Int, pos : Vector3, ang : Vector3, vel : Option[Vector3], unk2 : Option[Int], unk3 : Int, unk4 : Int, wheel_direction : Int, unk5 : Boolean, unk6 : Boolean) extends Action
final case class UpdateAmsSpawnPoint(zone : Zone) extends Action
} }

View file

@ -1,6 +1,7 @@
// Copyright (c) 2017 PSForever // Copyright (c) 2017 PSForever
package services.vehicle package services.vehicle
import net.psforever.objects.serverobject.tube.SpawnTube
import net.psforever.objects.{PlanetSideGameObject, Vehicle} import net.psforever.objects.{PlanetSideGameObject, Vehicle}
import net.psforever.packet.game.PlanetSideGUID import net.psforever.packet.game.PlanetSideGUID
import net.psforever.packet.game.objectcreate.ConstructorData import net.psforever.packet.game.objectcreate.ConstructorData
@ -28,4 +29,6 @@ object VehicleResponse {
final case class UnloadVehicle(vehicle_guid : PlanetSideGUID) extends Response final case class UnloadVehicle(vehicle_guid : PlanetSideGUID) extends Response
final case class UnstowEquipment(item_guid : PlanetSideGUID) extends Response final case class UnstowEquipment(item_guid : PlanetSideGUID) extends Response
final case class VehicleState(vehicle_guid : PlanetSideGUID, unk1 : Int, pos : Vector3, ang : Vector3, vel : Option[Vector3], unk2 : Option[Int], unk3 : Int, unk4 : Int, wheel_direction : Int, unk5 : Boolean, unk6 : Boolean) extends Response final case class VehicleState(vehicle_guid : PlanetSideGUID, unk1 : Int, pos : Vector3, ang : Vector3, vel : Option[Vector3], unk2 : Option[Int], unk3 : Int, unk4 : Int, wheel_direction : Int, unk5 : Boolean, unk6 : Boolean) extends Response
final case class UpdateAmsSpawnPoint(list : List[SpawnTube]) extends Response
} }

View file

@ -5,6 +5,8 @@ import akka.actor.{Actor, ActorRef, Props}
import net.psforever.objects.serverobject.pad.VehicleSpawnPad import net.psforever.objects.serverobject.pad.VehicleSpawnPad
import net.psforever.objects.zones.Zone import net.psforever.objects.zones.Zone
import services.vehicle.support.{DeconstructionActor, DelayedDeconstructionActor} import services.vehicle.support.{DeconstructionActor, DelayedDeconstructionActor}
import net.psforever.types.DriveState
import services.{GenericEventBus, Service} import services.{GenericEventBus, Service}
class VehicleService extends Actor { class VehicleService extends Actor {
@ -93,6 +95,10 @@ class VehicleService extends Actor {
VehicleEvents.publish( VehicleEvents.publish(
VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.VehicleState(vehicle_guid, unk1, pos, ang, vel, unk2, unk3, unk4, wheel_direction, unk5, unk6)) VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.VehicleState(vehicle_guid, unk1, pos, ang, vel, unk2, unk3, unk4, wheel_direction, unk5, unk6))
) )
//unlike other messages, just return to sender, don't publish
case VehicleAction.UpdateAmsSpawnPoint(zone : Zone) =>
sender ! VehicleServiceResponse(s"/$forChannel/Vehicle", Service.defaultPlayerGUID, VehicleResponse.UpdateAmsSpawnPoint(AmsSpawnPoints(zone)))
case _ => ; case _ => ;
} }
@ -161,7 +167,23 @@ class VehicleService extends Actor {
vehicleDelayedDecon ! DelayedDeconstructionActor.UnscheduleDeconstruction(vehicle.GUID) vehicleDelayedDecon ! DelayedDeconstructionActor.UnscheduleDeconstruction(vehicle.GUID)
vehicleDecon ! DeconstructionActor.RequestDeleteVehicle(vehicle, zone) vehicleDecon ! DeconstructionActor.RequestDeleteVehicle(vehicle, zone)
//correspondence from WorldSessionActor
case VehicleServiceMessage.AMSDeploymentChange(zone) =>
VehicleEvents.publish(
VehicleServiceResponse(s"/${zone.Id}/Vehicle", Service.defaultPlayerGUID, VehicleResponse.UpdateAmsSpawnPoint(AmsSpawnPoints(zone)))
)
case msg => case msg =>
log.info(s"Unhandled message $msg from $sender") log.info(s"Unhandled message $msg from $sender")
} }
import net.psforever.objects.serverobject.tube.SpawnTube
def AmsSpawnPoints(zone : Zone) : List[SpawnTube] = {
import net.psforever.objects.vehicles.UtilityType
import net.psforever.objects.GlobalDefinitions
zone.Vehicles
.filter(veh => veh.Definition == GlobalDefinitions.ams && veh.DeploymentState == DriveState.Deployed)
.flatMap(veh => veh.Utilities.values.filter(util => util.UtilType == UtilityType.ams_respawn_tube) )
.map(util => util().asInstanceOf[SpawnTube])
}
} }

View file

@ -13,4 +13,6 @@ object VehicleServiceMessage {
final case class RevokeActorControl(vehicle : Vehicle) final case class RevokeActorControl(vehicle : Vehicle)
final case class RequestDeleteVehicle(vehicle : Vehicle, continent : Zone) final case class RequestDeleteVehicle(vehicle : Vehicle, continent : Zone)
final case class UnscheduleDeconstruction(vehicle_guid : PlanetSideGUID) final case class UnscheduleDeconstruction(vehicle_guid : PlanetSideGUID)
final case class AMSDeploymentChange(zone : Zone)
} }

View file

@ -2,7 +2,7 @@
package services.vehicle.support package services.vehicle.support
import akka.actor.{Actor, ActorRef, Cancellable} import akka.actor.{Actor, ActorRef, Cancellable}
import net.psforever.objects.{DefaultCancellable, Vehicle} import net.psforever.objects.{DefaultCancellable, GlobalDefinitions, Vehicle}
import net.psforever.objects.guid.TaskResolver import net.psforever.objects.guid.TaskResolver
import net.psforever.objects.vehicles.Seat import net.psforever.objects.vehicles.Seat
import net.psforever.objects.zones.Zone import net.psforever.objects.zones.Zone
@ -96,9 +96,20 @@ class DeconstructionActor extends Actor {
heapEmptyProcess.cancel heapEmptyProcess.cancel
val now : Long = System.nanoTime val now : Long = System.nanoTime
val (vehiclesToScrap, vehiclesRemain) = PartitionEntries(vehicles, now) val (vehiclesToScrap, vehiclesRemain) = PartitionEntries(vehicles, now)
vehicles = vehiclesRemain //entries from original list before partition vehicles = vehiclesRemain
vehicleScrapHeap = vehicleScrapHeap ++ vehiclesToScrap //may include existing entries vehicleScrapHeap = vehicleScrapHeap ++ vehiclesToScrap //may include existing entries
vehiclesToScrap.foreach { RetirementTask } vehiclesToScrap.foreach(entry => {
val vehicle = entry.vehicle
val zone = entry.zone
RetirementTask(entry)
if(vehicle.Definition == GlobalDefinitions.ams) {
import net.psforever.types.DriveState
vehicle.DeploymentState = DriveState.Mobile //internally undeployed //TODO this should be temporary?
context.parent ! VehicleServiceMessage.AMSDeploymentChange(zone)
}
taskResolver ! DeconstructionTask(vehicle, zone)
})
if(vehiclesRemain.nonEmpty) { if(vehiclesRemain.nonEmpty) {
val short_timeout : FiniteDuration = math.max(1, DeconstructionActor.timeout_time - (now - vehiclesRemain.head.time)) nanoseconds val short_timeout : FiniteDuration = math.max(1, DeconstructionActor.timeout_time - (now - vehiclesRemain.head.time)) nanoseconds
import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.ExecutionContext.Implicits.global
@ -128,6 +139,7 @@ class DeconstructionActor extends Actor {
def RetirementTask(entry : DeconstructionActor.VehicleEntry) : Unit = { def RetirementTask(entry : DeconstructionActor.VehicleEntry) : Unit = {
val vehicle = entry.vehicle val vehicle = entry.vehicle
val zone = entry.zone val zone = entry.zone
vehicle.Position = Vector3.Zero //somewhere it will not disturb anything
zone.Transport ! Zone.Vehicle.Despawn(vehicle) zone.Transport ! Zone.Vehicle.Despawn(vehicle)
context.parent ! DeconstructionActor.DeleteVehicle(vehicle.GUID, zone.Id) //call up to the main event system context.parent ! DeconstructionActor.DeleteVehicle(vehicle.GUID, zone.Id) //call up to the main event system
} }
@ -135,7 +147,6 @@ class DeconstructionActor extends Actor {
def DestructionTask(entry : DeconstructionActor.VehicleEntry) : Unit = { def DestructionTask(entry : DeconstructionActor.VehicleEntry) : Unit = {
val vehicle = entry.vehicle val vehicle = entry.vehicle
val zone = entry.zone val zone = entry.zone
vehicle.Position = Vector3.Zero //somewhere it will not disturb anything
taskResolver ! DeconstructionTask(vehicle, zone) taskResolver ! DeconstructionTask(vehicle, zone)
} }