diff --git a/src/main/scala/net/psforever/actors/session/AvatarActor.scala b/src/main/scala/net/psforever/actors/session/AvatarActor.scala index 5709aa60..c7a39da0 100644 --- a/src/main/scala/net/psforever/actors/session/AvatarActor.scala +++ b/src/main/scala/net/psforever/actors/session/AvatarActor.scala @@ -1446,9 +1446,8 @@ class AvatarActor( name, cooldown.toSeconds, item match { - case t: ToolDefinition => GlobalDefinitions.isMaxArms(t) - case _: VehicleDefinition => true - case _ => false + case _: KitDefinition => false + case _ => true } ) case _ => ; @@ -2573,9 +2572,8 @@ class AvatarActor( name, cooldown.toSeconds - secondsSincePurchase, obj match { - case t: ToolDefinition => GlobalDefinitions.isMaxArms(t) - case _: VehicleDefinition => true - case _ => false + case _: KitDefinition => false + case _ => true } ) @@ -2591,9 +2589,9 @@ class AvatarActor( } } - def updatePurchaseTimer(name: String, time: Long, isActuallyAMachine: Boolean): Unit = { + def updatePurchaseTimer(name: String, time: Long, isNotAMedKit: Boolean): Unit = { sessionActor ! SessionActor.SendResponse( - AvatarVehicleTimerMessage(session.get.player.GUID, name, time, isActuallyAMachine) + AvatarVehicleTimerMessage(session.get.player.GUID, name, time, isNotAMedKit) ) } diff --git a/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala b/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala index e473fd12..35ef5494 100644 --- a/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala +++ b/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala @@ -6,6 +6,8 @@ import akka.actor.typed.scaladsl.adapter._ import akka.actor.{ActorContext, ActorRef, Cancellable, typed} import akka.pattern.ask import akka.util.Timeout +import net.psforever.login.WorldSession +import net.psforever.objects.inventory.InventoryItem import net.psforever.objects.serverobject.mount.Seat import net.psforever.objects.serverobject.tube.SpawnTube import net.psforever.objects.sourcing.{PlayerSource, SourceEntry, VehicleSource} @@ -559,19 +561,15 @@ class ZoningOperations( zone.Buildings.foreach({ case (_, building) => initBuilding(continentNumber, building.MapId, building) }) sendResponse(ZonePopulationUpdateMessage(continentNumber, 414, 138, popTR, 138, popNC, 138, popVS, 138, popBO)) + //TODO should actually not claim that the sanctuary or VR zones are locked by their respective empire if (continentNumber == 11) - sendResponse( - ContinentalLockUpdateMessage(continentNumber, PlanetSideEmpire.NC) - ) // "The NC have captured the NC Sanctuary." + sendResponse(ContinentalLockUpdateMessage(continentNumber, PlanetSideEmpire.NC)) else if (continentNumber == 12) - sendResponse( - ContinentalLockUpdateMessage(continentNumber, PlanetSideEmpire.TR) - ) // "The TR have captured the TR Sanctuary." + sendResponse(ContinentalLockUpdateMessage(continentNumber, PlanetSideEmpire.TR)) else if (continentNumber == 13) - sendResponse( - ContinentalLockUpdateMessage(continentNumber, PlanetSideEmpire.VS) - ) // "The VS have captured the VS Sanctuary." - else sendResponse(ContinentalLockUpdateMessage(continentNumber, PlanetSideEmpire.NEUTRAL)) + sendResponse(ContinentalLockUpdateMessage(continentNumber, PlanetSideEmpire.VS)) + else + sendResponse(ContinentalLockUpdateMessage(continentNumber, PlanetSideEmpire.NEUTRAL)) //CaptureFlagUpdateMessage() //VanuModuleUpdateMessage() //ModuleLimitsMessage() @@ -1804,6 +1802,7 @@ class ZoningOperations( session = session.copy(player = p, avatar = a) sessionData.persist() setupAvatarFunc = AvatarRejoin + dropMedicalApplicators(p) avatarActor ! AvatarActor.ReplaceAvatar(a) avatarLoginResponse(a) @@ -1813,6 +1812,7 @@ class ZoningOperations( deadState = DeadState.Dead session = session.copy(player = p, avatar = a) sessionData.persist() + dropMedicalApplicators(p) HandleReleaseAvatar(p, inZone) avatarActor ! AvatarActor.ReplaceAvatar(a) avatarLoginResponse(a) @@ -1894,8 +1894,12 @@ class ZoningOperations( } def handleNewPlayerLoaded(tplayer: Player): Unit = { - //new zone - log.info(s"${tplayer.Name} has spawned into ${session.zone.id}") + /* new zone, might be on `tplayer.Zone` but should definitely be on `session` */ + val zone = session.zone + val id = zone.id + val map = zone.map + val mapName = map.name + log.info(s"${tplayer.Name} has spawned into $id") sessionData.oldRefsMap.clear() sessionData.persist = UpdatePersistenceAndRefs tplayer.avatar = avatar @@ -1903,18 +1907,8 @@ class ZoningOperations( avatarActor ! AvatarActor.CreateImplants() avatarActor ! AvatarActor.InitializeImplants() //LoadMapMessage causes the client to send BeginZoningMessage, eventually leading to SetCurrentAvatar - val weaponsEnabled = - session.zone.map.name != "map11" && session.zone.map.name != "map12" && session.zone.map.name != "map13" - sendResponse( - LoadMapMessage( - session.zone.map.name, - session.zone.id, - 40100, - 25, - weaponsEnabled, - session.zone.map.checksum - ) - ) + val weaponsEnabled = !(mapName.equals("map11") || mapName.equals("map12") || mapName.equals("map13")) + sendResponse(LoadMapMessage(mapName, id, 40100, 25, weaponsEnabled, map.checksum)) if (isAcceptableNextSpawnPoint) { //important! the LoadMapMessage must be processed by the client before the avatar is created setupAvatarFunc() @@ -1934,7 +1928,7 @@ class ZoningOperations( } else { //look for different spawn point in same zone cluster ! ICS.GetNearbySpawnPoint( - session.zone.Number, + zone.Number, tplayer, Seq(SpawnGroup.Facility, SpawnGroup.Tower, SpawnGroup.AMS), context.self @@ -1974,6 +1968,25 @@ class ZoningOperations( /* support functions */ + private def dropMedicalApplicators(p: Player): Unit = { + WorldSession.DropLeftovers(p)( + (p.Holsters().zipWithIndex.collect { case (slot, index) if slot.Equipment.nonEmpty => InventoryItem(slot.Equipment.get, index) } ++ + p.Inventory.Items ++ + p.FreeHand.Equipment.flatMap { item => Some(InventoryItem(item, Player.FreeHandSlot)) }.toList) + .collect { + case entry @ InventoryItem(equipment, index) + if equipment.Definition == GlobalDefinitions.medicalapplicator && p.DrawnSlot == index => + p.Slot(index).Equipment = None + p.DrawnSlot = Player.HandsDownSlot + entry + case entry @ InventoryItem(equipment, index) + if equipment.Definition == GlobalDefinitions.medicalapplicator => + p.Slot(index).Equipment = None + entry + } + ) + } + def isAcceptableNextSpawnPoint: Boolean = isAcceptableSpawnPoint(nextSpawnPoint) def isAcceptableSpawnPoint(spawnPoint: SpawnPoint): Boolean = isAcceptableSpawnPoint(Some(spawnPoint)) diff --git a/src/main/scala/net/psforever/login/WorldSession.scala b/src/main/scala/net/psforever/login/WorldSession.scala index a4e921be..8701d74f 100644 --- a/src/main/scala/net/psforever/login/WorldSession.scala +++ b/src/main/scala/net/psforever/login/WorldSession.scala @@ -959,7 +959,7 @@ object WorldSession { * @param container the original object that contained the items * @param drops the items to be dropped on the ground */ - def DropLeftovers(container: PlanetSideServerObject with Container)(drops: List[InventoryItem]): Unit = { + def DropLeftovers(container: PlanetSideServerObject with Container)(drops: Iterable[InventoryItem]): Unit = { //drop or retire val zone = container.Zone val pos = container.Position diff --git a/src/main/scala/net/psforever/packet/game/AvatarVehicleTimerMessage.scala b/src/main/scala/net/psforever/packet/game/AvatarVehicleTimerMessage.scala index 254f3a77..a40d268b 100644 --- a/src/main/scala/net/psforever/packet/game/AvatarVehicleTimerMessage.scala +++ b/src/main/scala/net/psforever/packet/game/AvatarVehicleTimerMessage.scala @@ -7,10 +7,13 @@ import scodec.Codec import scodec.codecs._ /** - * @param player_guid player guid - * @param text internal name of the item or vehicle name, e.g., medkit, fury, trhev_antipersonnel - * @param time cooldown/delay in seconds - * @param unk `true` for vehicles and max exo-suits; `false` for other items + * @param player_guid player guid + * @param text internal name of the item or vehicle name, e.g., medkit, fury, trhev_antipersonnel + * @param time cooldown/delay in seconds + * @param unk unk; + * most likely has to do with the visibility of the timer in equipment purchasing; + * `false` for kit items; + * `true` for almost everything else */ final case class AvatarVehicleTimerMessage(player_guid: PlanetSideGUID, text: String, time: Long, unk: Boolean) extends PlanetSideGamePacket { diff --git a/src/main/scala/net/psforever/packet/game/LoadMapMessage.scala b/src/main/scala/net/psforever/packet/game/LoadMapMessage.scala index 21b3b7a3..c3f7e305 100644 --- a/src/main/scala/net/psforever/packet/game/LoadMapMessage.scala +++ b/src/main/scala/net/psforever/packet/game/LoadMapMessage.scala @@ -6,22 +6,29 @@ import scodec.Codec import scodec.codecs._ /** - * map_name and nav_map_name should match (unless you want to be lost :)) - * - * ex: - * map13 & home3 = vs sanc - * map10 & z10 = amerish - * map07 & z7 = esamir - */ + * Dispatched from server to client to instigate a zone change. + * The client should respond with a `BeginZoningMessage` packet. + * `map_name` and `zone_id` should correspond or the final product will be disorienting, even if it works. + * @param map_name designation of the physical zone; + * determines the (deployment) map screen + * @param zone_id designation of the entirety of the zone; + * determines the loading screen + * @param unk1 na; + * seems to match the initial projectile index (that can be assigned) + * @param unk2 na; + * seems to match the total number of unique projectile indices (that can be assigned) (before looping) + * @param weapons_unlocked live fire is permissible; + * restricts all actions instigated by that key bind + * @param checksum challenge number so that client can confirm server is using the correct version of this zone + */ final case class LoadMapMessage( - map_name: String, - nav_map_name: String, // Also determines loading screen - unk1: Int, - unk2: Long, - weapons_unlocked: Boolean, - checksum: Long -) //? - extends PlanetSideGamePacket { + map_name: String, + zone_id: String, // Also determines loading screen + unk1: Int, + unk2: Long, + weapons_unlocked: Boolean, + checksum: Long +) extends PlanetSideGamePacket { type Packet = LoadMapMessage def opcode = GamePacketOpcode.LoadMapMessage def encode = LoadMapMessage.encode(this) @@ -29,8 +36,8 @@ final case class LoadMapMessage( object LoadMapMessage extends Marshallable[LoadMapMessage] { implicit val codec: Codec[LoadMapMessage] = ( - ("map_name" | PacketHelpers.encodedString) :: // TODO: Implement encodedStringWithLimit - ("nav_map_name" | PacketHelpers.encodedString) :: //TODO: Implement encodedStringWithLimit + ("map_name" | PacketHelpers.encodedString) :: // TODO: Implement encodedStringWithLimit + ("zone_id" | PacketHelpers.encodedString) :: //TODO: Implement encodedStringWithLimit ("unk1" | uint16L) :: ("unk2" | uint32L) :: ("weapons_unlocked" | bool) ::