From 4634dffe00287b25fa2f87296599ef312cc53180 Mon Sep 17 00:00:00 2001 From: Jakob Gillich Date: Sat, 11 Jul 2020 12:50:29 +0200 Subject: [PATCH] ChatActor This removes roughly 1k LOC from WorldSessionActor and moves them to a new ChatActor. That was the initial goal anyway, but it wasn't that simple. There was no clear location to put this new actor, I didn't want to put it in pslogin since it isn't part of the "login server" (and neither is WSA). But since the new actor would have to talk to WSA and common does not depend on pslogin, I had a choice of putting more actors in pslogin or putting everything in common. I chose the latter. ChatActor and SessionActor (formerly WorldSessionActor) now live in `common/actors/session`. Since WSA also depends on other actors in pslogin, most of the pslogin code was moved to either common/login or common/util. PsLogin as the main entry point remains in pslogin since having the main code compile to a library has some advantages, and it will allow us to produce binaries for distinct login/world servers in the future if desired. For a second take, I'd suggest moving common to /src in the root directory. This change is enabled by a new immutable `Zone` object that is passed from SessionActor to ChatActor. Most of its members are still mutable references, and the code at the moment does depend on this being the case. Changes to the session object in SessionActor are forwarded through a SetZone message to ChatActor. As we split out more code into actors, we could use EventBus or typed Topic's instead. Also included is a reworked ChatService that was converted to a typed actor and uses the built-in Receptionist facility for service discovery. By receiving the session object from ChatActor, it can be much smarter about who to send messages to, rather than sending all messages to everyone and having them figure it out. But as this session object is not updated, it can only use static properties like player name and faction and not fluid properties like position. The following chat commands were added: command, note, gmbroadcast, [nc|tr|vs|broadcast, gmtell, gmpopup and !whitetext --- build.sbt | 3 +- .../psforever/actors/session/ChatActor.scala | 755 ++++++++++ .../actors/session/SessionActor.scala | 1338 +++++------------ .../psforever/login}/CryptoSessionActor.scala | 16 +- .../psforever/login}/LoginSessionActor.scala | 27 +- .../psforever/login}/PacketCodingActor.scala | 10 +- .../scala/net/psforever/login}/Session.scala | 10 +- .../net/psforever/login}/SessionRouter.scala | 13 +- .../net/psforever/login}/TcpListener.scala | 2 +- .../net/psforever/login}/UdpListener.scala | 2 +- .../login}/UdpNetworkSimulator.scala | 4 +- .../net/psforever/login}/WorldSession.scala | 6 +- .../login}/psadmin/CmdInternal.scala | 5 +- .../login}/psadmin/CmdListPlayers.scala | 8 +- .../login}/psadmin/CmdShutdown.scala | 3 +- .../login}/psadmin/PsAdminActor.scala | 17 +- .../login}/psadmin/PsAdminCommands.scala | 2 +- .../scala/net/psforever/objects/Session.scala | 17 + .../net/psforever/types/ChatMessageType.scala | 4 +- .../scala/net/psforever/util}/Config.scala | 23 +- .../scala/net/psforever/util}/Database.scala | 5 +- .../net/psforever/util/PointOfInterest.scala | 116 +- .../scala/net/psforever/zones}/Maps.scala | 4 +- .../scala/net/psforever/zones}/Zones.scala | 7 +- .../net/psforever/zones}/zonemaps/Map01.scala | 2 +- .../net/psforever/zones}/zonemaps/Map02.scala | 2 +- .../net/psforever/zones}/zonemaps/Map03.scala | 2 +- .../net/psforever/zones}/zonemaps/Map04.scala | 2 +- .../net/psforever/zones}/zonemaps/Map05.scala | 2 +- .../net/psforever/zones}/zonemaps/Map06.scala | 2 +- .../net/psforever/zones}/zonemaps/Map07.scala | 2 +- .../net/psforever/zones}/zonemaps/Map08.scala | 2 +- .../net/psforever/zones}/zonemaps/Map09.scala | 2 +- .../net/psforever/zones}/zonemaps/Map10.scala | 2 +- .../net/psforever/zones}/zonemaps/Map11.scala | 2 +- .../net/psforever/zones}/zonemaps/Map12.scala | 2 +- .../net/psforever/zones}/zonemaps/Map13.scala | 2 +- .../net/psforever/zones}/zonemaps/Map96.scala | 2 +- .../net/psforever/zones}/zonemaps/Map97.scala | 2 +- .../net/psforever/zones}/zonemaps/Map98.scala | 2 +- .../net/psforever/zones}/zonemaps/Map99.scala | 2 +- .../net/psforever/zones}/zonemaps/Ugd01.scala | 2 +- .../net/psforever/zones}/zonemaps/Ugd02.scala | 2 +- .../net/psforever/zones}/zonemaps/Ugd03.scala | 2 +- .../net/psforever/zones}/zonemaps/Ugd04.scala | 2 +- .../net/psforever/zones}/zonemaps/Ugd05.scala | 2 +- .../net/psforever/zones}/zonemaps/Ugd06.scala | 2 +- common/src/main/scala/services/Service.scala | 4 +- .../avatar/AvatarServiceResponse.scala | 2 +- .../main/scala/services/chat/ChatAction.scala | 62 - .../scala/services/chat/ChatResponse.scala | 90 -- .../scala/services/chat/ChatService.scala | 385 +++-- .../services/chat/ChatServiceMessage.scala | 4 - .../services/chat/ChatServiceResponse.scala | 18 - .../galaxy/GalaxyServiceResponse.scala | 2 +- .../services/local/LocalServiceResponse.scala | 2 +- .../services/teamwork/SquadService.scala | 13 +- .../teamwork/SquadServiceResponse.scala | 5 +- .../vehicle/VehicleServiceResponse.scala | 2 +- pslogin/src/main/scala/PsLogin.scala | 70 +- pslogin/src/main/scala/csr/CSRWarp.scala | 131 -- pslogin/src/main/scala/csr/CSRZone.scala | 113 -- pslogin/src/main/scala/csr/Traveler.scala | 38 - pslogin/src/test/scala/MDCTestProbe.scala | 1 + .../test/scala/PacketCodingActorTest.scala | 1 + .../actor/service/AvatarServiceTest.scala | 12 +- 66 files changed, 1560 insertions(+), 1838 deletions(-) create mode 100644 common/src/main/scala/net/psforever/actors/session/ChatActor.scala rename pslogin/src/main/scala/WorldSessionActor.scala => common/src/main/scala/net/psforever/actors/session/SessionActor.scala (92%) rename {pslogin/src/main/scala => common/src/main/scala/net/psforever/login}/CryptoSessionActor.scala (99%) rename {pslogin/src/main/scala => common/src/main/scala/net/psforever/login}/LoginSessionActor.scala (97%) rename {pslogin/src/main/scala => common/src/main/scala/net/psforever/login}/PacketCodingActor.scala (99%) rename {pslogin/src/main/scala => common/src/main/scala/net/psforever/login}/Session.scala (94%) rename {pslogin/src/main/scala => common/src/main/scala/net/psforever/login}/SessionRouter.scala (99%) rename {pslogin/src/main/scala => common/src/main/scala/net/psforever/login}/TcpListener.scala (98%) rename {pslogin/src/main/scala => common/src/main/scala/net/psforever/login}/UdpListener.scala (98%) rename {pslogin/src/main/scala => common/src/main/scala/net/psforever/login}/UdpNetworkSimulator.scala (99%) rename {pslogin/src/main/scala => common/src/main/scala/net/psforever/login}/WorldSession.scala (99%) rename {pslogin/src/main/scala => common/src/main/scala/net/psforever/login}/psadmin/CmdInternal.scala (91%) rename {pslogin/src/main/scala => common/src/main/scala/net/psforever/login}/psadmin/CmdListPlayers.scala (90%) rename {pslogin/src/main/scala => common/src/main/scala/net/psforever/login}/psadmin/CmdShutdown.scala (90%) rename {pslogin/src/main/scala => common/src/main/scala/net/psforever/login}/psadmin/PsAdminActor.scala (97%) rename {pslogin/src/main/scala => common/src/main/scala/net/psforever/login}/psadmin/PsAdminCommands.scala (97%) create mode 100644 common/src/main/scala/net/psforever/objects/Session.scala rename {pslogin/src/main/scala => common/src/main/scala/net/psforever/util}/Config.scala (87%) rename {pslogin/src/main/scala => common/src/main/scala/net/psforever/util}/Database.scala (89%) rename pslogin/src/main/scala/csr/CSRZoneImpl.scala => common/src/main/scala/net/psforever/util/PointOfInterest.scala (84%) rename {pslogin/src/main/scala => common/src/main/scala/net/psforever/zones}/Maps.scala (96%) rename {pslogin/src/main/scala => common/src/main/scala/net/psforever/zones}/Zones.scala (99%) rename {pslogin/src/main/scala => common/src/main/scala/net/psforever/zones}/zonemaps/Map01.scala (99%) rename {pslogin/src/main/scala => common/src/main/scala/net/psforever/zones}/zonemaps/Map02.scala (99%) rename {pslogin/src/main/scala => common/src/main/scala/net/psforever/zones}/zonemaps/Map03.scala (99%) rename {pslogin/src/main/scala => common/src/main/scala/net/psforever/zones}/zonemaps/Map04.scala (99%) rename {pslogin/src/main/scala => common/src/main/scala/net/psforever/zones}/zonemaps/Map05.scala (99%) rename {pslogin/src/main/scala => common/src/main/scala/net/psforever/zones}/zonemaps/Map06.scala (99%) rename {pslogin/src/main/scala => common/src/main/scala/net/psforever/zones}/zonemaps/Map07.scala (99%) rename {pslogin/src/main/scala => common/src/main/scala/net/psforever/zones}/zonemaps/Map08.scala (99%) rename {pslogin/src/main/scala => common/src/main/scala/net/psforever/zones}/zonemaps/Map09.scala (99%) rename {pslogin/src/main/scala => common/src/main/scala/net/psforever/zones}/zonemaps/Map10.scala (99%) rename {pslogin/src/main/scala => common/src/main/scala/net/psforever/zones}/zonemaps/Map11.scala (99%) rename {pslogin/src/main/scala => common/src/main/scala/net/psforever/zones}/zonemaps/Map12.scala (99%) rename {pslogin/src/main/scala => common/src/main/scala/net/psforever/zones}/zonemaps/Map13.scala (99%) rename {pslogin/src/main/scala => common/src/main/scala/net/psforever/zones}/zonemaps/Map96.scala (99%) rename {pslogin/src/main/scala => common/src/main/scala/net/psforever/zones}/zonemaps/Map97.scala (99%) rename {pslogin/src/main/scala => common/src/main/scala/net/psforever/zones}/zonemaps/Map98.scala (99%) rename {pslogin/src/main/scala => common/src/main/scala/net/psforever/zones}/zonemaps/Map99.scala (99%) rename {pslogin/src/main/scala => common/src/main/scala/net/psforever/zones}/zonemaps/Ugd01.scala (99%) rename {pslogin/src/main/scala => common/src/main/scala/net/psforever/zones}/zonemaps/Ugd02.scala (99%) rename {pslogin/src/main/scala => common/src/main/scala/net/psforever/zones}/zonemaps/Ugd03.scala (99%) rename {pslogin/src/main/scala => common/src/main/scala/net/psforever/zones}/zonemaps/Ugd04.scala (99%) rename {pslogin/src/main/scala => common/src/main/scala/net/psforever/zones}/zonemaps/Ugd05.scala (99%) rename {pslogin/src/main/scala => common/src/main/scala/net/psforever/zones}/zonemaps/Ugd06.scala (99%) delete mode 100644 common/src/main/scala/services/chat/ChatAction.scala delete mode 100644 common/src/main/scala/services/chat/ChatResponse.scala delete mode 100644 common/src/main/scala/services/chat/ChatServiceMessage.scala delete mode 100644 common/src/main/scala/services/chat/ChatServiceResponse.scala delete mode 100644 pslogin/src/main/scala/csr/CSRWarp.scala delete mode 100644 pslogin/src/main/scala/csr/CSRZone.scala delete mode 100644 pslogin/src/main/scala/csr/Traveler.scala diff --git a/build.sbt b/build.sbt index 938a7b61..29b0a77d 100644 --- a/build.sbt +++ b/build.sbt @@ -4,6 +4,7 @@ lazy val commonSettings = Seq( organization := "net.psforever", version := "1.0.2-SNAPSHOT", scalaVersion := "2.13.2", + Global / cancelable := false, semanticdbEnabled := true, semanticdbVersion := scalafixSemanticdb.revision, scalacOptions := Seq( @@ -110,7 +111,7 @@ lazy val pslogin = (project in file("pslogin")) // ActorTests have specific timing requirements and will be flaky if run in parallel parallelExecution in Test := false, // TODO(chord): remove exclusion when WorldSessionActor is refactored: https://github.com/psforever/PSF-LoginServer/issues/279 - coverageExcludedPackages := "net.psforever.pslogin.WorldSessionActor.*;net.psforever.pslogin.zonemaps.*", + coverageExcludedPackages := "net.psforever.actors.session.SessionActor.*;net.psforever.zones.zonemaps.*", // Copy all tests from Test -> QuietTest (we're only changing the run options) inConfig(QuietTest)(Defaults.testTasks) ) diff --git a/common/src/main/scala/net/psforever/actors/session/ChatActor.scala b/common/src/main/scala/net/psforever/actors/session/ChatActor.scala new file mode 100644 index 00000000..ba3252e8 --- /dev/null +++ b/common/src/main/scala/net/psforever/actors/session/ChatActor.scala @@ -0,0 +1,755 @@ +package net.psforever.actors.session + +import akka.actor.Cancellable +import akka.actor.typed.receptionist.Receptionist +import akka.actor.typed.{ActorRef, Behavior, PostStop, Signal} +import akka.actor.typed.scaladsl.{AbstractBehavior, ActorContext, Behaviors} +import net.psforever.objects.serverobject.pad.{VehicleSpawnControl, VehicleSpawnPad} +import net.psforever.objects.{Default, GlobalDefinitions, Player, Session} +import net.psforever.objects.serverobject.resourcesilo.ResourceSilo +import net.psforever.objects.serverobject.turret.{FacilityTurret, TurretUpgrade, WeaponTurrets} +import net.psforever.objects.zones.Zoning +import net.psforever.packet.PacketCoding +import net.psforever.packet.game.{ChatMsg, DeadState, RequestDestroyMessage, ZonePopulationUpdateMessage} +import net.psforever.types.{ChatMessageType, PlanetSideEmpire, PlanetSideGUID, Vector3} +import net.psforever.util.PointOfInterest +import net.psforever.zones.Zones +import services.chat.ChatService +import services.chat.ChatService.ChatChannel +import services.local.{LocalAction, LocalServiceMessage} + +import scala.concurrent.duration._ +import scala.concurrent.ExecutionContext.Implicits.global + +object ChatActor { + def apply(sessionActor: ActorRef[SessionActor.Command]): Behavior[Command] = + Behaviors.setup(context => new ChatActor(context, sessionActor)) + + sealed trait Command + + final case class JoinChannel(channel: ChatChannel) extends Command + final case class LeaveChannel(channel: ChatChannel) extends Command + final case class Message(message: ChatMsg) extends Command + final case class SetSession(session: Session) extends Command + + private case class ListingResponse(listing: Receptionist.Listing) extends Command + private case class IncomingMessage(session: Session, message: ChatMsg, channel: ChatChannel) extends Command +} + +class ChatActor(context: ActorContext[ChatActor.Command], sessionActor: ActorRef[SessionActor.Command]) + extends AbstractBehavior[ChatActor.Command](context) { + import ChatActor._ + + private[this] val log = org.log4s.getLogger + var channels: List[ChatChannel] = List() + var session: Option[Session] = None + var chatService: Option[ActorRef[ChatService.Command]] = None + var silenceTimer: Cancellable = Default.Cancellable + + val chatServiceAdapter: ActorRef[ChatService.MessageResponse] = context.messageAdapter[ChatService.MessageResponse] { + case ChatService.MessageResponse(session, message, channel) => IncomingMessage(session, message, channel) + } + + override def onSignal: PartialFunction[Signal, Behavior[Command]] = { + case PostStop => + silenceTimer.cancel() + if (chatService.isDefined) chatService.get ! ChatService.LeaveAllChannels(chatServiceAdapter) + this + } + + override def onMessage(msg: Command): Behavior[Command] = { + import ChatMessageType._ + + msg match { + case ListingResponse(ChatService.ChatServiceKey.Listing(listings)) => + chatService = Some(listings.head) + chatService.get ! ChatService.JoinChannel(chatServiceAdapter, session.get, ChatChannel.Default()) + channels ++= List(ChatChannel.Default()) + this + + case SetSession(newSession) => + session = Some(newSession) + if (chatService.isEmpty && newSession.player != null) { // TODO the player check sucks... + context.system.receptionist ! Receptionist.Find( + ChatService.ChatServiceKey, + context.messageAdapter[Receptionist.Listing](ListingResponse) + ) + } + this + + case JoinChannel(channel) => + chatService.get ! ChatService.JoinChannel(chatServiceAdapter, session.get, channel) + channels ++= List(channel) + this + + case LeaveChannel(channel) => + chatService.get ! ChatService.LeaveChannel(chatServiceAdapter, channel) + channels = channels.filter(_ == channel) + this + + /** Some messages are sent during login so we handle them prematurely because main message handler requires the + * session object and chat service and they may not be set yet + */ + case Message(ChatMsg(CMT_CULLWATERMARK, _, _, contents, _)) => + val connectionState = + if (contents.contains("40 80")) 100 + else if (contents.contains("120 200")) 25 + else 50 + sessionActor ! SessionActor.SetConnectionState(connectionState) + this + + case Message(ChatMsg(CMT_ANONYMOUS, _, _, _, _)) => + // ?? + this + + case Message(ChatMsg(CMT_TOGGLE_GM, _, _, _, _)) => + // ?? + this + + case Message(message) => + log.info("Chat: " + message) + + (session, chatService) match { + case (Some(session), Some(chatService)) => + (message.messageType, message.recipient.trim, message.contents.trim) match { + case (CMT_FLY, recipient, contents) if session.admin => + val flying = contents match { + case "on" => true + case "off" => false + case _ => !session.flying + } + sessionActor ! SessionActor.SetFlying(flying) + sessionActor ! SessionActor.SendResponse( + ChatMsg(CMT_FLY, false, recipient, if (flying) "on" else "off", None) + ) + + case (CMT_SPEED, recipient, contents) => + val speed = + try { + contents.toFloat + } catch { + case _: Throwable => + 1f + } + sessionActor ! SessionActor.SetSpeed(speed) + sessionActor ! SessionActor.SendResponse(message.copy(contents = f"$speed%.3f")) + + case (CMT_TOGGLESPECTATORMODE, _, contents) if session.admin => + val spectator = contents match { + case "on" => true + case "off" => false + case _ => !session.player.spectator + } + sessionActor ! SessionActor.SetSpectator(spectator) + sessionActor ! SessionActor.SendResponse(message.copy(contents = if (spectator) "on" else "off")) + + case (CMT_RECALL, _, _) => + val sanctuary = Zones.SanctuaryZoneId(session.player.Faction) + val errorMessage = session.zoningType match { + case Zoning.Method.Quit => Some("You can't recall to your sanctuary continent while quitting") + case Zoning.Method.InstantAction => + Some("You can't recall to your sanctuary continent while instant actioning") + case Zoning.Method.Recall => Some("You already requested to recall to your sanctuary continent") + case _ if session.zone.Id == sanctuary => + Some("You can't recall to your sanctuary when you are already in your sanctuary") + case _ if !session.player.isAlive || session.deadState != DeadState.Alive => + Some(if (session.player.isAlive) "@norecall_deconstructing" else "@norecall_dead") + case _ if session.player.VehicleSeated.nonEmpty => Some("@norecall_invehicle") + case _ => None + } + errorMessage match { + case Some(errorMessage) => + sessionActor ! SessionActor.SendResponse( + ChatMsg( + CMT_QUIT, + false, + "", + errorMessage, + None + ) + ) + case None => + sessionActor ! SessionActor.Recall(sanctuary) + } + + case (CMT_INSTANTACTION, _, _) => + if (session.zoningType == Zoning.Method.Quit) { + sessionActor ! SessionActor.SendResponse( + ChatMsg(CMT_QUIT, false, "", "You can't instant action while quitting.", None) + ) + } else if (session.zoningType == Zoning.Method.InstantAction) { + sessionActor ! SessionActor.SendResponse( + ChatMsg(CMT_QUIT, false, "", "@noinstantaction_instantactionting", None) + ) + } else if (session.zoningType == Zoning.Method.Recall) { + sessionActor ! SessionActor.SendResponse( + ChatMsg( + CMT_QUIT, + false, + "", + "You won't instant action. You already requested to recall to your sanctuary continent", + None + ) + ) + } else if (!session.player.isAlive || session.deadState != DeadState.Alive) { + if (session.player.isAlive) { + sessionActor ! SessionActor.SendResponse( + ChatMsg(CMT_QUIT, false, "", "@noinstantaction_deconstructing", None) + ) + } else { + sessionActor ! SessionActor.SendResponse( + ChatMsg(CMT_QUIT, false, "", "@noinstantaction_dead", None) + ) + } + } else if (session.player.VehicleSeated.nonEmpty) { + sessionActor ! SessionActor.SendResponse( + ChatMsg(CMT_QUIT, false, "", "@noinstantaction_invehicle", None) + ) + } else { + sessionActor ! SessionActor.InstantAction() + } + + case (CMT_QUIT, _, _) => + if (session.zoningType == Zoning.Method.Quit) { + sessionActor ! SessionActor.SendResponse(ChatMsg(CMT_QUIT, false, "", "@noquit_quitting", None)) + } else if (!session.player.isAlive || session.deadState != DeadState.Alive) { + if (session.player.isAlive) { + sessionActor ! SessionActor.SendResponse( + ChatMsg(CMT_QUIT, false, "", "@noquit_deconstructing", None) + ) + } else { + sessionActor ! SessionActor.SendResponse(ChatMsg(CMT_QUIT, false, "", "@noquit_dead", None)) + } + } else if (session.player.VehicleSeated.nonEmpty) { + sessionActor ! SessionActor.SendResponse(ChatMsg(CMT_QUIT, false, "", "@noquit_invehicle", None)) + } else { + sessionActor ! SessionActor.Quit() + } + + case (CMT_SUICIDE, _, _) => + if (session.player.isAlive && session.deadState != DeadState.Release) { + sessionActor ! SessionActor.Suicide() + } + + case (CMT_DESTROY, recipient, contents) => + val guid = contents.toInt + session.zone.GUID(session.zone.Map.TerminalToSpawnPad.getOrElse(guid, guid)) match { + case Some(pad: VehicleSpawnPad) => + pad.Actor ! VehicleSpawnControl.ProcessControl.Flush + case Some(turret: FacilityTurret) if turret.isUpgrading => + WeaponTurrets.FinishUpgradingMannedTurret(turret, TurretUpgrade.None) + case _ => + sessionActor ! SessionActor.SendResponse( + PacketCoding.CreateGamePacket(0, RequestDestroyMessage(PlanetSideGUID(guid))).packet + ) + } + sessionActor ! SessionActor.SendResponse(message) + + case (_, _, "!loc") => + val continent = session.zone + val player = session.player + val loc = + s"zone=${continent.Id} pos=${player.Position.x},${player.Position.y},${player.Position.z}; ori=${player.Orientation.x},${player.Orientation.y},${player.Orientation.z}" + log.info(loc) + sessionActor ! SessionActor.SendResponse(message.copy(contents = loc)) + + case (_, _, contents) if contents.startsWith("!list") => + val zone = contents.split(" ").lift(1) match { + case None => + Some(session.zone) + case Some(id) => + Zones.zones.get(id) + } + + zone match { + case Some(zone) => + sessionActor ! SessionActor.SendResponse( + ChatMsg( + CMT_GMOPEN, + message.wideContents, + "Server", + "\\#8Name (Faction) [ID] at PosX PosY PosZ", + message.note + ) + ) + + (zone.LivePlayers ++ zone.Corpses) + .filter(_.CharId != session.player.CharId) + .sortBy(_.Name) + .foreach(player => { + sessionActor ! SessionActor.SendResponse( + ChatMsg( + CMT_GMOPEN, + message.wideContents, + "Server", + s"\\#7${player.Name} (${player.Faction}) [${player.CharId}] at ${player.Position.x.toInt} ${player.Position.y.toInt} ${player.Position.z.toInt}", + message.note + ) + ) + }) + case None => + sessionActor ! SessionActor.SendResponse( + ChatMsg( + CMT_GMOPEN, + message.wideContents, + "Server", + "Invalid zone ID", + message.note + ) + ) + } + + case (_, _, contents) if session.admin && contents.startsWith("!kick") => + val input = contents.split("\\s+").drop(1) + if (input.length > 0) { + val numRegex = raw"(\d+)".r + val id = input(0) + val determination: Player => Boolean = id match { + case numRegex(_) => _.CharId == id.toLong + case _ => _.Name.equals(id) + } + session.zone.LivePlayers + .find(determination) + .orElse(session.zone.Corpses.find(determination)) match { + case Some(player) => + input.lift(1) match { + case Some(numRegex(time)) => + sessionActor ! SessionActor.Kick(player, Some(time.toLong)) + case _ => + sessionActor ! SessionActor.Kick(player) + } + case None => + sessionActor ! SessionActor.SendResponse( + ChatMsg( + CMT_GMOPEN, + message.wideContents, + "Server", + "Invalid player", + message.note + ) + ) + } + } + + case (CMT_CAPTUREBASE, _, contents) if session.admin => + val args = contents.split(" ").filter(_ != "") + + val (faction, factionPos) = args.zipWithIndex + .map { case (faction, pos) => (faction.toLowerCase, pos) } + .flatMap { + case ("tr", pos) => Some(PlanetSideEmpire.TR, pos) + case ("nc", pos) => Some(PlanetSideEmpire.NC, pos) + case ("vs", pos) => Some(PlanetSideEmpire.VS, pos) + case ("none", pos) => Some(PlanetSideEmpire.NEUTRAL, pos) + case _ => None + } + .headOption match { + case Some((faction, pos)) => (faction, Some(pos)) + case None => (session.player.Faction, None) + } + + val (buildingsOption, buildingPos) = args.zipWithIndex.flatMap { + case (_, pos) if factionPos.isDefined && factionPos.get == pos => None + case ("all", pos) => + Some( + Some( + session.zone.Buildings + .filter { + case (_, building) => building.CaptureTerminal.isDefined + } + .values + .toSeq + ), + Some(pos) + ) + case (name, pos) => + session.zone.Buildings.find { + case (_, building) => name.equalsIgnoreCase(building.Name) && building.CaptureTerminal.isDefined + } match { + case Some((_, building)) => Some(Some(Seq(building)), Some(pos)) + case None => + try { + // check if we have a timer + name.toInt + None + } catch { + case _: Throwable => + Some(None, Some(pos)) + } + } + }.headOption match { + case Some((buildings, pos)) => (buildings, pos) + case None => (None, None) + } + + val (timerOption, timerPos) = args.zipWithIndex.flatMap { + case (_, pos) + if factionPos.isDefined && factionPos.get == pos || buildingPos.isDefined && buildingPos.get == pos => + None + case (timer, pos) => + try { + val t = timer.toInt // TODO what is the timer format supposed to be? + Some(Some(t), Some(pos)) + } catch { + case _: Throwable => + Some(None, Some(pos)) + } + }.headOption match { + case Some((timer, posOption)) => (timer, posOption) + case None => (None, None) + } + + (factionPos, buildingPos, timerPos, buildingsOption, timerOption) match { + case // [[|none []] + (Some(0), None, Some(1), None, Some(_)) | (Some(0), None, None, None, None) | + (None, None, None, None, None) | + // [ [|none [timer]]] + (None | Some(1), Some(0), None, Some(_), None) | (Some(1), Some(0), Some(2), Some(_), Some(_)) | + // [all [|none]] + (Some(1) | None, Some(0), None, Some(_), None) => + val buildings = buildingsOption.getOrElse( + session.zone.Buildings + .filter { + case (_, building) => + building.PlayersInSOI.exists { soiPlayer => + session.player.CharId == soiPlayer.CharId + } + } + .map { case (_, building) => building } + ) + buildings foreach { building => + // TODO implement timer + building.Faction = faction + session.zone.LocalEvents ! LocalServiceMessage( + session.zone.Id, + LocalAction.SetEmpire(building.GUID, faction) + ) + } + case (_, Some(0), _, None, _) => + sessionActor ! SessionActor.SendResponse( + ChatMsg( + UNK_229, + true, + "", + s"\\#FF4040ERROR - \'${args(0)}\' is not a valid building name.", + None + ) + ) + case (Some(0), _, Some(1), _, None) | (Some(1), Some(0), Some(2), _, None) => + sessionActor ! SessionActor.SendResponse( + ChatMsg( + UNK_229, + true, + "", + s"\\#FF4040ERROR - \'${args(timerPos.get)}\' is not a valid timer value.", + None + ) + ) + case _ => + sessionActor ! SessionActor.SendResponse( + ChatMsg( + UNK_229, + true, + "", + "usage: /capturebase [[|none []] | [ [|none [timer]]] | [all [|none]]", + None + ) + ) + } + + case (CMT_GMBROADCAST | CMT_GMBROADCAST_NC | CMT_GMBROADCAST_VS | CMT_GMBROADCAST_TR, _, _) + if session.admin => + chatService ! ChatService.Message( + session, + message.copy(recipient = session.player.Name), + ChatChannel.Default() + ) + + case (CMT_GMTELL, _, _) if session.admin => + chatService ! ChatService.Message( + session, + message.copy(recipient = session.player.Name), + ChatChannel.Default() + ) + + case (CMT_GMBROADCASTPOPUP, _, _) if session.admin => + chatService ! ChatService.Message( + session, + message.copy(recipient = session.player.Name), + ChatChannel.Default() + ) + + case (_, _, contents) if contents.startsWith("!whitetext ") && session.admin => + chatService ! ChatService.Message( + session, + ChatMsg(UNK_227, true, "", contents.replace("!whitetext ", ""), None), + ChatChannel.Default() + ) + + case (_, "tr", contents) => + sessionActor ! SessionActor.SendResponse( + ZonePopulationUpdateMessage(4, 414, 138, contents.toInt, 138, contents.toInt / 2, 138, 0, 138, 0) + ) + + case (_, "nc", contents) => + sessionActor ! SessionActor.SendResponse( + ZonePopulationUpdateMessage(4, 414, 138, 0, 138, contents.toInt, 138, contents.toInt / 3, 138, 0) + ) + + case (_, "vs", contents) => + sessionActor ! SessionActor.SendResponse( + ZonePopulationUpdateMessage(4, 414, 138, contents.toInt * 2, 138, 0, 138, contents.toInt, 138, 0) + ) + + case (_, "bo", contents) => + sessionActor ! SessionActor.SendResponse( + ZonePopulationUpdateMessage(4, 414, 138, 0, 138, 0, 138, 0, 138, contents.toInt) + ) + + case (_, _, contents) if contents.startsWith("!ntu") && session.admin => + session.zone.Buildings.values.foreach(building => + building.Amenities.foreach(amenity => + amenity.Definition match { + case GlobalDefinitions.resource_silo => + val r = new scala.util.Random + val silo = amenity.asInstanceOf[ResourceSilo] + val ntu: Int = 900 + r.nextInt(100) - silo.ChargeLevel + // val ntu: Int = 0 + r.nextInt(100) - silo.ChargeLevel + silo.Actor ! ResourceSilo.UpdateChargeLevel(ntu) + + case _ => ; + } + ) + ) + + case (CMT_OPEN, _, _) if !session.player.silenced => + chatService ! ChatService.Message( + session, + message.copy(recipient = session.player.Name), + ChatChannel.Default() + ) + + case (CMT_VOICE, _, _) => + chatService ! ChatService.Message( + session, + message.copy(recipient = session.player.Name), + ChatChannel.Default() + ) + + case (CMT_TELL, _, _) if !session.player.silenced => + chatService ! ChatService.Message( + session, + message, + ChatChannel.Default() + ) + + case (CMT_BROADCAST, _, _) if !session.player.silenced => + chatService ! ChatService.Message( + session, + message.copy(recipient = session.player.Name), + ChatChannel.Default() + ) + + case (CMT_PLATOON, _, _) if !session.player.silenced => + chatService ! ChatService.Message( + session, + message.copy(recipient = session.player.Name), + ChatChannel.Default() + ) + + case (CMT_COMMAND, _, _) if session.admin => + chatService ! ChatService.Message( + session, + message.copy(recipient = session.player.Name), + ChatChannel.Default() + ) + + case (CMT_NOTE, _, _) => + chatService ! ChatService.Message(session, message, ChatChannel.Default()) + + case (CMT_SILENCE, _, _) if session.admin => + chatService ! ChatService.Message(session, message, ChatChannel.Default()) + + case (CMT_SQUAD, _, _) => + channels.foreach { + case channel: ChatChannel.Squad => + chatService ! ChatService.Message(session, message.copy(recipient = session.player.Name), channel) + case _ => + } + + case ( + CMT_WHO | CMT_WHO_CSR | CMT_WHO_CR | CMT_WHO_PLATOONLEADERS | CMT_WHO_SQUADLEADERS | CMT_WHO_TEAMS, + _, + _ + ) => + val players = session.zone.Players + val popTR = players.count(_.faction == PlanetSideEmpire.TR) + val popNC = players.count(_.faction == PlanetSideEmpire.NC) + val popVS = players.count(_.faction == PlanetSideEmpire.VS) + val contName = session.zone.Map.Name + + sessionActor ! SessionActor.SendResponse( + ChatMsg(ChatMessageType.CMT_WHO, true, "", "That command doesn't work for now, but : ", None) + ) + sessionActor ! SessionActor.SendResponse( + ChatMsg(ChatMessageType.CMT_WHO, true, "", "NC online : " + popNC + " on " + contName, None) + ) + sessionActor ! SessionActor.SendResponse( + ChatMsg(ChatMessageType.CMT_WHO, true, "", "TR online : " + popTR + " on " + contName, None) + ) + sessionActor ! SessionActor.SendResponse( + ChatMsg(ChatMessageType.CMT_WHO, true, "", "VS online : " + popVS + " on " + contName, None) + ) + + case (CMT_ZONE, _, contents) if session.admin => + val buffer = contents.toLowerCase.split("\\s+") + val (zone, gate, list) = (buffer.lift(0), buffer.lift(1)) match { + case (Some("-list"), None) => + (None, None, true) + case (Some(zoneId), Some("-list")) => + (PointOfInterest.get(zoneId), None, true) + case (Some(zoneId), gateId) => + val zone = PointOfInterest.get(zoneId) + val gate = (zone, gateId) match { + case (Some(zone), Some(gateId)) => PointOfInterest.getWarpgate(zone, gateId) + case (Some(zone), None) => Some(PointOfInterest.selectRandom(zone)) + case _ => None + } + (zone, gate, false) + case _ => + (None, None, false) + } + (zone, gate, list) match { + case (None, None, true) => + sessionActor ! SessionActor.SendResponse(ChatMsg(UNK_229, true, "", PointOfInterest.list, None)) + case (Some(zone), None, true) => + sessionActor ! SessionActor.SendResponse( + ChatMsg(UNK_229, true, "", PointOfInterest.listWarpgates(zone), None) + ) + case (Some(zone), Some(gate), false) => + sessionActor ! SessionActor.SetZone(zone.zonename, gate) + case (_, None, false) => + sessionActor ! SessionActor.SendResponse( + ChatMsg(UNK_229, true, "", "Gate id not defined (use '/zone -list')", None) + ) + case (_, _, _) if buffer.isEmpty || buffer(0).equals("-help") => + sessionActor ! SessionActor.SendResponse( + ChatMsg(UNK_229, true, "", "usage: /zone [gatename] | [-list]", None) + ) + } + + case (CMT_WARP, _, contents) if session.admin => + val buffer = contents.toLowerCase.split("\\s+") + val (coordinates, waypoint) = (buffer.lift(0), buffer.lift(1), buffer.lift(2)) match { + case (Some(x), Some(y), Some(z)) => (Some(x, y, z), None) + case (Some("to"), Some(character), None) => (None, None) // TODO not implemented + case (Some("near"), Some(objectName), None) => (None, None) // TODO not implemented + case (Some(waypoint), None, None) => (None, Some(waypoint)) + case _ => (None, None) + } + (coordinates, waypoint) match { + case (Some((x, y, z)), None) if List(x, y, z).forall { str => + val coordinate = str.toFloatOption + coordinate.isDefined && coordinate.get >= 0 && coordinate.get <= 8191 + } => + sessionActor ! SessionActor.SetPosition(Vector3(x.toFloat, y.toFloat, z.toFloat)) + case (None, Some(waypoint)) if waypoint != "-help" => + PointOfInterest.getWarpLocation(session.zone.Id, waypoint) match { + case Some(location) => sessionActor ! SessionActor.SetPosition(location) + case None => + sessionActor ! SessionActor.SendResponse( + ChatMsg(UNK_229, true, "", s"unknown location '$waypoint", None) + ) + } + case _ => + sessionActor ! SessionActor.SendResponse( + ChatMsg( + UNK_229, + true, + "", + s"usage: /warp OR /warp to OR /warp near OR /warp above OR /warp waypoint", + None + ) + ) + } + + case _ => + log.info("unhandled chat message $message") + } + case (None, _) | (_, None) => + log.error("failed to handle message because dependencies are missing") + } + this + + case IncomingMessage(fromSession, message, channel) => + (session) match { + case Some(session) => + message.messageType match { + case CMT_TELL | U_CMT_TELLFROM | CMT_BROADCAST | CMT_SQUAD | CMT_PLATOON | CMT_COMMAND | UNK_45 | UNK_71 | + CMT_NOTE | CMT_GMBROADCAST | CMT_GMBROADCAST_NC | CMT_GMBROADCAST_TR | CMT_GMBROADCAST_VS | + CMT_GMBROADCASTPOPUP | CMT_GMTELL | U_CMT_GMTELLFROM | UNK_227 => + sessionActor ! SessionActor.SendResponse(message) + case CMT_OPEN => + if ( + session.zone == fromSession.zone && + Vector3.Distance(session.player.Position, fromSession.player.Position) < 25 && + session.player.Faction == fromSession.player.Faction + ) { + sessionActor ! SessionActor.SendResponse(message) + } + case CMT_VOICE => + if ( + session.zone == fromSession.zone && + Vector3.Distance(session.player.Position, fromSession.player.Position) < 25 + ) { + sessionActor ! SessionActor.SendResponse(message) + } + case CMT_SILENCE => + val args = message.contents.split(" ") + val (name, time) = (args.lift(0), args.lift(1)) match { + case (Some(name), _) if name != session.player.Name => + log.error("received silence message for other player") + (None, None) + case (Some(name), None) => (Some(name), Some(5)) + case (Some(name), Some(time)) if time.toIntOption.isDefined => (Some(name), Some(time.toInt)) + case _ => (None, None) + } + (name, time) match { + case (Some(_), Some(time)) => + if (session.player.silenced) { + sessionActor ! SessionActor.SetSilenced(false) + sessionActor ! SessionActor.SendResponse( + ChatMsg(ChatMessageType.UNK_71, true, "", "@silence_off", None) + ) + if (!silenceTimer.isCancelled) silenceTimer.cancel() + } else { + sessionActor ! SessionActor.SetSilenced(true) + sessionActor ! SessionActor.SendResponse( + ChatMsg(ChatMessageType.UNK_71, true, "", "@silence_on", None) + ) + silenceTimer = context.system.scheduler.scheduleOnce( + time minutes, + () => { + sessionActor ! SessionActor.SetSilenced(false) + sessionActor ! SessionActor.SendResponse( + ChatMsg(ChatMessageType.UNK_71, true, "", "@silence_timeout", None) + ) + } + ) + } + + case (name, time) => + log.error(s"bad silence args $name $time") + } + case _ => + log.error(s"unexpected messageType $message") + } + case None => + log.error("failed to handle incoming message because dependencies are missing") + } + this + + } + } + +} diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/common/src/main/scala/net/psforever/actors/session/SessionActor.scala similarity index 92% rename from pslogin/src/main/scala/WorldSessionActor.scala rename to common/src/main/scala/net/psforever/actors/session/SessionActor.scala index f0f9152d..9efdd101 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/common/src/main/scala/net/psforever/actors/session/SessionActor.scala @@ -1,48 +1,24 @@ -package net.psforever.pslogin +package net.psforever.actors.session -import akka.actor.{Actor, ActorRef, Cancellable, MDCContextAware} import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicInteger -import org.log4s.MDC -import scala.collection.mutable.LongMap -import scala.concurrent.{Await, Future} -import scala.concurrent.duration._ -import scala.concurrent.ExecutionContext.Implicits.global -import scala.util.{Success, Failure} -import scodec.bits.ByteVector -import services.properties.PropertyOverrideManager -import org.joda.time.{LocalDateTime, Period} -import csr.{CSRWarp, CSRZone, Traveler} -import MDCContextAware.Implicits._ -import net.psforever.objects._ +import akka.actor.MDCContextAware.Implicits._ +import akka.actor.typed +import akka.actor.{Actor, ActorRef, Cancellable, MDCContextAware} +import net.psforever.objects.{GlobalDefinitions, _} import net.psforever.objects.avatar.{Certification, DeployableToolbox, FirstTimeEvents} -import net.psforever.objects.ballistics.{ - PlayerSource, - Projectile, - ProjectileResolution, - ResolvedProjectile, - SourceEntry -} -import net.psforever.objects.ce.{ - ComplexDeployable, - Deployable, - DeployableCategory, - DeployedItem, - SimpleDeployable, - TelepadLike -} +import net.psforever.objects.ballistics._ +import net.psforever.objects.ce._ import net.psforever.objects.definition._ import net.psforever.objects.definition.converter.{CorpseConverter, DestroyedVehicleConverter} import net.psforever.objects.entity.{SimpleWorldEntity, WorldEntity} import net.psforever.objects.equipment.{EffectTarget, Equipment, FireModeSwitch, JammableUnit} -import net.psforever.objects.GlobalDefinitions import net.psforever.objects.guid.{GUIDTask, Task, TaskResolver} import net.psforever.objects.inventory.{Container, InventoryItem} import net.psforever.objects.loadouts.{InfantryLoadout, SquadLoadout, VehicleLoadout} -import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject} import net.psforever.objects.serverobject.affinity.FactionAffinity -import net.psforever.objects.serverobject.damage.Damageable import net.psforever.objects.serverobject.containable.Containable +import net.psforever.objects.serverobject.damage.Damageable import net.psforever.objects.serverobject.deploy.Deployment import net.psforever.objects.serverobject.doors.Door import net.psforever.objects.serverobject.generator.Generator @@ -51,21 +27,15 @@ import net.psforever.objects.serverobject.implantmech.ImplantTerminalMech import net.psforever.objects.serverobject.locks.IFFLock import net.psforever.objects.serverobject.mblocker.Locker import net.psforever.objects.serverobject.mount.Mountable -import net.psforever.objects.serverobject.pad.{VehicleSpawnControl, VehicleSpawnPad} +import net.psforever.objects.serverobject.pad.VehicleSpawnPad import net.psforever.objects.serverobject.painbox.Painbox import net.psforever.objects.serverobject.resourcesilo.ResourceSilo import net.psforever.objects.serverobject.structures.{Amenity, Building, StructureType, WarpGate} -import net.psforever.objects.serverobject.terminals.{ - CaptureTerminal, - MatrixTerminalDefinition, - MedicalTerminalDefinition, - ProximityDefinition, - ProximityUnit, - Terminal -} +import net.psforever.objects.serverobject.terminals._ import net.psforever.objects.serverobject.tube.SpawnTube -import net.psforever.objects.serverobject.turret.{FacilityTurret, TurretUpgrade, WeaponTurret, WeaponTurrets} +import net.psforever.objects.serverobject.turret.{FacilityTurret, WeaponTurret} import net.psforever.objects.serverobject.zipline.ZipLinePath +import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject} import net.psforever.objects.teamwork.Squad import net.psforever.objects.vehicles.{ AccessPermissionGroup, @@ -77,63 +47,188 @@ import net.psforever.objects.vehicles.{ VehicleLockState } import net.psforever.objects.vehicles.Utility.InternalTelepad -import net.psforever.objects.vital.{ - DamageFromPainbox, - HealFromKit, - HealFromTerm, - PlayerSuicide, - RepairFromKit, - Vitality -} +import net.psforever.objects.vehicles._ +import net.psforever.objects.vital._ import net.psforever.objects.zones.{InterstellarCluster, Zone, ZoneHotSpotProjector, Zoning} import net.psforever.packet._ import net.psforever.packet.control._ -import net.psforever.packet.game._ -import net.psforever.packet.game.objectcreate.{ - DetailedCharacterData, - DroppedItemData, - ObjectClass, - ObjectCreateMessageParent, - PlacementData -} -import net.psforever.packet.game.{HotSpotInfo => PacketHotSpotInfo} +import net.psforever.packet.game.objectcreate._ +import net.psforever.packet.game.{HotSpotInfo => PacketHotSpotInfo, _} import net.psforever.persistence import net.psforever.types._ -import services.{RemoverActor, Service, ServiceManager} +import org.joda.time.{LocalDateTime, Period} +import org.log4s.MDC +import scodec.bits.ByteVector +import services.ServiceManager.LookupResult import services.account.{AccountPersistenceService, PlayerToken, ReceiveAccountData, RetrieveAccountData} import services.avatar.{AvatarAction, AvatarResponse, AvatarServiceMessage, AvatarServiceResponse} -import services.chat.{ChatAction, ChatServiceMessage, ChatServiceResponse} import services.galaxy.{GalaxyAction, GalaxyResponse, GalaxyServiceMessage, GalaxyServiceResponse} -import services.local.{LocalAction, LocalResponse, LocalServiceMessage, LocalServiceResponse} import services.local.support.RouterTelepadActivation -import services.ServiceManager.LookupResult +import services.local.{LocalAction, LocalResponse, LocalServiceMessage, LocalServiceResponse} +import services.properties.PropertyOverrideManager import services.support.SupportActor import services.teamwork.{SquadResponse, SquadServiceMessage, SquadServiceResponse, SquadAction => SquadServiceAction} import services.vehicle.{VehicleAction, VehicleResponse, VehicleServiceMessage, VehicleServiceResponse} -import Database._ +import services.{RemoverActor, Service, ServiceManager} +import net.psforever.login.{DropCryptoSession, DropSession, HelloFriend, RawPacket} +import net.psforever.util.Config +import net.psforever.util.Database._ +import net.psforever.login.WorldSession._ +import net.psforever.zones.Zones +import services.chat.ChatService +import scala.collection.mutable.LongMap +import scala.concurrent.ExecutionContext.Implicits.global +import scala.concurrent.duration._ +import scala.concurrent.{Await, Future} +import scala.util.{Failure, Success} +import akka.actor.typed.scaladsl.adapter._ -class WorldSessionActor extends Actor with MDCContextAware { +object SessionActor { - import WorldSessionActor._ - import WorldSession._ + /** Object purchasing cooldowns.
+ * key - object id
+ * value - time last used (ms) + */ + val delayedPurchaseEntries: Map[Int, Long] = Map( + GlobalDefinitions.ams.ObjectId -> 300000, //5min + GlobalDefinitions.ant.ObjectId -> 300000, //5min + GlobalDefinitions.apc_nc.ObjectId -> 300000, //5min + GlobalDefinitions.apc_tr.ObjectId -> 300000, //5min + GlobalDefinitions.apc_vs.ObjectId -> 300000, //5min + GlobalDefinitions.aurora.ObjectId -> 300000, //5min + GlobalDefinitions.battlewagon.ObjectId -> 300000, //5min + GlobalDefinitions.dropship.ObjectId -> 300000, //5min + GlobalDefinitions.flail.ObjectId -> 300000, //5min + GlobalDefinitions.fury.ObjectId -> 300000, //5min + GlobalDefinitions.galaxy_gunship.ObjectId -> 600000, //10min + GlobalDefinitions.lodestar.ObjectId -> 300000, //5min + GlobalDefinitions.liberator.ObjectId -> 300000, //5min + GlobalDefinitions.lightgunship.ObjectId -> 300000, //5min + GlobalDefinitions.lightning.ObjectId -> 300000, //5min + GlobalDefinitions.magrider.ObjectId -> 300000, //5min + GlobalDefinitions.mediumtransport.ObjectId -> 300000, //5min + GlobalDefinitions.mosquito.ObjectId -> 300000, //5min + GlobalDefinitions.phantasm.ObjectId -> 300000, //5min + GlobalDefinitions.prowler.ObjectId -> 300000, //5min + GlobalDefinitions.quadassault.ObjectId -> 300000, //5min + GlobalDefinitions.quadstealth.ObjectId -> 300000, //5min + GlobalDefinitions.router.ObjectId -> 300000, //5min + GlobalDefinitions.switchblade.ObjectId -> 300000, //5min + GlobalDefinitions.skyguard.ObjectId -> 300000, //5min + GlobalDefinitions.threemanheavybuggy.ObjectId -> 300000, //5min + GlobalDefinitions.thunderer.ObjectId -> 300000, //5min + GlobalDefinitions.two_man_assault_buggy.ObjectId -> 300000, //5min + GlobalDefinitions.twomanhoverbuggy.ObjectId -> 300000, //5min + GlobalDefinitions.twomanheavybuggy.ObjectId -> 300000, //5min + GlobalDefinitions.vanguard.ObjectId -> 300000, //5min + GlobalDefinitions.vulture.ObjectId -> 300000, //5min + GlobalDefinitions.wasp.ObjectId -> 300000, //5min + GlobalDefinitions.flamethrower.ObjectId -> 180000 //3min + ) + + /** Object use cooldowns.
+ * key - object id
+ * value - time last used (ms) + */ + val delayedGratificationEntries: Map[Int, Long] = Map( + GlobalDefinitions.medkit.ObjectId -> 5000, //5s + GlobalDefinitions.super_armorkit.ObjectId -> 1200000, //20min + GlobalDefinitions.super_medkit.ObjectId -> 1200000, //20min + GlobalDefinitions.super_staminakit.ObjectId -> 1200000 //20min + ) + + sealed trait Command + + final case class ResponseToSelf(pkt: PlanetSideGamePacket) + + 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 CreateCharacter( + name: String, + head: Int, + voice: CharacterVoice.Value, + gender: CharacterGender.Value, + empire: PlanetSideEmpire.Value + ) + private final case class ListAccountCharacters() + private final case class SetCurrentAvatar(tplayer: Player, max_attempts: Int, attempt: Int = 0) + private final case class ZoningReset() + + final case class SendResponse(packet: PlanetSidePacket) extends Command + final case class SetSpeed(speed: Float) extends Command + final case class SetFlying(flying: Boolean) extends Command + final case class SetSpectator(spectator: Boolean) extends Command + final case class SetZone(zoneId: String, position: Vector3) extends Command + final case class SetPosition(position: Vector3) extends Command + final case class SetConnectionState(connectionState: Int) extends Command + final case class SetSilenced(silenced: Boolean) extends Command + final case class Recall(zoneId: String) extends Command + final case class InstantAction() extends Command + final case class Quit() extends Command + final case class Suicide() extends Command + final case class Kick(player: Player, time: Option[Long] = None) extends Command + + final val ftes = ( + FirstTimeEvents.Maps ++ FirstTimeEvents.Monoliths ++ + FirstTimeEvents.Standard.All ++ FirstTimeEvents.Cavern.All ++ + FirstTimeEvents.TR.All ++ FirstTimeEvents.NC.All ++ FirstTimeEvents.VS.All ++ + FirstTimeEvents.Generic + ).toList + + /** + * The message that progresses some form of user-driven activity with a certain eventual outcome + * and potential feedback per cycle. + * @param delta how much the progress value changes each tick, which will be treated as a percentage; + * must be a positive value + * @param completionAction a finalizing action performed once the progress reaches 100(%) + * @param tickAction an action that is performed for each increase of progress + */ + final case class ProgressEvent(delta: Float, completionAction: () => Unit, tickAction: Float => Boolean) + + private final val zoningCountdownMessages: Seq[Int] = Seq(5, 10, 20) + + 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) + + private final case class FinalizeDeployable( + obj: PlanetSideGameObject with Deployable, + tool: ConstructionItem, + index: Int + ) + + private final case class LoadedRemoteProjectile(projectile_guid: PlanetSideGUID, projectile: Option[Projectile]) +} + +class SessionActor extends Actor with MDCContextAware { + + import SessionActor._ private[this] val log = org.log4s.getLogger private[this] val damageLog = org.log4s.getLogger(Damageable.LogChannel) - var sessionId: Long = 0 var leftRef: ActorRef = ActorRef.noSender var rightRef: ActorRef = ActorRef.noSender + var chatActor: typed.ActorRef[ChatActor.Command] = context.spawn(ChatActor(context.self), "chatActor") var accountIntermediary: ActorRef = ActorRef.noSender var accountPersistence: ActorRef = ActorRef.noSender - var chatService: ActorRef = ActorRef.noSender var galaxyService: ActorRef = ActorRef.noSender var squadService: ActorRef = ActorRef.noSender var taskResolver: ActorRef = Actor.noSender var propertyOverrideManager: ActorRef = Actor.noSender var cluster: ActorRef = Actor.noSender - var continent: Zone = Zone.Nowhere - var account: Account = null - var player: Player = null - var avatar: Avatar = null + var _session: Session = Session() var progressBarValue: Option[Float] = None var shooting: Option[PlanetSideGUID] = None //ChangeFireStateMessage_Start var prefire: Option[PlanetSideGUID] = None //if WeaponFireMessage precedes ChangeFireStateMessage_Start @@ -141,16 +236,12 @@ class WorldSessionActor extends Actor with MDCContextAware { var accessedContainer: Option[PlanetSideGameObject with Container] = None var connectionState: Int = 25 var flying: Boolean = false - var speed: Float = 1.0f - var admin: Boolean = false var loadConfZone: Boolean = false var noSpawnPointHere: Boolean = false var usingMedicalTerminal: Option[PlanetSideGUID] = None var controlled: Option[Int] = None - //keep track of avatar's ServerVehicleOverride state - var traveler: Traveler = null - var deadState: DeadState.Value = DeadState.Dead - var whenUsedLastMAXName: Array[String] = Array.fill[String](4)("") + var deadState: DeadState.Value = DeadState.Dead + var whenUsedLastMAXName: Array[String] = Array.fill[String](4)("") val projectiles: Array[Option[Projectile]] = Array.fill[Option[Projectile]](Projectile.RangeUID - Projectile.BaseUID)(None) val projectilesToCleanUp: Array[Boolean] = Array.fill[Boolean](Projectile.RangeUID - Projectile.BaseUID)(false) @@ -192,7 +283,6 @@ class WorldSessionActor extends Actor with MDCContextAware { * 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) @@ -237,13 +327,23 @@ class WorldSessionActor extends Actor with MDCContextAware { var zoningTimer: Cancellable = Default.Cancellable var zoningReset: Cancellable = Default.Cancellable + def session = _session + def session_=(session: Session) = { + chatActor ! ChatActor.SetSession(session) + _session = session + } + + def account: Account = _session.account + def continent: Zone = _session.zone // TODO continent -> zone + def player: Player = _session.player + def avatar: Avatar = _session.avatar + override def postStop(): Unit = { //normally, the player avatar persists a minute or so after disconnect; we are subject to the SessionReaper clientKeepAlive.cancel progressBarUpdate.cancel reviveTimer.cancel respawnTimer.cancel - chatService ! Service.Leave() galaxyService ! Service.Leave() continent.AvatarEvents ! Service.Leave() continent.LocalEvents ! Service.Leave() @@ -268,11 +368,11 @@ class WorldSessionActor extends Actor with MDCContextAware { def Initializing: Receive = { case HelloFriend(inSessionId, pipe) => - this.sessionId = inSessionId + session = session.copy(id = inSessionId) leftRef = sender() if (pipe.hasNext) { rightRef = pipe.next - rightRef !> HelloFriend(sessionId, pipe) + rightRef !> HelloFriend(session.id, pipe) } else { rightRef = sender() } @@ -281,7 +381,6 @@ class WorldSessionActor extends Actor with MDCContextAware { val serviceManager = ServiceManager.serviceManager serviceManager ! Lookup("accountIntermediary") serviceManager ! Lookup("accountPersistence") - serviceManager ! Lookup("chat") serviceManager ! Lookup("taskResolver") serviceManager ! Lookup("cluster") serviceManager ! Lookup("galaxy") @@ -313,28 +412,25 @@ class WorldSessionActor extends Actor with MDCContextAware { def Started: Receive = { case LookupResult("accountIntermediary", endpoint) => accountIntermediary = endpoint - log.info("ID: " + sessionId + " Got account intermediary service " + endpoint) + log.info("ID: " + session.id + " Got account intermediary service " + endpoint) case LookupResult("accountPersistence", endpoint) => accountPersistence = endpoint - log.info("ID: " + sessionId + " Got account persistence service " + endpoint) - case LookupResult("chat", endpoint) => - chatService = endpoint - log.info("ID: " + sessionId + " Got chat service " + endpoint) + log.info("ID: " + session.id + " Got account persistence service " + endpoint) case LookupResult("taskResolver", endpoint) => taskResolver = endpoint - log.info("ID: " + sessionId + " Got task resolver service " + endpoint) + log.info("ID: " + session.id + " Got task resolver service " + endpoint) case LookupResult("galaxy", endpoint) => galaxyService = endpoint - log.info("ID: " + sessionId + " Got galaxy service " + endpoint) + log.info("ID: " + session.id + " Got galaxy service " + endpoint) case LookupResult("cluster", endpoint) => cluster = endpoint - log.info("ID: " + sessionId + " Got cluster service " + endpoint) + log.info("ID: " + session.id + " Got cluster service " + endpoint) case LookupResult("squad", endpoint) => squadService = endpoint - log.info("ID: " + sessionId + " Got squad service " + endpoint) + log.info("ID: " + session.id + " Got squad service " + endpoint) case LookupResult("propertyOverrideManager", endpoint) => propertyOverrideManager = endpoint - log.info("ID: " + sessionId + " Got propertyOverrideManager service " + endpoint) + log.info("ID: " + session.id + " Got propertyOverrideManager service " + endpoint) case ControlPacket(_, ctrl) => handleControlPkt(ctrl) @@ -347,6 +443,93 @@ class WorldSessionActor extends Actor with MDCContextAware { case AvatarServiceResponse(toChannel, guid, reply) => HandleAvatarServiceResponse(toChannel, guid, reply) + case SendResponse(packet) => + sendResponse(packet) + + case SetSpeed(speed) => + session = session.copy(speed = speed) + + case SetFlying(flying) => + session = session.copy(flying = flying) + + case SetSpectator(spectator) => + session.player.spectator = spectator + + case Recall(zoneId) => + zoningType = Zoning.Method.Recall + zoningChatMessageType = ChatMessageType.CMT_RECALL + zoningStatus = Zoning.Status.Request + zoningReset = context.system.scheduler.scheduleOnce(10 seconds, self, ZoningReset()) + cluster ! Zoning.Recall.Request(player.Faction, zoneId) + + case InstantAction() => + zoningType = Zoning.Method.InstantAction + zoningChatMessageType = ChatMessageType.CMT_INSTANTACTION + zoningStatus = Zoning.Status.Request + zoningReset = context.system.scheduler.scheduleOnce(10 seconds, self, ZoningReset()) + cluster ! Zoning.InstantAction.Request(player.Faction) + + case Quit() => + //priority to quitting is given to quit over other zoning methods + if (session.zoningType == Zoning.Method.InstantAction || session.zoningType == Zoning.Method.Recall) { + CancelZoningProcessWithDescriptiveReason("cancel") + } + zoningType = Zoning.Method.Quit + zoningChatMessageType = ChatMessageType.CMT_QUIT + zoningStatus = Zoning.Status.Request + self ! Zoning.Quit() + + case Suicide() => + suicide(player) + + case Kick(player, time) => + AdministrativeKick(player) + accountPersistence ! AccountPersistenceService.Kick(player.Name, time) + + case SetZone(zoneId, position) => + PlayerActionsToCancel() + continent.GUID(player.VehicleSeated) match { + case Some(vehicle: Vehicle) if vehicle.MountedIn.isEmpty => + vehicle.PassengerInSeat(player) match { + case Some(0) => + deadState = DeadState.Release // cancel movement updates + vehicle.Position = position + LoadZonePhysicalSpawnPoint(zoneId, position, Vector3.Zero, 0) + case _ => // not seated as the driver, in which case we can't move + } + case None => + deadState = DeadState.Release // cancel movement updates + player.Position = position + // continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.ObjectDelete(player.GUID, player.GUID)) + LoadZonePhysicalSpawnPoint(zoneId, position, Vector3.Zero, 0) + case _ => // seated in something that is not a vehicle or the vehicle is cargo, in which case we can't move + } + + case SetPosition(position) => + PlayerActionsToCancel() + continent.GUID(player.VehicleSeated) match { + case Some(vehicle: Vehicle) if vehicle.MountedIn.isEmpty => + vehicle.PassengerInSeat(player) match { + case Some(0) => + deadState = DeadState.Release // cancel movement updates + vehicle.Position = position + LoadZonePhysicalSpawnPoint(continent.Id, position, Vector3.z(vehicle.Orientation.z), 0) + case _ => // not seated as the driver, in which case we can't move + } + case None => + deadState = DeadState.Release // cancel movement updates + player.Position = position + sendResponse(PlayerStateShiftMessage(ShiftState(0, position, player.Orientation.z, None))) + deadState = DeadState.Alive // must be set here + case _ => // seated in something that is not a vehicle or the vehicle is cargo, in which case we can't move + } + + case SetConnectionState(state) => + connectionState = state + + case SetSilenced(silenced) => + player.silenced = silenced + case CommonMessages.Progress(rate, finishedAction, stepAction) => if (progressBarValue.isEmpty) { progressBarValue = Some(-rate) @@ -418,9 +601,6 @@ class WorldSessionActor extends Actor with MDCContextAware { case LocalServiceResponse(toChannel, guid, reply) => HandleLocalServiceResponse(toChannel, guid, reply) - case ChatServiceResponse(toChannel, guid, avatar_name, cont, avatar_pos, avatar_faction, target, reply) => - HandleChatServiceResponse(toChannel, guid, avatar_name, cont, avatar_pos, avatar_faction, target, reply) - case Mountable.MountMessages(tplayer, reply) => HandleMountMessages(tplayer, reply) @@ -557,8 +737,7 @@ class WorldSessionActor extends Actor with MDCContextAware { //a finalization? what does this do? sendResponse(SquadDefinitionActionMessage(squad.GUID, 0, SquadAction.Unknown(18))) updateSquad = PeriodicUpdatesWhenEnrolledInSquad - squadChannel = Some(toChannel) - chatService ! Service.Join(squadChannel.get) + chatActor ! ChatActor.JoinChannel(ChatService.ChatChannel.Squad(squad.GUID)) case _ => //other player is joining our squad //load each member's entry @@ -630,8 +809,7 @@ class WorldSessionActor extends Actor with MDCContextAware { squad_supplement_id = 0 squadUpdateCounter = 0 updateSquad = NoSquadUpdates - chatService ! Service.Leave(squadChannel) - squadChannel = None + chatActor ! ChatActor.LeaveChannel(ChatService.ChatChannel.Squad(squad.GUID)) case _ => //remove each member's entry GiveSquadColorsInZone( @@ -782,7 +960,6 @@ class WorldSessionActor extends Actor with MDCContextAware { case CreateCharacter(name, head, voice, gender, empire) => import ctx._ log.info(s"Creating new character $name") - val result = ctx.run( query[persistence.Character].insert( _.name -> lift(name), @@ -946,7 +1123,9 @@ class WorldSessionActor extends Actor with MDCContextAware { ) reviveTimer.cancel if (spawn_group == 2) { - sendResponse(ChatMsg(ChatMessageType.CMT_OPEN, false, "", "No friendly AMS is deployed in this region.", None)) + sendResponse( + ChatMsg(ChatMessageType.CMT_OPEN, false, "", "No friendly AMS is deployed in this region.", None) + ) cluster ! Zone.Lattice.RequestSpawnPoint(zone_number, player, 0) } else { RequestSanctuaryZoneSpawn(player, zone_number) @@ -987,7 +1166,7 @@ class WorldSessionActor extends Actor with MDCContextAware { context.system.scheduler.scheduleOnce( obj.Definition.DeployTime milliseconds, self, - WorldSessionActor.FinalizeDeployable(obj, tool, index) + SessionActor.FinalizeDeployable(obj, tool, index) ) } else { TryDropFDU(tool, index, obj.Position) @@ -997,7 +1176,7 @@ class WorldSessionActor extends Actor with MDCContextAware { continent.Deployables ! Zone.Deployable.Dismiss(obj) } - case WorldSessionActor.FinalizeDeployable(obj: SensorDeployable, tool, index) => + case SessionActor.FinalizeDeployable(obj: SensorDeployable, tool, index) => //motion alarm sensor and sensor disruptor StartBundlingPackets() DeployableBuildActivity(obj) @@ -1009,7 +1188,7 @@ class WorldSessionActor extends Actor with MDCContextAware { FindReplacementConstructionItem(tool, index) StopBundlingPackets() - case WorldSessionActor.FinalizeDeployable(obj: BoomerDeployable, tool, index) => + case SessionActor.FinalizeDeployable(obj: BoomerDeployable, tool, index) => //boomers StartBundlingPackets() DeployableBuildActivity(obj) @@ -1030,7 +1209,7 @@ class WorldSessionActor extends Actor with MDCContextAware { } StopBundlingPackets() - case WorldSessionActor.FinalizeDeployable(obj: ExplosiveDeployable, tool, index) => + case SessionActor.FinalizeDeployable(obj: ExplosiveDeployable, tool, index) => //mines StartBundlingPackets() DeployableBuildActivity(obj) @@ -1038,7 +1217,7 @@ class WorldSessionActor extends Actor with MDCContextAware { FindReplacementConstructionItem(tool, index) StopBundlingPackets() - case WorldSessionActor.FinalizeDeployable(obj: ComplexDeployable, tool, index) => + case SessionActor.FinalizeDeployable(obj: ComplexDeployable, tool, index) => //tank_traps, spitfires, deployable field turrets and the deployable_shield_generator StartBundlingPackets() DeployableBuildActivity(obj) @@ -1046,7 +1225,7 @@ class WorldSessionActor extends Actor with MDCContextAware { FindReplacementConstructionItem(tool, index) StopBundlingPackets() - case WorldSessionActor.FinalizeDeployable(obj: TelepadDeployable, tool, index) => + case SessionActor.FinalizeDeployable(obj: TelepadDeployable, tool, index) => StartBundlingPackets() if (obj.Health > 0) { val guid = obj.GUID @@ -1082,7 +1261,7 @@ class WorldSessionActor extends Actor with MDCContextAware { } StopBundlingPackets() - case WorldSessionActor.FinalizeDeployable(obj: PlanetSideGameObject with Deployable, tool, index) => + case SessionActor.FinalizeDeployable(obj: PlanetSideGameObject with Deployable, tool, index) => val guid = obj.GUID val definition = obj.Definition StartBundlingPackets() @@ -1107,7 +1286,6 @@ class WorldSessionActor extends Actor with MDCContextAware { case InterstellarCluster.ClientInitializationComplete() => LivePlayerList.Add(avatar.CharId, avatar) - traveler = new Traveler(self, continent.Id) StartBundlingPackets() //PropertyOverrideMessage @@ -1125,14 +1303,6 @@ class WorldSessionActor extends Actor with MDCContextAware { sendResponse(FriendsResponse(FriendAction.InitializeFriendList, 0, true, true, Nil)) sendResponse(FriendsResponse(FriendAction.InitializeIgnoreList, 0, true, true, Nil)) //the following subscriptions last until character switch/logout - chatService ! Service.Join("local") - chatService ! Service.Join("squad") - chatService ! Service.Join("platoon") - chatService ! Service.Join("voice") - chatService ! Service.Join("tell") - chatService ! Service.Join("broadcast") - chatService ! Service.Join("note") - chatService ! Service.Join("gm") 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 @@ -1144,7 +1314,7 @@ class WorldSessionActor extends Actor with MDCContextAware { log.info(s"Zone ${zone.Id} will now load") loadConfZone = true val oldZone = continent - continent = zone + session = session.copy(zone = zone) //the only zone-level event system subscription necessary before BeginZoningMessage (for persistence purposes) continent.AvatarEvents ! Service.Join(player.Name) persist() @@ -1163,8 +1333,8 @@ class WorldSessionActor extends Actor with MDCContextAware { case InterstellarCluster.GiveWorld(zoneId, zone) => log.info(s"Zone $zoneId will now load") loadConfZone = true - val oldZone = continent - continent = zone + val oldZone = session.zone + session = session.copy(zone = zone) //the only zone-level event system subscription necessary before BeginZoningMessage (for persistence purposes) continent.AvatarEvents ! Service.Join(player.Name) persist() @@ -1182,8 +1352,8 @@ class WorldSessionActor extends Actor with MDCContextAware { case msg @ Zoning.InstantAction.Located(zone, _, spawn_point) => //in between subsequent reply messages, it does not matter if the destination changes //so long as there is at least one destination at all (including the fallback) - if (ContemplateZoningResponse(Zoning.InstantAction.Request(player.Faction), cluster)) { - val (pos, ori) = spawn_point.SpecificPoint(player) + if (ContemplateZoningResponse(Zoning.InstantAction.Request(session.player.Faction), cluster)) { + val (pos, ori) = spawn_point.SpecificPoint(session.player) SpawnThroughZoningProcess(zone, pos, ori) } else if (zoningStatus != Zoning.Status.None) { instantActionFallbackDestination = Some(msg) @@ -1192,9 +1362,9 @@ class WorldSessionActor extends Actor with MDCContextAware { case Zoning.InstantAction.NotLocated() => instantActionFallbackDestination match { case Some(Zoning.InstantAction.Located(zone, _, spawn_point)) - if spawn_point.Owner.Faction == player.Faction && !spawn_point.Offline => - if (ContemplateZoningResponse(Zoning.InstantAction.Request(player.Faction), cluster)) { - val (pos, ori) = spawn_point.SpecificPoint(player) + if spawn_point.Owner.Faction == session.player.Faction && !spawn_point.Offline => + if (ContemplateZoningResponse(Zoning.InstantAction.Request(session.player.Faction), cluster)) { + val (pos, ori) = spawn_point.SpecificPoint(session.player) SpawnThroughZoningProcess(zone, pos, ori) } else if (zoningCounter == 0) { CancelZoningProcessWithReason("@InstantActionNoHotspotsAvailable") @@ -1205,8 +1375,8 @@ class WorldSessionActor extends Actor with MDCContextAware { } case Zoning.Recall.Located(zone, spawn_point) => - if (ContemplateZoningResponse(Zoning.Recall.Request(player.Faction, zone.Id), cluster)) { - val (pos, ori) = spawn_point.SpecificPoint(player) + if (ContemplateZoningResponse(Zoning.Recall.Request(session.player.Faction, zone.Id), cluster)) { + val (pos, ori) = spawn_point.SpecificPoint(session.player) SpawnThroughZoningProcess(zone, pos, ori) } @@ -1225,12 +1395,21 @@ class WorldSessionActor extends Actor with MDCContextAware { case NewPlayerLoaded(tplayer) => //new zone log.info(s"Player ${tplayer.Name} has been loaded") + session = session.copy(player = tplayer) //LoadMapMessage causes the client to send BeginZoningMessage, eventually leading to SetCurrentAvatar val weaponsEnabled = - (continent.Map.Name != "map11" && continent.Map.Name != "map12" && continent.Map.Name != "map13") - sendResponse(LoadMapMessage(continent.Map.Name, continent.Id, 40100, 25, weaponsEnabled, continent.Map.Checksum)) + (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 + ) + ) //important! the LoadMapMessage must be processed by the client before the avatar is created - player = tplayer setupAvatarFunc() //interimUngunnedVehicle should have been setup by setupAvatarFunc, if it is applicable turnCounterFunc = interimUngunnedVehicle match { @@ -1247,7 +1426,7 @@ class WorldSessionActor extends Actor with MDCContextAware { case PlayerLoaded(tplayer) => //same zone log.info(s"Player ${tplayer.Name} will respawn") - player = tplayer + session = session.copy(player = tplayer) setupAvatarFunc() //interimUngunnedVehicle should have been setup by setupAvatarFunc, if it is applicable turnCounterFunc = interimUngunnedVehicle match { @@ -1363,8 +1542,7 @@ class WorldSessionActor extends Actor with MDCContextAware { case ReceiveAccountData(account) => import ctx._ log.info(s"Received account data for accountId = ${account.AccountId}") - this.account = account - admin = account.GM + session = session.copy(account = account, admin = account.GM) ctx.run(query[persistence.Account].filter(_.id == lift(account.AccountId)).map(_.id)).onComplete { case Success(accounts) => accounts.headOption match { @@ -1373,7 +1551,7 @@ class WorldSessionActor extends Actor with MDCContextAware { self ! ListAccountCharacters() case None => log.error(s"ReceiveAccountData: ${account.Username} data not found") - sendResponse(DropSession(sessionId, "You should not exist!")) + sendResponse(DropSession(session.id, "You should not exist!")) } case Failure(e) => log.error(s"ReceiveAccountData: ${e.getMessage}") @@ -1470,7 +1648,7 @@ class WorldSessionActor extends Actor with MDCContextAware { avatar.Implants(2).Unlocked = true avatar.Implants(2).Implant = GlobalDefinitions.targeting - player = new Player(avatar) + session = session.copy(player = new Player(avatar)) //xy-coordinates indicate sanctuary spawn bias: player.Position = math.abs(scala.util.Random.nextInt() % avatar.name.hashCode % 4) match { case 0 => Vector3(8192, 8192, 0) //NE @@ -1502,8 +1680,7 @@ class WorldSessionActor extends Actor with MDCContextAware { //rejoin current avatar/player log.info(s"LoginInfo: player $playerName is alive") deadState = DeadState.Alive - avatar = a - player = p + session = session.copy(avatar = a, player = p) persist() setupAvatarFunc = AvatarRejoin UpdateLoginTimeThenDoClientInitialization() @@ -1512,8 +1689,7 @@ class WorldSessionActor extends Actor with MDCContextAware { //convert player to a corpse (unless in vehicle); automatic recall to closest spawn point log.info(s"LoginInfo: player $playerName is dead") deadState = DeadState.Dead - avatar = a - player = p + session = session.copy(avatar = a, player = p) persist() player.Zone = inZone HandleReleaseAvatar(p, inZone) @@ -1523,17 +1699,19 @@ class WorldSessionActor extends Actor with MDCContextAware { //respawn avatar as a new player; automatic recall to closest spawn point log.info(s"LoginInfo: player $playerName had released recently") deadState = DeadState.RespawnTime - avatar = a - player = inZone.Corpses.findLast(c => c.Name == playerName) match { - case Some(c) => - c //the last corpse of this user should be where they died - case None => - val tplayer = Player(a) //throwaway - tplayer.Position = pos - tplayer.Release //for proper respawn - tplayer.Zone = inZone - tplayer - } + session = session.copy( + avatar = a, + player = inZone.Corpses.findLast(c => c.Name == playerName) match { + case Some(c) => + c //the last corpse of this user should be where they died + case None => + val tplayer = Player(a) //throwaway + tplayer.Position = pos + tplayer.Release //for proper respawn + tplayer.Zone = inZone + tplayer + } + ) UpdateLoginTimeThenDoClientInitialization() case _ => @@ -2521,130 +2699,6 @@ class WorldSessionActor extends Actor with MDCContextAware { } } - /** - * na - * @param toChannel na - * @param avatar_guid na - * @param target na - * @param reply na - */ - def HandleChatServiceResponse( - toChannel: String, - avatar_guid: PlanetSideGUID, - avatar_name: String, - cont: Zone, - avatar_pos: Vector3, - avatar_faction: PlanetSideEmpire.Value, - target: Int, - reply: ChatMsg - ): Unit = { - val tplayer_guid = - if (player.HasGUID) player.GUID - else PlanetSideGUID(0) - target match { - case 0 => // for other(s) user(s) - if (player.GUID != avatar_guid) { - reply.messageType match { - case ChatMessageType.CMT_TELL => - if (player.Name.equalsIgnoreCase(reply.recipient)) { - sendResponse(ChatMsg(reply.messageType, reply.wideContents, avatar_name, reply.contents, reply.note)) - } - case ChatMessageType.CMT_SILENCE => - val args = avatar_name.split(" ") - var silence_name: String = "" - var silence_time: Int = 5 - if (args.length == 1) { - silence_name = args(0) - } else if (args.length == 2) { - silence_name = args(0) - silence_time = args(1).toInt - } - if (player.Name == args(0)) { - if (!player.silenced) { - sendResponse( - ChatMsg(ChatMessageType.UNK_71, reply.wideContents, reply.recipient, "@silence_on", reply.note) - ) - player.silenced = true - context.system.scheduler.scheduleOnce( - silence_time minutes, - chatService, - ChatServiceMessage( - "gm", - ChatAction.GM( - PlanetSideGUID(0), - player.Name, - ChatMsg(ChatMessageType.CMT_SILENCE, true, "", player.Name, None) - ) - ) - ) - } else { - sendResponse( - ChatMsg(ChatMessageType.UNK_71, reply.wideContents, reply.recipient, "@silence_off", reply.note) - ) - player.silenced = false - } - } - case _ => - sendResponse(ChatMsg(reply.messageType, reply.wideContents, reply.recipient, reply.contents, reply.note)) - } - } - case 1 => // for player - if (player.Name == avatar_name) { - if ( - (reply.contents.length > 1 && (reply.contents.dropRight(reply.contents.length - 1) != "!" || reply.contents - .drop(1) - .dropRight(reply.contents.length - 2) == "!")) || reply.contents.length == 1 - ) { - sendResponse(ChatMsg(reply.messageType, reply.wideContents, reply.recipient, reply.contents, reply.note)) - } - } - case 2 => // both case - if ( - (reply.contents.length > 1 && (reply.contents.dropRight(reply.contents.length - 1) != "!" || reply.contents - .drop(1) - .dropRight(reply.contents.length - 2) == "!")) || reply.contents.length == 1 - ) { - reply.messageType match { - case ChatMessageType.CMT_OPEN => - if ( - Vector3.Distance( - player.Position, - avatar_pos - ) < 25 && player.Faction == avatar_faction && player.Continent == cont.Id - ) { - sendResponse( - ChatMsg(reply.messageType, reply.wideContents, reply.recipient, reply.contents, reply.note) - ) - } - case ChatMessageType.CMT_COMMAND => - sendResponse(ChatMsg(reply.messageType, reply.wideContents, reply.recipient, reply.contents, reply.note)) - case ChatMessageType.CMT_SQUAD => - if (squadChannel.nonEmpty) { - if ("/Chat/" + squadChannel.get == toChannel) { - sendResponse( - ChatMsg(reply.messageType, reply.wideContents, reply.recipient, reply.contents, reply.note) - ) - } - } - case ChatMessageType.CMT_PLATOON => - if (player.Faction == avatar_faction) { - sendResponse( - ChatMsg(reply.messageType, reply.wideContents, reply.recipient, reply.contents, reply.note) - ) - } - case ChatMessageType.CMT_VOICE => - if (Vector3.Distance(player.Position, avatar_pos) < 25 && player.Continent == cont.Id) { - sendResponse( - ChatMsg(reply.messageType, reply.wideContents, reply.recipient, reply.contents, reply.note) - ) - } - case _ => - sendResponse(ChatMsg(reply.messageType, reply.wideContents, reply.recipient, reply.contents, reply.note)) - } - } - } - } - /** * na * @param tplayer na @@ -2740,7 +2794,9 @@ class WorldSessionActor extends Actor with MDCContextAware { case Mountable.CanNotMount(obj: Vehicle, seat_num) => log.warn(s"MountVehicleMsg: ${tplayer.Name} attempted to mount $obj's seat $seat_num, but was not allowed") if (obj.SeatPermissionGroup(seat_num).contains(AccessPermissionGroup.Driver)) { - sendResponse(ChatMsg(ChatMessageType.CMT_OPEN, false, "", "You are not the driver of this vehicle.", None)) + sendResponse( + ChatMsg(ChatMessageType.CMT_OPEN, false, "", "You are not the driver of this vehicle.", None) + ) } case Mountable.CanNotMount(obj: Mountable, seat_num) => @@ -3139,7 +3195,7 @@ class WorldSessionActor extends Actor with MDCContextAware { case VehicleResponse.ServerVehicleOverrideStart(vehicle, pad) => val vdef = vehicle.Definition - ServerVehicleOverride(vehicle, vdef.AutoPilotSpeed1, GlobalDefinitions.isFlightVehicle(vdef): Int) + ServerVehicleOverride(vehicle, vdef.AutoPilotSpeed1, if (GlobalDefinitions.isFlightVehicle(vdef)) 1 else 0) case VehicleResponse.ServerVehicleOverrideEnd(vehicle, pad) => DriverVehicleControl(vehicle, vehicle.Definition.AutoPilotSpeed2) @@ -3294,7 +3350,7 @@ class WorldSessionActor extends Actor with MDCContextAware { */ def HandleSetCurrentAvatar(tplayer: Player): Unit = { log.info(s"HandleSetCurrentAvatar - ${tplayer.Name}") - player = tplayer + session = session.copy(player = tplayer) val guid = tplayer.GUID StartBundlingPackets() UpdateDeployableUIElements(Deployables.InitializeDeployableUIElements(avatar)) @@ -3696,7 +3752,7 @@ class WorldSessionActor extends Actor with MDCContextAware { characters.headOption match { case Some(character) => log.info(s"CharacterRequest/Select: character ${character.name} found in records") - avatar = character.toAvatar + session = session.copy(avatar = character.toAvatar) val faction: String = avatar.faction.toString.toLowerCase whenUsedLastMAXName(0) = faction + "hev" whenUsedLastMAXName(1) = faction + "hev_antipersonnel" @@ -3722,9 +3778,8 @@ class WorldSessionActor extends Actor with MDCContextAware { case msg@BeginZoningMessage() => log.info("Reticulating splines ...") zoneLoaded = None - val continentId = continent.Id - traveler.zone = continentId - val faction = player.Faction + val continentId = continent.Id + val faction = player.Faction val factionChannel = s"$faction" continent.AvatarEvents ! Service.Join(continentId) continent.AvatarEvents ! Service.Join(factionChannel) @@ -4289,525 +4344,8 @@ class WorldSessionActor extends Actor with MDCContextAware { case msg @ SetChatFilterMessage(send_channel, origin, whitelist) => //log.info("SetChatFilters: " + msg) - case msg @ ChatMsg(messagetype, has_wide_contents, recipient, contents, note_contents) => - import ChatMessageType._ - log.info("Chat: " + msg) - - (messagetype, admin, recipient.trim, contents.trim) match { - case (CMT_FLY, true, _, _) => - flying = !flying - sendResponse( - ChatMsg(CMT_FLY, msg.wideContents, recipient, if (flying) "on" else "off", msg.note) - ) - - case (CMT_SPEED, true, _, _) => - speed = - try { - contents.toFloat - } catch { - case _: Throwable => - 1f - } - sendResponse(ChatMsg(messagetype, has_wide_contents, recipient, f"$speed%.3f", note_contents)) - - case (CMT_TOGGLESPECTATORMODE, true, _, _) => - player.spectator = !player.spectator - sendResponse( - ChatMsg( - CMT_TOGGLESPECTATORMODE, - msg.wideContents, - msg.recipient, - if (player.spectator) "on" else "off", - msg.note - ) - ) - - case (CMT_RECALL, _, _, _) => - val sanctuary = Zones.SanctuaryZoneId(player.Faction) - val errorMessage = zoningType match { - case Zoning.Method.Quit => Some("You can't recall to your sanctuary continent while quitting") - case Zoning.Method.InstantAction => - Some("You can't recall to your sanctuary continent while instant actioning") - case Zoning.Method.Recall => Some("You already requested to recall to your sanctuary continent") - case _ if continent.Id == sanctuary => - Some("You can't recall to your sanctuary when you are already in your sanctuary") - case _$msg if !player.isAlive || deadState != DeadState.Alive => - Some(if (player.isAlive) "@norecall_deconstructing" else "@norecall_dead") - case _ if player.VehicleSeated.nonEmpty => Some("@norecall_invehicle") - case _ => None - } - errorMessage match { - case Some(errorMessage) => - sendResponse( - ChatMsg( - CMT_QUIT, - false, - "", - errorMessage, - None - ) - ) - case None => - zoningType = Zoning.Method.Recall - zoningChatMessageType = messagetype - zoningStatus = Zoning.Status.Request - zoningReset = context.system.scheduler.scheduleOnce(10 seconds, self, ZoningReset()) - cluster ! Zoning.Recall.Request(player.Faction, sanctuary) - } - - case (CMT_INSTANTACTION, _, _, _) => - if (zoningType == Zoning.Method.Quit) { - sendResponse( - ChatMsg(CMT_QUIT, false, "", "You can't instant action while quitting.", None) - ) - } else if (zoningType == Zoning.Method.InstantAction) { - sendResponse(ChatMsg(CMT_QUIT, false, "", "@noinstantaction_instantactionting", None)) - } else if (zoningType == Zoning.Method.Recall) { - sendResponse( - ChatMsg( - CMT_QUIT, - false, - "", - "You won't instant action. You already requested to recall to your sanctuary continent", - None - ) - ) - } else if (!player.isAlive || deadState != DeadState.Alive) { - if (player.isAlive) { - sendResponse(ChatMsg(CMT_QUIT, false, "", "@noinstantaction_deconstructing", None)) - } else { - sendResponse(ChatMsg(CMT_QUIT, false, "", "@noinstantaction_dead", None)) - } - } else if (player.VehicleSeated.nonEmpty) { - sendResponse(ChatMsg(CMT_QUIT, false, "", "@noinstantaction_invehicle", None)) - } else { - zoningType = Zoning.Method.InstantAction - zoningChatMessageType = messagetype - zoningStatus = Zoning.Status.Request - zoningReset = context.system.scheduler.scheduleOnce(10 seconds, self, ZoningReset()) - cluster ! Zoning.InstantAction.Request(player.Faction) - } - - case (CMT_QUIT, _, _, _) => - if (zoningType == Zoning.Method.Quit) { - sendResponse(ChatMsg(CMT_QUIT, false, "", "@noquit_quitting", None)) - } else if (!player.isAlive || deadState != DeadState.Alive) { - if (player.isAlive) { - sendResponse(ChatMsg(CMT_QUIT, false, "", "@noquit_deconstructing", None)) - } else { - sendResponse(ChatMsg(CMT_QUIT, false, "", "@noquit_dead", None)) - } - } else if (player.VehicleSeated.nonEmpty) { - sendResponse(ChatMsg(CMT_QUIT, false, "", "@noquit_invehicle", None)) - } else { - //priority to quitting is given to quit over other zoning methods - if (zoningType == Zoning.Method.InstantAction || zoningType == Zoning.Method.Recall) { - CancelZoningProcessWithDescriptiveReason("cancel") - } - zoningType = Zoning.Method.Quit - zoningChatMessageType = messagetype - zoningStatus = Zoning.Status.Request - self ! Zoning.Quit() - } - - case (CMT_SUICIDE, _, _, _) => - if (player.isAlive && deadState != DeadState.Release) { - Suicide(player) - } - - case (CMT_CULLWATERMARK, _, _, contents) => - if (contents.contains("40 80")) connectionState = 100 - else if (contents.contains("120 200")) connectionState = 25 - else connectionState = 50 - - case (CMT_DESTROY, _, _, contents) => - val guid = contents.toInt - continent.GUID(continent.Map.TerminalToSpawnPad.getOrElse(guid, guid)) match { - case Some(pad: VehicleSpawnPad) => - pad.Actor ! VehicleSpawnControl.ProcessControl.Flush - case Some(turret: FacilityTurret) if turret.isUpgrading => - WeaponTurrets.FinishUpgradingMannedTurret(turret, TurretUpgrade.None) - case _ => - self ! PacketCoding.CreateGamePacket(0, RequestDestroyMessage(PlanetSideGUID(guid))) - } - sendResponse(ChatMsg(messagetype, has_wide_contents, recipient, contents, note_contents)) - - case (_, _, _, "!loc") => - val loc = - s"zone=${continent.Id} pos=${player.Position.x},${player.Position.y},${player.Position.z}; ori=${player.Orientation.x},${player.Orientation.y},${player.Orientation.z}" - log.info(loc) - sendResponse(ChatMsg(messagetype, has_wide_contents, recipient, loc, note_contents)) - - case (_, _, _, contents) if contents.startsWith("!list") => - val localString: String = contents.drop(contents.indexOf(" ") + 1) - val zone = contents.split(" ").lift(1) match { - case None => - Some(continent) - case Some(id) => - Zones.zones.get(id) - } - - zone match { - case Some(zone) => - sendResponse( - ChatMsg( - CMT_GMOPEN, - has_wide_contents, - "Server", - "\\#8Name (Faction) [ID] at PosX PosY PosZ", - note_contents - ) - ) - - (zone.LivePlayers ++ zone.Corpses) - .filter(_.CharId != player.CharId) - .sortBy(_.Name) - .foreach(player => { - sendResponse( - ChatMsg( - CMT_GMOPEN, - has_wide_contents, - "Server", - s"\\#7${player.Name} (${player.Faction}) [${player.CharId}] at ${player.Position.x.toInt} ${player.Position.y.toInt} ${player.Position.z.toInt}", - note_contents - ) - ) - }) - case None => - sendResponse( - ChatMsg( - CMT_GMOPEN, - has_wide_contents, - "Server", - "Invalid zone ID", - note_contents - ) - ) - } - - case (_, true, _, contents) if contents.startsWith("!kick") => - val input = contents.split("\\s+").drop(1) - if (input.length > 0) { - val numRegex = raw"(\d+)".r - val id = input(0) - val determination: Player => Boolean = id match { - case numRegex(_) => { _.CharId == id.toLong } - case _ => { _.Name.equals(id) } - } - continent.LivePlayers.find(determination).orElse(continent.Corpses.find(determination)) match { - case Some(tplayer) if AdministrativeKick(tplayer) => - if (input.length > 1) { - val time = input(1) - time match { - case numRegex(_) => - accountPersistence ! AccountPersistenceService.Kick(tplayer.Name, Some(time.toLong)) - case _ => - accountPersistence ! AccountPersistenceService.Kick(tplayer.Name, None) - } - } - case None => - sendResponse( - ChatMsg( - CMT_GMOPEN, - has_wide_contents, - "Server", - "Invalid player", - note_contents - ) - ) - } - } - - case (CMT_CAPTUREBASE, true, _, contents) => - val args = contents.split(" ").filter(_ != "") - - val (faction, factionPos) = args.zipWithIndex - .map { case (faction, pos) => (faction.toLowerCase, pos) } - .map { - case ("tr", pos) => Some(PlanetSideEmpire.TR, pos) - case ("nc", pos) => Some(PlanetSideEmpire.NC, pos) - case ("vs", pos) => Some(PlanetSideEmpire.VS, pos) - case ("none", pos) => Some(PlanetSideEmpire.NEUTRAL, pos) - case _ => None - } - .flatten - .headOption match { - case Some((faction, pos)) => (faction, Some(pos)) - case None => (player.Faction, None) - } - - val (buildingsOption, buildingPos) = args.zipWithIndex - .map { - case (_, pos) if (factionPos.isDefined && factionPos.get == pos) => None - case ("all", pos) => - Some( - Some( - continent.Buildings - .filter { - case (_, building) => building.CaptureTerminal.isDefined - } - .values - .toSeq - ), - Some(pos) - ) - case (name, pos) => - continent.Buildings.find { - case (_, building) => name.equalsIgnoreCase(building.Name) && building.CaptureTerminal.isDefined - } match { - case Some((_, building)) => Some(Some(Seq(building)), Some(pos)) - case None => - try { - // check if we have a timer - name.toInt - None - } catch { - case _: Throwable => - Some(None, Some(pos)) - } - } - } - .flatten - .headOption match { - case Some((buildings, pos)) => (buildings, pos) - case None => (None, None) - } - val (timerOption, timerPos) = args.zipWithIndex - .map { - case (_, pos) - if (factionPos.isDefined && factionPos.get == pos || buildingPos.isDefined && buildingPos.get == pos) => - None - case (timer, pos) => - try { - val t = timer.toInt // TODO what is the timer format supposed to be? - Some(Some(t), Some(pos)) - } catch { - case _: Throwable => - Some(None, Some(pos)) - } - } - .flatten - .headOption match { - case Some((timer, posOption)) => (timer, posOption) - case None => (None, None) - } - - (factionPos, buildingPos, timerPos, buildingsOption, timerOption) match { - case // [[|none []] - (Some(0), None, Some(1), None, Some(_)) | (Some(0), None, None, None, None) | - (None, None, None, None, None) | - // [ [|none [timer]]] - (None | Some(1), Some(0), None, Some(_), None) | (Some(1), Some(0), Some(2), Some(_), Some(_)) | - // [all [|none]] - (Some(1) | None, Some(0), None, Some(_), None) => - val buildings = buildingsOption.getOrElse( - continent.Buildings - .filter { - case (_, building) => - building.PlayersInSOI.find { soiPlayer => - player.CharId == soiPlayer.CharId - }.isDefined - } - .map { case (_, building) => building } - ) - buildings foreach { building => - // TODO implement timer - building.Faction = faction - continent.LocalEvents ! LocalServiceMessage( - continent.Id, - LocalAction.SetEmpire(building.GUID, faction) - ) - } - case (_, Some(0), _, None, _) => - sendResponse( - ChatMsg( - UNK_229, - true, - "", - s"\\#FF4040ERROR - \'${args(0)}\' is not a valid building name.", - None - ) - ) - case (Some(0), _, Some(1), _, None) | (Some(1), Some(0), Some(2), _, None) => - sendResponse( - ChatMsg( - UNK_229, - true, - "", - s"\\#FF4040ERROR - \'${args(timerPos.get)}\' is not a valid timer value.", - None - ) - ) - case _ => - sendResponse( - ChatMsg( - UNK_229, - true, - "", - "usage: /capturebase [[|none []] | [ [|none [timer]]] | [all [|none]]", - None - ) - ) - } - - case (_, _, "tr", _) => - sendResponse( - ZonePopulationUpdateMessage(4, 414, 138, contents.toInt, 138, contents.toInt / 2, 138, 0, 138, 0) - ) - - case (_, _, "nc", _) => - sendResponse( - ZonePopulationUpdateMessage(4, 414, 138, 0, 138, contents.toInt, 138, contents.toInt / 3, 138, 0) - ) - - case (_, _, "vs", _) => - ZonePopulationUpdateMessage(4, 414, 138, contents.toInt * 2, 138, 0, 138, contents.toInt, 138, 0) - - case (_, _, "bo", _) => - sendResponse(ZonePopulationUpdateMessage(4, 414, 138, 0, 138, 0, 138, 0, 138, contents.toInt)) - - case (_, _, _, contents) if contents.startsWith("!ntu") => - continent.Buildings.values.foreach(building => - building.Amenities.foreach(amenity => - amenity.Definition match { - case GlobalDefinitions.resource_silo => - val r = new scala.util.Random - val silo = amenity.asInstanceOf[ResourceSilo] - val ntu: Int = 900 + r.nextInt(100) - silo.NtuCapacitor - silo.Actor ! ResourceSilo.UpdateChargeLevel(ntu) - - case _ => ; - } - ) - ) - - case (CMT_OPEN, _, _, _) if !player.silenced => - chatService ! ChatServiceMessage( - "local", - ChatAction.Local(player.GUID, player.Name, continent, player.Position, player.Faction, msg) - ) - - case (CMT_VOICE, _, _, _) => - chatService ! ChatServiceMessage( - "voice", - ChatAction.Voice(player.GUID, player.Name, continent, player.Position, player.Faction, msg) - ) - case (CMT_TELL, _, _, _) if !player.silenced => - chatService ! ChatServiceMessage("tell", ChatAction.Tell(player.GUID, player.Name, msg)) - - case (CMT_BROADCAST, _, _, _) if !player.silenced => - chatService ! ChatServiceMessage( - "broadcast", - ChatAction.Broadcast(player.GUID, player.Name, continent, player.Position, player.Faction, msg) - ) - - case (CMT_NOTE, _, _, _) => - chatService ! ChatServiceMessage("note", ChatAction.Note(player.GUID, player.Name, msg)) - - case (CMT_SILENCE, true, _, _) => - chatService ! ChatServiceMessage("gm", ChatAction.GM(player.GUID, player.Name, msg)) - - case (CMT_SQUAD, _, _, _) => - if (squadChannel.nonEmpty) { - chatService ! ChatServiceMessage( - squadChannel.get, - ChatAction.Squad(player.GUID, player.Name, continent, player.Position, player.Faction, msg) - ) - } - - case (CMT_PLATOON, _, _, _) if !player.silenced => - chatService ! ChatServiceMessage( - "platoon", - ChatAction.Platoon(player.GUID, player.Name, continent, player.Position, player.Faction, msg) - ) - - case (CMT_COMMAND, true, _, _) => - chatService ! ChatServiceMessage( - "command", - ChatAction.Command(player.GUID, player.Name, continent, player.Position, player.Faction, msg) - ) - - case ( - CMT_WHO | CMT_WHO_CSR | CMT_WHO_CR | CMT_WHO_PLATOONLEADERS | CMT_WHO_SQUADLEADERS | CMT_WHO_TEAMS, - _, - _, - _ - ) => - val poplist = continent.Players - val popTR = poplist.count(_.faction == PlanetSideEmpire.TR) - val popNC = poplist.count(_.faction == PlanetSideEmpire.NC) - val popVS = poplist.count(_.faction == PlanetSideEmpire.VS) - val contName = continent.Map.Name - StartBundlingPackets() - sendResponse(ChatMsg(ChatMessageType.CMT_WHO, true, "", "That command doesn't work for now, but : ", None)) - sendResponse(ChatMsg(ChatMessageType.CMT_WHO, true, "", "NC online : " + popNC + " on " + contName, None)) - sendResponse(ChatMsg(ChatMessageType.CMT_WHO, true, "", "TR online : " + popTR + " on " + contName, None)) - sendResponse(ChatMsg(ChatMessageType.CMT_WHO, true, "", "VS online : " + popVS + " on " + contName, None)) - StopBundlingPackets() - - case _ => - } - - CSRZone.read(traveler, msg) match { - case (true, zone, pos) => - if ( - player.isAlive && zone != player.Continent && (admin || zone == "z8" || zone == "c1" || zone == "c2" || zone == "c3" || zone == "c4" || zone == "c5" || zone == "c6" || - zone == "tzshtr" || zone == "tzcotr" || zone == "tzdrtr" || - zone == "tzshnc" || zone == "tzconc" || zone == "tzdrnc" || - zone == "tzshvs" || zone == "tzcovs" || zone == "tzdrvs") - ) { - deadState = DeadState.Release //cancel movement updates - PlayerActionsToCancel() - continent.GUID(player.VehicleSeated) match { - case Some(vehicle: Vehicle) if vehicle.MountedIn.isEmpty => - vehicle.PassengerInSeat(player) match { - case Some(0) => - vehicle.Position = pos - LoadZonePhysicalSpawnPoint(zone, pos, Vector3.Zero, 0) - case _ => //not seated as the driver, in which case we can't move - deadState = DeadState.Alive - } - case None => - player.Position = pos - //continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.ObjectDelete(player.GUID, player.GUID)) - LoadZonePhysicalSpawnPoint(zone, pos, Vector3.Zero, 0) - case _ => //seated in something that is not a vehicle or the vehicle is cargo, in which case we can't move - deadState = DeadState.Alive - } - } - case (_, _, _) => ; - } - - CSRWarp.read(traveler, msg) match { - case (true, pos) => - // continent.Id == "c1" || continent.Id == "c2" || continent.Id == "c3" || continent.Id == "c4" || continent.Id == "c5" || continent.Id == "c6" || - if ( - player.isAlive && (admin || continent.Id == "z8" || - continent.Id == "tzshtr" || continent.Id == "tzcotr" || continent.Id == "tzdrtr" || - continent.Id == "tzshnc" || continent.Id == "tzconc" || continent.Id == "tzdrnc" || - continent.Id == "tzshvs" || continent.Id == "tzcovs" || continent.Id == "tzdrvs") - ) { - deadState = DeadState.Release //cancel movement updates - PlayerActionsToCancel() - continent.GUID(player.VehicleSeated) match { - case Some(vehicle: Vehicle) if vehicle.MountedIn.isEmpty => - vehicle.PassengerInSeat(player) match { - case Some(0) => - vehicle.Position = pos - LoadZonePhysicalSpawnPoint(continent.Id, pos, Vector3.z(vehicle.Orientation.z), 0) - case _ => //not seated as the driver, in which case we can't move - deadState = DeadState.Alive - } - case None => - player.Position = pos - sendResponse(PlayerStateShiftMessage(ShiftState(0, pos, player.Orientation.z, None))) - deadState = DeadState.Alive //must be set here - case _ => //seated in something that is not a vehicle or the vehicle is cargo, in which case we can't move - deadState = DeadState.Alive - } - } - case (_, _) => ; - } + case msg: ChatMsg => + chatActor ! ChatActor.Message(msg) case msg @ VoiceHostRequest(unk, PlanetSideGUID(player_guid), data) => log.info("Player " + player_guid + " requested in-game voice chat.") @@ -5146,7 +4684,7 @@ class WorldSessionActor extends Actor with MDCContextAware { /* line 1c: vehicle is the same faction as player and either the owner is absent or the vehicle is destroyed */ /* line 2: vehicle is not mounted in anything or, if it is, its seats are empty */ if ( - (admin || + (session.admin || (player.VehicleOwned.contains(object_guid) && vehicle.Owner.contains(player.GUID)) || (player.Faction == vehicle.Faction && ((vehicle.Owner.isEmpty || continent .GUID(vehicle.Owner.get) @@ -6717,12 +6255,9 @@ class WorldSessionActor extends Actor with MDCContextAware { def RegisterVehicleFromSpawnPad(obj: Vehicle, pad: VehicleSpawnPad): TaskResolver.GiveTask = { TaskResolver.GiveTask( new Task() { - private val localVehicle = obj - private val localPad = pad.Actor - private val localSession: String = sessionId.toString - private val localPlayer = player - private val localVehicleService = continent.VehicleEvents - private val localZone = continent + private val localVehicle = obj + private val localPad = pad.Actor + private val localPlayer = player override def Description: String = s"register a ${localVehicle.Definition.Name} for spawn pad" @@ -7777,7 +7312,7 @@ class WorldSessionActor extends Actor with MDCContextAware { * `PlayerSuicide` * @param tplayer the player to be killed */ - def Suicide(tplayer: Player): Unit = { + def suicide(tplayer: Player): Unit = { tplayer.History(PlayerSuicide(PlayerSource(tplayer))) tplayer.Actor ! Player.Die() } @@ -7821,13 +7356,11 @@ class WorldSessionActor extends Actor with MDCContextAware { shooting = None case None => ; } - if (flying) { - sendResponse(ChatMsg(ChatMessageType.CMT_FLY, false, "", "off", None)) - flying = false + if (session.flying) { + chatActor ! ChatActor.Message(ChatMsg(ChatMessageType.CMT_FLY, false, "", "off", None)) } - if (speed > 1) { - sendResponse(ChatMsg(ChatMessageType.CMT_SPEED, false, "", "1.000", None)) - speed = 1f + if (session.speed > 1) { + chatActor ! ChatActor.Message(ChatMsg(ChatMessageType.CMT_SPEED, false, "", "1.000", None)) } } @@ -7958,7 +7491,7 @@ class WorldSessionActor extends Actor with MDCContextAware { * The priority of object confirmation is `direct` then `occupant.VehicleSeated`. * Once an object is found, the remainder are ignored. * @param direct a game object in which the player may be sat - * @param target the player who is sat and may have specified the game object in which mounted + * @param occupant the player who is sat and may have specified the game object in which mounted * @return a tuple consisting of a vehicle reference and a seat index * if and only if the vehicle is known to this client and the `WorldSessioNActor`-global `player` occupies it; * `(None, None)`, otherwise (even if the vehicle can be determined) @@ -8819,8 +8352,9 @@ class WorldSessionActor extends Actor with MDCContextAware { val func = data.damage_model.Calculate(data) target match { case obj: Player if obj.CanDamage && obj.Actor != Default.Actor => - if (obj.spectator) { - AdministrativeKick(player, obj != player) // little thing for auto kick + // auto kick spectators doing damage to others + if (obj.spectator && obj != player) { + AdministrativeKick(player) } else { obj.Actor ! Vitality.Damage(func) } @@ -9083,7 +8617,9 @@ class WorldSessionActor extends Actor with MDCContextAware { continent.LocalEvents ! LocalServiceMessage.Deployables(RemoverActor.ClearSpecific(List(old), continent)) continent.LocalEvents ! LocalServiceMessage.Deployables(RemoverActor.AddTask(old, continent, Some(0 seconds))) if (msg) { //max test - sendResponse(ChatMsg(ChatMessageType.UNK_229, false, "", s"@${definition.Descriptor}OldestDestroyed", None)) + sendResponse( + ChatMsg(ChatMessageType.UNK_229, false, "", s"@${definition.Descriptor}OldestDestroyed", None) + ) } case None => ; //should be an invalid case log.warn( @@ -9402,14 +8938,14 @@ class WorldSessionActor extends Actor with MDCContextAware { * @see `interstellarFerry` * @see `LoadZoneAsPlayer` * @see `LoadZoneInVehicle` - * @param zone_id the zone in which the player will be placed + * @param zoneId the zone in which the player will be placed * @param pos the game world coordinates where the player will be positioned * @param ori the direction in which the player will be oriented * @param respawnTime the character downtime spent respawning, as clocked on the redeployment screen; * does not factor in any time required for loading zone or game objects */ - def LoadZonePhysicalSpawnPoint(zone_id: String, pos: Vector3, ori: Vector3, respawnTime: Long): Unit = { - log.info(s"Load in zone $zone_id at position $pos in $respawnTime seconds") + def LoadZonePhysicalSpawnPoint(zoneId: String, pos: Vector3, ori: Vector3, respawnTime: Long): Unit = { + log.info(s"Load in zone $zoneId at position $pos in $respawnTime seconds") respawnTimer.cancel reviveTimer.cancel val backpack = player.isBackpack @@ -9433,7 +8969,7 @@ class WorldSessionActor extends Actor with MDCContextAware { val newPlayer = RespawnClone(player) newPlayer.Position = pos newPlayer.Orientation = ori - LoadZoneAsPlayer(newPlayer, zone_id) + LoadZoneAsPlayer(newPlayer, zoneId) } else { //deactivate non-passive implants player.Implants.indices.foreach { index => @@ -9444,7 +8980,7 @@ class WorldSessionActor extends Actor with MDCContextAware { } interstellarFerry.orElse(continent.GUID(player.VehicleSeated)) match { case Some(vehicle: Vehicle) => //driver or passenger in vehicle using a warp gate, or a droppod - LoadZoneInVehicle(vehicle, pos, ori, zone_id) + LoadZoneInVehicle(vehicle, pos, ori, zoneId) case _ if player.HasGUID => //player is deconstructing self or instant action val player_guid = player.GUID @@ -9455,12 +8991,12 @@ class WorldSessionActor extends Actor with MDCContextAware { ) player.Position = pos player.Orientation = ori - LoadZoneAsPlayer(player, zone_id) + LoadZoneAsPlayer(player, zoneId) case _ => //player is logging in player.Position = pos player.Orientation = ori - LoadZoneAsPlayer(player, zone_id) + LoadZoneAsPlayer(player, zoneId) } } import scala.concurrent.ExecutionContext.Implicits.global @@ -9500,7 +9036,7 @@ class WorldSessionActor extends Actor with MDCContextAware { val original = player if (player.isBackpack) { //unregister avatar locker + GiveWorld - player = tplayer + session = session.copy(player = tplayer) (taskResolver, TaskBeforeZoneChange(GUIDTask.UnregisterLocker(original.Locker)(continent.GUID), zone_id)) } else if (player.HasGUID) { //unregister avatar whole + GiveWorld @@ -10996,31 +10532,26 @@ class WorldSessionActor extends Actor with MDCContextAware { turnCounterFunc(player.GUID) } - def AdministrativeKick(tplayer: Player, permitKickSelf: Boolean = false): Boolean = { - if (permitKickSelf || tplayer != player) { //stop kicking yourself - tplayer.death_by = -1 - accountPersistence ! AccountPersistenceService.Kick(tplayer.Name) - //get out of that vehicle - GetMountableAndSeat(None, tplayer, continent) match { - case (Some(obj), Some(seatNum)) => - tplayer.VehicleSeated = None - obj.Seats(seatNum).Occupant = None - continent.VehicleEvents ! VehicleServiceMessage( - continent.Id, - VehicleAction.KickPassenger(tplayer.GUID, seatNum, false, obj.GUID) - ) - case _ => ; - } - true - } else { - false + def AdministrativeKick(tplayer: Player) = { + tplayer.death_by = -1 + accountPersistence ! AccountPersistenceService.Kick(tplayer.Name) + //get out of that vehicle + GetMountableAndSeat(None, tplayer, continent) match { + case (Some(obj), Some(seatNum)) => + tplayer.VehicleSeated = None + obj.Seats(seatNum).Occupant = None + continent.VehicleEvents ! VehicleServiceMessage( + continent.Id, + VehicleAction.KickPassenger(tplayer.GUID, seatNum, false, obj.GUID) + ) + case _ => ; } } def KickedByAdministration(): Unit = { sendResponse(DisconnectMessage("Your account has been logged out by a Customer Service Representative.")) Thread.sleep(300) - sendResponse(DropSession(sessionId, "kick by GM")) + sendResponse(DropSession(session.id, "kick by GM")) } def ImmediateDisconnect(): Unit = { @@ -11028,7 +10559,7 @@ class WorldSessionActor extends Actor with MDCContextAware { accountPersistence ! AccountPersistenceService.Logout(avatar.name) } sendResponse(DropCryptoSession()) - sendResponse(DropSession(sessionId, "user quit")) + sendResponse(DropSession(session.id, "user quit")) } def HandleWeaponFire(weaponGUID : PlanetSideGUID, projectileGUID : PlanetSideGUID, shotOrigin : Vector3) : Unit = { @@ -11249,7 +10780,7 @@ class WorldSessionActor extends Actor with MDCContextAware { } def sendResponse(msg: Any): Unit = { - MDC("sessionId") = sessionId.toString + MDC("sessionId") = session.id.toString rightRef !> msg } @@ -11258,112 +10789,3 @@ class WorldSessionActor extends Actor with MDCContextAware { sendResponse(RawPacket(pkt)) } } - -object WorldSessionActor { - - /** Object purchasing cooldowns.
- * key - object id
- * value - time last used (ms) - */ - val delayedPurchaseEntries: Map[Int, Long] = Map( - GlobalDefinitions.ams.ObjectId -> 300000, //5min - GlobalDefinitions.ant.ObjectId -> 300000, //5min - GlobalDefinitions.apc_nc.ObjectId -> 300000, //5min - GlobalDefinitions.apc_tr.ObjectId -> 300000, //5min - GlobalDefinitions.apc_vs.ObjectId -> 300000, //5min - GlobalDefinitions.aurora.ObjectId -> 300000, //5min - GlobalDefinitions.battlewagon.ObjectId -> 300000, //5min - GlobalDefinitions.dropship.ObjectId -> 300000, //5min - GlobalDefinitions.flail.ObjectId -> 300000, //5min - GlobalDefinitions.fury.ObjectId -> 300000, //5min - GlobalDefinitions.galaxy_gunship.ObjectId -> 600000, //10min - GlobalDefinitions.lodestar.ObjectId -> 300000, //5min - GlobalDefinitions.liberator.ObjectId -> 300000, //5min - GlobalDefinitions.lightgunship.ObjectId -> 300000, //5min - GlobalDefinitions.lightning.ObjectId -> 300000, //5min - GlobalDefinitions.magrider.ObjectId -> 300000, //5min - GlobalDefinitions.mediumtransport.ObjectId -> 300000, //5min - GlobalDefinitions.mosquito.ObjectId -> 300000, //5min - GlobalDefinitions.phantasm.ObjectId -> 300000, //5min - GlobalDefinitions.prowler.ObjectId -> 300000, //5min - GlobalDefinitions.quadassault.ObjectId -> 300000, //5min - GlobalDefinitions.quadstealth.ObjectId -> 300000, //5min - GlobalDefinitions.router.ObjectId -> 300000, //5min - GlobalDefinitions.switchblade.ObjectId -> 300000, //5min - GlobalDefinitions.skyguard.ObjectId -> 300000, //5min - GlobalDefinitions.threemanheavybuggy.ObjectId -> 300000, //5min - GlobalDefinitions.thunderer.ObjectId -> 300000, //5min - GlobalDefinitions.two_man_assault_buggy.ObjectId -> 300000, //5min - GlobalDefinitions.twomanhoverbuggy.ObjectId -> 300000, //5min - GlobalDefinitions.twomanheavybuggy.ObjectId -> 300000, //5min - GlobalDefinitions.vanguard.ObjectId -> 300000, //5min - GlobalDefinitions.vulture.ObjectId -> 300000, //5min - GlobalDefinitions.wasp.ObjectId -> 300000, //5min - GlobalDefinitions.flamethrower.ObjectId -> 180000 //3min - ) - - /** Object use cooldowns.
- * key - object id
- * value - time last used (ms) - */ - val delayedGratificationEntries: Map[Int, Long] = Map( - GlobalDefinitions.medkit.ObjectId -> 5000, //5s - GlobalDefinitions.super_armorkit.ObjectId -> 1200000, //20min - GlobalDefinitions.super_medkit.ObjectId -> 1200000, //20min - GlobalDefinitions.super_staminakit.ObjectId -> 1200000 //20min - ) - - final case class ResponseToSelf(pkt: PlanetSideGamePacket) - - 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 CreateCharacter( - name: String, - head: Int, - voice: CharacterVoice.Value, - gender: CharacterGender.Value, - empire: PlanetSideEmpire.Value - ) - private final case class ListAccountCharacters() - private final case class SetCurrentAvatar(tplayer: Player, max_attempts: Int, attempt: Int = 0) - private final case class ZoningReset() - - final val ftes = ( - FirstTimeEvents.Maps ++ FirstTimeEvents.Monoliths ++ - FirstTimeEvents.Standard.All ++ FirstTimeEvents.Cavern.All ++ - FirstTimeEvents.TR.All ++ FirstTimeEvents.NC.All ++ FirstTimeEvents.VS.All ++ - FirstTimeEvents.Generic - ).toList - - /** - * The message that progresses some form of user-driven activity with a certain eventual outcome - * and potential feedback per cycle. - * @param delta how much the progress value changes each tick, which will be treated as a percentage; - * must be a positive value - * @param completionAction a finalizing action performed once the progress reaches 100(%) - * @param tickAction an action that is performed for each increase of progress - */ - final case class ProgressEvent(delta: Float, completionAction: () => Unit, tickAction: Float => Boolean) - - private final val zoningCountdownMessages: Seq[Int] = Seq(5, 10, 20) - - protected final case class SquadUIElement( - name: String, - index: Int, - zone: Int, - health: Int, - armor: Int, - position: Vector3 - ) - - private final case class FinalizeDeployable( - obj: PlanetSideGameObject with Deployable, - tool: ConstructionItem, - index: Int - ) - - private final case class LoadedRemoteProjectile(projectile_guid: PlanetSideGUID, projectile: Option[Projectile]) -} diff --git a/pslogin/src/main/scala/CryptoSessionActor.scala b/common/src/main/scala/net/psforever/login/CryptoSessionActor.scala similarity index 99% rename from pslogin/src/main/scala/CryptoSessionActor.scala rename to common/src/main/scala/net/psforever/login/CryptoSessionActor.scala index e0cf5c62..b30b70b5 100644 --- a/pslogin/src/main/scala/CryptoSessionActor.scala +++ b/common/src/main/scala/net/psforever/login/CryptoSessionActor.scala @@ -1,18 +1,18 @@ -package net.psforever.pslogin +package net.psforever.login -import akka.actor.{Actor, ActorRef, MDCContextAware} -import net.psforever.crypto.CryptoInterface.CryptoStateWithMAC -import net.psforever.crypto.CryptoInterface -import net.psforever.packet._ -import scodec.Attempt.{Failure, Successful} -import scodec.bits._ import java.security.SecureRandom +import akka.actor.MDCContextAware.Implicits._ +import akka.actor.{Actor, ActorRef, MDCContextAware} +import net.psforever.crypto.CryptoInterface +import net.psforever.crypto.CryptoInterface.CryptoStateWithMAC +import net.psforever.packet._ import net.psforever.packet.control._ import net.psforever.packet.crypto._ import net.psforever.packet.game.PingMsg import org.log4s.MDC -import MDCContextAware.Implicits._ +import scodec.Attempt.{Failure, Successful} +import scodec.bits._ sealed trait CryptoSessionAPI final case class DropCryptoSession() extends CryptoSessionAPI diff --git a/pslogin/src/main/scala/LoginSessionActor.scala b/common/src/main/scala/net/psforever/login/LoginSessionActor.scala similarity index 97% rename from pslogin/src/main/scala/LoginSessionActor.scala rename to common/src/main/scala/net/psforever/login/LoginSessionActor.scala index 60bd418e..ef42e3f5 100644 --- a/pslogin/src/main/scala/LoginSessionActor.scala +++ b/common/src/main/scala/net/psforever/login/LoginSessionActor.scala @@ -1,27 +1,28 @@ -package net.psforever.pslogin +package net.psforever.login -import java.net.InetSocketAddress +import java.net.{InetAddress, InetSocketAddress} + +import akka.actor.MDCContextAware.Implicits._ import akka.actor.{Actor, ActorRef, Cancellable, MDCContextAware} -import net.psforever.packet.{PlanetSideGamePacket, _} +import com.github.t3hnar.bcrypt._ +import net.psforever.objects.{Account, Default} import net.psforever.packet.control._ +import net.psforever.packet.game.LoginRespMessage.{LoginError, StationError, StationSubscriptionStatus} import net.psforever.packet.game._ +import net.psforever.packet.{PlanetSideGamePacket, _} +import net.psforever.persistence +import net.psforever.types.PlanetSideEmpire +import net.psforever.util.Config +import net.psforever.util.Database._ import org.log4s.MDC import scodec.bits._ -import MDCContextAware.Implicits._ -import net.psforever.objects.Account -import net.psforever.objects.Default -import net.psforever.types.PlanetSideEmpire import services.ServiceManager import services.ServiceManager.Lookup import services.account.{ReceiveIPAddress, RetrieveIPAddress, StoreAccountData} -import com.github.t3hnar.bcrypt._ -import net.psforever.packet.game.LoginRespMessage.{LoginError, StationError, StationSubscriptionStatus} -import net.psforever.persistence + +import scala.concurrent.Future import scala.concurrent.duration._ import scala.util.{Failure, Success} -import scala.concurrent.Future -import Database._ -import java.net.InetAddress class LoginSessionActor extends Actor with MDCContextAware { private[this] val log = org.log4s.getLogger diff --git a/pslogin/src/main/scala/PacketCodingActor.scala b/common/src/main/scala/net/psforever/login/PacketCodingActor.scala similarity index 99% rename from pslogin/src/main/scala/PacketCodingActor.scala rename to common/src/main/scala/net/psforever/login/PacketCodingActor.scala index 9cd1e1c5..7ea56164 100644 --- a/pslogin/src/main/scala/PacketCodingActor.scala +++ b/common/src/main/scala/net/psforever/login/PacketCodingActor.scala @@ -1,13 +1,13 @@ -package net.psforever.pslogin +package net.psforever.login +import akka.actor.MDCContextAware.Implicits._ import akka.actor.{Actor, ActorRef, Cancellable, MDCContextAware} +import net.psforever.objects.Default import net.psforever.packet._ +import net.psforever.packet.control.{HandleGamePacket, _} +import org.log4s.MDC import scodec.Attempt.{Failure, Successful} import scodec.bits._ -import org.log4s.MDC -import MDCContextAware.Implicits._ -import net.psforever.objects.Default -import net.psforever.packet.control.{HandleGamePacket, _} import scala.annotation.tailrec import scala.collection.mutable diff --git a/pslogin/src/main/scala/Session.scala b/common/src/main/scala/net/psforever/login/Session.scala similarity index 94% rename from pslogin/src/main/scala/Session.scala rename to common/src/main/scala/net/psforever/login/Session.scala index dd13c9a7..777ed596 100644 --- a/pslogin/src/main/scala/Session.scala +++ b/common/src/main/scala/net/psforever/login/Session.scala @@ -1,13 +1,11 @@ -package net.psforever.pslogin +package net.psforever.login import java.net.InetSocketAddress -import akka.actor._ -import scodec.bits._ - -import akka.actor.{ActorContext, ActorRef, PoisonPill} +import akka.actor.MDCContextAware.Implicits._ +import akka.actor.{ActorContext, ActorRef, PoisonPill, _} import com.github.nscala_time.time.Imports._ -import MDCContextAware.Implicits._ +import scodec.bits._ sealed trait SessionState final case class New() extends SessionState diff --git a/pslogin/src/main/scala/SessionRouter.scala b/common/src/main/scala/net/psforever/login/SessionRouter.scala similarity index 99% rename from pslogin/src/main/scala/SessionRouter.scala rename to common/src/main/scala/net/psforever/login/SessionRouter.scala index b6261aeb..1b1e197f 100644 --- a/pslogin/src/main/scala/SessionRouter.scala +++ b/common/src/main/scala/net/psforever/login/SessionRouter.scala @@ -1,19 +1,18 @@ -package net.psforever.pslogin +package net.psforever.login import java.net.InetSocketAddress -import akka.actor._ -import org.log4s.MDC -import scodec.bits._ - -import scala.collection.mutable import akka.actor.SupervisorStrategy.Stop +import akka.actor._ import net.psforever.packet.PacketCoding import net.psforever.packet.control.ConnectionClose +import net.psforever.util.Config +import org.log4s.MDC +import scodec.bits._ import services.ServiceManager import services.ServiceManager.Lookup import services.account.{IPAddress, StoreIPAddress} - +import scala.collection.mutable import scala.concurrent.duration._ sealed trait SessionRouterAPI diff --git a/pslogin/src/main/scala/TcpListener.scala b/common/src/main/scala/net/psforever/login/TcpListener.scala similarity index 98% rename from pslogin/src/main/scala/TcpListener.scala rename to common/src/main/scala/net/psforever/login/TcpListener.scala index f221071b..1a65c966 100644 --- a/pslogin/src/main/scala/TcpListener.scala +++ b/common/src/main/scala/net/psforever/login/TcpListener.scala @@ -1,4 +1,4 @@ -package net.psforever.pslogin +package net.psforever.login import java.net.{InetAddress, InetSocketAddress} diff --git a/pslogin/src/main/scala/UdpListener.scala b/common/src/main/scala/net/psforever/login/UdpListener.scala similarity index 98% rename from pslogin/src/main/scala/UdpListener.scala rename to common/src/main/scala/net/psforever/login/UdpListener.scala index dc281810..e48b0d92 100644 --- a/pslogin/src/main/scala/UdpListener.scala +++ b/common/src/main/scala/net/psforever/login/UdpListener.scala @@ -1,4 +1,4 @@ -package net.psforever.pslogin +package net.psforever.login import java.net.{InetAddress, InetSocketAddress} diff --git a/pslogin/src/main/scala/UdpNetworkSimulator.scala b/common/src/main/scala/net/psforever/login/UdpNetworkSimulator.scala similarity index 99% rename from pslogin/src/main/scala/UdpNetworkSimulator.scala rename to common/src/main/scala/net/psforever/login/UdpNetworkSimulator.scala index 378e71b8..bc823667 100644 --- a/pslogin/src/main/scala/UdpNetworkSimulator.scala +++ b/common/src/main/scala/net/psforever/login/UdpNetworkSimulator.scala @@ -1,11 +1,11 @@ -package net.psforever.pslogin +package net.psforever.login import akka.actor.{Actor, ActorRef} import akka.io._ -import scala.util.Random import scala.collection.mutable import scala.concurrent.duration._ +import scala.util.Random /** Parameters for the Network simulator * diff --git a/pslogin/src/main/scala/WorldSession.scala b/common/src/main/scala/net/psforever/login/WorldSession.scala similarity index 99% rename from pslogin/src/main/scala/WorldSession.scala rename to common/src/main/scala/net/psforever/login/WorldSession.scala index a43e1b26..fa06f1d8 100644 --- a/pslogin/src/main/scala/WorldSession.scala +++ b/common/src/main/scala/net/psforever/login/WorldSession.scala @@ -1,15 +1,15 @@ -package net.psforever.pslogin +package net.psforever.login import akka.actor.ActorRef import akka.pattern.{AskTimeoutException, ask} import akka.util.Timeout -import net.psforever.objects.{AmmoBox, GlobalDefinitions, Player, Tool} import net.psforever.objects.equipment.{Ammo, Equipment} import net.psforever.objects.guid.{GUIDTask, Task, TaskResolver} import net.psforever.objects.inventory.{Container, InventoryItem} import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.serverobject.containable.Containable import net.psforever.objects.zones.Zone +import net.psforever.objects.{AmmoBox, GlobalDefinitions, Player, Tool} import net.psforever.packet.game.ObjectHeldMessage import net.psforever.types.{PlanetSideGUID, TransactionType, Vector3} import services.Service @@ -19,7 +19,7 @@ import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.Future import scala.concurrent.duration._ import scala.language.implicitConversions -import scala.util.{Success, Failure} +import scala.util.{Failure, Success} object WorldSession { diff --git a/pslogin/src/main/scala/psadmin/CmdInternal.scala b/common/src/main/scala/net/psforever/login/psadmin/CmdInternal.scala similarity index 91% rename from pslogin/src/main/scala/psadmin/CmdInternal.scala rename to common/src/main/scala/net/psforever/login/psadmin/CmdInternal.scala index 8197b8a4..91ea3d32 100644 --- a/pslogin/src/main/scala/psadmin/CmdInternal.scala +++ b/common/src/main/scala/net/psforever/login/psadmin/CmdInternal.scala @@ -1,8 +1,7 @@ -package net.psforever.pslogin.psadmin +package net.psforever.login.psadmin +import net.psforever.util.Config import scala.collection.mutable -import net.psforever.pslogin.Config - import scala.jdk.CollectionConverters._ object CmdInternal { diff --git a/pslogin/src/main/scala/psadmin/CmdListPlayers.scala b/common/src/main/scala/net/psforever/login/psadmin/CmdListPlayers.scala similarity index 90% rename from pslogin/src/main/scala/psadmin/CmdListPlayers.scala rename to common/src/main/scala/net/psforever/login/psadmin/CmdListPlayers.scala index 639aa612..e6af302e 100644 --- a/pslogin/src/main/scala/psadmin/CmdListPlayers.scala +++ b/common/src/main/scala/net/psforever/login/psadmin/CmdListPlayers.scala @@ -1,10 +1,10 @@ -package net.psforever.pslogin.psadmin +package net.psforever.login.psadmin -import akka.actor.ActorRef -import akka.actor.Actor -import scala.collection.mutable.Map +import akka.actor.{Actor, ActorRef} import net.psforever.objects.zones.InterstellarCluster +import scala.collection.mutable.Map + class CmdListPlayers(args: Array[String], services: Map[String, ActorRef]) extends Actor { private[this] val log = org.log4s.getLogger(self.path.name) diff --git a/pslogin/src/main/scala/psadmin/CmdShutdown.scala b/common/src/main/scala/net/psforever/login/psadmin/CmdShutdown.scala similarity index 90% rename from pslogin/src/main/scala/psadmin/CmdShutdown.scala rename to common/src/main/scala/net/psforever/login/psadmin/CmdShutdown.scala index 75037125..9ddbcb22 100644 --- a/pslogin/src/main/scala/psadmin/CmdShutdown.scala +++ b/common/src/main/scala/net/psforever/login/psadmin/CmdShutdown.scala @@ -1,6 +1,7 @@ -package net.psforever.pslogin.psadmin +package net.psforever.login.psadmin import akka.actor.{Actor, ActorRef} + import scala.collection.mutable.Map class CmdShutdown(args: Array[String], services: Map[String, ActorRef]) extends Actor { diff --git a/pslogin/src/main/scala/psadmin/PsAdminActor.scala b/common/src/main/scala/net/psforever/login/psadmin/PsAdminActor.scala similarity index 97% rename from pslogin/src/main/scala/psadmin/PsAdminActor.scala rename to common/src/main/scala/net/psforever/login/psadmin/PsAdminActor.scala index ac0896eb..91da77db 100644 --- a/pslogin/src/main/scala/psadmin/PsAdminActor.scala +++ b/common/src/main/scala/net/psforever/login/psadmin/PsAdminActor.scala @@ -1,20 +1,19 @@ -package net.psforever.pslogin.psadmin +package net.psforever.login.psadmin import java.net.InetSocketAddress -import akka.actor.{ActorRef, Props} -import akka.actor.{Actor, Stash} -import akka.io.Tcp -import scodec.bits._ -import scodec.interop.akka._ -import scala.collection.mutable.Map -import akka.util.ByteString +import akka.actor.{Actor, ActorRef, Props, Stash} +import akka.io.Tcp +import akka.util.ByteString import org.json4s._ import org.json4s.native.Serialization.write - +import scodec.bits._ +import scodec.interop.akka._ import services.ServiceManager.Lookup import services._ +import scala.collection.mutable.Map + object PsAdminActor { val whiteSpaceRegex = """\s+""".r } diff --git a/pslogin/src/main/scala/psadmin/PsAdminCommands.scala b/common/src/main/scala/net/psforever/login/psadmin/PsAdminCommands.scala similarity index 97% rename from pslogin/src/main/scala/psadmin/PsAdminCommands.scala rename to common/src/main/scala/net/psforever/login/psadmin/PsAdminCommands.scala index a8ea6311..e5febd8a 100644 --- a/pslogin/src/main/scala/psadmin/PsAdminCommands.scala +++ b/common/src/main/scala/net/psforever/login/psadmin/PsAdminCommands.scala @@ -1,4 +1,4 @@ -package net.psforever.pslogin.psadmin +package net.psforever.login.psadmin import scala.collection.mutable diff --git a/common/src/main/scala/net/psforever/objects/Session.scala b/common/src/main/scala/net/psforever/objects/Session.scala new file mode 100644 index 00000000..885a1d01 --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/Session.scala @@ -0,0 +1,17 @@ +package net.psforever.objects + +import net.psforever.objects.zones.{Zone, Zoning} +import net.psforever.packet.game.DeadState + +case class Session( + id: Long = 0, + zone: Zone = Zone.Nowhere, + account: Account = null, + player: Player = null, + avatar: Avatar = null, + admin: Boolean = false, + zoningType: Zoning.Method.Value = Zoning.Method.None, + deadState: DeadState.Value = DeadState.Alive, + speed: Float = 1.0f, + flying: Boolean = false +) diff --git a/common/src/main/scala/net/psforever/types/ChatMessageType.scala b/common/src/main/scala/net/psforever/types/ChatMessageType.scala index aea7f4e7..603023e7 100644 --- a/common/src/main/scala/net/psforever/types/ChatMessageType.scala +++ b/common/src/main/scala/net/psforever/types/ChatMessageType.scala @@ -63,8 +63,8 @@ object ChatMessageType extends Enumeration { CMT_GMTELL, // /gmtell (actually causes normal /tell 0x20 when not a gm???) CMT_NOTE, // /note CMT_GMBROADCASTPOPUP, // /gmpopup - U_CMT_GMTELLFROM, // ??? Recipient of /gmtell? - U_CMT_TELLFROM, // ??? Recipient of /t? + U_CMT_GMTELLFROM, // Acknowledgement of /gmtell for sender + U_CMT_TELLFROM, // Acknowledgement of /tell for sender UNK_45, // ??? empty CMT_CULLWATERMARK, // ??? This actually causes the client to ping back to the server with some stringified numbers "80 120" (with the same 46 chatmsg causing infinite loop?) - may be incorrect decoding CMT_INSTANTACTION, // /instantaction OR /ia diff --git a/pslogin/src/main/scala/Config.scala b/common/src/main/scala/net/psforever/util/Config.scala similarity index 87% rename from pslogin/src/main/scala/Config.scala rename to common/src/main/scala/net/psforever/util/Config.scala index 4455b4c8..e89600ce 100644 --- a/pslogin/src/main/scala/Config.scala +++ b/common/src/main/scala/net/psforever/util/Config.scala @@ -1,25 +1,24 @@ -package net.psforever.pslogin +package net.psforever.util import java.nio.file.Paths import com.typesafe.config.{Config => TypesafeConfig} - -import scala.concurrent.duration._ -import net.psforever.packet.game.ServerType -import pureconfig.{ConfigConvert, ConfigSource} -import pureconfig.ConfigConvert.{viaNonEmptyStringOpt} import enumeratum.values.{IntEnum, IntEnumEntry} -import pureconfig.generic.auto._ // intellij: this is not unused - +import net.psforever.packet.game.ServerType +import pureconfig.ConfigConvert.viaNonEmptyStringOpt +import pureconfig.ConfigReader.Result +import pureconfig.{ConfigConvert, ConfigSource} +import scala.concurrent.duration._ import scala.reflect.ClassTag +import pureconfig.generic.auto._ // intellij: this is not unused object Config { // prog.home is defined when we are running from SBT pack val directory: String = System.getProperty("prog.home") match { case null => - Paths.get("config").toAbsolutePath().toString() + Paths.get("config").toAbsolutePath.toString case home => - Paths.get(home, "config").toAbsolutePath().toString() + Paths.get(home, "config").toAbsolutePath.toString } implicit def enumeratumIntConfigConvert[A <: IntEnumEntry](implicit @@ -42,7 +41,7 @@ object Config { ConfigSource.defaultApplication } - val result = source.load[AppConfig] + val result: Result[AppConfig] = source.load[AppConfig] // Raw config object - prefer app when possible lazy val config: TypesafeConfig = source.config().toOption.get @@ -88,7 +87,7 @@ case class DatabaseConfig( database: String, sslmode: String ) { - def toJdbc = s"jdbc:postgresql://${host}:${port}/${database}" + def toJdbc = s"jdbc:postgresql://$host:$port/$database" } case class AntiCheatConfig( diff --git a/pslogin/src/main/scala/Database.scala b/common/src/main/scala/net/psforever/util/Database.scala similarity index 89% rename from pslogin/src/main/scala/Database.scala rename to common/src/main/scala/net/psforever/util/Database.scala index 1e406518..dce3d563 100644 --- a/pslogin/src/main/scala/Database.scala +++ b/common/src/main/scala/net/psforever/util/Database.scala @@ -1,7 +1,6 @@ -package net.psforever.pslogin +package net.psforever.util -import io.getquill.PostgresJAsyncContext -import io.getquill.SnakeCase +import io.getquill.{PostgresJAsyncContext, SnakeCase} import net.psforever.persistence object Database { diff --git a/pslogin/src/main/scala/csr/CSRZoneImpl.scala b/common/src/main/scala/net/psforever/util/PointOfInterest.scala similarity index 84% rename from pslogin/src/main/scala/csr/CSRZoneImpl.scala rename to common/src/main/scala/net/psforever/util/PointOfInterest.scala index c3c20ebc..e6e70866 100644 --- a/pslogin/src/main/scala/csr/CSRZoneImpl.scala +++ b/common/src/main/scala/net/psforever/util/PointOfInterest.scala @@ -1,13 +1,10 @@ -package net.psforever.pslogin.csr +package net.psforever.util import net.psforever.types.Vector3 import scala.collection.mutable import scala.util.Random -/* -The following is STILL for development and fun. - */ /** * A crude representation of the information needed to describe a continent (hitherto, a "zone"). * The information is mainly catered to the simulation of the CSR commands `/zone` and `/warp`. @@ -16,7 +13,7 @@ The following is STILL for development and fun. * @param map the map name of the zone (this map is loaded) * @param zonename the zone's internal name */ -class CSRZoneImpl(val alias: String, val map: String, val zonename: String) { +class PointOfInterest(val alias: String, val map: String, val zonename: String) { /** * A listing of warpgates, geowarps, and island warpgates in this zone. @@ -33,49 +30,49 @@ class CSRZoneImpl(val alias: String, val map: String, val zonename: String) { private val locations: mutable.HashMap[String, Vector3] = mutable.HashMap() } -object CSRZoneImpl { +object PointOfInterest { /** * A listing of all zones that can be visited by their internal name. * The keys in this map should be directly usable by the `/zone` command. */ - private val zones = Map[String, CSRZoneImpl]( - "z1" -> CSRZoneImpl("Solsar", "map01", "z1"), - "z2" -> CSRZoneImpl("Hossin", "map02", "z2"), - "z3" -> CSRZoneImpl("Cyssor", "map03", "z3"), - "z4" -> CSRZoneImpl("Ishundar", "map04", "z4"), - "z5" -> CSRZoneImpl("Forseral", "map05", "z5"), - "z6" -> CSRZoneImpl("Ceryshen", "map06", "z6"), - "z7" -> CSRZoneImpl("Esamir", "map07", "z7"), - "z8" -> CSRZoneImpl("Oshur", "map08", "z8"), - "z9" -> CSRZoneImpl("Searhus", "map09", "z9"), - "z10" -> CSRZoneImpl("Amerish", "map10", "z10"), - "home1" -> CSRZoneImpl("NC Sanctuary", "map11", "home1"), - "home2" -> CSRZoneImpl("TR Sanctuary", "map12", "home2"), - "home3" -> CSRZoneImpl("VS Sanctuary", "map13", "home3"), - "tzshtr" -> CSRZoneImpl("VR Shooting Range TR", "map14", "tzshtr"), - "tzdrtr" -> CSRZoneImpl("VR Driving Range TR", "map15", "tzdrtr"), - "tzcotr" -> CSRZoneImpl("VR Combat Zone TR", "map16", "tzcotr"), - "tzshvs" -> CSRZoneImpl("VR Shooting Range VS", "map14", "tzshvs"), - "tzdrvs" -> CSRZoneImpl("VR Driving Range VS", "map15", "tzdrvs"), - "tzcovs" -> CSRZoneImpl("VR Combat Zone VS", "map16", "tzcovs"), - "tzshnc" -> CSRZoneImpl("VR Shooting Range NC", "map14", "tzshnc"), - "tzdrnc" -> CSRZoneImpl("VR Driving Range NC", "map15", "tzdrnc"), - "tzconc" -> CSRZoneImpl("VR Combat Zone NC", "map16", "tzconc"), - "c1" -> CSRZoneImpl("Supai", "ugd01", "c1"), - "c2" -> CSRZoneImpl("Hunhau", "ugd02", "c2"), - "c3" -> CSRZoneImpl("Adlivun", "ugd03", "c3"), - "c4" -> CSRZoneImpl("Byblos", "ugd04", "c4"), - "c5" -> CSRZoneImpl("Annwn", "ugd05", "c5"), - "c6" -> CSRZoneImpl("Drugaskan", "ugd06", "c6"), - "i4" -> CSRZoneImpl("Nexus", "map96", "i4"), - "i3" -> CSRZoneImpl("Desolation", "map97", "i3"), - "i2" -> CSRZoneImpl("Ascension", "map98", "i2"), - "i1" -> CSRZoneImpl("Extinction", "map99", "i1"), - "homebo" -> CSRZoneImpl("Black_ops_hq", "Black_ops_hq", "homebo"), - "station1" -> CSRZoneImpl("TR Station", "Station1", "station1"), - "station2" -> CSRZoneImpl("NC Station", "Station2", "station2"), - "station3" -> CSRZoneImpl("VS Station", "Station3", "station3") + private val zones = Map[String, PointOfInterest]( + "z1" -> PointOfInterest("Solsar", "map01", "z1"), + "z2" -> PointOfInterest("Hossin", "map02", "z2"), + "z3" -> PointOfInterest("Cyssor", "map03", "z3"), + "z4" -> PointOfInterest("Ishundar", "map04", "z4"), + "z5" -> PointOfInterest("Forseral", "map05", "z5"), + "z6" -> PointOfInterest("Ceryshen", "map06", "z6"), + "z7" -> PointOfInterest("Esamir", "map07", "z7"), + "z8" -> PointOfInterest("Oshur", "map08", "z8"), + "z9" -> PointOfInterest("Searhus", "map09", "z9"), + "z10" -> PointOfInterest("Amerish", "map10", "z10"), + "home1" -> PointOfInterest("NC Sanctuary", "map11", "home1"), + "home2" -> PointOfInterest("TR Sanctuary", "map12", "home2"), + "home3" -> PointOfInterest("VS Sanctuary", "map13", "home3"), + "tzshtr" -> PointOfInterest("VR Shooting Range TR", "map14", "tzshtr"), + "tzdrtr" -> PointOfInterest("VR Driving Range TR", "map15", "tzdrtr"), + "tzcotr" -> PointOfInterest("VR Combat Zone TR", "map16", "tzcotr"), + "tzshvs" -> PointOfInterest("VR Shooting Range VS", "map14", "tzshvs"), + "tzdrvs" -> PointOfInterest("VR Driving Range VS", "map15", "tzdrvs"), + "tzcovs" -> PointOfInterest("VR Combat Zone VS", "map16", "tzcovs"), + "tzshnc" -> PointOfInterest("VR Shooting Range NC", "map14", "tzshnc"), + "tzdrnc" -> PointOfInterest("VR Driving Range NC", "map15", "tzdrnc"), + "tzconc" -> PointOfInterest("VR Combat Zone NC", "map16", "tzconc"), + "c1" -> PointOfInterest("Supai", "ugd01", "c1"), + "c2" -> PointOfInterest("Hunhau", "ugd02", "c2"), + "c3" -> PointOfInterest("Adlivun", "ugd03", "c3"), + "c4" -> PointOfInterest("Byblos", "ugd04", "c4"), + "c5" -> PointOfInterest("Annwn", "ugd05", "c5"), + "c6" -> PointOfInterest("Drugaskan", "ugd06", "c6"), + "i4" -> PointOfInterest("Nexus", "map96", "i4"), + "i3" -> PointOfInterest("Desolation", "map97", "i3"), + "i2" -> PointOfInterest("Ascension", "map98", "i2"), + "i1" -> PointOfInterest("Extinction", "map99", "i1"), + "homebo" -> PointOfInterest("Black_ops_hq", "Black_ops_hq", "homebo"), + "station1" -> PointOfInterest("TR Station", "Station1", "station1"), + "station2" -> PointOfInterest("NC Station", "Station2", "station2"), + "station3" -> PointOfInterest("VS Station", "Station3", "station3") ) /** @@ -134,14 +131,14 @@ object CSRZoneImpl { * @param map the map name of the zone (this map is loaded) * @param zonename the zone's internal name */ - def apply(alias: String, map: String, zonename: String): CSRZoneImpl = new CSRZoneImpl(alias, map, zonename) + def apply(alias: String, map: String, zonename: String): PointOfInterest = new PointOfInterest(alias, map, zonename) /** * Get a valid `CSRZone`'s information. * @param zoneId a name that describes the zone and should be searchable * @return the `CSRZone`, or `None` */ - def get(zoneId: String): Option[CSRZoneImpl] = { + def get(zoneId: String): Option[PointOfInterest] = { var zId = zoneId.toLowerCase if (alias.get(zId).isDefined) zId = alias(zId) @@ -151,16 +148,19 @@ object CSRZoneImpl { /** * Get a location within the `CSRZone`. * The location should be a facility or a warpgate or interesting. - * @param zone the `CSRZone` - * @param locId a name that describes a known location in the provided `CSRZone` and is searchable + * @param zoneId the `CSRZone` + * @param locationId a name that describes a known location in the provided `CSRZone` and is searchable * @return the coordinates of that location, or None */ - def getWarpLocation(zone: CSRZoneImpl, locId: String): Option[Vector3] = { - val low_locId = locId.toLowerCase - var location = zone.locations.get(low_locId) - if (location.isEmpty) - location = zone.gates.get(low_locId) - location + def getWarpLocation(zoneId: String, locationId: String): Option[Vector3] = { + get(zoneId) match { + case Some(poi) => + poi.locations.get(locationId) match { + case Some(position) => Some(position) + case None => poi.gates.get(locationId) + } + case None => None + } } /** @@ -169,7 +169,7 @@ object CSRZoneImpl { * @param gateId a name that describes a known warpgate in the provided `CSRZone` and is searchable * @return the coordinates of that warpgate, or None */ - def getWarpgate(zone: CSRZoneImpl, gateId: String): Option[Vector3] = { + def getWarpgate(zone: PointOfInterest, gateId: String): Option[Vector3] = { zone.gates.get(gateId.toLowerCase) } @@ -178,7 +178,7 @@ object CSRZoneImpl { * @return all of the zonenames */ def list: String = { - "zonenames: z1 - z10, home1 - home3, tzshnc, tzdrnc, tzconc, tzshtr, tzdrtr, tzcotr, tzshvs, tzdrvs, tzcovs, c1 - c6, i1 - i4; zones are also aliased to their continent name" + "zone names: z1 - z10, home1 - home3, tzshnc, tzdrnc, tzconc, tzshtr, tzdrtr, tzcotr, tzshvs, tzdrvs, tzcovs, c1 - c6, i1 - i4; zones are also aliased to their continent name" } /** @@ -186,7 +186,7 @@ object CSRZoneImpl { * @param zone the `CSRZone` * @return all of the location keys */ - def listLocations(zone: CSRZoneImpl): String = { + def listLocations(zone: PointOfInterest): String = { var out: String = "warps: " if (zone.locations.nonEmpty) { out += zone.locations.keys.toArray.sorted.mkString(", ") @@ -200,7 +200,7 @@ object CSRZoneImpl { * @param zone the `CSRZone` * @return all of the warpgate keys */ - def listWarpgates(zone: CSRZoneImpl): String = { + def listWarpgates(zone: PointOfInterest): String = { var out: String = "gatenames: " if (zone.gates.isEmpty) out += "none" @@ -214,7 +214,7 @@ object CSRZoneImpl { * @param zone the `CSRZone` * @return the coordinates of the spawn point */ - def selectRandom(zone: CSRZoneImpl): Vector3 = { + def selectRandom(zone: PointOfInterest): Vector3 = { var outlets = zone.locations //random location? if (outlets.nonEmpty) { return outlets.values.toArray.apply(rand.nextInt(outlets.size)) diff --git a/pslogin/src/main/scala/Maps.scala b/common/src/main/scala/net/psforever/zones/Maps.scala similarity index 96% rename from pslogin/src/main/scala/Maps.scala rename to common/src/main/scala/net/psforever/zones/Maps.scala index 801b67a2..770576ee 100644 --- a/pslogin/src/main/scala/Maps.scala +++ b/common/src/main/scala/net/psforever/zones/Maps.scala @@ -1,9 +1,9 @@ -package net.psforever.pslogin +package net.psforever.zones import net.psforever.objects.LocalProjectile import net.psforever.objects.ballistics.Projectile import net.psforever.objects.zones.ZoneMap -import zonemaps._ +import net.psforever.zones.zonemaps._ import scala.concurrent.Future import scala.util.{Failure, Success} diff --git a/pslogin/src/main/scala/Zones.scala b/common/src/main/scala/net/psforever/zones/Zones.scala similarity index 99% rename from pslogin/src/main/scala/Zones.scala rename to common/src/main/scala/net/psforever/zones/Zones.scala index 6029be2d..d2cccbc5 100644 --- a/pslogin/src/main/scala/Zones.scala +++ b/common/src/main/scala/net/psforever/zones/Zones.scala @@ -1,4 +1,4 @@ -package net.psforever.pslogin +package net.psforever.zones import akka.actor.ActorContext import net.psforever.objects.GlobalDefinitions @@ -6,10 +6,9 @@ import net.psforever.objects.serverobject.resourcesilo.ResourceSilo import net.psforever.objects.serverobject.structures.WarpGate import net.psforever.objects.zones.Zone import net.psforever.types.PlanetSideEmpire - import scala.concurrent.duration._ -import scala.concurrent.Await import scala.collection.immutable.HashMap +import scala.concurrent.Await object Zones { val zones: HashMap[String, Zone] = HashMap( @@ -547,8 +546,8 @@ object Zones { * @return the duration */ def StandardTimeRules(defender: SourceEntry, attacker: SourceEntry): FiniteDuration = { - import net.psforever.objects.ballistics._ import net.psforever.objects.GlobalDefinitions + import net.psforever.objects.ballistics._ if (attacker.Faction == defender.Faction) { 0 seconds } else { diff --git a/pslogin/src/main/scala/zonemaps/Map01.scala b/common/src/main/scala/net/psforever/zones/zonemaps/Map01.scala similarity index 99% rename from pslogin/src/main/scala/zonemaps/Map01.scala rename to common/src/main/scala/net/psforever/zones/zonemaps/Map01.scala index 8ff2b91a..001e2585 100644 --- a/pslogin/src/main/scala/zonemaps/Map01.scala +++ b/common/src/main/scala/net/psforever/zones/zonemaps/Map01.scala @@ -1,4 +1,4 @@ -package net.psforever.pslogin.zonemaps +package net.psforever.zones.zonemaps import net.psforever.objects.GlobalDefinitions._ import net.psforever.objects.serverobject.doors.Door diff --git a/pslogin/src/main/scala/zonemaps/Map02.scala b/common/src/main/scala/net/psforever/zones/zonemaps/Map02.scala similarity index 99% rename from pslogin/src/main/scala/zonemaps/Map02.scala rename to common/src/main/scala/net/psforever/zones/zonemaps/Map02.scala index e479bcd8..33f0c620 100644 --- a/pslogin/src/main/scala/zonemaps/Map02.scala +++ b/common/src/main/scala/net/psforever/zones/zonemaps/Map02.scala @@ -1,4 +1,4 @@ -package net.psforever.pslogin.zonemaps +package net.psforever.zones.zonemaps import net.psforever.objects.GlobalDefinitions._ import net.psforever.objects.serverobject.doors.Door diff --git a/pslogin/src/main/scala/zonemaps/Map03.scala b/common/src/main/scala/net/psforever/zones/zonemaps/Map03.scala similarity index 99% rename from pslogin/src/main/scala/zonemaps/Map03.scala rename to common/src/main/scala/net/psforever/zones/zonemaps/Map03.scala index 3c8d0513..6917b82a 100644 --- a/pslogin/src/main/scala/zonemaps/Map03.scala +++ b/common/src/main/scala/net/psforever/zones/zonemaps/Map03.scala @@ -1,4 +1,4 @@ -package net.psforever.pslogin.zonemaps +package net.psforever.zones.zonemaps import net.psforever.objects.GlobalDefinitions._ import net.psforever.objects.serverobject.doors.Door diff --git a/pslogin/src/main/scala/zonemaps/Map04.scala b/common/src/main/scala/net/psforever/zones/zonemaps/Map04.scala similarity index 99% rename from pslogin/src/main/scala/zonemaps/Map04.scala rename to common/src/main/scala/net/psforever/zones/zonemaps/Map04.scala index 277a6d20..937f9ff6 100644 --- a/pslogin/src/main/scala/zonemaps/Map04.scala +++ b/common/src/main/scala/net/psforever/zones/zonemaps/Map04.scala @@ -1,4 +1,4 @@ -package net.psforever.pslogin.zonemaps +package net.psforever.zones.zonemaps import net.psforever.objects.GlobalDefinitions._ import net.psforever.objects.serverobject.doors.Door diff --git a/pslogin/src/main/scala/zonemaps/Map05.scala b/common/src/main/scala/net/psforever/zones/zonemaps/Map05.scala similarity index 99% rename from pslogin/src/main/scala/zonemaps/Map05.scala rename to common/src/main/scala/net/psforever/zones/zonemaps/Map05.scala index 492e857e..20b1fb14 100644 --- a/pslogin/src/main/scala/zonemaps/Map05.scala +++ b/common/src/main/scala/net/psforever/zones/zonemaps/Map05.scala @@ -1,4 +1,4 @@ -package net.psforever.pslogin.zonemaps +package net.psforever.zones.zonemaps import net.psforever.objects.GlobalDefinitions._ import net.psforever.objects.serverobject.doors.Door diff --git a/pslogin/src/main/scala/zonemaps/Map06.scala b/common/src/main/scala/net/psforever/zones/zonemaps/Map06.scala similarity index 99% rename from pslogin/src/main/scala/zonemaps/Map06.scala rename to common/src/main/scala/net/psforever/zones/zonemaps/Map06.scala index 41913d5c..8a7b7e6b 100644 --- a/pslogin/src/main/scala/zonemaps/Map06.scala +++ b/common/src/main/scala/net/psforever/zones/zonemaps/Map06.scala @@ -1,4 +1,4 @@ -package net.psforever.pslogin.zonemaps +package net.psforever.zones.zonemaps import net.psforever.objects.GlobalDefinitions._ import net.psforever.objects.serverobject.doors.Door diff --git a/pslogin/src/main/scala/zonemaps/Map07.scala b/common/src/main/scala/net/psforever/zones/zonemaps/Map07.scala similarity index 99% rename from pslogin/src/main/scala/zonemaps/Map07.scala rename to common/src/main/scala/net/psforever/zones/zonemaps/Map07.scala index 2c88f59e..eed3bc7d 100644 --- a/pslogin/src/main/scala/zonemaps/Map07.scala +++ b/common/src/main/scala/net/psforever/zones/zonemaps/Map07.scala @@ -1,4 +1,4 @@ -package net.psforever.pslogin.zonemaps +package net.psforever.zones.zonemaps import net.psforever.objects.GlobalDefinitions._ import net.psforever.objects.serverobject.doors.Door diff --git a/pslogin/src/main/scala/zonemaps/Map08.scala b/common/src/main/scala/net/psforever/zones/zonemaps/Map08.scala similarity index 99% rename from pslogin/src/main/scala/zonemaps/Map08.scala rename to common/src/main/scala/net/psforever/zones/zonemaps/Map08.scala index fdbc152a..ce4b8d01 100644 --- a/pslogin/src/main/scala/zonemaps/Map08.scala +++ b/common/src/main/scala/net/psforever/zones/zonemaps/Map08.scala @@ -1,4 +1,4 @@ -package net.psforever.pslogin.zonemaps +package net.psforever.zones.zonemaps import net.psforever.objects.GlobalDefinitions._ import net.psforever.objects.serverobject.doors.Door diff --git a/pslogin/src/main/scala/zonemaps/Map09.scala b/common/src/main/scala/net/psforever/zones/zonemaps/Map09.scala similarity index 99% rename from pslogin/src/main/scala/zonemaps/Map09.scala rename to common/src/main/scala/net/psforever/zones/zonemaps/Map09.scala index cafef4e5..59994d89 100644 --- a/pslogin/src/main/scala/zonemaps/Map09.scala +++ b/common/src/main/scala/net/psforever/zones/zonemaps/Map09.scala @@ -1,4 +1,4 @@ -package net.psforever.pslogin.zonemaps +package net.psforever.zones.zonemaps import net.psforever.objects.GlobalDefinitions._ import net.psforever.objects.serverobject.doors.Door diff --git a/pslogin/src/main/scala/zonemaps/Map10.scala b/common/src/main/scala/net/psforever/zones/zonemaps/Map10.scala similarity index 99% rename from pslogin/src/main/scala/zonemaps/Map10.scala rename to common/src/main/scala/net/psforever/zones/zonemaps/Map10.scala index 4dfc8ea5..ccb15318 100644 --- a/pslogin/src/main/scala/zonemaps/Map10.scala +++ b/common/src/main/scala/net/psforever/zones/zonemaps/Map10.scala @@ -1,4 +1,4 @@ -package net.psforever.pslogin.zonemaps +package net.psforever.zones.zonemaps import net.psforever.objects.GlobalDefinitions._ import net.psforever.objects.serverobject.doors.Door diff --git a/pslogin/src/main/scala/zonemaps/Map11.scala b/common/src/main/scala/net/psforever/zones/zonemaps/Map11.scala similarity index 99% rename from pslogin/src/main/scala/zonemaps/Map11.scala rename to common/src/main/scala/net/psforever/zones/zonemaps/Map11.scala index 6a1e7f89..0d431c3b 100644 --- a/pslogin/src/main/scala/zonemaps/Map11.scala +++ b/common/src/main/scala/net/psforever/zones/zonemaps/Map11.scala @@ -1,4 +1,4 @@ -package net.psforever.pslogin.zonemaps +package net.psforever.zones.zonemaps import net.psforever.objects.GlobalDefinitions._ import net.psforever.objects.serverobject.doors.Door diff --git a/pslogin/src/main/scala/zonemaps/Map12.scala b/common/src/main/scala/net/psforever/zones/zonemaps/Map12.scala similarity index 99% rename from pslogin/src/main/scala/zonemaps/Map12.scala rename to common/src/main/scala/net/psforever/zones/zonemaps/Map12.scala index f9203a99..71c6a715 100644 --- a/pslogin/src/main/scala/zonemaps/Map12.scala +++ b/common/src/main/scala/net/psforever/zones/zonemaps/Map12.scala @@ -1,4 +1,4 @@ -package net.psforever.pslogin.zonemaps +package net.psforever.zones.zonemaps import net.psforever.objects.GlobalDefinitions._ import net.psforever.objects.serverobject.doors.Door diff --git a/pslogin/src/main/scala/zonemaps/Map13.scala b/common/src/main/scala/net/psforever/zones/zonemaps/Map13.scala similarity index 99% rename from pslogin/src/main/scala/zonemaps/Map13.scala rename to common/src/main/scala/net/psforever/zones/zonemaps/Map13.scala index 302b0f48..29eb4ad6 100644 --- a/pslogin/src/main/scala/zonemaps/Map13.scala +++ b/common/src/main/scala/net/psforever/zones/zonemaps/Map13.scala @@ -1,4 +1,4 @@ -package net.psforever.pslogin.zonemaps +package net.psforever.zones.zonemaps import net.psforever.objects.GlobalDefinitions._ import net.psforever.objects.serverobject.doors.Door diff --git a/pslogin/src/main/scala/zonemaps/Map96.scala b/common/src/main/scala/net/psforever/zones/zonemaps/Map96.scala similarity index 99% rename from pslogin/src/main/scala/zonemaps/Map96.scala rename to common/src/main/scala/net/psforever/zones/zonemaps/Map96.scala index 7906dd90..c3e13377 100644 --- a/pslogin/src/main/scala/zonemaps/Map96.scala +++ b/common/src/main/scala/net/psforever/zones/zonemaps/Map96.scala @@ -1,4 +1,4 @@ -package net.psforever.pslogin.zonemaps +package net.psforever.zones.zonemaps import net.psforever.objects.GlobalDefinitions._ import net.psforever.objects.serverobject.doors.Door diff --git a/pslogin/src/main/scala/zonemaps/Map97.scala b/common/src/main/scala/net/psforever/zones/zonemaps/Map97.scala similarity index 99% rename from pslogin/src/main/scala/zonemaps/Map97.scala rename to common/src/main/scala/net/psforever/zones/zonemaps/Map97.scala index 004becdd..a07a0f93 100644 --- a/pslogin/src/main/scala/zonemaps/Map97.scala +++ b/common/src/main/scala/net/psforever/zones/zonemaps/Map97.scala @@ -1,4 +1,4 @@ -package net.psforever.pslogin.zonemaps +package net.psforever.zones.zonemaps import net.psforever.objects.GlobalDefinitions._ import net.psforever.objects.serverobject.doors.Door diff --git a/pslogin/src/main/scala/zonemaps/Map98.scala b/common/src/main/scala/net/psforever/zones/zonemaps/Map98.scala similarity index 99% rename from pslogin/src/main/scala/zonemaps/Map98.scala rename to common/src/main/scala/net/psforever/zones/zonemaps/Map98.scala index 11102374..34f35559 100644 --- a/pslogin/src/main/scala/zonemaps/Map98.scala +++ b/common/src/main/scala/net/psforever/zones/zonemaps/Map98.scala @@ -1,4 +1,4 @@ -package net.psforever.pslogin.zonemaps +package net.psforever.zones.zonemaps import net.psforever.objects.GlobalDefinitions._ import net.psforever.objects.serverobject.doors.Door diff --git a/pslogin/src/main/scala/zonemaps/Map99.scala b/common/src/main/scala/net/psforever/zones/zonemaps/Map99.scala similarity index 99% rename from pslogin/src/main/scala/zonemaps/Map99.scala rename to common/src/main/scala/net/psforever/zones/zonemaps/Map99.scala index 1e59ed04..317169e8 100644 --- a/pslogin/src/main/scala/zonemaps/Map99.scala +++ b/common/src/main/scala/net/psforever/zones/zonemaps/Map99.scala @@ -1,4 +1,4 @@ -package net.psforever.pslogin.zonemaps +package net.psforever.zones.zonemaps import net.psforever.objects.GlobalDefinitions._ import net.psforever.objects.serverobject.doors.Door diff --git a/pslogin/src/main/scala/zonemaps/Ugd01.scala b/common/src/main/scala/net/psforever/zones/zonemaps/Ugd01.scala similarity index 99% rename from pslogin/src/main/scala/zonemaps/Ugd01.scala rename to common/src/main/scala/net/psforever/zones/zonemaps/Ugd01.scala index 6b94cd07..12227cf5 100644 --- a/pslogin/src/main/scala/zonemaps/Ugd01.scala +++ b/common/src/main/scala/net/psforever/zones/zonemaps/Ugd01.scala @@ -1,4 +1,4 @@ -package net.psforever.pslogin.zonemaps +package net.psforever.zones.zonemaps import net.psforever.objects.GlobalDefinitions._ import net.psforever.objects.serverobject.doors.Door diff --git a/pslogin/src/main/scala/zonemaps/Ugd02.scala b/common/src/main/scala/net/psforever/zones/zonemaps/Ugd02.scala similarity index 99% rename from pslogin/src/main/scala/zonemaps/Ugd02.scala rename to common/src/main/scala/net/psforever/zones/zonemaps/Ugd02.scala index eda42b99..d3dbb5ad 100644 --- a/pslogin/src/main/scala/zonemaps/Ugd02.scala +++ b/common/src/main/scala/net/psforever/zones/zonemaps/Ugd02.scala @@ -1,4 +1,4 @@ -package net.psforever.pslogin.zonemaps +package net.psforever.zones.zonemaps import net.psforever.objects.GlobalDefinitions._ import net.psforever.objects.serverobject.doors.Door diff --git a/pslogin/src/main/scala/zonemaps/Ugd03.scala b/common/src/main/scala/net/psforever/zones/zonemaps/Ugd03.scala similarity index 99% rename from pslogin/src/main/scala/zonemaps/Ugd03.scala rename to common/src/main/scala/net/psforever/zones/zonemaps/Ugd03.scala index ba76df73..6558c683 100644 --- a/pslogin/src/main/scala/zonemaps/Ugd03.scala +++ b/common/src/main/scala/net/psforever/zones/zonemaps/Ugd03.scala @@ -1,4 +1,4 @@ -package net.psforever.pslogin.zonemaps +package net.psforever.zones.zonemaps import net.psforever.objects.GlobalDefinitions._ import net.psforever.objects.serverobject.doors.Door diff --git a/pslogin/src/main/scala/zonemaps/Ugd04.scala b/common/src/main/scala/net/psforever/zones/zonemaps/Ugd04.scala similarity index 99% rename from pslogin/src/main/scala/zonemaps/Ugd04.scala rename to common/src/main/scala/net/psforever/zones/zonemaps/Ugd04.scala index 2308559d..fbbcd316 100644 --- a/pslogin/src/main/scala/zonemaps/Ugd04.scala +++ b/common/src/main/scala/net/psforever/zones/zonemaps/Ugd04.scala @@ -1,4 +1,4 @@ -package net.psforever.pslogin.zonemaps +package net.psforever.zones.zonemaps import net.psforever.objects.GlobalDefinitions._ import net.psforever.objects.serverobject.doors.Door diff --git a/pslogin/src/main/scala/zonemaps/Ugd05.scala b/common/src/main/scala/net/psforever/zones/zonemaps/Ugd05.scala similarity index 99% rename from pslogin/src/main/scala/zonemaps/Ugd05.scala rename to common/src/main/scala/net/psforever/zones/zonemaps/Ugd05.scala index c2586ea3..89e2eba8 100644 --- a/pslogin/src/main/scala/zonemaps/Ugd05.scala +++ b/common/src/main/scala/net/psforever/zones/zonemaps/Ugd05.scala @@ -1,4 +1,4 @@ -package net.psforever.pslogin.zonemaps +package net.psforever.zones.zonemaps import net.psforever.objects.GlobalDefinitions._ import net.psforever.objects.serverobject.doors.Door diff --git a/pslogin/src/main/scala/zonemaps/Ugd06.scala b/common/src/main/scala/net/psforever/zones/zonemaps/Ugd06.scala similarity index 99% rename from pslogin/src/main/scala/zonemaps/Ugd06.scala rename to common/src/main/scala/net/psforever/zones/zonemaps/Ugd06.scala index de3d0c89..2b188ee9 100644 --- a/pslogin/src/main/scala/zonemaps/Ugd06.scala +++ b/common/src/main/scala/net/psforever/zones/zonemaps/Ugd06.scala @@ -1,4 +1,4 @@ -package net.psforever.pslogin.zonemaps +package net.psforever.zones.zonemaps import net.psforever.objects.GlobalDefinitions._ import net.psforever.objects.serverobject.doors.Door diff --git a/common/src/main/scala/services/Service.scala b/common/src/main/scala/services/Service.scala index c06dac01..f5cdf0d2 100644 --- a/common/src/main/scala/services/Service.scala +++ b/common/src/main/scala/services/Service.scala @@ -15,14 +15,14 @@ object Service { } trait GenericEventBusMsg { - def toChannel: String + def channel: String } class GenericEventBus[A <: GenericEventBusMsg] extends ActorEventBus with SubchannelClassification { type Event = A type Classifier = String - protected def classify(event: Event): Classifier = event.toChannel + protected def classify(event: Event): Classifier = event.channel protected def subclassification = new Subclassification[Classifier] { diff --git a/common/src/main/scala/services/avatar/AvatarServiceResponse.scala b/common/src/main/scala/services/avatar/AvatarServiceResponse.scala index c38a4f8b..a6ba345f 100644 --- a/common/src/main/scala/services/avatar/AvatarServiceResponse.scala +++ b/common/src/main/scala/services/avatar/AvatarServiceResponse.scala @@ -12,7 +12,7 @@ import net.psforever.types.{ExoSuitType, PlanetSideEmpire, PlanetSideGUID, Trans import services.GenericEventBusMsg final case class AvatarServiceResponse( - toChannel: String, + channel: String, avatar_guid: PlanetSideGUID, replyMessage: AvatarResponse.Response ) extends GenericEventBusMsg diff --git a/common/src/main/scala/services/chat/ChatAction.scala b/common/src/main/scala/services/chat/ChatAction.scala deleted file mode 100644 index c56e40f8..00000000 --- a/common/src/main/scala/services/chat/ChatAction.scala +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (c) 2017 PSForever -package services.chat - -import net.psforever.objects.zones.Zone -import net.psforever.packet.game.ChatMsg -import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID, Vector3} - -object ChatAction { - sealed trait Action - - final case class Local( - player_guid: PlanetSideGUID, - player_name: String, - continent: Zone, - player_pos: Vector3, - player_faction: PlanetSideEmpire.Value, - msg: ChatMsg - ) extends Action - final case class Tell(player_guid: PlanetSideGUID, player_name: String, msg: ChatMsg) extends Action - final case class Broadcast( - player_guid: PlanetSideGUID, - player_name: String, - continent: Zone, - player_pos: Vector3, - player_faction: PlanetSideEmpire.Value, - msg: ChatMsg - ) extends Action - final case class Voice( - player_guid: PlanetSideGUID, - player_name: String, - continent: Zone, - player_pos: Vector3, - player_faction: PlanetSideEmpire.Value, - msg: ChatMsg - ) extends Action - final case class Note(player_guid: PlanetSideGUID, player_name: String, msg: ChatMsg) extends Action - final case class Squad( - player_guid: PlanetSideGUID, - player_name: String, - continent: Zone, - player_pos: Vector3, - player_faction: PlanetSideEmpire.Value, - msg: ChatMsg - ) extends Action - final case class Platoon( - player_guid: PlanetSideGUID, - player_name: String, - continent: Zone, - player_pos: Vector3, - player_faction: PlanetSideEmpire.Value, - msg: ChatMsg - ) extends Action - final case class Command( - player_guid: PlanetSideGUID, - player_name: String, - continent: Zone, - player_pos: Vector3, - player_faction: PlanetSideEmpire.Value, - msg: ChatMsg - ) extends Action - final case class GM(player_guid: PlanetSideGUID, player_name: String, msg: ChatMsg) extends Action -} diff --git a/common/src/main/scala/services/chat/ChatResponse.scala b/common/src/main/scala/services/chat/ChatResponse.scala deleted file mode 100644 index f618951d..00000000 --- a/common/src/main/scala/services/chat/ChatResponse.scala +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright (c) 2017 PSForever -package services.chat - -import net.psforever.types.{ChatMessageType, PlanetSideGUID} - -object ChatResponse { - sealed trait Response - - final case class Local( - sender: String, - messageType: ChatMessageType.Value, - wideContents: Boolean, - recipient: String, - contents: String, - note: Option[String] - ) extends Response - final case class Tell( - sender: String, - messageType: ChatMessageType.Value, - wideContents: Boolean, - recipient: String, - contents: String, - note: Option[String] - ) extends Response - final case class UTell( - sender: String, - messageType: ChatMessageType.Value, - wideContents: Boolean, - recipient: String, - contents: String, - note: Option[String] - ) extends Response - final case class Broadcast( - messageType: ChatMessageType.Value, - wideContents: Boolean, - recipient: String, - contents: String, - note: Option[String] - ) extends Response - final case class Voice( - messageType: ChatMessageType.Value, - wideContents: Boolean, - recipient: String, - contents: String, - note: Option[String] - ) extends Response - final case class Unk45( - sender: String, - messageType: ChatMessageType.Value, - wideContents: Boolean, - recipient: String, - contents: String, - note: Option[String] - ) extends Response - final case class Squad( - sender: String, - messageType: ChatMessageType.Value, - wideContents: Boolean, - recipient: String, - contents: String, - note: Option[String] - ) extends Response - final case class Platoon( - sender: String, - messageType: ChatMessageType.Value, - wideContents: Boolean, - recipient: String, - contents: String, - note: Option[String] - ) extends Response - final case class Command( - sender: String, - messageType: ChatMessageType.Value, - wideContents: Boolean, - recipient: String, - contents: String, - note: Option[String] - ) extends Response - - final case class Text( - toChannel: String, - avatar_guid: PlanetSideGUID, - personal: Int, - messageType: ChatMessageType.Value, - wideContents: Boolean, - recipient: String, - contents: String, - note: Option[String] - ) -} diff --git a/common/src/main/scala/services/chat/ChatService.scala b/common/src/main/scala/services/chat/ChatService.scala index ec6e51dc..460d070a 100644 --- a/common/src/main/scala/services/chat/ChatService.scala +++ b/common/src/main/scala/services/chat/ChatService.scala @@ -1,227 +1,188 @@ // Copyright (c) 2017 PSForever package services.chat -import akka.actor.Actor -import net.psforever.objects.LivePlayerList +import akka.actor.typed.receptionist.{Receptionist, ServiceKey} +import akka.actor.typed.{ActorRef, Behavior} +import akka.actor.typed.scaladsl.{AbstractBehavior, ActorContext, Behaviors} +import net.psforever.objects.Session import net.psforever.packet.game.ChatMsg -import net.psforever.types.ChatMessageType -import services.{GenericEventBus, Service} +import net.psforever.types.{ChatMessageType, PlanetSideEmpire, PlanetSideGUID} -class ChatService extends Actor { - private[this] val log = org.log4s.getLogger +object ChatService { + val ChatServiceKey: ServiceKey[Command] = ServiceKey[ChatService.Command]("chatService") - override def preStart = { - log.info("Starting....") + def apply(): Behavior[Command] = + Behaviors.setup { context => + context.system.receptionist ! Receptionist.Register(ChatServiceKey, context.self) + new ChatService(context) + } + + sealed trait Command + + final case class JoinChannel(actor: ActorRef[MessageResponse], session: Session, channel: ChatChannel) extends Command + final case class LeaveChannel(actor: ActorRef[MessageResponse], channel: ChatChannel) extends Command + final case class LeaveAllChannels(actor: ActorRef[MessageResponse]) extends Command + + final case class Message(session: Session, message: ChatMsg, channel: ChatChannel) extends Command + final case class MessageResponse(session: Session, message: ChatMsg, channel: ChatChannel) + + trait ChatChannel + object ChatChannel { + // one of the default channels that the player is always subscribed to (local, broadcast, command...) + final case class Default() extends ChatChannel + final case class Squad(guid: PlanetSideGUID) extends ChatChannel } - val ChatEvents = new GenericEventBus[ChatServiceResponse] +} - def receive = { - case Service.Join(channel) => - val path = s"/Chat/$channel" - val who = sender() - log.info(s"$who has joined $path") - ChatEvents.subscribe(who, path) - case Service.Leave(None) => - ChatEvents.unsubscribe(sender()) - case Service.Leave(Some(channel)) => - val path = s"/Chat/$channel" - val who = sender() - log.info(s"$who has left $path") - ChatEvents.unsubscribe(who, path) - case Service.LeaveAll() => - ChatEvents.unsubscribe(sender()) +class ChatService(context: ActorContext[ChatService.Command]) extends AbstractBehavior[ChatService.Command](context) { - case ChatServiceMessage(forChannel, action) => - action match { - case ChatAction.Local(player_guid, player_name, cont, player_pos, player_faction, msg) => // local - ChatEvents.publish( - ChatServiceResponse( - s"/Chat/$forChannel", - player_guid, - player_name, - cont, - player_pos, - player_faction, - 2, - ChatMsg(ChatMessageType.CMT_OPEN, msg.wideContents, player_name, msg.contents, None) - ) - ) - case ChatAction.Tell(player_guid, player_name, msg) => // tell - var good: Boolean = false - LivePlayerList - .WorldPopulation(_ => true) - .foreach(char => { - if (char.name.equalsIgnoreCase(msg.recipient)) { - good = true - } - }) - if (good) { - ChatEvents.publish( - ChatServiceResponse( - s"/Chat/$forChannel", - player_guid, - player_name, - target = 0, - replyMessage = ChatMsg(ChatMessageType.CMT_TELL, msg.wideContents, msg.recipient, msg.contents, None) - ) - ) - ChatEvents.publish( - ChatServiceResponse( - s"/Chat/$forChannel", - player_guid, - player_name, - target = 1, - replyMessage = - ChatMsg(ChatMessageType.U_CMT_TELLFROM, msg.wideContents, msg.recipient, msg.contents, None) - ) - ) - } else { - ChatEvents.publish( - ChatServiceResponse( - s"/Chat/$forChannel", - player_guid, - player_name, - target = 1, - replyMessage = - ChatMsg(ChatMessageType.U_CMT_TELLFROM, msg.wideContents, msg.recipient, msg.contents, None) - ) - ) - ChatEvents.publish( - ChatServiceResponse( - s"/Chat/$forChannel", - player_guid, - player_name, - target = 1, - replyMessage = ChatMsg(ChatMessageType.UNK_45, msg.wideContents, "", "@NoTell_Target", None) - ) - ) - } - case ChatAction.Broadcast(player_guid, player_name, cont, player_pos, player_faction, msg) => // broadcast - ChatEvents.publish( - ChatServiceResponse( - s"/Chat/$forChannel", - player_guid, - player_name, - cont, - player_pos, - player_faction, - 2, - ChatMsg(msg.messageType, msg.wideContents, player_name, msg.contents, None) - ) - ) - case ChatAction.Voice(player_guid, player_name, cont, player_pos, player_faction, msg) => // voice - ChatEvents.publish( - ChatServiceResponse( - s"/Chat/$forChannel", - player_guid, - player_name, - cont, - player_pos, - player_faction, - 2, - ChatMsg(ChatMessageType.CMT_VOICE, false, player_name, msg.contents, None) - ) - ) + import ChatService._ + import ChatMessageType._ - case ChatAction.Note(player_guid, player_name, msg) => // note - ChatEvents.publish( - ChatServiceResponse( - s"/Chat/$forChannel", - player_guid, - player_name, - target = 1, - replyMessage = ChatMsg(ChatMessageType.U_CMT_GMTELLFROM, true, msg.recipient, msg.contents, None) - ) - ) - ChatEvents.publish( - ChatServiceResponse( - s"/Chat/$forChannel", - player_guid, - player_name, - target = 1, - replyMessage = ChatMsg( - ChatMessageType.CMT_GMTELL, - true, - "Server", - "Why do you try to /note ? That's a GM command ! ... Or not, nobody can /note", - None - ) - ) - ) - case ChatAction.Squad(player_guid, player_name, cont, player_pos, player_faction, msg) => // squad - ChatEvents.publish( - ChatServiceResponse( - s"/Chat/$forChannel", - player_guid, - player_name, - cont, - player_pos, - player_faction, - 2, - ChatMsg(ChatMessageType.CMT_SQUAD, msg.wideContents, player_name, msg.contents, None) - ) - ) - case ChatAction.Platoon(player_guid, player_name, cont, player_pos, player_faction, msg) => // platoon - ChatEvents.publish( - ChatServiceResponse( - s"/Chat/$forChannel", - player_guid, - player_name, - cont, - player_pos, - player_faction, - 2, - ChatMsg(ChatMessageType.CMT_PLATOON, msg.wideContents, player_name, msg.contents, None) - ) - ) - case ChatAction.Command(player_guid, player_name, cont, player_pos, player_faction, msg) => // command - ChatEvents.publish( - ChatServiceResponse( - s"/Chat/$forChannel", - player_guid, - player_name, - cont, - player_pos, - player_faction, - 2, - ChatMsg(ChatMessageType.CMT_COMMAND, msg.wideContents, player_name, msg.contents, None) - ) - ) - case ChatAction.GM(player_guid, player_name, msg) => // GM - msg.messageType match { - case ChatMessageType.CMT_SILENCE => - ChatEvents.publish( - ChatServiceResponse( - s"/Chat/$forChannel", - player_guid, - msg.contents, - target = 0, - replyMessage = ChatMsg(ChatMessageType.CMT_SILENCE, true, "", "", None) + private[this] val log = org.log4s.getLogger + var subscriptions: List[JoinChannel] = List[JoinChannel]() + + override def onMessage(msg: Command): Behavior[Command] = { + msg match { + case subscription: JoinChannel => + subscriptions ++= List(subscription) + this + + case LeaveChannel(actor, channel) => + subscriptions = subscriptions.filter { + case JoinChannel(a, _, c) => actor != a && channel != c + } + this + + case LeaveAllChannels(actor) => + subscriptions = subscriptions.filter { + case JoinChannel(a, _, _) => actor != a + } + this + + case Message(session, message, channel) => + (channel, message.messageType) match { + case (ChatChannel.Squad(_), CMT_SQUAD) => + case (ChatChannel.Default(), messageType) if messageType != CMT_SQUAD => + case _ => + log.error(s"invalid chat channel $channel for messageType ${message.messageType}") + return this + } + val subs = subscriptions.filter(_.channel == channel) + message.messageType match { + case CMT_TELL | CMT_GMTELL => + subs.find(_.session.player.Name == session.player.Name).foreach { + case JoinChannel(sender, _, _) => + sender ! MessageResponse( + session, + message.copy(messageType = if (message.messageType == CMT_TELL) U_CMT_TELLFROM else U_CMT_GMTELLFROM), + channel ) - ) -// if(player_guid != PlanetSideGUID(0)) { -// -// val args = msg.contents.split(" ") -// var silence_name : String = "" -// var silence_time : Int = 5 -// if (args.length == 1) { -// silence_name = args(0) -// } -// else if (args.length == 2) { -// silence_name = args(0) -// silence_time = args(1).toInt -// } -// ChatEvents.publish( -// ChatServiceResponse(s"/Chat/$forChannel", player_guid, player_name, target = 1, replyMessage = ChatMsg(ChatMessageType.UNK_45, true, "", silence_name + " silenced for " + silence_time + " min(s)", None)) -// ) -// } - case _ => ; - } - case _ => ; - } + subs.find(_.session.player.Name == message.recipient) match { + case Some(JoinChannel(receiver, _, _)) => + receiver ! MessageResponse(session, message, channel) + case None => + sender ! MessageResponse( + session, + ChatMsg(ChatMessageType.UNK_45, false, "", "@NoTell_Target", None), + channel + ) + } - case msg => - log.info(s"Unhandled message $msg from $sender") + } + + case CMT_SILENCE => + val args = message.contents.split(" ") + val (name, time, error) = (args.lift(0), args.lift(1)) match { + case (Some(name), None) => (Some(name), Some(5), None) + case (Some(name), Some(time)) => + time.toIntOption match { + case Some(time) => + (Some(name), Some(time), None) + case None => + (None, None, Some("bad time format")) + } + case _ => (None, None, None) + } + + val sender = subs.find(_.session.player.Name == session.player.Name) + + (sender, name, time, error) match { + case (Some(sender), Some(name), Some(_), None) => + val recipient = subs.find(_.session.player.Name == name) + recipient match { + case Some(recipient) => + if (recipient.session.player.silenced) { + sender.actor ! MessageResponse( + session, + ChatMsg(UNK_71, true, "", "@silence_disabled_ack", None), + channel + ) + } else { + sender.actor ! MessageResponse( + session, + ChatMsg(UNK_71, true, "", "@silence_enabled_ack", None), + channel + ) + } + recipient.actor ! MessageResponse(session, message, channel) + case None => + sender.actor ! MessageResponse( + session, + ChatMsg(UNK_71, true, "", s"unknown player '$name'", None), + channel + ) + } + case (Some(sender), _, _, error) => + sender.actor ! MessageResponse( + session, + ChatMsg(UNK_71, false, "", error.getOrElse("usage: /silence [