Simple player spawning proof of concept using buildings mapped to their spawn tube amenities in a "spawn group."

This commit is contained in:
FateJH 2018-03-17 23:52:58 -04:00
parent 001f9a40e9
commit 3e9e3df0fa
4 changed files with 162 additions and 79 deletions

View file

@ -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 {

View file

@ -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()
}

View file

@ -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,

View file

@ -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()