diff --git a/src/main/scala/net/psforever/actors/session/normal/AvatarHandlerLogic.scala b/src/main/scala/net/psforever/actors/session/normal/AvatarHandlerLogic.scala index e9936cda..c81be928 100644 --- a/src/main/scala/net/psforever/actors/session/normal/AvatarHandlerLogic.scala +++ b/src/main/scala/net/psforever/actors/session/normal/AvatarHandlerLogic.scala @@ -10,7 +10,7 @@ import net.psforever.objects.serverobject.containable.ContainableBehavior import net.psforever.objects.serverobject.mount.Mountable import net.psforever.objects.sourcing.PlayerSource import net.psforever.objects.vital.interaction.Adversarial -import net.psforever.packet.game.{AvatarImplantMessage, CreateShortcutMessage, ImplantAction} +import net.psforever.packet.game.{AvatarImplantMessage, CreateShortcutMessage, ImplantAction, PlanetsideStringAttributeMessage} import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage} import net.psforever.types.ImplantType @@ -252,6 +252,9 @@ class AvatarHandlerLogic(val ops: SessionAvatarHandlers, implicit val context: A case AvatarResponse.PlanetsideAttributeSelf(attributeType, attributeValue) if isSameTarget => sendResponse(PlanetsideAttributeMessage(guid, attributeType, attributeValue)) + case AvatarResponse.PlanetsideStringAttribute(attributeType, attributeValue) => + sendResponse(PlanetsideStringAttributeMessage(guid, attributeType, attributeValue)) + case AvatarResponse.GenericObjectAction(objectGuid, actionCode) if isNotSameTarget => sendResponse(GenericObjectActionMessage(objectGuid, actionCode)) diff --git a/src/main/scala/net/psforever/actors/session/support/SessionOutfitHandlers.scala b/src/main/scala/net/psforever/actors/session/support/SessionOutfitHandlers.scala index e9249d6d..b3bedc36 100644 --- a/src/main/scala/net/psforever/actors/session/support/SessionOutfitHandlers.scala +++ b/src/main/scala/net/psforever/actors/session/support/SessionOutfitHandlers.scala @@ -83,10 +83,10 @@ object SessionOutfitHandlers { player.outfit_name = outfit.name player.Zone.AvatarEvents ! AvatarServiceMessage(player.Zone.id, - AvatarAction.PlanetsideAttributeToAll(player.GUID, 39, player.outfit_id)) + AvatarAction.PlanetsideAttributeToAll(player.GUID, 39, outfit.id)) player.Zone.AvatarEvents ! AvatarServiceMessage(player.Zone.id, - AvatarAction.PlanetsideStringAttribute(player.GUID, 0, player.outfit_name)) + AvatarAction.PlanetsideStringAttribute(player.GUID, 0, outfit.name)) session.chat.JoinChannel(OutfitChannel(player.outfit_id)) } @@ -168,10 +168,10 @@ object SessionOutfitHandlers { invited.outfit_name = outfit.name invited.Zone.AvatarEvents ! AvatarServiceMessage(invited.Zone.id, - AvatarAction.PlanetsideAttributeToAll(invited.GUID, 39, invited.outfit_id)) + AvatarAction.PlanetsideAttributeToAll(invited.GUID, 39, outfit.id)) invited.Zone.AvatarEvents ! AvatarServiceMessage(invited.Zone.id, - AvatarAction.PlanetsideStringAttribute(invited.GUID, 0, invited.outfit_name)) + AvatarAction.PlanetsideStringAttribute(invited.GUID, 0, outfit.name)) case (None, _, _) => PlayerControl.sendResponse(invited.Zone, invited.Name, @@ -223,14 +223,11 @@ object SessionOutfitHandlers { kickedBy.outfit_name = "" kickedBy.outfit_id = 0 - val pZone = zones.filter(z => z.id == kickedBy.Zone.id).head - pZone.AllPlayers.filter(p => p.Faction == kickedBy.Faction).foreach { friendly => - PlayerControl.sendResponse(friendly.Zone, friendly.Name, - PlanetsideAttributeMessage(kickedBy.GUID, 39, 0)) + kickedBy.Zone.AvatarEvents ! AvatarServiceMessage(kickedBy.Zone.id, + AvatarAction.PlanetsideAttributeToAll(kickedBy.GUID, 39, 0)) - PlayerControl.sendResponse(friendly.Zone, friendly.Name, - PlanetsideStringAttributeMessage(kickedBy.GUID, 0, "")) - } + kickedBy.Zone.AvatarEvents ! AvatarServiceMessage(kickedBy.Zone.id, + AvatarAction.PlanetsideStringAttribute(kickedBy.GUID, 0, "")) } }.recover { case e => e.printStackTrace() @@ -426,11 +423,8 @@ object SessionOutfitHandlers { val outfit_id = player.outfit_id - // update MOTD - updateOutfitMotd(outfit_id, message) - - // TODO this does not notify clients with open windows. Do they update in the first place? val outfitDetails = for { + _ <- updateOutfitMotd(outfit_id, message) outfitOpt <- ctx.run(getOutfitById(outfit_id)).map(_.headOption) memberCount <- ctx.run(query[Outfitmember].filter(_.outfit_id == lift(outfit_id)).size) pointsTotal <- ctx.run(querySchema[OutfitpointMv]("outfitpoint_mv").filter(_.outfit_id == lift(outfit_id))) @@ -487,6 +481,56 @@ object SessionOutfitHandlers { // S >> C OutfitEvent(Unk2, 529744, Unk2(OutfitInfo(PlanetSide_Forever_Vanu, 0, 0, 3, OutfitRankNames(, , , , , , , ), Vanu outfit for the planetside forever project! -find out more about the PSEMU project at PSforever.net, 0, 1, 0, 1458331641, 0, 0, 0))) } + def HandleLoginOutfitCheck(player: Player, session: SessionData): Unit = { + ctx.run(getOutfitOnLogin(player.avatar.id)).flatMap { memberships => + memberships.headOption match { + case Some(membership) => + val outfitId = membership.outfit_id + (for { + outfitOpt <- ctx.run(getOutfitById(outfitId)).map(_.headOption) + memberCount <- ctx.run(getOutfitMemberCount(outfitId)) + points <- ctx.run(getOutfitPoints(outfitId)).map(_.headOption.map(_.points).getOrElse(0L)) + } yield (outfitOpt, memberCount, points)) + .map { + case (Some(outfit), memberCount, points) => + val seconds: Long = outfit.created.atZone(java.time.ZoneOffset.UTC).toInstant.toEpochMilli / 1000 + + PlayerControl.sendResponse(player.Zone, player.Name, + OutfitEvent(outfitId, Unk2(OutfitInfo( + outfit.name, points, points, memberCount, + OutfitRankNames(outfit.rank0.getOrElse(""), outfit.rank1.getOrElse(""), outfit.rank2.getOrElse(""), + outfit.rank3.getOrElse(""), outfit.rank4.getOrElse(""), outfit.rank5.getOrElse(""), + outfit.rank6.getOrElse(""), outfit.rank7.getOrElse("")), + outfit.motd.getOrElse(""), + 14, unk11 = true, 0, seconds, 0, 0, 0)))) + + PlayerControl.sendResponse(player.Zone, player.Name, + OutfitMemberUpdate(outfit.id, player.CharId, membership.rank, flag = true)) + + session.chat.JoinChannel(OutfitChannel(outfit.id)) + player.outfit_id = outfit.id + player.outfit_name = outfit.name + + player.Zone.AvatarEvents ! AvatarServiceMessage(player.Zone.id, + AvatarAction.PlanetsideAttributeToAll(player.GUID, 39, outfit.id)) + + player.Zone.AvatarEvents ! AvatarServiceMessage(player.Zone.id, + AvatarAction.PlanetsideStringAttribute(player.GUID, 0, outfit.name)) + + case (None, _, _) => + PlayerControl.sendResponse(player.Zone, player.Name, + ChatMsg(ChatMessageType.UNK_227, "Failed to load outfit")) + } + .recover { case _ => + PlayerControl.sendResponse(player.Zone, player.Name, + ChatMsg(ChatMessageType.UNK_227, "Failed to load outfit")) + } + case None => + Future.successful(()) + } + } + } + /* supporting functions */ def sanitizeOutfitName(name: String): Option[String] = { @@ -605,6 +649,10 @@ object SessionOutfitHandlers { querySchema[OutfitpointMv]("outfitpoint_mv").filter(_.outfit_id == lift(id)) } + def getOutfitOnLogin(avatarId: Long): Quoted[EntityQuery[Outfitmember]] = quote { + query[Outfitmember].filter(_.avatar_id == lift(avatarId)) + } + def getOutfitMembersWithDetails(outfitId: Long): Quoted[Query[(Long, String, Long, Int, LocalDateTime)]] = quote { query[Outfitmember] .filter(_.outfit_id == lift(outfitId)) 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 f9dd3a06..95502b19 100644 --- a/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala +++ b/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala @@ -2532,6 +2532,7 @@ class ZoningOperations( sessionLogic.general.toggleTeleportSystem(obj, TelepadLike.AppraiseTeleportationSystem(obj, continent)) } } + SessionOutfitHandlers.HandleLoginOutfitCheck(player, sessionLogic) //make weather happen sendResponse(WeatherMessage(List(),List( StormInfo(Vector3(0.1f, 0.15f, 0.0f), 240, 217), @@ -2655,6 +2656,7 @@ class ZoningOperations( log.debug(s"AvatarRejoin: ${player.Name} - $guid -> $data") } setupAvatarFunc = AvatarCreate + SessionOutfitHandlers.HandleLoginOutfitCheck(player, sessionLogic) //make weather happen sendResponse(WeatherMessage(List(),List( StormInfo(Vector3(0.1f, 0.15f, 0.0f), 240, 217), @@ -3196,8 +3198,6 @@ class ZoningOperations( continent.AllPlayers.filter(_.ExoSuit == ExoSuitType.MAX).foreach(max => sendResponse(PlanetsideAttributeMessage(max.GUID, 4, max.Armor))) // AvatarAwardMessage //populateAvatarAwardRibbonsFunc(1, 20L) - - sendResponse(PlanetsideStringAttributeMessage(guid, 0, "Outfit Name")) //squad stuff (loadouts, assignment) sessionLogic.squad.squadSetup() //MapObjectStateBlockMessage and ObjectCreateMessage? diff --git a/src/main/scala/net/psforever/services/avatar/AvatarService.scala b/src/main/scala/net/psforever/services/avatar/AvatarService.scala index f61e4394..54afc5bf 100644 --- a/src/main/scala/net/psforever/services/avatar/AvatarService.scala +++ b/src/main/scala/net/psforever/services/avatar/AvatarService.scala @@ -198,6 +198,14 @@ class AvatarService(zone: Zone) extends Actor { AvatarResponse.PlanetsideAttributeSelf(attribute_type, attribute_value) ) ) + case AvatarAction.PlanetsideStringAttribute(guid, attribute_type, attribute_value) => + AvatarEvents.publish( + AvatarServiceResponse( + s"/$forChannel/Avatar", + guid, + AvatarResponse.PlanetsideStringAttribute(attribute_type, attribute_value) + ) + ) case AvatarAction.PlayerState( guid, pos, diff --git a/src/main/scala/net/psforever/services/avatar/AvatarServiceResponse.scala b/src/main/scala/net/psforever/services/avatar/AvatarServiceResponse.scala index 5c26d9a0..cce788e7 100644 --- a/src/main/scala/net/psforever/services/avatar/AvatarServiceResponse.scala +++ b/src/main/scala/net/psforever/services/avatar/AvatarServiceResponse.scala @@ -47,7 +47,7 @@ object AvatarResponse { final case class EquipmentInHand(pkt: ObjectCreateMessage) extends Response final case class GenericObjectAction(object_guid: PlanetSideGUID, action_code: Int) extends Response final case class HitHint(source_guid: PlanetSideGUID) extends Response - final case class Killed(cause: DamageResult, mount_guid: Option[PlanetSideGUID]) extends Response + final case class Killed(cause: DamageResult, mount_guid: Option[PlanetSideGUID]) extends Response final case class LoadPlayer(pkt: ObjectCreateMessage) extends Response final case class LoadProjectile(pkt: ObjectCreateMessage) extends Response final case class ObjectDelete(item_guid: PlanetSideGUID, unk: Int) extends Response @@ -56,6 +56,7 @@ object AvatarResponse { final case class PlanetsideAttribute(attribute_type: Int, attribute_value: Long) extends Response final case class PlanetsideAttributeToAll(attribute_type: Int, attribute_value: Long) extends Response final case class PlanetsideAttributeSelf(attribute_type: Int, attribute_value: Long) extends Response + final case class PlanetsideStringAttribute(attribute_type: Int, attribute_value: String) extends Response final case class PlayerState( pos: Vector3, vel: Option[Vector3],