mirror of
https://github.com/2revoemag/PSF-BotServer.git
synced 2026-03-03 20:20:22 +00:00
commit
ce99ea6ffc
55 changed files with 9310 additions and 1566 deletions
|
|
@ -22,6 +22,7 @@ import services.ServiceManager
|
|||
import services.avatar._
|
||||
import services.galaxy.GalaxyService
|
||||
import services.local._
|
||||
import services.teamwork.SquadService
|
||||
import services.vehicle.VehicleService
|
||||
|
||||
import scala.collection.JavaConverters._
|
||||
|
|
@ -258,6 +259,7 @@ object PsLogin {
|
|||
serviceManager ! ServiceManager.Register(Props[LocalService], "local")
|
||||
serviceManager ! ServiceManager.Register(Props[VehicleService], "vehicle")
|
||||
serviceManager ! ServiceManager.Register(Props[GalaxyService], "galaxy")
|
||||
serviceManager ! ServiceManager.Register(Props[SquadService], "squad")
|
||||
serviceManager ! ServiceManager.Register(Props(classOf[InterstellarCluster], continentList), "cluster")
|
||||
|
||||
//attach event bus entry point to each zone
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ import net.psforever.objects.serverobject.terminals._
|
|||
import net.psforever.objects.serverobject.terminals.Terminal.TerminalMessage
|
||||
import net.psforever.objects.serverobject.tube.SpawnTube
|
||||
import net.psforever.objects.serverobject.turret.{FacilityTurret, TurretUpgrade, WeaponTurret}
|
||||
import net.psforever.objects.teamwork.Squad
|
||||
import net.psforever.objects.vehicles.{AccessPermissionGroup, Cargo, Utility, VehicleLockState, _}
|
||||
import net.psforever.objects.vital._
|
||||
import net.psforever.objects.zones.{InterstellarCluster, Zone, ZoneHotSpotProjector}
|
||||
|
|
@ -52,7 +53,9 @@ import services.galaxy.{GalaxyResponse, GalaxyServiceResponse}
|
|||
import services.local.{LocalAction, LocalResponse, LocalServiceMessage, LocalServiceResponse}
|
||||
import services.vehicle.support.TurretUpgrader
|
||||
import services.vehicle.{VehicleAction, VehicleResponse, VehicleServiceMessage, VehicleServiceResponse}
|
||||
import services.teamwork.{SquadAction => SquadServiceAction, SquadServiceMessage, SquadServiceResponse, SquadResponse, SquadService}
|
||||
|
||||
import scala.collection.mutable.LongMap
|
||||
import scala.concurrent.duration._
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
import scala.annotation.tailrec
|
||||
|
|
@ -76,6 +79,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
var localService : ActorRef = ActorRef.noSender
|
||||
var vehicleService : ActorRef = ActorRef.noSender
|
||||
var galaxyService : ActorRef = ActorRef.noSender
|
||||
var squadService : ActorRef = ActorRef.noSender
|
||||
var taskResolver : ActorRef = Actor.noSender
|
||||
var cluster : ActorRef = Actor.noSender
|
||||
var continent : Zone = Zone.Nowhere
|
||||
|
|
@ -97,6 +101,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
var whenUsedLastKit : Long = 0
|
||||
val projectiles : Array[Option[Projectile]] = Array.fill[Option[Projectile]](Projectile.RangeUID - Projectile.BaseUID)(None)
|
||||
var drawDeloyableIcon : PlanetSideGameObject with Deployable => Unit = RedrawDeployableIcons
|
||||
var updateSquad : () => Unit = NoSquadUpdates
|
||||
var recentTeleportAttempt : Long = 0
|
||||
var lastTerminalOrderFulfillment : Boolean = true /**
|
||||
* used during zone transfers to maintain reference to seated vehicle (which does not yet exist in the new zone)
|
||||
|
|
@ -113,6 +118,21 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
* no harm should come from leaving the field set to an old unique identifier value after the transfer period
|
||||
*/
|
||||
var interstellarFerryTopLevelGUID : Option[PlanetSideGUID] = None
|
||||
val squadUI : LongMap[SquadUIElement] = new LongMap[SquadUIElement]()
|
||||
var squad_supplement_id : Int = 0
|
||||
/**
|
||||
* When joining or creating a squad, the original state of the avatar's internal LFS variable is blanked.
|
||||
* This `WSA`-local variable is then used to indicate the ongoing state of the LFS UI component,
|
||||
* now called "Looking for Squad Member."
|
||||
* Only the squad leader may toggle the LFSM marquee.
|
||||
* Upon leaving or disbanding a squad, this value is made false.
|
||||
* Control switching between the `Avatar`-local and the `WorldSessionActor`-local variable is contingent on `squadUI` being populated.
|
||||
*/
|
||||
var lfsm : Boolean = false
|
||||
var squadChannel : Option[String] = None
|
||||
var squadSetup : () => Unit = FirstTimeSquadSetup
|
||||
var squadUpdateCounter : Int = 0
|
||||
val queuedSquadActions : Seq[() => Unit] = Seq(SquadUpdates, NoSquadUpdates, NoSquadUpdates, NoSquadUpdates)
|
||||
|
||||
var amsSpawnPoints : List[SpawnPoint] = Nil
|
||||
var clientKeepAlive : Cancellable = DefaultCancellable.obj
|
||||
|
|
@ -144,6 +164,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
galaxyService ! Service.Leave()
|
||||
LivePlayerList.Remove(sessionId)
|
||||
if(player != null && player.HasGUID) {
|
||||
squadService ! Service.Leave(Some(player.CharId.toString))
|
||||
val player_guid = player.GUID
|
||||
//handle orphaned deployables
|
||||
DisownDeployables()
|
||||
|
|
@ -155,6 +176,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
.collect { case ((index, Some(obj))) => InventoryItem(obj, index) }
|
||||
) ++ player.Inventory.Items)
|
||||
.filterNot({ case InventoryItem(obj, _) => obj.isInstanceOf[BoomerTrigger] || obj.isInstanceOf[Telepad] })
|
||||
//put any temporary value back into the avatar
|
||||
//TODO final character save before doing any of this (use equipment)
|
||||
continent.Population ! Zone.Population.Release(avatar)
|
||||
if(player.isAlive) {
|
||||
|
|
@ -266,6 +288,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
ServiceManager.serviceManager ! Lookup("taskResolver")
|
||||
ServiceManager.serviceManager ! Lookup("cluster")
|
||||
ServiceManager.serviceManager ! Lookup("galaxy")
|
||||
ServiceManager.serviceManager ! Lookup("squad")
|
||||
|
||||
case _ =>
|
||||
log.error("Unknown message")
|
||||
|
|
@ -291,6 +314,9 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
case ServiceManager.LookupResult("cluster", endpoint) =>
|
||||
cluster = endpoint
|
||||
log.info("ID: " + sessionId + " Got cluster service " + endpoint)
|
||||
case ServiceManager.LookupResult("squad", endpoint) =>
|
||||
squadService = endpoint
|
||||
log.info("ID: " + sessionId + " Got squad service " + endpoint)
|
||||
|
||||
case ControlPacket(_, ctrl) =>
|
||||
handleControlPkt(ctrl)
|
||||
|
|
@ -336,6 +362,231 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
case VehicleServiceResponse(toChannel, guid, reply) =>
|
||||
HandleVehicleServiceResponse(toChannel, guid, reply)
|
||||
|
||||
case SquadServiceResponse(_, excluded, response) =>
|
||||
if(!excluded.exists(_ == avatar.CharId)) {
|
||||
response match {
|
||||
case SquadResponse.ListSquadFavorite(line, task) =>
|
||||
sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), line, SquadAction.ListSquadFavorite(task)))
|
||||
|
||||
case SquadResponse.InitList(infos) =>
|
||||
sendResponse(ReplicationStreamMessage(infos))
|
||||
|
||||
case SquadResponse.UpdateList(infos) if infos.nonEmpty =>
|
||||
sendResponse(
|
||||
ReplicationStreamMessage(6, None,
|
||||
infos.map { case (index, squadInfo) =>
|
||||
SquadListing(index, squadInfo)
|
||||
}.toVector
|
||||
)
|
||||
)
|
||||
|
||||
case SquadResponse.RemoveFromList(infos) if infos.nonEmpty =>
|
||||
sendResponse(
|
||||
ReplicationStreamMessage(1, None,
|
||||
infos.map { index =>
|
||||
SquadListing(index, None)
|
||||
}.toVector
|
||||
)
|
||||
)
|
||||
|
||||
case SquadResponse.Detail(guid, detail) =>
|
||||
sendResponse(SquadDetailDefinitionUpdateMessage(guid, detail))
|
||||
|
||||
case SquadResponse.AssociateWithSquad(squad_guid) =>
|
||||
sendResponse(SquadDefinitionActionMessage(squad_guid, 0, SquadAction.AssociateWithSquad()))
|
||||
|
||||
case SquadResponse.SetListSquad(squad_guid) =>
|
||||
sendResponse(SquadDefinitionActionMessage(squad_guid, 0, SquadAction.SetListSquad()))
|
||||
|
||||
case SquadResponse.Membership(request_type, unk1, unk2, char_id, opt_char_id, player_name, unk5, unk6) =>
|
||||
val name = request_type match {
|
||||
case SquadResponseType.Invite if unk5 =>
|
||||
//player_name is our name; the name of the player indicated by unk3 is needed
|
||||
LivePlayerList.WorldPopulation({ case (_, a : Avatar) => char_id == a.CharId }).headOption match {
|
||||
case Some(player) =>
|
||||
player.name
|
||||
case None =>
|
||||
player_name
|
||||
}
|
||||
case _ =>
|
||||
player_name
|
||||
}
|
||||
sendResponse(SquadMembershipResponse(request_type, unk1, unk2, char_id, opt_char_id, name, unk5, unk6))
|
||||
|
||||
case SquadResponse.WantsSquadPosition(_, name) =>
|
||||
sendResponse(
|
||||
ChatMsg(
|
||||
ChatMessageType.CMT_SQUAD, true, name,
|
||||
s"\\#6 would like to join your squad. (respond with \\#3/accept\\#6 or \\#3/reject\\#6)",
|
||||
None
|
||||
)
|
||||
)
|
||||
|
||||
case SquadResponse.Join(squad, positionsToUpdate, toChannel) =>
|
||||
val leader = squad.Leader
|
||||
val membershipPositions = positionsToUpdate map squad.Membership.zipWithIndex
|
||||
StartBundlingPackets()
|
||||
membershipPositions.find({ case(member, _) => member.CharId == avatar.CharId }) match {
|
||||
case Some((ourMember, ourIndex)) =>
|
||||
//we are joining the squad
|
||||
//load each member's entry (our own too)
|
||||
squad_supplement_id = squad.GUID.guid + 1
|
||||
membershipPositions.foreach { case(member, index) =>
|
||||
sendResponse(SquadMemberEvent.Add(squad_supplement_id, member.CharId, index, member.Name, member.ZoneId, unk7 = 0))
|
||||
squadUI(member.CharId) = SquadUIElement(member.Name, index, member.ZoneId, member.Health, member.Armor, member.Position)
|
||||
}
|
||||
//repeat our entry
|
||||
sendResponse(SquadMemberEvent.Add(squad_supplement_id, ourMember.CharId, ourIndex, ourMember.Name, ourMember.ZoneId, unk7 = 0)) //repeat of our entry
|
||||
val playerGuid = player.GUID
|
||||
//turn lfs off
|
||||
val factionOnContinentChannel = s"${continent.Id}/${player.Faction}"
|
||||
if(avatar.LFS) {
|
||||
avatar.LFS = false
|
||||
sendResponse(PlanetsideAttributeMessage(playerGuid, 53, 0))
|
||||
avatarService ! AvatarServiceMessage(factionOnContinentChannel, AvatarAction.PlanetsideAttribute(playerGuid, 53, 0))
|
||||
}
|
||||
//squad colors
|
||||
GiveSquadColorsInZone()
|
||||
avatarService ! AvatarServiceMessage(factionOnContinentChannel, AvatarAction.PlanetsideAttribute(playerGuid, 31, squad_supplement_id))
|
||||
//associate with member position in squad
|
||||
sendResponse(PlanetsideAttributeMessage(playerGuid, 32, ourIndex))
|
||||
//a finalization? what does this do?
|
||||
sendResponse(SquadDefinitionActionMessage(squad.GUID, 0, SquadAction.Unknown(18)))
|
||||
updateSquad = PeriodicUpdatesWhenEnrolledInSquad
|
||||
squadChannel = Some(toChannel)
|
||||
case _ =>
|
||||
//other player is joining our squad
|
||||
//load each member's entry
|
||||
GiveSquadColorsInZone(
|
||||
membershipPositions.map { case(member, index) =>
|
||||
val charId = member.CharId
|
||||
sendResponse(SquadMemberEvent.Add(squad_supplement_id, charId, index, member.Name, member.ZoneId, unk7 = 0))
|
||||
squadUI(charId) = SquadUIElement(member.Name, index, member.ZoneId, member.Health, member.Armor, member.Position)
|
||||
charId
|
||||
}
|
||||
)
|
||||
}
|
||||
StopBundlingPackets()
|
||||
//send an initial dummy update for map icon(s)
|
||||
sendResponse(SquadState(PlanetSideGUID(squad_supplement_id),
|
||||
membershipPositions
|
||||
.filterNot { case (member, _) => member.CharId == avatar.CharId }
|
||||
.map{ case (member, _) => SquadStateInfo(member.CharId, member.Health, member.Armor, member.Position, 2,2, false, 429, None,None) }
|
||||
.toList
|
||||
))
|
||||
|
||||
case SquadResponse.Leave(squad, positionsToUpdate) =>
|
||||
StartBundlingPackets()
|
||||
positionsToUpdate.find({ case(member, _) => member == avatar.CharId }) match {
|
||||
case Some((ourMember, ourIndex)) =>
|
||||
//we are leaving the squad
|
||||
//remove each member's entry (our own too)
|
||||
positionsToUpdate.foreach { case(member, index) =>
|
||||
sendResponse(SquadMemberEvent.Remove(squad_supplement_id, member, index))
|
||||
squadUI.remove(member)
|
||||
}
|
||||
//uninitialize
|
||||
val playerGuid = player.GUID
|
||||
sendResponse(SquadMemberEvent.Remove(squad_supplement_id, ourMember, ourIndex)) //repeat of our entry
|
||||
sendResponse(PlanetsideAttributeMessage(playerGuid, 31, 0)) //disassociate with squad?
|
||||
avatarService ! AvatarServiceMessage(s"${continent.Id}/${player.Faction}", AvatarAction.PlanetsideAttribute(playerGuid, 31, 0))
|
||||
sendResponse(PlanetsideAttributeMessage(playerGuid, 32, 0)) //disassociate with member position in squad?
|
||||
sendResponse(PlanetsideAttributeMessage(playerGuid, 34, 4294967295L)) //unknown, perhaps unrelated?
|
||||
lfsm = false
|
||||
//a finalization? what does this do?
|
||||
sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SquadAction.Unknown(18)))
|
||||
squad_supplement_id = 0
|
||||
squadUpdateCounter = 0
|
||||
updateSquad = NoSquadUpdates
|
||||
squadChannel = None
|
||||
case _ =>
|
||||
//remove each member's entry
|
||||
GiveSquadColorsInZone(
|
||||
positionsToUpdate.map { case(member, index) =>
|
||||
sendResponse(SquadMemberEvent.Remove(squad_supplement_id, member, index))
|
||||
squadUI.remove(member)
|
||||
member
|
||||
},
|
||||
value = 0
|
||||
)
|
||||
}
|
||||
StopBundlingPackets()
|
||||
|
||||
case SquadResponse.AssignMember(squad, from_index, to_index) =>
|
||||
//we've already swapped position internally; now we swap the cards
|
||||
SwapSquadUIElements(squad, from_index, to_index)
|
||||
|
||||
case SquadResponse.PromoteMember(squad, char_id, from_index, to_index) =>
|
||||
val charId = player.CharId
|
||||
val guid = player.GUID
|
||||
lazy val factionOnContinentChannel = s"${continent.Id}/${player.Faction}"
|
||||
//are we being demoted?
|
||||
if(squadUI(charId).index == 0) {
|
||||
//lfsm -> lfs
|
||||
if(lfsm) {
|
||||
sendResponse(PlanetsideAttributeMessage(guid, 53, 0))
|
||||
avatarService ! AvatarServiceMessage(factionOnContinentChannel, AvatarAction.PlanetsideAttribute(guid, 53, 0))
|
||||
}
|
||||
lfsm = false
|
||||
sendResponse(PlanetsideAttributeMessage(guid, 32, from_index)) //associate with member position in squad
|
||||
}
|
||||
//are we being promoted?
|
||||
else if(charId == char_id) {
|
||||
sendResponse(PlanetsideAttributeMessage(guid, 32, 0)) //associate with member position in squad
|
||||
}
|
||||
avatarService ! AvatarServiceMessage(factionOnContinentChannel, AvatarAction.PlanetsideAttribute(guid, 31, squad_supplement_id))
|
||||
//we must fix the squad cards backend
|
||||
SwapSquadUIElements(squad, from_index, to_index)
|
||||
|
||||
case SquadResponse.UpdateMembers(squad, positions) =>
|
||||
val pairedEntries = positions.collect {
|
||||
case entry if squadUI.contains(entry.char_id) =>
|
||||
(entry, squadUI(entry.char_id))
|
||||
}
|
||||
//prune entries
|
||||
val updatedEntries = pairedEntries
|
||||
.collect({
|
||||
case (entry, element) if entry.zone_number != element.zone =>
|
||||
//zone gets updated for these entries
|
||||
sendResponse(SquadMemberEvent.UpdateZone(squad_supplement_id, entry.char_id, element.index, entry.zone_number))
|
||||
squadUI(entry.char_id) = SquadUIElement(element.name, element.index, entry.zone_number, entry.health, entry.armor, entry.pos)
|
||||
entry
|
||||
case (entry, element) if entry.health != element.health || entry.armor != element.armor || entry.pos != element.position =>
|
||||
//other elements that need to be updated
|
||||
squadUI(entry.char_id) = SquadUIElement(element.name, element.index, entry.zone_number, entry.health, entry.armor, entry.pos)
|
||||
entry
|
||||
})
|
||||
.filterNot(_.char_id == avatar.CharId) //we want to update our backend, but not our frontend
|
||||
if(updatedEntries.nonEmpty) {
|
||||
sendResponse(
|
||||
SquadState(
|
||||
PlanetSideGUID(squad_supplement_id),
|
||||
updatedEntries.map { entry => SquadStateInfo(entry.char_id, entry.health, entry.armor, entry.pos, 2,2, false, 429, None,None)}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
case SquadResponse.SquadSearchResults() =>
|
||||
//I don't actually know how to return search results
|
||||
sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SquadAction.NoSquadSearchResults()))
|
||||
|
||||
case SquadResponse.InitWaypoints(char_id, waypoints) =>
|
||||
StartBundlingPackets()
|
||||
waypoints.foreach { case (waypoint_type, info, unk) =>
|
||||
sendResponse(SquadWaypointEvent.Add(squad_supplement_id, char_id, waypoint_type, WaypointEvent(info.zone_number, info.pos, unk)))
|
||||
}
|
||||
StopBundlingPackets()
|
||||
|
||||
case SquadResponse.WaypointEvent(WaypointEventAction.Add, char_id, waypoint_type, _, Some(info), unk) =>
|
||||
sendResponse(SquadWaypointEvent.Add(squad_supplement_id, char_id, waypoint_type, WaypointEvent(info.zone_number, info.pos, unk)))
|
||||
|
||||
case SquadResponse.WaypointEvent(WaypointEventAction.Remove, char_id, waypoint_type, _, _, _) =>
|
||||
sendResponse(SquadWaypointEvent.Remove(squad_supplement_id, char_id, waypoint_type))
|
||||
|
||||
case _ => ;
|
||||
}
|
||||
}
|
||||
|
||||
case Deployment.CanDeploy(obj, state) =>
|
||||
val vehicle_guid = obj.GUID
|
||||
//TODO remove this arbitrary allowance angle when no longer helpful
|
||||
|
|
@ -484,7 +735,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
player.Stamina = stamina
|
||||
player.Armor = armor
|
||||
}
|
||||
sendResponse(CharacterInfoMessage(15, PlanetSideZoneID(10000), 41605313, player.GUID, false, 6404428))
|
||||
sendResponse(CharacterInfoMessage(15, PlanetSideZoneID(10000), avatar.CharId, player.GUID, false, 6404428))
|
||||
RemoveCharacterSelectScreenGUID(player)
|
||||
sendResponse(CharacterInfoMessage(0, PlanetSideZoneID(1), 0, PlanetSideGUID(0), true, 0))
|
||||
sendResponse(CharacterInfoMessage(0, PlanetSideZoneID(1), 0, PlanetSideGUID(0), true, 0))
|
||||
|
|
@ -802,7 +1053,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
traveler = new Traveler(self, continent.Id)
|
||||
//PropertyOverrideMessage
|
||||
sendResponse(PlanetsideAttributeMessage(PlanetSideGUID(0), 112, 0)) // disable festive backpacks
|
||||
sendResponse(ReplicationStreamMessage(5, Some(6), Vector(SquadListing()))) //clear squad list
|
||||
sendResponse(ReplicationStreamMessage(5, Some(6), Vector.empty)) //clear squad list
|
||||
sendResponse(FriendsResponse(FriendAction.InitializeFriendList, 0, true, true, Nil))
|
||||
sendResponse(FriendsResponse(FriendAction.InitializeIgnoreList, 0, true, true, Nil))
|
||||
avatarService ! Service.Join(avatar.name) //channel will be player.Name
|
||||
|
|
@ -810,6 +1061,8 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
vehicleService ! Service.Join(avatar.name) //channel will be player.Name
|
||||
galaxyService ! Service.Join("galaxy") //for galaxy-wide messages
|
||||
galaxyService ! Service.Join(s"${avatar.faction}") //for hotspots
|
||||
squadService ! Service.Join(s"${avatar.faction}") //channel will be player.Faction
|
||||
squadService ! Service.Join(s"${avatar.CharId}") //channel will be player.CharId (in order to work with packets)
|
||||
cluster ! InterstellarCluster.GetWorld("home3")
|
||||
|
||||
case InterstellarCluster.GiveWorld(zoneId, zone) =>
|
||||
|
|
@ -2828,7 +3081,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
sendResponse(CreateShortcutMessage(guid, 1, 0, true, Shortcut.MEDKIT))
|
||||
sendResponse(ChangeShortcutBankMessage(guid, 0))
|
||||
//Favorites lists
|
||||
val (inf, veh) = avatar.Loadouts.partition { case (index, _) => index < 10 }
|
||||
val (inf, veh) = avatar.EquipmentLoadouts.Loadouts.partition { case (index, _) => index < 10 }
|
||||
inf.foreach {
|
||||
case (index, loadout : InfantryLoadout) =>
|
||||
sendResponse(FavoritesMessage(LoadoutType.Infantry, guid, index, loadout.label, InfantryLoadout.DetermineSubtypeB(loadout.exosuit, loadout.subtype)))
|
||||
|
|
@ -2840,9 +3093,14 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
sendResponse(SetChatFilterMessage(ChatChannel.Local, false, ChatChannel.values.toList)) //TODO will not always be "on" like this
|
||||
deadState = DeadState.Alive
|
||||
sendResponse(AvatarDeadStateMessage(DeadState.Alive, 0, 0, tplayer.Position, player.Faction, true))
|
||||
sendResponse(PlanetsideAttributeMessage(guid, 53, 1))
|
||||
//looking for squad (members)
|
||||
if(tplayer.LFS || lfsm) {
|
||||
sendResponse(PlanetsideAttributeMessage(guid, 53, 1))
|
||||
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(guid, 53, 1))
|
||||
}
|
||||
sendResponse(AvatarSearchCriteriaMessage(guid, List(0, 0, 0, 0, 0, 0)))
|
||||
(1 to 73).foreach(i => {
|
||||
// not all GUID's are set, and not all of the set ones will always be zero; what does this section do?
|
||||
sendResponse(PlanetsideAttributeMessage(PlanetSideGUID(i), 67, 0))
|
||||
})
|
||||
(0 to 30).foreach(i => {
|
||||
|
|
@ -2851,7 +3109,9 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
})
|
||||
//AvatarAwardMessage
|
||||
//DisplayAwardMessage
|
||||
//SquadDefinitionActionMessage and SquadDetailDefinitionUpdateMessage
|
||||
sendResponse(PlanetsideStringAttributeMessage(guid, 0, "Outfit Name"))
|
||||
//squad stuff (loadouts, assignment)
|
||||
squadSetup()
|
||||
//MapObjectStateBlockMessage and ObjectCreateMessage?
|
||||
//TacticsMessage?
|
||||
//change the owner on our deployables (re-draw the icons for our deployables too)
|
||||
|
|
@ -2888,6 +3148,97 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* These messages are dispatched when first starting up the client and connecting to the server for the first time.
|
||||
* While many of thee messages will be reused for other situations, they appear in this order only during startup.
|
||||
*/
|
||||
def FirstTimeSquadSetup() : Unit = {
|
||||
sendResponse(SquadDetailDefinitionUpdateMessage.Init)
|
||||
sendResponse(ReplicationStreamMessage(5, Some(6), Vector.empty)) //clear squad list
|
||||
sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SquadAction.Unknown(6)))
|
||||
//only need to load these once - they persist between zone transfers and respawns
|
||||
avatar.SquadLoadouts.Loadouts.foreach {
|
||||
case (index, loadout : SquadLoadout) =>
|
||||
sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), index, SquadAction.ListSquadFavorite(loadout.task)))
|
||||
}
|
||||
//non-squad GUID-0 counts as the settings when not joined with a squad
|
||||
sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SquadAction.AssociateWithSquad()))
|
||||
sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SquadAction.SetListSquad()))
|
||||
sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SquadAction.Unknown(18)))
|
||||
squadService ! SquadServiceMessage(player, continent, SquadServiceAction.InitSquadList())
|
||||
squadService ! SquadServiceMessage(player, continent, SquadServiceAction.InitCharId())
|
||||
squadSetup = RespawnSquadSetup
|
||||
}
|
||||
|
||||
/**
|
||||
* These messages are used during each subsequent respawn to reset the squad colors on player nameplates and marquees.
|
||||
* By using `squadUI` to maintain relevant information about squad members,
|
||||
* especially the unique character identifier number,
|
||||
* only the zone-specific squad members will receive the important messages about their squad member's spawn.
|
||||
*/
|
||||
def RespawnSquadSetup() : Unit = {
|
||||
if(squadUI.nonEmpty) {
|
||||
sendResponse(PlanetsideAttributeMessage(player.GUID, 31, squad_supplement_id))
|
||||
avatarService ! AvatarServiceMessage(s"${continent.Id}/${player.Faction}", AvatarAction.PlanetsideAttribute(player.GUID, 31, squad_supplement_id))
|
||||
sendResponse(PlanetsideAttributeMessage(player.GUID, 32, squadUI(player.CharId).index))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* These messages are used during each subsequent respawn to reset the squad colors on player nameplates and marquees.
|
||||
* During a zone change,
|
||||
* on top of other squad mates in the zone needing to have their knowledge of this player's squad colors changed,
|
||||
* the player must also set squad colors for each other squad members.
|
||||
* Default respawn functionality may resume afterwards.
|
||||
*/
|
||||
def ZoneChangeSquadSetup() : Unit = {
|
||||
RespawnSquadSetup()
|
||||
GiveSquadColorsInZone()
|
||||
squadSetup = RespawnSquadSetup
|
||||
}
|
||||
|
||||
/**
|
||||
* Allocate all squad members in zone and give their nameplates and their marquees the appropriate squad color.
|
||||
*/
|
||||
def GiveSquadColorsInZone() : Unit = {
|
||||
GiveSquadColorsInZone(squadUI.keys, squad_supplement_id)
|
||||
}
|
||||
|
||||
/**
|
||||
* Allocate the listed squad members in zone and give their nameplates and their marquees the appropriate squad color.
|
||||
* @param members members of the squad to target
|
||||
*/
|
||||
def GiveSquadColorsInZone(members : Iterable[Long]) : Unit = {
|
||||
GiveSquadColorsInZone(members, squad_supplement_id)
|
||||
}
|
||||
|
||||
/**
|
||||
* Allocate the listed squad members in zone and give their nameplates and their marquees the appropriate squad color.
|
||||
* @see `PlanetsideAttributeMessage`
|
||||
* @param members members of the squad to target
|
||||
* @param value the assignment value
|
||||
*/
|
||||
def GiveSquadColorsInZone(members : Iterable[Long], value : Long) : Unit = {
|
||||
SquadMembersInZone(members).foreach {
|
||||
members => sendResponse(PlanetsideAttributeMessage(members.GUID, 31, value))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For the listed squad member unique character identifier numbers,
|
||||
* find and return all squad members in the current zone.
|
||||
* @param members members of the squad to target
|
||||
* @return a list of `Player` objects
|
||||
*/
|
||||
def SquadMembersInZone(members : Iterable[Long]) : Iterable[Player] = {
|
||||
val players = continent.LivePlayers
|
||||
for {
|
||||
charId <- members
|
||||
player = players.find { _.CharId == charId }
|
||||
if player.nonEmpty
|
||||
} yield player.get
|
||||
}
|
||||
|
||||
def handleControlPkt(pkt : PlanetSideControlPacket) = {
|
||||
pkt match {
|
||||
case sync @ ControlSync(diff, _, _, _, _, _, fa, fb) =>
|
||||
|
|
@ -2908,10 +3259,14 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
val clientVersion = s"Client Version: $majorVersion.$minorVersion.$revision, $buildDate"
|
||||
log.info(s"New world login to $server with Token:$token. $clientVersion")
|
||||
//TODO begin temp player character auto-loading; remove later
|
||||
//this is all just temporary character creation used in the dev branch, making explicit values that allow for testing
|
||||
//the unique character identifier number for this testing character is based on the original test character,
|
||||
//whose identifier number was 41605314
|
||||
//all head features, faction, and sex also match that test character
|
||||
import net.psforever.objects.GlobalDefinitions._
|
||||
import net.psforever.types.CertificationType._
|
||||
val faction = PlanetSideEmpire.VS
|
||||
val avatar = Avatar(s"TestCharacter$sessionId", faction, CharacterGender.Female, 41, CharacterVoice.Voice1)
|
||||
val avatar = new Avatar(41605313L+sessionId, s"TestCharacter$sessionId", faction, CharacterGender.Female, 41, CharacterVoice.Voice1)
|
||||
avatar.Certifications += StandardAssault
|
||||
avatar.Certifications += MediumAssault
|
||||
avatar.Certifications += StandardExoSuit
|
||||
|
|
@ -2946,6 +3301,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
avatar.Certifications += AssaultEngineering
|
||||
avatar.Certifications += Hacking
|
||||
avatar.Certifications += AdvancedHacking
|
||||
avatar.CEP = 6000001
|
||||
this.avatar = avatar
|
||||
|
||||
InitializeDeployableQuantities(avatar) //set deployables ui elements
|
||||
|
|
@ -3043,7 +3399,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
sendResponse(TimeOfDayMessage(1191182336))
|
||||
//custom
|
||||
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.empty)) //clear squad list
|
||||
sendResponse(PlanetsideAttributeMessage(PlanetSideGUID(0), 112, 0)) // disable festive backpacks
|
||||
//(0 to 255).foreach(i => { sendResponse(SetEmpireMessage(PlanetSideGUID(i), PlanetSideEmpire.VS)) })
|
||||
|
||||
|
|
@ -3339,6 +3695,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
case None => false
|
||||
}
|
||||
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlayerState(avatar_guid, msg, spectator, wepInHand))
|
||||
updateSquad()
|
||||
}
|
||||
|
||||
case msg @ ChildObjectStateMessage(object_guid, pitch, yaw) =>
|
||||
|
|
@ -3380,6 +3737,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
obj.Cloaked = obj.Definition.CanCloak && is_cloaked
|
||||
vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.VehicleState(player.GUID, vehicle_guid, unk1, pos, ang, vel, flight, unk6, unk7, wheels, unk9, is_cloaked))
|
||||
}
|
||||
updateSquad()
|
||||
case (None, _) =>
|
||||
//log.error(s"VehicleState: no vehicle $vehicle_guid found in zone")
|
||||
//TODO placing a "not driving" warning here may trigger as we are disembarking the vehicle
|
||||
|
|
@ -4466,7 +4824,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
log.info(s"GenericObject: $player is MAX with an unexpected weapon - ${definition.Name}")
|
||||
}
|
||||
}
|
||||
else if(action == 16) {
|
||||
else if(action == 16) { //max deployment
|
||||
log.info(s"GenericObject: $player has released the anchors")
|
||||
player.UsingSpecial = SpecialExoSuitDefinition.Mode.Normal
|
||||
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(player.GUID, 19, 0))
|
||||
|
|
@ -4484,6 +4842,30 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
log.info(s"GenericObject: $player is MAX with an unexpected weapon - ${definition.Name}")
|
||||
}
|
||||
}
|
||||
else if(action == 36) { //Looking For Squad ON
|
||||
if(squadUI.nonEmpty) {
|
||||
if(!lfsm && squadUI(player.CharId).index == 0) {
|
||||
lfsm = true
|
||||
avatarService ! AvatarServiceMessage(s"${continent.Id}/${player.Faction}", AvatarAction.PlanetsideAttribute(player.GUID, 53, 1))
|
||||
}
|
||||
}
|
||||
else if(!avatar.LFS) {
|
||||
avatar.LFS = true
|
||||
avatarService ! AvatarServiceMessage(s"${continent.Id}/${player.Faction}", AvatarAction.PlanetsideAttribute(player.GUID, 53, 1))
|
||||
}
|
||||
}
|
||||
else if(action == 37) { //Looking For Squad OFF
|
||||
if(squadUI.nonEmpty) {
|
||||
if(lfsm && squadUI(player.CharId).index == 0) {
|
||||
lfsm = false
|
||||
avatarService ! AvatarServiceMessage(s"${continent.Id}/${player.Faction}", AvatarAction.PlanetsideAttribute(player.GUID, 53, 0))
|
||||
}
|
||||
}
|
||||
else if(avatar.LFS) {
|
||||
avatar.LFS = false
|
||||
avatarService ! AvatarServiceMessage(s"${continent.Id}/${player.Faction}", AvatarAction.PlanetsideAttribute(player.GUID, 53, 0))
|
||||
}
|
||||
}
|
||||
|
||||
case msg @ ItemTransactionMessage(terminal_guid, transaction_type, _, _, _, _) =>
|
||||
log.info("ItemTransaction: " + msg)
|
||||
|
|
@ -4522,18 +4904,18 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
None
|
||||
}) match {
|
||||
case Some(owner : Player) => //InfantryLoadout
|
||||
avatar.SaveLoadout(owner, name, lineno)
|
||||
avatar.EquipmentLoadouts.SaveLoadout(owner, name, lineno)
|
||||
import InfantryLoadout._
|
||||
sendResponse(FavoritesMessage(list, player_guid, line, name, DetermineSubtypeB(player.ExoSuit, DetermineSubtype(player))))
|
||||
case Some(owner : Vehicle) => //VehicleLoadout
|
||||
avatar.SaveLoadout(owner, name, lineno)
|
||||
avatar.EquipmentLoadouts.SaveLoadout(owner, name, lineno)
|
||||
sendResponse(FavoritesMessage(list, player_guid, line, name))
|
||||
case Some(_) | None =>
|
||||
log.error("FavoritesRequest: unexpected owner for favorites")
|
||||
}
|
||||
|
||||
case FavoritesAction.Delete =>
|
||||
avatar.DeleteLoadout(lineno)
|
||||
avatar.EquipmentLoadouts.DeleteLoadout(lineno)
|
||||
sendResponse(FavoritesMessage(list, player_guid, line, ""))
|
||||
|
||||
case FavoritesAction.Unknown =>
|
||||
|
|
@ -4778,8 +5160,17 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
case msg @ AvatarGrenadeStateMessage(player_guid, state) =>
|
||||
log.info("AvatarGrenadeStateMessage: " + msg)
|
||||
|
||||
case msg @ SquadDefinitionActionMessage(a, b, c, d, e, f, g, h, i) =>
|
||||
log.info("SquadDefinitionAction: " + msg)
|
||||
case msg @ SquadDefinitionActionMessage(u1, u2, action) =>
|
||||
log.info(s"SquadDefinitionAction: $msg")
|
||||
squadService ! SquadServiceMessage(player, continent, SquadServiceAction.Definition(u1, u2, action))
|
||||
|
||||
case msg @ SquadMembershipRequest(request_type, unk2, unk3, player_name, unk5) =>
|
||||
log.info(s"$msg")
|
||||
squadService ! SquadServiceMessage(player, continent, SquadServiceAction.Membership(request_type, unk2, unk3, player_name, unk5))
|
||||
|
||||
case msg @ SquadWaypointRequest(request, _, wtype, unk, info) =>
|
||||
log.info(s"Waypoint Request: $msg")
|
||||
squadService ! SquadServiceMessage(player, continent, SquadServiceAction.Waypoint(request, wtype, unk, info))
|
||||
|
||||
case msg @ GenericCollisionMsg(u1, p, t, php, thp, pv, tv, ppos, tpos, u2, u3, u4) =>
|
||||
log.info("Ouch! " + msg)
|
||||
|
|
@ -7520,9 +7911,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
/**
|
||||
* Properly format a `DestroyDisplayMessage` packet
|
||||
* given sufficient information about a target (victim) and an actor (killer).
|
||||
* For the packet, the `*_charId` field is most important to determining distinction between players.
|
||||
* The "char id" is not a currently supported field for different players so a name hash is used instead.
|
||||
* The virtually negligent chance of a name hash collision is covered.
|
||||
* For the packet, the `charId` field is important for determining distinction between players.
|
||||
* @param killer the killer's entry
|
||||
* @param victim the victim's entry
|
||||
* @param method the manner of death
|
||||
|
|
@ -7531,13 +7920,6 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
* @return a `DestroyDisplayMessage` packet that is properly formatted
|
||||
*/
|
||||
def DestroyDisplayMessage(killer : SourceEntry, victim : SourceEntry, method : Int, unk : Int = 121) : DestroyDisplayMessage = {
|
||||
//TODO charId should reflect the player more properly
|
||||
val killerCharId = math.abs(killer.Name.hashCode)
|
||||
var victimCharId = math.abs(victim.Name.hashCode)
|
||||
if(killerCharId == victimCharId && !killer.Name.equals(victim.Name)) {
|
||||
//odds of hash collision in a populated zone should be close to odds of being struck by lightning
|
||||
victimCharId = Int.MaxValue - victimCharId + 1
|
||||
}
|
||||
val killer_seated = killer match {
|
||||
case obj : PlayerSource => obj.Seated
|
||||
case _ => false
|
||||
|
|
@ -7547,9 +7929,9 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
case _ => false
|
||||
}
|
||||
new DestroyDisplayMessage(
|
||||
killer.Name, killerCharId, killer.Faction, killer_seated,
|
||||
killer.Name, killer.CharId, killer.Faction, killer_seated,
|
||||
unk, method,
|
||||
victim.Name, victimCharId, victim.Faction, victim_seated
|
||||
victim.Name, victim.CharId, victim.Faction, victim_seated
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -8478,6 +8860,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
})
|
||||
DisownDeployables()
|
||||
drawDeloyableIcon = RedrawDeployableIcons //important for when SetCurrentAvatar initializes the UI next zone
|
||||
squadSetup = ZoneChangeSquadSetup
|
||||
continent.Population ! Zone.Population.Leave(avatar)
|
||||
}
|
||||
|
||||
|
|
@ -8947,6 +9330,76 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
}
|
||||
}
|
||||
|
||||
def SwapSquadUIElements(squad : Squad, fromIndex : Int, toIndex : Int) : Unit = {
|
||||
if(squadUI.nonEmpty) {
|
||||
val fromMember = squad.Membership(toIndex) //the players have already been swapped in the backend object
|
||||
val fromCharId = fromMember.CharId
|
||||
val toMember = squad.Membership(fromIndex) //the players have already been swapped in the backend object
|
||||
val toCharId = toMember.CharId
|
||||
val id = 11
|
||||
if(toCharId > 0) {
|
||||
//toMember and fromMember have swapped places
|
||||
val fromElem = squadUI(fromCharId)
|
||||
val toElem = squadUI(toCharId)
|
||||
squadUI(toCharId) = SquadUIElement(fromElem.name, toIndex, fromElem.zone, fromElem.health, fromElem.armor, fromElem.position)
|
||||
squadUI(fromCharId) = SquadUIElement(toElem.name, fromIndex, toElem.zone, toElem.health, toElem.armor, toElem.position)
|
||||
sendResponse(SquadMemberEvent.Add(id, toCharId, toIndex, fromElem.name, fromElem.zone, unk7 = 0))
|
||||
sendResponse(SquadMemberEvent.Add(id, fromCharId, fromIndex, toElem.name, toElem.zone, unk7 = 0))
|
||||
sendResponse(
|
||||
SquadState(
|
||||
PlanetSideGUID(id),
|
||||
List(
|
||||
SquadStateInfo(fromCharId, toElem.health, toElem.armor, toElem.position, 2, 2, false, 429, None, None),
|
||||
SquadStateInfo(toCharId, fromElem.health, fromElem.armor, fromElem.position, 2, 2, false, 429, None, None)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
else {
|
||||
//previous fromMember has moved toMember
|
||||
val elem = squadUI(fromCharId)
|
||||
squadUI(fromCharId) = SquadUIElement(elem.name, toIndex, elem.zone, elem.health, elem.armor, elem.position)
|
||||
sendResponse(SquadMemberEvent.Remove(id, fromCharId, fromIndex))
|
||||
sendResponse(SquadMemberEvent.Add(id, fromCharId, toIndex, elem.name, elem.zone, unk7 = 0))
|
||||
sendResponse(
|
||||
SquadState(
|
||||
PlanetSideGUID(id),
|
||||
List(SquadStateInfo(fromCharId, elem.health, elem.armor, elem.position, 2, 2, false, 429, None, None))
|
||||
)
|
||||
)
|
||||
}
|
||||
val charId = avatar.CharId
|
||||
if(toCharId == charId) {
|
||||
sendResponse(PlanetsideAttributeMessage(player.GUID, 32, toIndex))
|
||||
}
|
||||
else if(fromCharId == charId) {
|
||||
sendResponse(PlanetsideAttributeMessage(player.GUID, 32, fromIndex))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def NoSquadUpdates() : Unit = { }
|
||||
|
||||
def SquadUpdates() : Unit = {
|
||||
squadService ! SquadServiceMessage(
|
||||
player,
|
||||
continent,
|
||||
continent.GUID(player.VehicleSeated) match {
|
||||
case Some(vehicle : Vehicle) =>
|
||||
SquadServiceAction.Update(player.CharId, vehicle.Health, vehicle.MaxHealth, vehicle.Shields, vehicle.MaxShields, vehicle.Position, continent.Number)
|
||||
case Some(obj : PlanetSideGameObject with WeaponTurret) =>
|
||||
SquadServiceAction.Update(player.CharId, obj.Health, obj.MaxHealth, 0, 0, obj.Position, continent.Number)
|
||||
case _ =>
|
||||
SquadServiceAction.Update(player.CharId, player.Health, player.MaxHealth, player.Armor, player.MaxArmor, player.Position, continent.Number)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
def PeriodicUpdatesWhenEnrolledInSquad() : Unit = {
|
||||
queuedSquadActions(squadUpdateCounter)()
|
||||
squadUpdateCounter = (squadUpdateCounter + 1) % queuedSquadActions.length
|
||||
}
|
||||
|
||||
def failWithError(error : String) = {
|
||||
log.error(error)
|
||||
sendResponse(ConnectionClose())
|
||||
|
|
@ -9103,6 +9556,8 @@ object WorldSessionActor {
|
|||
completeAction : () => Unit,
|
||||
tickAction : Option[() => Unit] = None)
|
||||
|
||||
protected final case class SquadUIElement(name : String, index : Int, zone : Int, health : Int, armor : Int, position : Vector3)
|
||||
|
||||
private final case class NtuCharging(tplayer: Player,
|
||||
vehicle: Vehicle)
|
||||
private final case class NtuDischarging(tplayer: Player, vehicle: Vehicle, silo_guid: PlanetSideGUID)
|
||||
|
|
|
|||
|
|
@ -485,7 +485,7 @@ class PacketCodingActorITest extends ActorTest {
|
|||
)
|
||||
val obj = DetailedPlayerData(pos, app, char, InventoryData(Nil), DrawnSlot.None)
|
||||
val pkt = MultiPacketBundle(List(ObjectCreateDetailedMessage(0x79, PlanetSideGUID(75), obj)))
|
||||
val string_hex = hex"000900001879060000bc84b000000000000000000002040000097049006c006c006c004900490049006c006c006c0049006c0049006c006c0049006c006c006c0049006c006c00490084524000000000000000000000000000000020000007f35703fffffffffffffffffffffffffffffffc000000000000000000000000000000000000000190019000640000000000c800c80000000000000000000000000000000000000001c00042c54686c7000000000000000000000000000000000000000000000000000000000000000000000400e0"
|
||||
val string_hex = hex"00090000186c060000bc84b000000000000000000002040000097049006c006c006c004900490049006c006c006c0049006c0049006c006c0049006c006c006c0049006c006c00490084524000000000000000000000000000000020000007f35703fffffffffffffffffffffffffffffffc000000000000000000000000000000000000000190019000640000000000c800c80000000000000000000000000000000000000001c00042c54686c7000000000000000000000000000000000000000000000000000000000000100000000400e0"
|
||||
|
||||
"PacketCodingActor" should {
|
||||
"bundle an r-originating packet into an l-facing SlottedMetaPacket byte stream data (SlottedMetaPacket)" in {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue