diff --git a/common/src/main/scala/net/psforever/objects/Player.scala b/common/src/main/scala/net/psforever/objects/Player.scala index f2a01282..505fcd5c 100644 --- a/common/src/main/scala/net/psforever/objects/Player.scala +++ b/common/src/main/scala/net/psforever/objects/Player.scala @@ -615,7 +615,7 @@ object Player { def Release(player : Player) : Player = { if(player.Release) { - val obj = new Player(player.Name, player.Faction, player.Sex, player.Voice, player.Head) + val obj = new Player(player.Name, player.Faction, player.Sex, player.Head, player.Voice) obj.VehicleOwned = player.VehicleOwned obj.Continent = player.Continent //hand over loadouts @@ -629,11 +629,11 @@ object Player { } }) //hand over knife - obj.Slot(4).Equipment = player.Slot(4).Equipment - player.Slot(4).Equipment = None - //hand over ??? - obj.fifthSlot.Equipment = player.fifthSlot.Equipment - player.fifthSlot.Equipment = None +// obj.Slot(4).Equipment = player.Slot(4).Equipment +// player.Slot(4).Equipment = None + //hand over locker contents +// obj.fifthSlot.Equipment = player.fifthSlot.Equipment +// player.fifthSlot.Equipment = None obj } else { diff --git a/common/src/main/scala/net/psforever/objects/zones/InterstellarCluster.scala b/common/src/main/scala/net/psforever/objects/zones/InterstellarCluster.scala index ddd70cca..ec5d0d00 100644 --- a/common/src/main/scala/net/psforever/objects/zones/InterstellarCluster.scala +++ b/common/src/main/scala/net/psforever/objects/zones/InterstellarCluster.scala @@ -49,9 +49,9 @@ class InterstellarCluster(zones : List[Zone]) extends Actor { log.error(s"Requested zone $zoneId could not be found") } - case InterstellarCluster.RequestClientInitialization(tplayer) => + case InterstellarCluster.RequestClientInitialization() => zones.foreach(zone => { sender ! Zone.ClientInitialization(zone.ClientInitialization()) }) - sender ! InterstellarCluster.ClientInitializationComplete(tplayer) //will be processed after all Zones + sender ! InterstellarCluster.ClientInitializationComplete() //will be processed after all Zones case _ => ; } @@ -95,17 +95,13 @@ object InterstellarCluster { /** * Signal to the cluster that a new client needs to be initialized for all listed `Zone` destinations. - * @param tplayer the `Player` belonging to the client; - * may be superfluous * @see `Zone` */ - final case class RequestClientInitialization(tplayer : Player) + final case class RequestClientInitialization() /** * Return signal intended to inform the original sender that all `Zone`s have finished being initialized. - * @param tplayer the `Player` belonging to the client; - * may be superfluous * @see `WorldSessionActor` */ - final case class ClientInitializationComplete(tplayer : Player) + final case class ClientInitializationComplete() } diff --git a/common/src/main/scala/net/psforever/packet/game/SpawnRequestMessage.scala b/common/src/main/scala/net/psforever/packet/game/SpawnRequestMessage.scala index cbb35a95..045aa6d0 100644 --- a/common/src/main/scala/net/psforever/packet/game/SpawnRequestMessage.scala +++ b/common/src/main/scala/net/psforever/packet/game/SpawnRequestMessage.scala @@ -5,7 +5,14 @@ import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacke import scodec.Codec import scodec.codecs._ - +/** + * + * @param unk1 na + * @param unk2 na + * @param unk3 na + * @param unk4 na + * @param unk5 continent? + */ final case class SpawnRequestMessage(unk1 : Int, unk2 : Long, unk3 : Int, diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index 41405724..ce97439f 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -28,6 +28,7 @@ import net.psforever.objects.serverobject.terminals.Terminal.TerminalMessage import net.psforever.objects.vehicles.{AccessPermissionGroup, Utility, VehicleLockState} import net.psforever.objects.serverobject.structures.{Building, WarpGate} import net.psforever.objects.serverobject.terminals.Terminal +import net.psforever.objects.serverobject.tube.SpawnTube import net.psforever.objects.vehicles.{AccessPermissionGroup, VehicleLockState} import net.psforever.objects.zones.{InterstellarCluster, Zone} import net.psforever.packet.game.objectcreate._ @@ -152,14 +153,15 @@ class WorldSessionActor extends Actor with MDCContextAware { sendResponse(KeepAliveMessage()) case AvatarServiceResponse(_, guid, reply) => + val tplayer_guid = if(player.HasGUID) { player.GUID} else { PlanetSideGUID(0) } reply match { case AvatarResponse.ArmorChanged(suit, subtype) => - if(player.GUID != guid) { + if(tplayer_guid != guid) { sendResponse(ArmorChangedMessage(guid, suit, subtype)) } case AvatarResponse.ChangeAmmo(weapon_guid, weapon_slot, previous_guid, ammo_id, ammo_guid, ammo_data) => - if(player.GUID != guid) { + if(tplayer_guid != guid) { sendResponse(ObjectDetachMessage(weapon_guid, previous_guid, Vector3(0,0,0), 0f, 0f, 0f)) sendResponse( ObjectCreateMessage( @@ -173,27 +175,27 @@ class WorldSessionActor extends Actor with MDCContextAware { } case AvatarResponse.ChangeFireMode(item_guid, mode) => - if(player.GUID != guid) { + if(tplayer_guid != guid) { sendResponse(ChangeFireModeMessage(item_guid, mode)) } case AvatarResponse.ChangeFireState_Start(weapon_guid) => - if(player.GUID != guid) { + if(tplayer_guid != guid) { sendResponse(ChangeFireStateMessage_Start(weapon_guid)) } case AvatarResponse.ChangeFireState_Stop(weapon_guid) => - if(player.GUID != guid) { + if(tplayer_guid != guid) { sendResponse(ChangeFireStateMessage_Stop(weapon_guid)) } case AvatarResponse.ConcealPlayer() => - if(player.GUID != guid) { + if(tplayer_guid != guid) { sendResponse(GenericObjectActionMessage(guid, 36)) } case AvatarResponse.EquipmentInHand(slot, item) => - if(player.GUID != guid) { + if(tplayer_guid != guid) { val definition = item.Definition sendResponse( ObjectCreateMessage( @@ -206,7 +208,7 @@ class WorldSessionActor extends Actor with MDCContextAware { } case AvatarResponse.EquipmentOnGround(pos, orient, item_id, item_guid, item_data) => - if(player.GUID != guid) { + if(tplayer_guid != guid) { sendResponse( ObjectCreateMessage( item_id, @@ -217,27 +219,27 @@ class WorldSessionActor extends Actor with MDCContextAware { } case AvatarResponse.LoadPlayer(pdata) => - if(player.GUID != guid) { + if(tplayer_guid != guid) { sendResponse(ObjectCreateMessage(ObjectClass.avatar, guid, pdata)) } case AvatarResponse.ObjectDelete(item_guid, unk) => - if(player.GUID != guid) { + if(tplayer_guid != guid) { sendResponse(ObjectDeleteMessage(item_guid, unk)) } case AvatarResponse.ObjectHeld(slot) => - if(player.GUID != guid) { + if(tplayer_guid != guid) { sendResponse(ObjectHeldMessage(guid, slot, false)) } case AvatarResponse.PlanetsideAttribute(attribute_type, attribute_value) => - if(player.GUID != guid) { + if(tplayer_guid != guid) { sendResponse(PlanetsideAttributeMessage(guid, attribute_type, attribute_value)) } case AvatarResponse.PlayerState(msg, spectating, weaponInHand) => - if(player.GUID != guid) { + if(tplayer_guid != guid) { val now = System.currentTimeMillis() val (location, time, distanceSq) : (Vector3, Long, Float) = if(spectating) { @@ -278,12 +280,12 @@ class WorldSessionActor extends Actor with MDCContextAware { } case AvatarResponse.Reload(item_guid) => - if(player.GUID != guid) { + if(tplayer_guid != guid) { sendResponse(ReloadMessage(item_guid, 1, 0)) } case AvatarResponse.WeaponDryFire(weapon_guid) => - if(player.GUID != guid) { + if(tplayer_guid != guid) { sendResponse(WeaponDryFireMessage(weapon_guid)) } @@ -291,9 +293,10 @@ class WorldSessionActor extends Actor with MDCContextAware { } case LocalServiceResponse(_, guid, reply) => + val tplayer_guid = if(player.HasGUID) { player.GUID} else { PlanetSideGUID(0) } reply match { case LocalResponse.DoorOpens(door_guid) => - if(player.GUID != guid) { + if(tplayer_guid != guid) { sendResponse(GenericObjectStateMsg(door_guid, 16)) } @@ -315,28 +318,29 @@ class WorldSessionActor extends Actor with MDCContextAware { } case VehicleServiceResponse(_, guid, reply) => + val tplayer_guid = if(player.HasGUID) { player.GUID} else { PlanetSideGUID(0) } reply match { case VehicleResponse.Awareness(vehicle_guid) => //resets exclamation point fte marker (once) sendResponse(PlanetsideAttributeMessage(guid, 21, vehicle_guid.guid.toLong)) case VehicleResponse.ChildObjectState(object_guid, pitch, yaw) => - if(player.GUID != guid) { + if(tplayer_guid != guid) { sendResponse(ChildObjectStateMessage(object_guid, pitch, yaw)) } case VehicleResponse.DismountVehicle(unk1, unk2) => - if(player.GUID != guid) { + if(tplayer_guid != guid) { sendResponse(DismountVehicleMsg(guid, unk1, unk2)) } case VehicleResponse.DeployRequest(object_guid, state, unk1, unk2, pos) => - if(player.GUID != guid) { + if(tplayer_guid != guid) { sendResponse(DeployRequestMessage(guid, object_guid, state, unk1, unk2, pos)) } case VehicleResponse.InventoryState(obj, parent_guid, start, con_data) => - if(player.GUID != guid) { + if(tplayer_guid != guid) { //TODO prefer ObjectDetachMessage, but how to force ammo pools to update properly? val obj_guid = obj.GUID sendResponse(ObjectDeleteMessage(obj_guid, 0)) @@ -352,7 +356,7 @@ class WorldSessionActor extends Actor with MDCContextAware { case VehicleResponse.KickPassenger(unk1, unk2, vehicle_guid) => sendResponse(DismountVehicleMsg(guid, unk1, unk2)) - if(guid == player.GUID) { + if(tplayer_guid == guid) { continent.GUID(vehicle_guid) match { case Some(obj : Vehicle) => UnAccessContents(obj) @@ -362,23 +366,23 @@ class WorldSessionActor extends Actor with MDCContextAware { case VehicleResponse.LoadVehicle(vehicle, vtype, vguid, vdata) => //this is not be suitable for vehicles with people who are seated in it before it spawns (if that is possible) - if(player.GUID != guid) { + if(tplayer_guid != guid) { sendResponse(ObjectCreateMessage(vtype, vguid, vdata)) ReloadVehicleAccessPermissions(vehicle) } case VehicleResponse.MountVehicle(vehicle_guid, seat) => - if(player.GUID != guid) { + if(tplayer_guid != guid) { sendResponse(ObjectAttachMessage(vehicle_guid, guid, seat)) } case VehicleResponse.SeatPermissions(vehicle_guid, seat_group, permission) => - if(player.GUID != guid) { + if(tplayer_guid != guid) { sendResponse(PlanetsideAttributeMessage(vehicle_guid, seat_group, permission)) } case VehicleResponse.StowEquipment(vehicle_guid, slot, item_type, item_guid, item_data) => - if(player.GUID != guid) { + if(tplayer_guid != guid) { //TODO prefer ObjectAttachMessage, but how to force ammo pools to update properly? sendResponse( ObjectCreateDetailedMessage(item_type, item_guid, ObjectCreateMessageParent(vehicle_guid, slot), item_data) @@ -389,13 +393,13 @@ class WorldSessionActor extends Actor with MDCContextAware { sendResponse(ObjectDeleteMessage(vehicle_guid, 0)) case VehicleResponse.UnstowEquipment(item_guid) => - if(player.GUID != guid) { + if(tplayer_guid != guid) { //TODO prefer ObjectDetachMessage, but how to force ammo pools to update properly? sendResponse(ObjectDeleteMessage(item_guid, 0)) } case VehicleResponse.VehicleState(vehicle_guid, unk1, pos, ang, vel, unk2, unk3, unk4, wheel_direction, unk5, unk6) => - if(player.GUID != guid) { + if(tplayer_guid != guid) { sendResponse(VehicleStateMessage(vehicle_guid, unk1, pos, ang, vel, unk2, unk3, unk4, wheel_direction, unk5, unk6)) if(player.VehicleSeated.contains(vehicle_guid)) { player.Position = pos @@ -962,23 +966,6 @@ class WorldSessionActor extends Actor with MDCContextAware { sendResponse(CharacterInfoMessage(0, PlanetSideZoneID(1), 0, PlanetSideGUID(0), true, 0)) - case InterstellarCluster.GiveWorld(zoneId, zone) => - log.info(s"Zone $zoneId has been loaded") - player.Continent = zoneId - continent = zone - taskResolver ! RegisterAvatar(player) - - case PlayerLoaded(tplayer) => - log.info(s"Player $tplayer has been loaded") - //init for whole server - galaxy ! InterstellarCluster.RequestClientInitialization(tplayer) - - case PlayerFailedToLoad(tplayer) => - player.Continent match { - case _ => - failWithError(s"$tplayer failed to load anywhere") - } - case VehicleLoaded(_/*vehicle*/) => ; //currently being handled by VehicleSpawnPad.LoadVehicle during testing phase @@ -1001,23 +988,36 @@ class WorldSessionActor extends Actor with MDCContextAware { sendResponse(ZoneForcedCavernConnectionsMessage(continentNumber, 0)) sendResponse(HotSpotUpdateMessage(continentNumber, 1, Nil)) //normally set in bulk; should be fine doing per continent - case InterstellarCluster.ClientInitializationComplete(tplayer)=> + case InterstellarCluster.ClientInitializationComplete() => //PropertyOverrideMessage sendResponse(PlanetsideAttributeMessage(PlanetSideGUID(0), 112, 1)) sendResponse(ReplicationStreamMessage(5, Some(6), Vector(SquadListing()))) //clear squad list sendResponse(FriendsResponse(FriendAction.InitializeFriendList, 0, true, true, Nil)) sendResponse(FriendsResponse(FriendAction.InitializeIgnoreList, 0, true, true, Nil)) + galaxy ! InterstellarCluster.GetWorld("z6") + case InterstellarCluster.GiveWorld(zoneId, zone) => + log.info(s"Zone $zoneId has been loaded") + player.Continent = zoneId + continent = zone + taskResolver ! RegisterNewAvatar(player) + + case NewPlayerLoaded(tplayer) => + log.info(s"Player $tplayer has been loaded") //LoadMapMessage will cause the client to send back a BeginZoningMessage packet (see below) sendResponse(LoadMapMessage(continent.Map.Name, continent.Id, 40100,25,true,3770441820L)) - log.info("Load the now-registered player") - //load the now-registered player - tplayer.Spawn - tplayer.Health = 50 - val dcdata = tplayer.Definition.Packet.DetailedConstructorData(tplayer).get - sendResponse(ObjectCreateDetailedMessage(ObjectClass.avatar, tplayer.GUID, dcdata)) - avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.LoadPlayer(tplayer.GUID, tplayer.Definition.Packet.ConstructorData(tplayer).get)) - log.debug(s"ObjectCreateDetailedMessage: $dcdata") + AvatarCreate() + + case PlayerLoaded(tplayer) => + log.info(s"Player $tplayer has been loaded") + AvatarCreate() + self ! SetCurrentAvatar(tplayer) + + case PlayerFailedToLoad(tplayer) => + player.Continent match { + case _ => + failWithError(s"$tplayer failed to load anywhere") + } case SetCurrentAvatar(tplayer) => val guid = tplayer.GUID @@ -1178,6 +1178,7 @@ class WorldSessionActor extends Actor with MDCContextAware { } var player : Player = null + var spawnZones : Map[Int, Building] = null def handleGamePkt(pkt : PlanetSideGamePacket) = pkt match { case ConnectToWorldRequestMessage(server, token, majorVersion, minorVersion, revision, buildDate, unk) => @@ -1252,7 +1253,8 @@ class WorldSessionActor extends Actor with MDCContextAware { //TODO check if can spawn on last continent/location from player? //TODO if yes, get continent guid accessors //TODO if no, get sanctuary guid accessors and reset the player's expectations - galaxy ! InterstellarCluster.GetWorld("z6") + //galaxy ! InterstellarCluster.GetWorld("z6") + galaxy ! InterstellarCluster.RequestClientInitialization() case default => log.error("Unsupported " + default + " in " + msg) } @@ -1264,6 +1266,12 @@ class WorldSessionActor extends Actor with MDCContextAware { log.info("Reticulating splines ...") configZone(continent) //todo density sendResponse(TimeOfDayMessage(1191182336)) + /** WIP */ + spawnZones = Map( + 7 -> continent.Building(2).get, + 6 -> continent.Building(48).get + ) + //custom sendResponse(ContinentalLockUpdateMessage(13, PlanetSideEmpire.VS)) // "The VS have captured the VS Sanctuary." sendResponse(ReplicationStreamMessage(5, Some(6), Vector(SquadListing()))) //clear squad list @@ -1347,18 +1355,20 @@ class WorldSessionActor extends Actor with MDCContextAware { self ! SetCurrentAvatar(player) case msg @ PlayerStateMessageUpstream(avatar_guid, pos, vel, yaw, pitch, yaw_upper, seq_time, unk3, is_crouching, is_jumping, unk4, is_cloaking, unk5, unk6) => - player.Position = pos - player.Velocity = vel - player.Orientation = Vector3(player.Orientation.x, pitch, yaw) - player.FacingYawUpper = yaw_upper - player.Crouching = is_crouching - player.Jumping = is_jumping + if(player.isAlive && player.GUID == avatar_guid) { + player.Position = pos + player.Velocity = vel + player.Orientation = Vector3(player.Orientation.x, pitch, yaw) + player.FacingYawUpper = yaw_upper + player.Crouching = is_crouching + player.Jumping = is_jumping - val wepInHand : Boolean = player.Slot(player.DrawnSlot).Equipment match { - case Some(item) => item.Definition == GlobalDefinitions.bolt_driver - case None => false + val wepInHand : Boolean = player.Slot(player.DrawnSlot).Equipment match { + case Some(item) => item.Definition == GlobalDefinitions.bolt_driver + case None => false + } + avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlayerState(avatar_guid, msg, spectator, wepInHand)) } - avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlayerState(avatar_guid, msg, spectator, wepInHand)) case msg @ ChildObjectStateMessage(object_guid, pitch, yaw) => //the majority of the following check retrieves information to determine if we are in control of the child @@ -1418,11 +1428,32 @@ class WorldSessionActor extends Actor with MDCContextAware { case msg @ ReleaseAvatarRequestMessage() => log.info(s"ReleaseAvatarRequest: ${player.GUID} on ${continent.Id} has released") + player.Release sendResponse(PlanetsideAttributeMessage(player.GUID, 6, 1)) sendResponse(AvatarDeadStateMessage(DeadState.Release, 0, 0, player.Position, 2, true)) + player = Player.Release(player) //swap new player case msg @ SpawnRequestMessage(u1, u2, u3, u4, u5) => log.info(s"SpawnRequestMessage: $msg") + spawnZones.get(u2.toInt) match { + case Some(building) => + scala.util.Random.shuffle(building.Amenities.filter(_.isInstanceOf[SpawnTube])).headOption match { //TODO temporary shuffle + case Some(tube) => + log.info(s"SpawnRequestMessage: new player was at position ${player.Position}") + player.Position = tube.Position + player.Orientation = tube.Orientation + player.FacingYawUpper = 0 + log.info(s"SpawnRequestMessage: new player moved to position ${player.Position}") + sendResponse(AvatarDeadStateMessage(DeadState.RespawnTime, 10000, 10000, Vector3.Zero, 2, true)) + import scala.concurrent.duration._ + import scala.concurrent.ExecutionContext.Implicits.global + context.system.scheduler.scheduleOnce(10 seconds, taskResolver, RegisterAvatar(player)) + case None => + log.warn(s"SpawnRequestMessage: can not find a spawn point in this spawn group - $u2") + } + case None => + log.warn(s"SpawnRequestMessage: can not find somewhere to spawn in ${continent.Id}") + } case msg @ SetChatFilterMessage(send_channel, origin, whitelist) => log.info("SetChatFilters: " + msg) @@ -1467,6 +1498,7 @@ class WorldSessionActor extends Actor with MDCContextAware { if(messagetype == ChatMessageType.CMT_SUICIDE) { val player_guid = player.GUID val pos = player.Position + player.Die sendResponse(PlanetsideAttributeMessage(player_guid, 0, 0)) sendResponse(PlanetsideAttributeMessage(player_guid, 2, 0)) sendResponse(DestroyMessage(player_guid, player_guid, PlanetSideGUID(0), pos)) @@ -2444,6 +2476,40 @@ class WorldSessionActor extends Actor with MDCContextAware { }) } + /** + * Construct tasking that registers all aspects of a `Player` avatar. + * `Players` are complex objects that contain a variety of other register-able objects and each of these objects much be handled. + * @param tplayer the avatar `Player` + * @return a `TaskResolver.GiveTask` message + */ + private def RegisterNewAvatar(tplayer : Player) : TaskResolver.GiveTask = { + TaskResolver.GiveTask( + new Task() { + private val localPlayer = tplayer + private val localAnnounce = self + + override def isComplete : Task.Resolution.Value = { + if(localPlayer.HasGUID) { + Task.Resolution.Success + } + else { + Task.Resolution.Incomplete + } + } + + def Execute(resolver : ActorRef) : Unit = { + log.info(s"Player $localPlayer is registered") + resolver ! scala.util.Success(this) + localAnnounce ! NewPlayerLoaded(localPlayer) //alerts WSA + } + + override def onFailure(ex : Throwable) : Unit = { + localAnnounce ! PlayerFailedToLoad(localPlayer) //alerts WSA + } + }, List(GUIDTask.RegisterAvatar(tplayer)(continent.GUID)) + ) + } + /** * Construct tasking that registers all aspects of a `Player` avatar. * `Players` are complex objects that contain a variety of other register-able objects and each of these objects much be handled. @@ -3211,6 +3277,19 @@ class WorldSessionActor extends Actor with MDCContextAware { }) } + /** + * TODO write + */ + def AvatarCreate() : Unit = { + player.Spawn + player.Health = 50 + val packet = player.Definition.Packet + val dcdata = packet.DetailedConstructorData(player).get + sendResponse(ObjectCreateDetailedMessage(ObjectClass.avatar, player.GUID, dcdata)) + avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.LoadPlayer(player.GUID, packet.ConstructorData(player).get)) + log.debug(s"ObjectCreateDetailedMessage: $dcdata") + } + def failWithError(error : String) = { log.error(error) sendResponse(ConnectionClose()) @@ -3245,6 +3324,7 @@ object WorldSessionActor { private final case class PokeClient() private final case class ServerLoaded() + private final case class NewPlayerLoaded(tplayer : Player) private final case class PlayerLoaded(tplayer : Player) private final case class PlayerFailedToLoad(tplayer : Player) private final case class ListAccountCharacters()