diff --git a/README.md b/README.md index e4431b9a..bfc4eef4 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,7 @@ The Login and World servers require PostgreSQL for persistence. The default database is named `psforever` and the credentials are `psforever:psforever`. To change these, create a configuration file at `config/psforever.conf`. For configuration options and their defaults, see -[`application.conf`](/pslogin/src/main/resources/application.conf). This database user will need +[`application.conf`](/common/src/main/resources/application.conf). The database user will need ALL access to tables, sequences, and functions. The permissions required can be summarized by the SQL below. Loading this in requires access to a graphical tool such as [pgAdmin](https://www.pgadmin.org/download/) (highly recommended) or a PostgreSQL terminal (`psql`) for advanced users. diff --git a/build.sbt b/build.sbt index 29b0a77d..92b2ebe5 100644 --- a/build.sbt +++ b/build.sbt @@ -89,9 +89,8 @@ lazy val psloginPackSettings = Seq( packMain := Map("ps-login" -> "net.psforever.pslogin.PsLogin"), packArchivePrefix := "pslogin", packExtraClasspath := Map("ps-login" -> Seq("${PROG_HOME}/pscrypto-lib", "${PROG_HOME}/config")), - packResourceDir += (baseDirectory.value / "pscrypto-lib" -> "pscrypto-lib"), - packResourceDir += (baseDirectory.value / "config" -> "config"), - packResourceDir += (baseDirectory.value / "pslogin/src/main/resources" -> "config") + packResourceDir += (baseDirectory.value / "pscrypto-lib" -> "pscrypto-lib"), + packResourceDir += (baseDirectory.value / "config" -> "config") ) lazy val root = (project in file(".")) @@ -142,4 +141,4 @@ lazy val decodePackets = (project in file("tools/decode-packets")) lazy val decodePacketsPackSettings = Seq(packMain := Map("psf-decode-packets" -> "DecodePackets")) // Special test configuration for really quiet tests (used in CI) -lazy val QuietTest = config("quiet") extend (Test) +lazy val QuietTest = config("quiet") extend Test diff --git a/pslogin/src/main/resources/akka.conf b/common/src/main/resources/akka.conf similarity index 100% rename from pslogin/src/main/resources/akka.conf rename to common/src/main/resources/akka.conf diff --git a/pslogin/src/main/resources/application.conf b/common/src/main/resources/application.conf similarity index 89% rename from pslogin/src/main/resources/application.conf rename to common/src/main/resources/application.conf index 25344e9e..4b0e235a 100644 --- a/pslogin/src/main/resources/application.conf +++ b/common/src/main/resources/application.conf @@ -58,6 +58,15 @@ database { # The SSL configuration of the database connection. # One of: disable prefer require verify-full sslmode = prefer + + # The maximum number of active connections. + maxActiveConnections = 5 +} + +# Enable non-standard game properties +game { + # Allow instant action to AMS + instant-action-ams = no } anti-cheat { @@ -110,5 +119,13 @@ kamon { apm.api-key = "" } -include "akka" -include "dispatchers" +sentry { + # Enables submission of warnings and errors to Sentry + enable = no + + # Sentry DSN (Data Source Name) + dsn = "" +} + +include "akka.conf" +include "dispatchers.conf" diff --git a/pslogin/src/main/resources/dispatchers.conf b/common/src/main/resources/dispatchers.conf similarity index 100% rename from pslogin/src/main/resources/dispatchers.conf rename to common/src/main/resources/dispatchers.conf diff --git a/common/src/main/scala/net/psforever/actors/commands/NtuCommand.scala b/common/src/main/scala/net/psforever/actors/commands/NtuCommand.scala new file mode 100644 index 00000000..441274d9 --- /dev/null +++ b/common/src/main/scala/net/psforever/actors/commands/NtuCommand.scala @@ -0,0 +1,29 @@ +package net.psforever.actors.commands + +import akka.actor.typed.ActorRef +import net.psforever.objects.NtuContainer + +object NtuCommand { + + trait Command + + /** Message for announcing it has nanites it can offer the recipient. + * + * @param source the nanite container recognized as the sender + */ + final case class Offer(source: NtuContainer) extends Command + + /** Message for asking for nanites from the recipient. + * + * @param amount the amount of nanites requested + */ + final case class Request(amount: Int, replyTo: ActorRef[Grant]) extends Command + + /** Response for transferring nanites to a recipient. + * + * @param source the nanite container recognized as the sender + * @param amount the nanites transferred in this package + */ + final case class Grant(source: NtuContainer, amount: Int) + +} diff --git a/common/src/main/scala/net/psforever/actors/session/ChatActor.scala b/common/src/main/scala/net/psforever/actors/session/ChatActor.scala index ba3252e8..69824cfb 100644 --- a/common/src/main/scala/net/psforever/actors/session/ChatActor.scala +++ b/common/src/main/scala/net/psforever/actors/session/ChatActor.scala @@ -2,8 +2,9 @@ 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 akka.actor.typed.{ActorRef, Behavior, PostStop, SupervisorStrategy} +import akka.actor.typed.scaladsl.{ActorContext, Behaviors, StashBuffer} +import net.psforever.actors.zone.BuildingActor import net.psforever.objects.serverobject.pad.{VehicleSpawnControl, VehicleSpawnPad} import net.psforever.objects.{Default, GlobalDefinitions, Player, Session} import net.psforever.objects.serverobject.resourcesilo.ResourceSilo @@ -16,14 +17,19 @@ 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)) + Behaviors + .supervise[Command] { + Behaviors.withStash(100) { buffer => + Behaviors.setup(context => new ChatActor(context, buffer, sessionActor).start()) + } + } + .onFailure[Exception](SupervisorStrategy.restart) sealed trait Command @@ -36,8 +42,12 @@ object ChatActor { 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) { +class ChatActor( + context: ActorContext[ChatActor.Command], + buffer: StashBuffer[ChatActor.Command], + sessionActor: ActorRef[SessionActor.Command] +) { + import ChatActor._ private[this] val log = org.log4s.getLogger @@ -50,200 +60,224 @@ class ChatActor(context: ActorContext[ChatActor.Command], sessionActor: ActorRef 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 + context.system.receptionist ! Receptionist.Find( + ChatService.ChatServiceKey, + context.messageAdapter[Receptionist.Listing](ListingResponse) + ) + + def start(): Behavior[Command] = { + Behaviors + .receiveMessage[Command] { + case ListingResponse(ChatService.ChatServiceKey.Listing(listings)) => + chatService = Some(listings.head) + channels ++= List(ChatChannel.Default()) + postStartBehaviour() + + case SetSession(newSession) => + session = Some(newSession) + postStartBehaviour() + + case other => + buffer.stash(other) + Behaviors.same + } + .receiveSignal { + case (_, _: PostStop) => + silenceTimer.cancel() + if (chatService.isDefined) chatService.get ! ChatService.LeaveAllChannels(chatServiceAdapter) + Behaviors.same + case _ => + Behaviors.same + } } - override def onMessage(msg: Command): Behavior[Command] = { + def postStartBehaviour(): Behavior[Command] = { + (session, chatService) match { + case (Some(session), Some(chatService)) if session.player != null => + chatService ! ChatService.JoinChannel(chatServiceAdapter, session, ChatChannel.Default()) + buffer.unstashAll(active(session, chatService)) + case _ => + Behaviors.same + } + + } + + def active(session: Session, chatService: ActorRef[ChatService.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 + Behaviors.receiveMessagePartial { 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 + active(newSession, chatService) case JoinChannel(channel) => - chatService.get ! ChatService.JoinChannel(chatServiceAdapter, session.get, channel) + chatService ! ChatService.JoinChannel(chatServiceAdapter, session, channel) channels ++= List(channel) - this + Behaviors.same case LeaveChannel(channel) => - chatService.get ! ChatService.LeaveChannel(chatServiceAdapter, channel) + chatService ! 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 + Behaviors.same 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) + (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_ANONYMOUS, _, _) => + // ? + + case (CMT_TOGGLE_GM, _, _) => + // ? + + case (CMT_CULLWATERMARK, _, contents) => + val connectionState = + if (contents.contains("40 80")) 100 + else if (contents.contains("120 200")) 25 + else 50 + sessionActor ! SessionActor.SetConnectionState(connectionState) + + 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 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 == Zones.SanctuaryZoneId(session.player.Faction) => + 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_FLY, false, recipient, if (flying) "on" else "off", None) + ChatMsg( + CMT_QUIT, + false, + "", + errorMessage, + None + ) ) + case None => + sessionActor ! SessionActor.Recall() + } - 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_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_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_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_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_SUICIDE, _, _) => + if (session.player.isAlive && session.deadState != DeadState.Release) { + sessionActor ! SessionActor.Suicide() + } - 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_DESTROY, _, 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 (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) + /** Messages starting with ! are custom chat commands */ + case (messageType, recipient, contents) if contents.startsWith("!") => + (messageType, recipient, contents) match { + case (_, _, contents) if contents.startsWith("!whitetext ") && session.admin => + chatService ! ChatService.Message( + session, + ChatMsg(UNK_227, true, "", contents.replace("!whitetext ", ""), None), + ChatChannel.Default() + ) case (_, _, "!loc") => val continent = session.zone @@ -331,181 +365,6 @@ class ChatActor(context: ActorContext[ChatActor.Command], sessionActor: ActorRef } } - 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 => @@ -513,8 +372,7 @@ class ChatActor(context: ActorContext[ChatActor.Command], sessionActor: ActorRef 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 + val ntu: Int = 900 + r.nextInt(100) - silo.NtuCapacitor silo.Actor ! ResourceSilo.UpdateChargeLevel(ntu) case _ => ; @@ -522,234 +380,395 @@ class ChatActor(context: ActorContext[ChatActor.Command], sessionActor: ActorRef ) ) - 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") + // unknown ! commands are ignored } - case (None, _) | (_, None) => - log.error("failed to handle message because dependencies are missing") + + 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.Actor ! BuildingActor.SetFaction(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 (_, "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 (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(s"unhandled chat message $message") } - this + Behaviors.same 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) { + 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_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) - ) - } + 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 + case (name, time) => + log.error(s"bad silence args $name $time") + } + case _ => + log.error(s"unexpected messageType $message") + + } + Behaviors.same } + } } diff --git a/common/src/main/scala/net/psforever/actors/session/SessionActor.scala b/common/src/main/scala/net/psforever/actors/session/SessionActor.scala index 9efdd101..1705fd21 100644 --- a/common/src/main/scala/net/psforever/actors/session/SessionActor.scala +++ b/common/src/main/scala/net/psforever/actors/session/SessionActor.scala @@ -2,8 +2,10 @@ package net.psforever.actors.session import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicInteger + import akka.actor.MDCContextAware.Implicits._ import akka.actor.typed +import akka.actor.typed.receptionist.Receptionist import akka.actor.{Actor, ActorRef, Cancellable, MDCContextAware} import net.psforever.objects.{GlobalDefinitions, _} import net.psforever.objects.avatar.{Certification, DeployableToolbox, FirstTimeEvents} @@ -47,9 +49,8 @@ import net.psforever.objects.vehicles.{ VehicleLockState } import net.psforever.objects.vehicles.Utility.InternalTelepad -import net.psforever.objects.vehicles._ import net.psforever.objects.vital._ -import net.psforever.objects.zones.{InterstellarCluster, Zone, ZoneHotSpotProjector, Zoning} +import net.psforever.objects.zones.{Zone, ZoneHotSpotProjector, Zoning} import net.psforever.packet._ import net.psforever.packet.control._ import net.psforever.packet.game.objectcreate._ @@ -69,13 +70,14 @@ import services.properties.PropertyOverrideManager import services.support.SupportActor import services.teamwork.{SquadResponse, SquadServiceMessage, SquadServiceResponse, SquadAction => SquadServiceAction} import services.vehicle.{VehicleAction, VehicleResponse, VehicleServiceMessage, VehicleServiceResponse} -import services.{RemoverActor, Service, ServiceManager} +import services.{InterstellarClusterService, 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._ @@ -146,6 +148,7 @@ object SessionActor { 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, @@ -153,22 +156,35 @@ object SessionActor { 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 + private final case class ListAccountCharacters() + + private final case class SetCurrentAvatar(tplayer: Player, max_attempts: Int, attempt: Int = 0) + + 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() 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 = ( @@ -213,7 +229,6 @@ object SessionActor { } class SessionActor extends Actor with MDCContextAware { - import SessionActor._ private[this] val log = org.log4s.getLogger @@ -227,7 +242,7 @@ class SessionActor extends Actor with MDCContextAware { var squadService: ActorRef = ActorRef.noSender var taskResolver: ActorRef = Actor.noSender var propertyOverrideManager: ActorRef = Actor.noSender - var cluster: ActorRef = Actor.noSender + var cluster: typed.ActorRef[InterstellarClusterService.Command] = Actor.noSender var _session: Session = Session() var progressBarValue: Option[Float] = None var shooting: Option[PlanetSideGUID] = None //ChangeFireStateMessage_Start @@ -320,23 +335,24 @@ class SessionActor extends Actor with MDCContextAware { var setAvatar: Boolean = false var turnCounterFunc: PlanetSideGUID => Unit = TurnCounterDuringInterim - var clientKeepAlive: Cancellable = Default.Cancellable - var progressBarUpdate: Cancellable = Default.Cancellable - var reviveTimer: Cancellable = Default.Cancellable - var respawnTimer: Cancellable = Default.Cancellable - var zoningTimer: Cancellable = Default.Cancellable - var zoningReset: Cancellable = Default.Cancellable + var clientKeepAlive: Cancellable = Default.Cancellable + var progressBarUpdate: Cancellable = Default.Cancellable + var reviveTimer: Cancellable = Default.Cancellable + var respawnTimer: Cancellable = Default.Cancellable + var zoningTimer: 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 + + 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 @@ -382,11 +398,15 @@ class SessionActor extends Actor with MDCContextAware { serviceManager ! Lookup("accountIntermediary") serviceManager ! Lookup("accountPersistence") serviceManager ! Lookup("taskResolver") - serviceManager ! Lookup("cluster") serviceManager ! Lookup("galaxy") serviceManager ! Lookup("squad") serviceManager ! Lookup("propertyOverrideManager") + ServiceManager.receptionist ! Receptionist.Find( + InterstellarClusterService.InterstellarClusterServiceKey, + context.self + ) + case _ => log.error("Unknown message") context.stop(self) @@ -422,9 +442,6 @@ class SessionActor extends Actor with MDCContextAware { case LookupResult("galaxy", endpoint) => galaxyService = endpoint log.info("ID: " + session.id + " Got galaxy service " + endpoint) - case LookupResult("cluster", endpoint) => - cluster = endpoint - log.info("ID: " + session.id + " Got cluster service " + endpoint) case LookupResult("squad", endpoint) => squadService = endpoint log.info("ID: " + session.id + " Got squad service " + endpoint) @@ -432,6 +449,9 @@ class SessionActor extends Actor with MDCContextAware { propertyOverrideManager = endpoint log.info("ID: " + session.id + " Got propertyOverrideManager service " + endpoint) + case InterstellarClusterService.InterstellarClusterServiceKey.Listing(listings) => + cluster = listings.head + case ControlPacket(_, ctrl) => handleControlPkt(ctrl) case GamePacket(_, _, pkt) => @@ -455,19 +475,26 @@ class SessionActor extends Actor with MDCContextAware { case SetSpectator(spectator) => session.player.spectator = spectator - case Recall(zoneId) => + case Recall() => 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) + beginZoningCountdown(() => { + cluster ! InterstellarClusterService.GetRandomSpawnPoint( + Zones.SanctuaryZoneNumber(player.Faction), + player.Faction, + Seq(SpawnGroup.Sanctuary), + context.self + ) + }) 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) + beginZoningCountdown(() => { + cluster ! InterstellarClusterService.GetInstantActionSpawnPoint(player.Faction, context.self) + }) case Quit() => //priority to quitting is given to quit over other zoning methods @@ -477,7 +504,10 @@ class SessionActor extends Actor with MDCContextAware { zoningType = Zoning.Method.Quit zoningChatMessageType = ChatMessageType.CMT_QUIT zoningStatus = Zoning.Status.Request - self ! Zoning.Quit() + beginZoningCountdown(() => { + log.info("Good-bye") + ImmediateDisconnect() + }) case Suicide() => suicide(player) @@ -494,14 +524,14 @@ class SessionActor extends Actor with MDCContextAware { case Some(0) => deadState = DeadState.Release // cancel movement updates vehicle.Position = position - LoadZonePhysicalSpawnPoint(zoneId, position, Vector3.Zero, 0) + LoadZonePhysicalSpawnPoint(zoneId, position, Vector3.Zero, 0 seconds) 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) + LoadZonePhysicalSpawnPoint(zoneId, position, Vector3.Zero, 0 seconds) case _ => // seated in something that is not a vehicle or the vehicle is cargo, in which case we can't move } @@ -513,7 +543,7 @@ class SessionActor extends Actor with MDCContextAware { case Some(0) => deadState = DeadState.Release // cancel movement updates vehicle.Position = position - LoadZonePhysicalSpawnPoint(continent.Id, position, Vector3.z(vehicle.Orientation.z), 0) + LoadZonePhysicalSpawnPoint(continent.Id, position, Vector3.z(vehicle.Orientation.z), 0 seconds) case _ => // not seated as the driver, in which case we can't move } case None => @@ -586,7 +616,7 @@ class SessionActor extends Actor with MDCContextAware { deadState = DeadState.Release sendResponse(AvatarDeadStateMessage(DeadState.Release, 0, 0, player.Position, player.Faction, true)) interstellarFerry = Some(v) //on the other continent and registered to that continent's GUID system - LoadZonePhysicalSpawnPoint(v.Continent, v.Position, v.Orientation, 1) + LoadZonePhysicalSpawnPoint(v.Continent, v.Position, v.Orientation, 1 seconds) case None => interstellarFerry match { case None => @@ -930,30 +960,25 @@ class SessionActor extends Actor with MDCContextAware { case Deployment.CanDeploy(obj, state) => if (state == DriveState.Deploying) { log.info(s"DeployRequest: $obj transitioning to deploy state") - } - else if (state == DriveState.Deployed) { + } else if (state == DriveState.Deployed) { log.info(s"DeployRequest: $obj has been Deployed") - } - else { + } else { CanNotChangeDeployment(obj, state, "incorrect deploy state") } case Deployment.CanUndeploy(obj, state) => if (state == DriveState.Undeploying) { log.info(s"DeployRequest: $obj transitioning to undeploy state") - } - else if (state == DriveState.Mobile) { + } else if (state == DriveState.Mobile) { log.info(s"DeployRequest: $obj is Mobile") - } - else { + } else { CanNotChangeDeployment(obj, state, "incorrect undeploy state") } case Deployment.CanNotChangeDeployment(obj, state, reason) => if (Deployment.CheckForDeployState(state) && !VehicleControl.DeploymentAngleCheck(obj)) { CanNotChangeDeployment(obj, state, "ground too steep") - } - else { + } else { CanNotChangeDeployment(obj, state, reason) } @@ -1034,50 +1059,6 @@ class SessionActor extends Actor with MDCContextAware { failWithError(s"ListAccountCharacters: query failed - ${e.getMessage}") } - case Zone.ClientInitialization(zone) => - Thread.sleep(connectionState) - val continentNumber = zone.Number - val poplist = zone.Players - val popBO = 0 - //TODO black ops test (partition) - val popTR = poplist.count(_.faction == PlanetSideEmpire.TR) - val popNC = poplist.count(_.faction == PlanetSideEmpire.NC) - val popVS = poplist.count(_.faction == PlanetSideEmpire.VS) - - // StopBundlingPackets() is called on ClientInitializationComplete - StartBundlingPackets() - zone.Buildings.foreach({ case (id, building) => initBuilding(continentNumber, building.MapId, building) }) - sendResponse(ZonePopulationUpdateMessage(continentNumber, 414, 138, popTR, 138, popNC, 138, popVS, 138, popBO)) - if (continentNumber == 11) - sendResponse( - ContinentalLockUpdateMessage(continentNumber, PlanetSideEmpire.NC) - ) // "The NC have captured the NC Sanctuary." - else if (continentNumber == 12) - sendResponse( - ContinentalLockUpdateMessage(continentNumber, PlanetSideEmpire.TR) - ) // "The TR have captured the TR Sanctuary." - else if (continentNumber == 13) - sendResponse( - ContinentalLockUpdateMessage(continentNumber, PlanetSideEmpire.VS) - ) // "The VS have captured the VS Sanctuary." - else sendResponse(ContinentalLockUpdateMessage(continentNumber, PlanetSideEmpire.NEUTRAL)) - //CaptureFlagUpdateMessage() - //VanuModuleUpdateMessage() - //ModuleLimitsMessage() - sendResponse(ZoneInfoMessage(continentNumber, true, 0)) - sendResponse(ZoneLockInfoMessage(continentNumber, false, true)) - sendResponse(ZoneForcedCavernConnectionsMessage(continentNumber, 0)) - sendResponse( - HotSpotUpdateMessage( - continentNumber, - 1, - ZoneHotSpotProjector - .SpecificHotSpotInfo(player.Faction, zone.HotSpots) - .map { spot => PacketHotSpotInfo(spot.DisplayLocation.x, spot.DisplayLocation.y, 40) } - ) - ) //normally set for all zones in bulk; should be fine manually updating per zone like this - - StopBundlingPackets() case Zone.Population.PlayerHasLeft(zone, None) => log.info(s"$avatar does not have a body on ${zone.Id}") @@ -1092,43 +1073,30 @@ class SessionActor extends Actor with MDCContextAware { case Zone.Population.PlayerAlreadySpawned(zone, tplayer) => log.warn(s"${tplayer.Name} is already spawned on zone ${zone.Id}; a clerical error?") - case Zone.Lattice.SpawnPoint(zone_id, spawn_tube) => + case InterstellarClusterService.SpawnPointResponse(response) => + val currentZoningType = zoningType CancelZoningProcess() - var (pos, ori) = spawn_tube.SpecificPoint(continent.GUID(player.VehicleSeated) match { - case Some(obj: Vehicle) if !obj.Destroyed => - obj - case _ => - player - }) - spawn_tube.Owner match { - case building: Building => - log.info(s"Zone.Lattice.SpawnPoint: spawn point on $zone_id in building ${building.MapId} selected") - case vehicle: Vehicle => - log.info(s"Zone.Lattice.SpawnPoint: spawn point on $zone_id at ams ${vehicle.GUID.guid} selected") - case owner => - log.warn( - s"Zone.Lattice.SpawnPoint: spawn point on $zone_id at ${spawn_tube.Position} has unexpected owner $owner" - ) - } - LoadZonePhysicalSpawnPoint(zone_id, pos, ori, CountSpawnDelay(zone_id, spawn_tube, continent.Id)) + PlayerActionsToCancel() + CancelAllProximityUnits() + continent.Population ! Zone.Population.Release(avatar) + response match { + case Some((zone, spawnPoint)) => + val (pos, ori) = spawnPoint.SpecificPoint(continent.GUID(player.VehicleSeated) match { + case Some(obj: Vehicle) if !obj.Destroyed => + obj + case _ => + player + }) + LoadZonePhysicalSpawnPoint(zone.Id, pos, ori, CountSpawnDelay(zone.Id, spawnPoint, continent.Id)) + case None => + currentZoningType match { + case Zoning.Method.InstantAction => + CancelZoningProcessWithReason("@InstantActionNoHotspotsAvailable") + case _ => + log.error("got None spawn point response from InterstellarClusterService") + RequestSanctuaryZoneSpawn(player, 0) + } - case Zone.Lattice.NoValidSpawnPoint(zone_number, None) => - log.warn(s"Zone.Lattice.SpawnPoint: zone $zone_number could not be accessed as requested") - reviveTimer.cancel - RequestSanctuaryZoneSpawn(player, zone_number) - - case Zone.Lattice.NoValidSpawnPoint(zone_number, Some(spawn_group)) => - log.warn( - s"Zone.Lattice.SpawnPoint: zone $zone_number has no available ${player.Faction} targets in spawn group $spawn_group" - ) - reviveTimer.cancel - if (spawn_group == 2) { - 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) } case msg @ Zone.Vehicle.CanNotSpawn(zone, vehicle, reason) => @@ -1284,7 +1252,50 @@ class SessionActor extends Actor with MDCContextAware { case Zone.Deployable.DeployableIsDismissed(obj) => taskResolver ! GUIDTask.UnregisterObjectTask(obj)(continent.GUID) - case InterstellarCluster.ClientInitializationComplete() => + case InterstellarClusterService.ZonesResponse(zones) => + zones.foreach { zone => + Thread.sleep(connectionState) + val continentNumber = zone.Number + val popBO = 0 + //TODO black ops test (partition) + val popTR = zone.Players.count(_.faction == PlanetSideEmpire.TR) + val popNC = zone.Players.count(_.faction == PlanetSideEmpire.NC) + val popVS = zone.Players.count(_.faction == PlanetSideEmpire.VS) + + StartBundlingPackets() + zone.Buildings.foreach({ case (id, building) => initBuilding(continentNumber, building.MapId, building) }) + sendResponse(ZonePopulationUpdateMessage(continentNumber, 414, 138, popTR, 138, popNC, 138, popVS, 138, popBO)) + if (continentNumber == 11) + sendResponse( + ContinentalLockUpdateMessage(continentNumber, PlanetSideEmpire.NC) + ) // "The NC have captured the NC Sanctuary." + else if (continentNumber == 12) + sendResponse( + ContinentalLockUpdateMessage(continentNumber, PlanetSideEmpire.TR) + ) // "The TR have captured the TR Sanctuary." + else if (continentNumber == 13) + sendResponse( + ContinentalLockUpdateMessage(continentNumber, PlanetSideEmpire.VS) + ) // "The VS have captured the VS Sanctuary." + else sendResponse(ContinentalLockUpdateMessage(continentNumber, PlanetSideEmpire.NEUTRAL)) + //CaptureFlagUpdateMessage() + //VanuModuleUpdateMessage() + //ModuleLimitsMessage() + sendResponse(ZoneInfoMessage(continentNumber, true, 0)) + sendResponse(ZoneLockInfoMessage(continentNumber, false, true)) + sendResponse(ZoneForcedCavernConnectionsMessage(continentNumber, 0)) + sendResponse( + HotSpotUpdateMessage( + continentNumber, + 1, + ZoneHotSpotProjector + .SpecificHotSpotInfo(player.Faction, zone.HotSpots) + .map { spot => PacketHotSpotInfo(spot.DisplayLocation.x, spot.DisplayLocation.y, 40) } + ) + ) //normally set for all zones in bulk; should be fine manually updating per zone like this + + StopBundlingPackets() + } LivePlayerList.Add(avatar.CharId, avatar) StartBundlingPackets() //PropertyOverrideMessage @@ -1325,16 +1336,21 @@ class SessionActor extends Actor with MDCContextAware { self ! NewPlayerLoaded(player) } else { zoneReload = true - cluster ! Zone.Lattice.RequestSpawnPoint(zone.Number, player, 0) + cluster ! InterstellarClusterService.GetNearbySpawnPoint( + continent.Number, + player, + Seq(SpawnGroup.Facility, SpawnGroup.Tower), + context.self + ) } } StopBundlingPackets() - case InterstellarCluster.GiveWorld(zoneId, zone) => - log.info(s"Zone $zoneId will now load") + case InterstellarClusterService.ZoneResponse(zone) => + log.info(s"Zone ${zone.get.Id} will now load") loadConfZone = true val oldZone = session.zone - session = session.copy(zone = zone) + session = session.copy(zone = zone.get) //the only zone-level event system subscription necessary before BeginZoningMessage (for persistence purposes) continent.AvatarEvents ! Service.Join(player.Name) persist() @@ -1348,48 +1364,6 @@ class SessionActor extends Actor with MDCContextAware { case _ => taskResolver ! RegisterNewAvatar(player) } - - 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(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) - } - - case Zoning.InstantAction.NotLocated() => - instantActionFallbackDestination match { - case Some(Zoning.InstantAction.Located(zone, _, spawn_point)) - 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") - } - case _ => - //no instant action available - CancelZoningProcessWithReason("@InstantActionNoHotspotsAvailable") - } - - case Zoning.Recall.Located(zone, spawn_point) => - if (ContemplateZoningResponse(Zoning.Recall.Request(session.player.Faction, zone.Id), cluster)) { - val (pos, ori) = spawn_point.SpecificPoint(session.player) - SpawnThroughZoningProcess(zone, pos, ori) - } - - case Zoning.Recall.Denied(reason) => - CancelZoningProcessWithReason(s"@norecall_sanctuary_$reason", Some(ChatMessageType.CMT_QUIT)) - - case Zoning.Quit() => - if (ContemplateZoningResponse(Zoning.Quit(), self)) { - log.info("Good-bye") - ImmediateDisconnect() - } - - case ZoningReset() => CancelZoningProcess() case NewPlayerLoaded(tplayer) => @@ -1398,15 +1372,15 @@ class SessionActor extends Actor with MDCContextAware { session = session.copy(player = tplayer) //LoadMapMessage causes the client to send BeginZoningMessage, eventually leading to SetCurrentAvatar val weaponsEnabled = - (session.zone.Map.Name != "map11" && session.zone.Map.Name != "map12" && session.zone.Map.Name != "map13") + (session.zone.map.Name != "map11" && session.zone.map.Name != "map12" && session.zone.map.Name != "map13") sendResponse( LoadMapMessage( - session.zone.Map.Name, + session.zone.map.Name, session.zone.Id, 40100, 25, weaponsEnabled, - session.zone.Map.Checksum + session.zone.map.Checksum ) ) //important! the LoadMapMessage must be processed by the client before the avatar is created @@ -1458,7 +1432,7 @@ class SessionActor extends Actor with MDCContextAware { and is at some stage of being added to the zone in which they will have control agency in that zone. Whether or not the zone is loaded in the earlier case depends on the destination with respect to the current location. Once all of the following is (assumed) accomplished, - the servwer will attempt to declare that user's player the avatar of the user's client. + the server will attempt to declare that user's player the avatar of the user's client. Reception of certain packets that represent "reported user activity" after that marks the end of avatar loading. If the maximum number of unsuccessful attempts is reached, some course of action is taken. If the player dies, the process does not need to continue. @@ -1494,7 +1468,7 @@ class SessionActor extends Actor with MDCContextAware { continent.Population ! Zone.Population.Leave(avatar) //does not matter if it doesn't work zoneLoaded = None zoneReload = true - LoadZonePhysicalSpawnPoint(toZoneId, pos, orient, respawnTime = 0L) + LoadZonePhysicalSpawnPoint(toZoneId, pos, orient, 0 seconds) } } else if (tplayer.isAlive) { if ( @@ -1504,7 +1478,7 @@ class SessionActor extends Actor with MDCContextAware { case _ => true }) ) { - if(!setAvatar || waitingOnUpstream) { + if (!setAvatar || waitingOnUpstream) { setCurrentAvatarFunc(tplayer) respawnTimer = context.system.scheduler.scheduleOnce( delay = (if (attempt <= max_attempts / 2) 10 else 5) seconds, @@ -1522,7 +1496,7 @@ class SessionActor extends Actor with MDCContextAware { } } - case Vitality.DamageResolution(target : TelepadDeployable, _) => + case Vitality.DamageResolution(target: TelepadDeployable, _) => //telepads if (target.Health <= 0) { //update if destroyed @@ -1762,7 +1736,7 @@ class SessionActor extends Actor with MDCContextAware { def UpdateLoginTimeThenDoClientInitialization(): Unit = { UpdateCharacterLoginTime(avatar.CharId).onComplete { case _ => - cluster ! InterstellarCluster.RequestClientInitialization() + cluster ! InterstellarClusterService.FilterZones(z => true, context.self) } } @@ -1797,12 +1771,10 @@ class SessionActor extends Actor with MDCContextAware { * and, this is another pass of the countdown.
*
* Once the countdown reaches 0, the transportation that has been promised by the zoning attempt may begin. - * @param nextStepMsg send this message to the `InterGalacticCluster` for the next step of the zoning process, - * if there will be a next step - * @return `true`, if the zoning transportation process should start; - * `false`, otherwise + * + * @param runnable execute for the next step of the zoning process */ - def ContemplateZoningResponse(nextStepMsg: Any, to: ActorRef): Boolean = { + def beginZoningCountdown(runnable: Runnable): Unit = { val descriptor = zoningType.toString.toLowerCase if (zoningStatus == Zoning.Status.Request) { DeactivateImplants() @@ -1810,30 +1782,24 @@ class SessionActor extends Actor with MDCContextAware { val (time, origin) = ZoningStartInitialMessageAndTimer() zoningCounter = time sendResponse(ChatMsg(ChatMessageType.CMT_QUIT, false, "", s"@${descriptor}_$origin", None)) - import scala.concurrent.ExecutionContext.Implicits.global - zoningReset.cancel zoningTimer.cancel - zoningReset = context.system.scheduler.scheduleOnce(10 seconds, self, ZoningReset()) - zoningTimer = context.system.scheduler.scheduleOnce(5 seconds, to, nextStepMsg) - false + zoningTimer = context.system.scheduler.scheduleOnce(5 seconds) { + beginZoningCountdown(runnable) + } } else if (zoningStatus == Zoning.Status.Countdown) { zoningCounter -= 5 - zoningReset.cancel zoningTimer.cancel if (zoningCounter > 0) { if (zoningCountdownMessages.contains(zoningCounter)) { sendResponse(ChatMsg(zoningChatMessageType, false, "", s"@${descriptor}_$zoningCounter", None)) } - //again - zoningReset = context.system.scheduler.scheduleOnce(10 seconds, self, ZoningReset()) - zoningTimer = context.system.scheduler.scheduleOnce(5 seconds, to, nextStepMsg) - false + zoningTimer = context.system.scheduler.scheduleOnce(5 seconds) { + beginZoningCountdown(runnable) + } } else { //zoning deployment - true + runnable.run() } - } else { - false } } @@ -1847,50 +1813,28 @@ class SessionActor extends Actor with MDCContextAware { * @return a `Tuple` composed of the initial countdown time and the descriptor for message composition */ def ZoningStartInitialMessageAndTimer(): (Int, String) = { - val location = (if (Zones.SanctuaryZoneNumber(player.Faction) == continent.Number) { - Zoning.Time.Sanctuary - } else { - val playerPosition = player.Position.xy - (continent.Buildings.values - .filter { building => - val radius = building.Definition.SOIRadius - Vector3.DistanceSquared(building.Position.xy, playerPosition) < radius * radius - }) match { - case Nil => - Zoning.Time.None - case List(building) => - if (building.Faction == player.Faction) Zoning.Time.Friendly - else if (building.Faction == PlanetSideEmpire.NEUTRAL) Zoning.Time.Neutral - else Zoning.Time.Enemy - case buildings => - if (buildings.exists(_.Faction == player.Faction)) Zoning.Time.Friendly - else if (buildings.exists(_.Faction == PlanetSideEmpire.NEUTRAL)) Zoning.Time.Neutral - else Zoning.Time.Enemy - } - }) - (location.id, location.descriptor.toLowerCase) - } - - /** - * Use the zoning process using some spawnable entity in the destination zone. - * @param zone the destination zone - * @param spawnPosition the destination spawn position - * @param spawnOrientation the destination spawn orientation - */ - def SpawnThroughZoningProcess(zone: Zone, spawnPosition: Vector3, spawnOrientation: Vector3): Unit = { - CancelZoningProcess() - PlayerActionsToCancel() - CancelAllProximityUnits() - continent.Population ! Zone.Population.Release(avatar) - val respawnTime: Long = if (zone.Number == continent.Number) { - //distract the user while he slips through the cracks of reality - GoToDeploymentMap() - 1L + val location = if (Zones.SanctuaryZoneNumber(player.Faction) == continent.Number) { + Zoning.Time.Sanctuary } else { - //zone loading will take long enough - 0L + val playerPosition = player.Position.xy + (continent.Buildings.values + .filter { building => + val radius = building.Definition.SOIRadius + Vector3.DistanceSquared(building.Position.xy, playerPosition) < radius * radius + }) match { + case Nil => + Zoning.Time.None + case List(building) => + if (building.Faction == player.Faction) Zoning.Time.Friendly + else if (building.Faction == PlanetSideEmpire.NEUTRAL) Zoning.Time.Neutral + else Zoning.Time.Enemy + case buildings => + if (buildings.exists(_.Faction == player.Faction)) Zoning.Time.Friendly + else if (buildings.exists(_.Faction == PlanetSideEmpire.NEUTRAL)) Zoning.Time.Neutral + else Zoning.Time.Enemy + } } - LoadZonePhysicalSpawnPoint(zone.Id, spawnPosition, spawnOrientation, respawnTime) + (location.id, location.descriptor.toLowerCase) } /** @@ -1941,7 +1885,7 @@ class SessionActor extends Actor with MDCContextAware { droppod.Invalidate() //now, we must short-circuit the jury-rig interstellarFerry = Some(droppod) //leverage vehicle gating player.Position = droppod.Position - LoadZonePhysicalSpawnPoint(zone.Id, droppod.Position, Vector3.Zero, 0L) + LoadZonePhysicalSpawnPoint(zone.Id, droppod.Position, Vector3.Zero, 0 seconds) /* Don't even think about it. */ } @@ -1973,7 +1917,6 @@ class SessionActor extends Actor with MDCContextAware { */ def CancelZoningProcess(): Unit = { zoningTimer.cancel - zoningReset.cancel zoningType = Zoning.Method.None zoningStatus = Zoning.Status.None zoningCounter = 0 @@ -2117,7 +2060,7 @@ class SessionActor extends Actor with MDCContextAware { } case AvatarResponse.Killed(mount) => - val respawnTimer = 300000 //milliseconds + val respawnTimer = 300.seconds ToggleMaxSpecialState(enable = false) keepAliveFunc = NormalKeepAlive zoningStatus = Zoning.Status.None @@ -2139,12 +2082,15 @@ class SessionActor extends Actor with MDCContextAware { } reviveTimer.cancel if (player.death_by == 0) { - import scala.concurrent.ExecutionContext.Implicits.global - reviveTimer = context.system.scheduler.scheduleOnce( - respawnTimer milliseconds, - cluster, - Zone.Lattice.RequestSpawnPoint(Zones.SanctuaryZoneNumber(player.Faction), player, 7) - ) + reviveTimer = context.system.scheduler.scheduleOnce(respawnTimer) { + cluster ! InterstellarClusterService.GetRandomSpawnPoint( + Zones.SanctuaryZoneNumber(player.Faction), + player.Faction, + Seq(SpawnGroup.Sanctuary), + context.self + ) + } + } else { HandleReleaseAvatar(player, continent) } @@ -2719,7 +2665,7 @@ class SessionActor extends Actor with MDCContextAware { CancelAllProximityUnits() sendResponse(PlanetsideAttributeMessage(obj_guid, 0, obj.Health)) sendResponse(PlanetsideAttributeMessage(obj_guid, 68, obj.Shields)) //shield health - if(obj.Definition == GlobalDefinitions.ant) { + if (obj.Definition == GlobalDefinitions.ant) { sendResponse(PlanetsideAttributeMessage(obj_guid, 45, obj.NtuCapacitorScaled)) } if (obj.Definition.MaxCapacitor > 0) { @@ -2904,7 +2850,7 @@ class SessionActor extends Actor with MDCContextAware { lastTerminalOrderFulfillment = true case Terminal.BuyVehicle(vehicle, weapons, trunk) => - continent.Map.TerminalToSpawnPad.get(msg.terminal_guid.guid) match { + continent.map.TerminalToSpawnPad.get(msg.terminal_guid.guid) match { case Some(pad_guid) => val definition = vehicle.Definition val vid = definition.ObjectId @@ -3294,13 +3240,14 @@ class SessionActor extends Actor with MDCContextAware { * If the background process recording value is never set before running the initial operation * or gets unset by failing a `tickAction` check * the process is stopped. + * * @see `progressBarUpdate` * @see `progressBarValue` * @see `WorldSessionActor.Progress` - * @param delta how much the progress changes each tick - * @param completeAction a custom action performed once the process is completed - * @param tickAction an optional action is is performed for each tick of progress; - * also performs a continuity check to determine if the process has been disrupted + * @param delta how much the progress changes each tick + * @param completionAction a custom action performed once the process is completed + * @param tickAction an optional action is is performed for each tick of progress; + * also performs a continuity check to determine if the process has been disrupted */ def HandleProgressChange(delta: Float, completionAction: () => Unit, tickAction: Float => Boolean): Unit = { progressBarUpdate.cancel @@ -3498,8 +3445,10 @@ class SessionActor extends Actor with MDCContextAware { sendResponse(PlanetsideAttributeMessage(vehicle.GUID, 68, vehicle.Shields)) } // ANT capacitor - if(vehicle.Definition == GlobalDefinitions.ant) { - sendResponse(PlanetsideAttributeMessage(vehicle.GUID, 45, vehicle.NtuCapacitorScaled)) // set ntu on vehicle UI + if (vehicle.Definition == GlobalDefinitions.ant) { + sendResponse( + PlanetsideAttributeMessage(vehicle.GUID, 45, vehicle.NtuCapacitorScaled) + ) // set ntu on vehicle UI } LoadZoneTransferPassengerMessages( guid, @@ -3775,7 +3724,7 @@ class SessionActor extends Actor with MDCContextAware { case KeepAliveMessage(_) => keepAliveFunc() - case msg@BeginZoningMessage() => + case msg @ BeginZoningMessage() => log.info("Reticulating splines ...") zoneLoaded = None val continentId = continent.Id @@ -3789,18 +3738,19 @@ class SessionActor extends Actor with MDCContextAware { continent.VehicleEvents ! Service.Join(avatar.name) continent.VehicleEvents ! Service.Join(continentId) continent.VehicleEvents ! Service.Join(factionChannel) - if(connectionState != 100) configZone(continent) + if (connectionState != 100) configZone(continent) sendResponse(TimeOfDayMessage(1191182336)) //custom - sendResponse(ReplicationStreamMessage(5, Some(6), Vector.empty)) //clear squad list + sendResponse(ReplicationStreamMessage(5, Some(6), Vector.empty)) //clear squad list sendResponse(PlanetsideAttributeMessage(PlanetSideGUID(0), 112, 0)) // disable festive backpacks //find and reclaim own deployables, if any val guid = player.GUID - val foundDeployables = continent.DeployableList.filter(obj => obj.OwnerName.contains(player.Name) && obj.Health > 0) + val foundDeployables = + continent.DeployableList.filter(obj => obj.OwnerName.contains(player.Name) && obj.Health > 0) continent.LocalEvents ! LocalServiceMessage.Deployables(RemoverActor.ClearSpecific(foundDeployables, continent)) foundDeployables.foreach(obj => { - if(avatar.Deployables.Add(obj)) { + if (avatar.Deployables.Add(obj)) { obj.Owner = guid log.info(s"Found a ${obj.Definition.Name} of ours while loading the zone") } @@ -3820,7 +3770,7 @@ class SessionActor extends Actor with MDCContextAware { ) }) turrets.foreach(obj => { - val objGUID = obj.GUID + val objGUID = obj.GUID val definition = obj.Definition sendResponse( ObjectCreateMessage( @@ -3830,11 +3780,14 @@ class SessionActor extends Actor with MDCContextAware { ) ) //seated players - obj.asInstanceOf[Mountable].Seats.values + obj + .asInstanceOf[Mountable] + .Seats + .values .map(_.Occupant) .collect { case Some(occupant) => - if(occupant.isAlive) { + if (occupant.isAlive) { val tdefintion = occupant.Definition sendResponse( ObjectCreateMessage( @@ -3853,8 +3806,8 @@ class SessionActor extends Actor with MDCContextAware { obj.Definition.DeployCategory == DeployableCategory.Sensors && !obj.Destroyed && (obj match { - case jObj : JammableUnit => !jObj.Jammed; - case _ => true + case jObj: JammableUnit => !jObj.Jammed; + case _ => true }) ) .foreach(obj => { @@ -3865,10 +3818,15 @@ class SessionActor extends Actor with MDCContextAware { continent.DeployableList .filter(obj => obj.Faction == faction && !obj.Destroyed) .foreach(obj => { - if(obj.Health != obj.DefaultHealth) { + if (obj.Health != obj.DefaultHealth) { sendResponse(PlanetsideAttributeMessage(obj.GUID, 0, obj.Health)) } - val deployInfo = DeployableInfo(obj.GUID, Deployable.Icon(obj.Definition.Item), obj.Position, obj.Owner.getOrElse(PlanetSideGUID(0))) + val deployInfo = DeployableInfo( + obj.GUID, + Deployable.Icon(obj.Definition.Item), + obj.Position, + obj.Owner.getOrElse(PlanetSideGUID(0)) + ) sendResponse(DeployableObjectsInfoMessage(DeploymentAction.Build, deployInfo)) }) //render Equipment that was dropped into zone before the player arrived @@ -3878,19 +3836,25 @@ class SessionActor extends Actor with MDCContextAware { ObjectCreateMessage( definition.ObjectId, item.GUID, - DroppedItemData(PlacementData(item.Position, item.Orientation), definition.Packet.ConstructorData(item).get) + DroppedItemData( + PlacementData(item.Position, item.Orientation), + definition.Packet.ConstructorData(item).get + ) ) ) }) //load active players in zone (excepting players who are seated or players who are us) val live = continent.LivePlayers - live.filterNot(tplayer => { - tplayer.GUID == player.GUID || tplayer.VehicleSeated.nonEmpty - }) + live + .filterNot(tplayer => { + tplayer.GUID == player.GUID || tplayer.VehicleSeated.nonEmpty + }) .foreach(char => { val tdefintion = char.Definition - sendResponse(ObjectCreateMessage(tdefintion.ObjectId, char.GUID, char.Definition.Packet.ConstructorData(char).get)) - if(char.UsingSpecial == SpecialExoSuitDefinition.Mode.Anchored) { + sendResponse( + ObjectCreateMessage(tdefintion.ObjectId, char.GUID, char.Definition.Packet.ConstructorData(char).get) + ) + if (char.UsingSpecial == SpecialExoSuitDefinition.Mode.Anchored) { sendResponse(PlanetsideAttributeMessage(char.GUID, 19, 1)) } }) @@ -3903,55 +3867,38 @@ class SessionActor extends Actor with MDCContextAware { val (a, b) = continent.Vehicles.partition(vehicle => { vehicle.Destroyed && vehicle.Definition.DestroyedModel.nonEmpty }) - (a, (continent.GUID(player.VehicleSeated) match { - case Some(vehicle : Vehicle) if vehicle.PassengerInSeat(player).isDefined => - b.partition { - _.GUID != vehicle.GUID - } - case Some(_) => - //vehicle, but we're not seated in it - player.VehicleSeated = None - (b, List.empty[Vehicle]) - case None => - //throw error since VehicleSeated didn't point to a vehicle? - player.VehicleSeated = None - (b, List.empty[Vehicle]) - })) + ( + a, + (continent.GUID(player.VehicleSeated) match { + case Some(vehicle: Vehicle) if vehicle.PassengerInSeat(player).isDefined => + b.partition { + _.GUID != vehicle.GUID + } + case Some(_) => + //vehicle, but we're not seated in it + player.VehicleSeated = None + (b, List.empty[Vehicle]) + case None => + //throw error since VehicleSeated didn't point to a vehicle? + player.VehicleSeated = None + (b, List.empty[Vehicle]) + }) + ) } val allActiveVehicles = vehicles ++ usedVehicle //active vehicles (and some wreckage) vehicles.foreach(vehicle => { - val vguid = vehicle.GUID + val vguid = vehicle.GUID val vdefinition = vehicle.Definition - sendResponse(ObjectCreateMessage(vdefinition.ObjectId, vguid, vdefinition.Packet.ConstructorData(vehicle).get)) + sendResponse( + ObjectCreateMessage(vdefinition.ObjectId, vguid, vdefinition.Packet.ConstructorData(vehicle).get) + ) //occupants other than driver vehicle.Seats .filter({ case (index, seat) => seat.isOccupied && live.contains(seat.Occupant.get) && index > 0 }) - .foreach({ case (index, seat) => - val tplayer = seat.Occupant.get - val tdefintion = tplayer.Definition - sendResponse( - ObjectCreateMessage( - tdefintion.ObjectId, - tplayer.GUID, - ObjectCreateMessageParent(vguid, index), - tdefintion.Packet.ConstructorData(tplayer).get - ) - ) - }) - }) - vehicles.collect { case vehicle if vehicle.Faction == faction => - Vehicles.ReloadAccessPermissions(vehicle, player.Name) - } - //our vehicle would have already been loaded; see NewPlayerLoaded/AvatarCreate - usedVehicle.headOption match { - case Some(vehicle) => - //depict any other passengers already in this zone - val vguid = vehicle.GUID - vehicle.Seats - .filter({ case (index, seat) => seat.isOccupied && !seat.Occupant.contains(player) && live.contains(seat.Occupant.get) && index > 0 }) - .foreach({ case (index, seat) => - val tplayer = seat.Occupant.get + .foreach({ + case (index, seat) => + val tplayer = seat.Occupant.get val tdefintion = tplayer.Definition sendResponse( ObjectCreateMessage( @@ -3961,13 +3908,41 @@ class SessionActor extends Actor with MDCContextAware { tdefintion.Packet.ConstructorData(tplayer).get ) ) + }) + }) + vehicles.collect { + case vehicle if vehicle.Faction == faction => + Vehicles.ReloadAccessPermissions(vehicle, player.Name) + } + //our vehicle would have already been loaded; see NewPlayerLoaded/AvatarCreate + usedVehicle.headOption match { + case Some(vehicle) => + //depict any other passengers already in this zone + val vguid = vehicle.GUID + vehicle.Seats + .filter({ + case (index, seat) => + seat.isOccupied && !seat.Occupant.contains(player) && live.contains(seat.Occupant.get) && index > 0 + }) + .foreach({ + case (index, seat) => + val tplayer = seat.Occupant.get + val tdefintion = tplayer.Definition + sendResponse( + ObjectCreateMessage( + tdefintion.ObjectId, + tplayer.GUID, + ObjectCreateMessageParent(vguid, index), + tdefintion.Packet.ConstructorData(tplayer).get + ) + ) }) //since we would have only subscribed recently, we need to reload seat access states (0 to 3).foreach { group => sendResponse(PlanetsideAttributeMessage(vguid, group + 10, vehicle.PermissionGroup(group).get.id)) } //positive shield strength - if(vehicle.Shields > 0) { + if (vehicle.Shields > 0) { sendResponse(PlanetsideAttributeMessage(vguid, 68, vehicle.Shields)) } case _ => ; //no vehicle @@ -3983,11 +3958,17 @@ class SessionActor extends Actor with MDCContextAware { ) }) //cargo occupants (including our own vehicle as cargo) - allActiveVehicles.collect { case vehicle if vehicle.CargoHolds.nonEmpty => - vehicle.CargoHolds.collect({ case (index, hold) if hold.isOccupied => { - CargoBehavior.CargoMountBehaviorForAll(vehicle, hold.Occupant.get, index) //CargoMountBehaviorForUs can fail to attach the cargo vehicle on some clients - } - }) + allActiveVehicles.collect { + case vehicle if vehicle.CargoHolds.nonEmpty => + vehicle.CargoHolds.collect({ + case (index, hold) if hold.isOccupied => { + CargoBehavior.CargoMountBehaviorForAll( + vehicle, + hold.Occupant.get, + index + ) //CargoMountBehaviorForUs can fail to attach the cargo vehicle on some clients + } + }) } //special deploy states val deployedVehicles = allActiveVehicles.filter(_.DeploymentState == DriveState.Deployed) @@ -3999,9 +3980,9 @@ class SessionActor extends Actor with MDCContextAware { //special effects sendResponse(PlanetsideAttributeMessage(obj.GUID, 52, 1)) // ant panel glow Vehicles.FindANTChargingSource(obj, None).orElse(Vehicles.FindANTDischargingTarget(obj, None)) match { - case Some(silo : ResourceSilo) => + case Some(silo: ResourceSilo) => sendResponse(PlanetsideAttributeMessage(silo.GUID, 49, 1)) // silo orb particle effect - case Some(_ : WarpGate) => + case Some(_: WarpGate) => sendResponse(PlanetsideAttributeMessage(obj.GUID, 49, 1)) // ant orb particle effect case _ => ; } @@ -4014,78 +3995,80 @@ class SessionActor extends Actor with MDCContextAware { } //implant terminals - continent.Map.TerminalToInterface.foreach({ case ((terminal_guid, interface_guid)) => - val parent_guid = PlanetSideGUID(terminal_guid) - continent.GUID(interface_guid) match { - case Some(obj : Terminal) => - val objDef = obj.Definition - sendResponse( - ObjectCreateMessage( - objDef.ObjectId, - PlanetSideGUID(interface_guid), - ObjectCreateMessageParent(parent_guid, 1), - objDef.Packet.ConstructorData(obj).get + continent.map.TerminalToInterface.foreach({ + case ((terminal_guid, interface_guid)) => + val parent_guid = PlanetSideGUID(terminal_guid) + continent.GUID(interface_guid) match { + case Some(obj: Terminal) => + val objDef = obj.Definition + sendResponse( + ObjectCreateMessage( + objDef.ObjectId, + PlanetSideGUID(interface_guid), + ObjectCreateMessageParent(parent_guid, 1), + objDef.Packet.ConstructorData(obj).get + ) ) - ) - case _ => ; - } - //seat terminal occupants - continent.GUID(terminal_guid) match { - case Some(obj : Mountable) => - obj.Seats(0).Occupant match { + case _ => ; + } + //seat terminal occupants + continent.GUID(terminal_guid) match { + case Some(obj: Mountable) => + obj.Seats(0).Occupant match { + case Some(tplayer) => + val tdefintion = tplayer.Definition + sendResponse( + ObjectCreateMessage( + tdefintion.ObjectId, + tplayer.GUID, + ObjectCreateMessageParent(parent_guid, 0), + tdefintion.Packet.ConstructorData(tplayer).get + ) + ) + case None => ; + } + case _ => ; + } + }) + + //base turrets + continent.map.TurretToWeapon + .map { case ((turret_guid, _)) => continent.GUID(turret_guid) } + .collect { + case Some(turret: FacilityTurret) => + val pguid = turret.GUID + //attached weapon + if (!turret.isUpgrading) { + turret.ControlledWeapon(wepNumber = 1) match { + case Some(obj: Tool) => + val objDef = obj.Definition + sendResponse( + ObjectCreateMessage( + objDef.ObjectId, + obj.GUID, + ObjectCreateMessageParent(pguid, 1), + objDef.Packet.ConstructorData(obj).get + ) + ) + case _ => ; + } + } + //reserved ammunition? + //TODO need to register if it exists + //seat turret occupant + turret.Seats(0).Occupant match { case Some(tplayer) => val tdefintion = tplayer.Definition sendResponse( ObjectCreateMessage( tdefintion.ObjectId, tplayer.GUID, - ObjectCreateMessageParent(parent_guid, 0), + ObjectCreateMessageParent(pguid, 0), tdefintion.Packet.ConstructorData(tplayer).get ) ) case None => ; } - case _ => ; - } - }) - - //base turrets - continent.Map.TurretToWeapon - .map { case ((turret_guid, _)) => continent.GUID(turret_guid) } - .collect { case Some(turret : FacilityTurret) => - val pguid = turret.GUID - //attached weapon - if(!turret.isUpgrading) { - turret.ControlledWeapon(wepNumber = 1) match { - case Some(obj : Tool) => - val objDef = obj.Definition - sendResponse( - ObjectCreateMessage( - objDef.ObjectId, - obj.GUID, - ObjectCreateMessageParent(pguid, 1), - objDef.Packet.ConstructorData(obj).get - ) - ) - case _ => ; - } - } - //reserved ammunition? - //TODO need to register if it exists - //seat turret occupant - turret.Seats(0).Occupant match { - case Some(tplayer) => - val tdefintion = tplayer.Definition - sendResponse( - ObjectCreateMessage( - tdefintion.ObjectId, - tplayer.GUID, - ObjectCreateMessageParent(pguid, 0), - tdefintion.Packet.ConstructorData(tplayer).get - ) - ) - case None => ; - } } continent.VehicleEvents ! VehicleServiceMessage(continent.Id, VehicleAction.UpdateAmsSpawnPoint(continent)) upstreamMessageCount = 0 @@ -4332,11 +4315,21 @@ class SessionActor extends Actor with MDCContextAware { GoToDeploymentMap() HandleReleaseAvatar(player, continent) - case msg @ SpawnRequestMessage(u1, spawn_type, u3, u4, zone_number) => + case msg @ SpawnRequestMessage(u1, spawnGroup, u3, u4, zoneNumber) => log.info(s"SpawnRequestMessage: $msg") if (deadState != DeadState.RespawnTime) { deadState = DeadState.RespawnTime - cluster ! Zone.Lattice.RequestSpawnPoint(zone_number.toInt, player, spawn_type.id.toInt) + cluster ! InterstellarClusterService.GetNearbySpawnPoint( + spawnGroup match { + case SpawnGroup.Sanctuary => + Zones.SanctuaryZoneNumber(player.Faction) + case _ => + zoneNumber + }, + player, + Seq(spawnGroup), + context.self + ) } else { log.warn("SpawnRequestMessage: request consumed; already respawning ...") } @@ -4643,7 +4636,7 @@ class SessionActor extends Actor with MDCContextAware { case msg @ ZipLineMessage(player_guid, forwards, action, path_id, pos) => log.info("ZipLineMessage: " + msg) val (isTeleporter: Boolean, path: Option[ZipLinePath]) = - continent.ZipLinePaths.find(x => x.PathId == path_id) match { + continent.zipLinePaths.find(x => x.PathId == path_id) match { case Some(x) => (x.IsTeleporter, Some(x)) case _ => log.warn(s"Couldn't find zipline path ${path_id} in zone ${continent.Number} / ${continent.Id}") @@ -4879,7 +4872,7 @@ class SessionActor extends Actor with MDCContextAware { ValidObject(object_guid) match { case Some(door: Door) => if ( - player.Faction == door.Faction || (continent.Map.DoorToLock.get(object_guid.guid) match { + player.Faction == door.Faction || (continent.map.DoorToLock.get(object_guid.guid) match { case Some(lock_guid) => val lock = continent.GUID(lock_guid).get.asInstanceOf[IFFLock] val owner = lock.Owner.asInstanceOf[Building] @@ -4902,7 +4895,7 @@ class SessionActor extends Actor with MDCContextAware { sendResponse(GenericObjectStateMsg(object_guid, 16)) } - case Some(resourceSilo : ResourceSilo) => + case Some(resourceSilo: ResourceSilo) => resourceSilo.Actor ! CommonMessages.Use(player) case Some(panel: IFFLock) => @@ -5783,7 +5776,14 @@ class SessionActor extends Actor with MDCContextAware { log.info(s"AvatarFirstTimeEvent: $event_name") avatar.FirstTimeEvents = avatar.FirstTimeEvents :+ event_name - case msg @ WarpgateRequest(continent_guid, building_guid, dest_building_guid, dest_continent_guid, unk1, unk2) => + case msg @ WarpgateRequest( + continent_guid, + building_guid, + destinationBuildingGuid, + destinationZoneGuid, + unk1, + unk2 + ) => log.info(s"WarpgateRequest: $msg") CancelZoningProcessWithDescriptiveReason("cancel_use") if (deadState != DeadState.RespawnTime) { @@ -5795,7 +5795,12 @@ class SessionActor extends Actor with MDCContextAware { true })) => deadState = DeadState.RespawnTime - cluster ! Zone.Lattice.RequestSpecificSpawnPoint(dest_continent_guid.guid, player, dest_building_guid) + cluster ! InterstellarClusterService.GetSpawnPoint( + destinationZoneGuid.guid, + player, + destinationBuildingGuid, + context.self + ) case Some(wg: WarpGate) if (!wg.Active) => log.info(s"WarpgateRequest: inactive WarpGate") @@ -6425,36 +6430,6 @@ class SessionActor extends Actor with MDCContextAware { } } - /** - * Before calling `Interstellar.GetWorld` to change zones, perform the following task (which can be a nesting of subtasks). - * @param priorTask the tasks to perform - * @param zoneId the zone to load afterwards - * @return a `TaskResolver.GiveTask` message - */ - def TaskBeforeZoneChange(priorTask: TaskResolver.GiveTask, zoneId: String): TaskResolver.GiveTask = { - TaskResolver.GiveTask( - new Task() { - private val localZone = continent - private val localNewZone = zoneId - private val localAvatarMsg = Zone.Population.Leave(avatar) - private val localService = cluster - private val localServiceMsg = InterstellarCluster.GetWorld(zoneId) - - override def Description: String = - s"additional tasking in zone ${localZone.Id} before switching to zone $localNewZone" - - override def isComplete: Task.Resolution.Value = priorTask.task.isComplete - - def Execute(resolver: ActorRef): Unit = { - localZone.Population ! localAvatarMsg - localService ! localServiceMsg - resolver ! Success(this) - } - }, - List(priorTask) - ) - } - def CallBackForTask(task: TaskResolver.GiveTask, sendTo: ActorRef, pass: Any): TaskResolver.GiveTask = { TaskResolver.GiveTask( new Task() { @@ -7042,52 +7017,7 @@ class SessionActor extends Actor with MDCContextAware { * @param building the building object */ def initFacility(continentNumber: Int, buildingNumber: Int, building: Building): Unit = { - val ( - ntuLevel, - isHacked, - empireHack, - hackTimeRemaining, - controllingEmpire, - unk1, - unk1x, - generatorState, - spawnTubesNormal, - forceDomeActive, - latticeBenefit, - cavernBenefit, - unk4, - unk5, - unk6, - unk7, - unk7x, - boostSpawnPain, - boostGeneratorPain - ) = building.Info - sendResponse( - BuildingInfoUpdateMessage( - building.Zone.Number, - building.MapId, - ntuLevel, - isHacked, - empireHack, - hackTimeRemaining, - controllingEmpire, - unk1, - unk1x, - generatorState, - spawnTubesNormal, - forceDomeActive, - latticeBenefit, - cavernBenefit, - unk4, - unk5, - unk6, - unk7, - unk7x, - boostSpawnPain, - boostGeneratorPain - ) - ) + sendResponse(building.infoUpdateMessage()) sendResponse(DensityLevelUpdateMessage(continentNumber, buildingNumber, List(0, 0, 0, 0, 0, 0, 0, 0))) } @@ -7201,8 +7131,8 @@ class SessionActor extends Actor with MDCContextAware { //silo capacity sendResponse(PlanetsideAttributeMessage(amenityId, 45, silo.CapacitorDisplay)) //warning lights - sendResponse(PlanetsideAttributeMessage(silo.Owner.GUID, 47, if(silo.LowNtuWarningOn) 1 else 0)) - if(silo.NtuCapacitor == 0) { + sendResponse(PlanetsideAttributeMessage(silo.Owner.GUID, 47, if (silo.LowNtuWarningOn) 1 else 0)) + if (silo.NtuCapacitor == 0) { sendResponse(PlanetsideAttributeMessage(silo.Owner.GUID, 48, 1)) } case door: Door if door.isOpen => @@ -7896,19 +7826,27 @@ class SessionActor extends Actor with MDCContextAware { * @param currentZone the current zone number */ def RequestSanctuaryZoneSpawn(tplayer: Player, currentZone: Int): Unit = { - val sanctNumber = Zones.SanctuaryZoneNumber(tplayer.Faction) - if (currentZone == sanctNumber) { - if (!player.isAlive) { - sendResponse(DisconnectMessage("Player failed to load on faction's sanctuary continent. Oh no.")) - } - //we are already on sanctuary, alive; what more is there to do? - } else { - continent.GUID(player.VehicleSeated) match { - case Some(obj: Vehicle) if !obj.Destroyed => - cluster ! Zone.Lattice.RequestSpawnPoint(sanctNumber, tplayer, 12) //warp gates for functioning vehicles - case _ => - cluster ! Zone.Lattice.RequestSpawnPoint(sanctNumber, tplayer, 7) //player character spawns - } + if (currentZone == Zones.SanctuaryZoneNumber(tplayer.Faction)) { + log.error("RequestSanctuaryZoneSpawn called for player already in sanctuary.") + sendResponse(DisconnectMessage("RequestSanctuaryZoneSpawn called for player already in sanctuary.")) + return + } + + continent.GUID(player.VehicleSeated) match { + case Some(obj: Vehicle) if !obj.Destroyed => + cluster ! InterstellarClusterService.GetRandomSpawnPoint( + Zones.SanctuaryZoneNumber(player.Faction), + player.Faction, + Seq(SpawnGroup.WarpGate), + context.self + ) + case _ => + cluster ! InterstellarClusterService.GetRandomSpawnPoint( + Zones.SanctuaryZoneNumber(player.Faction), + player.Faction, + Seq(SpawnGroup.Sanctuary), + context.self + ) } } @@ -8934,28 +8872,27 @@ class SessionActor extends Actor with MDCContextAware { * If the player is alive and mounted in a vehicle, a different can of worms is produced. * The ramifications of these conditions are not fully satisfied until the player loads into the new zone. * Even then, the conclusion becomes delayed while a slightly lagged mechanism hoists players between zones. + * * @see `AvatarDeadStateMessage` * @see `interstellarFerry` * @see `LoadZoneAsPlayer` * @see `LoadZoneInVehicle` - * @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 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(zoneId: String, pos: Vector3, ori: Vector3, respawnTime: Long): Unit = { - log.info(s"Load in zone $zoneId at position $pos in $respawnTime seconds") + def LoadZonePhysicalSpawnPoint(zoneId: String, pos: Vector3, ori: Vector3, respawnTime: FiniteDuration): Unit = { + log.info(s"Load in zone $zoneId at position $pos in $respawnTime") respawnTimer.cancel reviveTimer.cancel - val backpack = player.isBackpack - val respawnTimeMillis = respawnTime * 1000 //ms deadState = DeadState.RespawnTime sendResponse( AvatarDeadStateMessage( DeadState.RespawnTime, - respawnTimeMillis, - respawnTimeMillis, + respawnTime.toMillis, + respawnTime.toMillis, Vector3.Zero, player.Faction, true @@ -8963,15 +8900,16 @@ class SessionActor extends Actor with MDCContextAware { ) shiftPosition = Some(pos) shiftOrientation = Some(ori) - val (target, msg) = - if (backpack) { //if the player is dead, he is handled as dead infantry, even if he died in a vehicle - //new player is spawning + + respawnTimer = context.system.scheduler.scheduleOnce(respawnTime) { + if (player.isBackpack) { // if the player is dead, he is handled as dead infantry, even if he died in a vehicle + // new player is spawning val newPlayer = RespawnClone(player) newPlayer.Position = pos newPlayer.Orientation = ori LoadZoneAsPlayer(newPlayer, zoneId) } else { - //deactivate non-passive implants + // deactivate non-passive implants player.Implants.indices.foreach { index => val implantSlot = player.ImplantSlot(index) if (implantSlot.Active && implantSlot.Charge(player.ExoSuit) > 0) { @@ -8979,10 +8917,10 @@ class SessionActor 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 + case Some(vehicle: Vehicle) => // driver or passenger in vehicle using a warp gate, or a droppod LoadZoneInVehicle(vehicle, pos, ori, zoneId) - case _ if player.HasGUID => //player is deconstructing self or instant action + case _ if player.HasGUID => // player is deconstructing self or instant action val player_guid = player.GUID sendResponse(ObjectDeleteMessage(player_guid, 4)) continent.AvatarEvents ! AvatarServiceMessage( @@ -8999,52 +8937,50 @@ class SessionActor extends Actor with MDCContextAware { LoadZoneAsPlayer(player, zoneId) } } - import scala.concurrent.ExecutionContext.Implicits.global - respawnTimer = context.system.scheduler.scheduleOnce(respawnTime seconds, target, msg) + } + } /** * Deal with a target player as free-standing infantry in the course of a redeployment action to a target continent * whether that action is the result of a deconstruction (reconstruction), a death (respawning), - * or other position shifting action handled directly by the server.
- *
+ * or other position shifting action handled directly by the server. + * * The two important vectors are still whether the zone being transported to is the same or is different * and whether the target player is alive or released (note: not just "dead" ...). - * @see `LoadZoneCommonTransferActivity` - * @see `GUIDTask.UnregisterAvatar` - * @see `GUIDTask.UnregisterLocker` - * @see `PlayerLoaded` - * @see `Player.isBackpack` - * @see `RegisterAvatar` - * @see `TaskBeforeZoneChange` - * @param tplayer the target player being moved around; - * not necessarily the same player as the `WorldSessionActor`-global `player` - * @param zone_id the zone in which the player will be placed - * @return a tuple composed of an `ActorRef` destination and a message to send to that destination + * + * @param targetPlayer the target player being moved around; + * not necessarily the same player as the `WorldSessionActor`-global `player` + * @param zoneId the zone in which the player will be placed */ - def LoadZoneAsPlayer(tplayer: Player, zone_id: String): (ActorRef, Any) = { - if (!zoneReload && zone_id == continent.Id) { - if (player.isBackpack) { //important! test the actor-wide player ref, not the parameter - //respawning from unregistered player - (taskResolver, RegisterAvatar(tplayer)) + def LoadZoneAsPlayer(targetPlayer: Player, zoneId: String): Unit = { + log.info(s"loadZoneAsPlayer $targetPlayer $zoneId") + if (!zoneReload && zoneId == continent.Id) { + if (player.isBackpack) { // important! test the actor-wide player ref, not the parameter + // respawning from unregistered player + taskResolver ! RegisterAvatar(targetPlayer) } else { - //move existing player; this is the one case where the original GUID is retained by the player - (self, PlayerLoaded(tplayer)) + // move existing player; this is the one case where the original GUID is retained by the player + self ! PlayerLoaded(targetPlayer) } } else { LoadZoneCommonTransferActivity() val original = player if (player.isBackpack) { - //unregister avatar locker + GiveWorld - session = session.copy(player = tplayer) - (taskResolver, TaskBeforeZoneChange(GUIDTask.UnregisterLocker(original.Locker)(continent.GUID), zone_id)) + session = session.copy(player = targetPlayer) + taskThenZoneChange( + GUIDTask.UnregisterLocker(original.Locker)(continent.GUID), + InterstellarClusterService.FindZone(_.Id == zoneId, context.self) + ) } else if (player.HasGUID) { - //unregister avatar whole + GiveWorld - (taskResolver, TaskBeforeZoneChange(GUIDTask.UnregisterAvatar(original)(continent.GUID), zone_id)) + taskThenZoneChange( + GUIDTask.UnregisterAvatar(original)(continent.GUID), + InterstellarClusterService.FindZone(_.Id == zoneId, context.self) + ) } else { - //not currently registered; so we'll just GiveWorld - (cluster, InterstellarCluster.GetWorld(zone_id)) + cluster ! InterstellarClusterService.FindZone(_.Id == zoneId, context.self) } + } } @@ -9057,10 +8993,7 @@ class SessionActor extends Actor with MDCContextAware { * Any seated position that isn't the driver is a passenger. * The most important role performed in this function is to declare a reference to the vehicle itsself * since no other connection from the player to the vehicle is guaranteed to persist in a meaningful way during the transfer. - * @see `interstellarFerry` - * @see `LoadZoneInVehicleAsDriver` - * @see `LoadZoneInVehicleAsPassenger` - * @see `Vehicle.PassengerInSeat` + * * @param vehicle the target vehicle being moved around; * WILL necessarily be the same vehicles as is controlled by the `WorldSessionActor`-global `player` * @param pos the game world coordinates where the vehicle will be positioned @@ -9069,7 +9002,7 @@ class SessionActor extends Actor with MDCContextAware { * or in which the vehicle has already been placed * @return a tuple composed of an ActorRef` destination and a message to send to that destination */ - def LoadZoneInVehicle(vehicle: Vehicle, pos: Vector3, ori: Vector3, zone_id: String): (ActorRef, Any) = { + def LoadZoneInVehicle(vehicle: Vehicle, pos: Vector3, ori: Vector3, zone_id: String) = { interstellarFerry = Some(vehicle) if (vehicle.PassengerInSeat(player).contains(0)) { vehicle.Position = pos @@ -9094,19 +9027,14 @@ class SessionActor extends Actor with MDCContextAware { * the vehicle to be deleted might not be the one immediately mounted. * A reference to the top-level ferrying vehicle's former globally unique identifier has been retained for this purpose. * This vehicle can be deleted for everyone if no more work can be detected. - * @see `interstellarFerryTopLevelGUID` - * @see `LoadZoneCommonTransferActivity` - * @see `PlayerLoaded` - * @see `TaskBeforeZoneChange` - * @see `UnAccessContents` - * @see `UnregisterDrivenVehicle` + * * @param vehicle the target vehicle being moved around; * WILL necessarily be the same vehicles as is controlled by the `WorldSessionActor`-global `player` - * @param zone_id the zone in which the vehicle and driver will be placed, + * @param zoneId the zone in which the vehicle and driver will be placed, * or in which the vehicle has already been placed * @return a tuple composed of an `ActorRef` destination and a message to send to that destination */ - def LoadZoneInVehicleAsDriver(vehicle: Vehicle, zone_id: String): (ActorRef, Any) = { + def LoadZoneInVehicleAsDriver(vehicle: Vehicle, zoneId: String) = { log.info(s"LoadZoneInVehicleAsDriver: ${player.Name} is driving a ${vehicle.Definition.Name}") val manifest = vehicle.PrepareGatingManifest() log.info(s"$manifest") @@ -9130,23 +9058,26 @@ class SessionActor extends Actor with MDCContextAware { ) } // - if (!zoneReload && zone_id == continent.Id) { + if (!zoneReload && zoneId == continent.Id) { if (vehicle.Definition == GlobalDefinitions.droppod) { //instant action droppod in the same zone - (taskResolver, RegisterDroppod(vehicle, player)) + taskResolver ! RegisterDroppod(vehicle, player) } else { //transferring a vehicle between spawn points (warp gates) in the same zone - (self, PlayerLoaded(player)) + self ! PlayerLoaded(player) } } else if (vehicle.Definition == GlobalDefinitions.droppod) { LoadZoneCommonTransferActivity() - player.Continent = zone_id //forward-set the continent id to perform a test - (taskResolver, TaskBeforeZoneChange(GUIDTask.UnregisterAvatar(player)(continent.GUID), zone_id)) + player.Continent = zoneId //forward-set the continent id to perform a test + taskThenZoneChange( + GUIDTask.UnregisterAvatar(player)(continent.GUID), + InterstellarClusterService.FindZone(_.Id == zoneId, context.self) + ) } else { UnAccessContents(vehicle) LoadZoneCommonTransferActivity() player.VehicleSeated = vehicle.GUID - player.Continent = zone_id //forward-set the continent id to perform a test + player.Continent = zoneId //forward-set the continent id to perform a test interstellarFerryTopLevelGUID = (if ( manifest.passengers.isEmpty && manifest.cargo.count { case (name, _) => !name.equals("MISSING_DRIVER") } == 0 @@ -9162,7 +9093,10 @@ class SessionActor extends Actor with MDCContextAware { }) //unregister vehicle and driver whole + GiveWorld continent.Transport ! Zone.Vehicle.Despawn(vehicle) - (taskResolver, TaskBeforeZoneChange(UnregisterDrivenVehicle(vehicle, player), zone_id)) + taskThenZoneChange( + UnregisterDrivenVehicle(vehicle, player), + InterstellarClusterService.FindZone(_.Id == zoneId, context.self) + ) } } @@ -9180,6 +9114,7 @@ class SessionActor extends Actor with MDCContextAware { * the vehicle to be deleted might not be the one immediately mounted. * A reference to the top-level ferrying vehicle's former globally unique identifier has been retained for this purpose. * This vehicle can be deleted for everyone if no more work can be detected. + * * @see `GUIDTask.UnregisterAvatar` * @see `LoadZoneCommonTransferActivity` * @see `Vehicles.AllGatedOccupantsInSameZone` @@ -9187,22 +9122,25 @@ class SessionActor extends Actor with MDCContextAware { * @see `TaskBeforeZoneChange` * @see `UnAccessContents` * @param vehicle the target vehicle being moved around - * @param zone_id the zone in which the vehicle and driver will be placed + * @param zoneId the zone in which the vehicle and driver will be placed * @return a tuple composed of an `ActorRef` destination and a message to send to that destination */ - def LoadZoneInVehicleAsPassenger(vehicle: Vehicle, zone_id: String): (ActorRef, Any) = { + def LoadZoneInVehicleAsPassenger(vehicle: Vehicle, zoneId: String): Unit = { log.info(s"LoadZoneInVehicleAsPassenger: ${player.Name} is the passenger of a ${vehicle.Definition.Name}") - if (!zoneReload && zone_id == continent.Id) { + if (!zoneReload && zoneId == continent.Id) { //transferring a vehicle between spawn points (warp gates) in the same zone - (self, PlayerLoaded(player)) + self ! PlayerLoaded(player) } else { LoadZoneCommonTransferActivity() player.VehicleSeated = vehicle.GUID - player.Continent = zone_id //forward-set the continent id to perform a test + player.Continent = zoneId //forward-set the continent id to perform a test val continentId = continent.Id interstellarFerryTopLevelGUID = None - //unregister avatar + GiveWorld - (taskResolver, TaskBeforeZoneChange(GUIDTask.UnregisterAvatar(player)(continent.GUID), zone_id)) + + taskThenZoneChange( + GUIDTask.UnregisterAvatar(player)(continent.GUID), + InterstellarClusterService.FindZone(_.Id == zoneId, context.self) + ) } } @@ -9214,7 +9152,6 @@ class SessionActor extends Actor with MDCContextAware { * The messages address the avatar of their recipient `WorldSessionActor` objects. * @param player_guid the driver of the target vehicle * @param toZoneId the zone where the target vehicle will be moved - * @param toChannel the vehicle-specific channel with which all passengers are coordinated to the vehicle * @param vehicle the vehicle (object) */ def LoadZoneTransferPassengerMessages(player_guid: PlanetSideGUID, toZoneId: String, vehicle: Vehicle): Unit = { @@ -9242,6 +9179,25 @@ class SessionActor extends Actor with MDCContextAware { } } + /** Before changing zones, perform the following task (which can be a nesting of subtasks). */ + def taskThenZoneChange( + task: TaskResolver.GiveTask, + zoneMessage: InterstellarClusterService.FindZone + ): Unit = { + taskResolver ! TaskResolver.GiveTask( + new Task() { + override def isComplete: Task.Resolution.Value = task.task.isComplete + + def Execute(resolver: ActorRef): Unit = { + continent.Population ! Zone.Population.Leave(avatar) + cluster ! zoneMessage + resolver ! Success(this) + } + }, + List(task) + ) + } + /** * Common behavior when transferring between zones * encompassing actions that disassociate the player with entities they left (will leave) in the previous zone. @@ -10113,19 +10069,20 @@ class SessionActor extends Actor with MDCContextAware { * Given an origin and a destination, determine how long the process of traveling should take in reconstruction time. * For most destinations, the unit of receiving ("spawn point") determines the reconstruction time. * In a special consideration, travel to any sanctuary or sanctuary-special zone should be as immediate as zone loading. - * @param toZoneId the zone where the target is headed + * + * @param toZoneId the zone where the target is headed * @param toSpawnPoint the unit the target is using as a destination - * @param fromZoneId the zone where the target current is located - * @return how long in seconds the spawning process will take + * @param fromZoneId the zone where the target current is located + * @return how long the spawning process will take */ - def CountSpawnDelay(toZoneId: String, toSpawnPoint: SpawnPoint, fromZoneId: String): Long = { + def CountSpawnDelay(toZoneId: String, toSpawnPoint: SpawnPoint, fromZoneId: String): FiniteDuration = { val sanctuaryZoneId = Zones.SanctuaryZoneId(player.Faction) if (fromZoneId.equals("Nowhere") || sanctuaryZoneId.equals(toZoneId)) { //to sanctuary - 0L + 0 seconds } else if (!player.isAlive) { - toSpawnPoint.Definition.Delay //TODO +cumulative death penalty + toSpawnPoint.Definition.Delay seconds //TODO +cumulative death penalty } else { - toSpawnPoint.Definition.Delay + toSpawnPoint.Definition.Delay seconds } } diff --git a/common/src/main/scala/net/psforever/actors/zone/BuildingActor.scala b/common/src/main/scala/net/psforever/actors/zone/BuildingActor.scala new file mode 100644 index 00000000..97e0b868 --- /dev/null +++ b/common/src/main/scala/net/psforever/actors/zone/BuildingActor.scala @@ -0,0 +1,175 @@ +package net.psforever.actors.zone + +import akka.actor.typed.receptionist.Receptionist +import akka.actor.typed.scaladsl.{ActorContext, Behaviors, StashBuffer} +import akka.actor.typed.{ActorRef, Behavior, SupervisorStrategy} +import akka.{actor => classic} +import net.psforever.actors.commands.NtuCommand +import net.psforever.objects.serverobject.structures.{Building, WarpGate} +import net.psforever.objects.zones.Zone +import net.psforever.persistence +import net.psforever.types.PlanetSideEmpire +import net.psforever.util.Database._ +import services.galaxy.{GalaxyAction, GalaxyServiceMessage} +import services.local.{LocalAction, LocalServiceMessage} +import services.{InterstellarClusterService, ServiceManager} + +import scala.concurrent.ExecutionContext.Implicits.global +import scala.util.{Failure, Success} + +object BuildingActor { + def apply(zone: Zone, building: Building): Behavior[Command] = + Behaviors + .supervise[Command] { + Behaviors.withStash(100) { buffer => + Behaviors.setup(context => new BuildingActor(context, buffer, zone, building).start()) + } + } + .onFailure[Exception](SupervisorStrategy.restart) + + sealed trait Command + + private case class ReceptionistListing(listing: Receptionist.Listing) extends Command + + private case class ServiceManagerLookupResult(result: ServiceManager.LookupResult) extends Command + + final case class SetFaction(faction: PlanetSideEmpire.Value) extends Command + + // TODO remove + // Changes to building objects should go through BuildingActor + // Once they do, we won't need this anymore + final case class MapUpdate() extends Command + + final case class Ntu(command: NtuCommand.Command) extends Command +} + +class BuildingActor( + context: ActorContext[BuildingActor.Command], + buffer: StashBuffer[BuildingActor.Command], + zone: Zone, + building: Building +) { + + import BuildingActor._ + + private[this] val log = org.log4s.getLogger + var galaxyService: Option[classic.ActorRef] = None + var interstellarCluster: Option[ActorRef[InterstellarClusterService.Command]] = None + + context.system.receptionist ! Receptionist.Find( + InterstellarClusterService.InterstellarClusterServiceKey, + context.messageAdapter[Receptionist.Listing](ReceptionistListing) + ) + + ServiceManager.serviceManager ! ServiceManager.LookupFromTyped( + "galaxy", + context.messageAdapter[ServiceManager.LookupResult](ServiceManagerLookupResult) + ) + + def start(): Behavior[Command] = { + Behaviors.receiveMessage { + case ReceptionistListing(InterstellarClusterService.InterstellarClusterServiceKey.Listing(listings)) => + interstellarCluster = listings.headOption + postStartBehaviour() + + case ServiceManagerLookupResult(ServiceManager.LookupResult(request, endpoint)) => + request match { + case "galaxy" => galaxyService = Some(endpoint) + } + postStartBehaviour() + + case other => + buffer.stash(other) + Behaviors.same + } + } + + def postStartBehaviour(): Behavior[Command] = { + (galaxyService, interstellarCluster) match { + case (Some(galaxyService), Some(interstellarCluster)) => + buffer.unstashAll(active(galaxyService, interstellarCluster)) + case _ => + Behaviors.same + } + } + + def active( + galaxyService: classic.ActorRef, + interstellarCluster: ActorRef[InterstellarClusterService.Command] + ): Behavior[Command] = { + Behaviors.receiveMessagePartial { + case SetFaction(faction) => + import ctx._ + ctx + .run( + query[persistence.Building] + .filter(_.localId == lift(building.MapId)) + .filter(_.zoneId == lift(zone.Number)) + ) + .onComplete { + case Success(res) => + res.headOption match { + case Some(_) => + ctx + .run( + query[persistence.Building] + .filter(_.localId == lift(building.MapId)) + .filter(_.zoneId == lift(zone.Number)) + .update(_.factionId -> lift(building.Faction.id)) + ) + .onComplete { + case Success(_) => + case Failure(e) => log.error(e.getMessage) + } + case _ => + ctx + .run( + query[persistence.Building] + .insert( + _.localId -> lift(building.MapId), + _.factionId -> lift(building.Faction.id), + _.zoneId -> lift(zone.Number) + ) + ) + .onComplete { + case Success(_) => + case Failure(e) => log.error(e.getMessage) + } + } + case Failure(e) => log.error(e.getMessage) + } + building.Faction = faction + galaxyService ! GalaxyServiceMessage(GalaxyAction.MapUpdate(building.infoUpdateMessage())) + zone.LocalEvents ! LocalServiceMessage(zone.Id, LocalAction.SetEmpire(building.GUID, faction)) + Behaviors.same + + case MapUpdate() => + galaxyService ! GalaxyServiceMessage(GalaxyAction.MapUpdate(building.infoUpdateMessage())) + Behaviors.same + + case Ntu(msg) => + ntu(msg) + } + } + + def ntu(msg: NtuCommand.Command): Behavior[Command] = { + import NtuCommand._ + val ntuBuilding = building match { + case b: WarpGate => b + case _ => return Behaviors.unhandled + } + + msg match { + case Offer(source) => + case Request(amount, replyTo) => + ntuBuilding match { + case warpGate: WarpGate => replyTo ! Grant(warpGate, if (warpGate.Active) amount else 0) + case _ => return Behaviors.unhandled + } + + } + + Behaviors.same + } + +} diff --git a/common/src/main/scala/net/psforever/actors/zone/ZoneActor.scala b/common/src/main/scala/net/psforever/actors/zone/ZoneActor.scala new file mode 100644 index 00000000..a96b47a6 --- /dev/null +++ b/common/src/main/scala/net/psforever/actors/zone/ZoneActor.scala @@ -0,0 +1,124 @@ +package net.psforever.actors.zone + +import akka.actor.typed.{ActorRef, Behavior, SupervisorStrategy} +import akka.actor.typed.scaladsl.{AbstractBehavior, ActorContext, Behaviors} +import net.psforever.objects.ballistics.SourceEntry +import net.psforever.objects.ce.Deployable +import net.psforever.objects.equipment.Equipment +import net.psforever.objects.serverobject.structures.StructureType +import net.psforever.objects.zones.Zone +import net.psforever.objects.{ConstructionItem, PlanetSideGameObject, Player, Vehicle} +import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID, Vector3} +import scala.collection.mutable.ListBuffer +import akka.actor.typed.scaladsl.adapter._ +import net.psforever.util.Database._ +import net.psforever.persistence +import scala.util.{Failure, Success} +import scala.concurrent.ExecutionContext.Implicits.global + +object ZoneActor { + def apply(zone: Zone): Behavior[Command] = + Behaviors + .supervise[Command] { + Behaviors.setup(context => new ZoneActor(context, zone)) + } + .onFailure[Exception](SupervisorStrategy.restart) + + sealed trait Command + + final case class GetZone(replyTo: ActorRef[ZoneResponse]) extends Command + + final case class ZoneResponse(zone: Zone) + + final case class AddPlayer(player: Player) extends Command + + final case class RemovePlayer(player: Player) extends Command + + final case class DropItem(item: Equipment, position: Vector3, orientation: Vector3) extends Command + + final case class PickupItem(guid: PlanetSideGUID) extends Command + + final case class BuildDeployable(obj: PlanetSideGameObject with Deployable, withTool: ConstructionItem) + extends Command + + final case class DismissDeployable(obj: PlanetSideGameObject with Deployable) extends Command + + final case class SpawnVehicle(vehicle: Vehicle) extends Command + + final case class DespawnVehicle(vehicle: Vehicle) extends Command + + final case class HotSpotActivity(defender: SourceEntry, attacker: SourceEntry, location: Vector3) extends Command + + // TODO remove + // Changes to zone objects should go through ZoneActor + // Once they do, we won't need this anymore + final case class ZoneMapUpdate() extends Command + +} + +class ZoneActor(context: ActorContext[ZoneActor.Command], zone: Zone) + extends AbstractBehavior[ZoneActor.Command](context) { + + import ZoneActor._ + import ctx._ + + private[this] val log = org.log4s.getLogger + val players: ListBuffer[Player] = ListBuffer() + + zone.actor = context.self + zone.init(context.toClassic) + + ctx.run(query[persistence.Building].filter(_.zoneId == lift(zone.Number))).onComplete { + case Success(buildings) => + buildings.foreach { building => + zone.BuildingByMapId(building.localId) match { + case Some(b) => b.Faction = PlanetSideEmpire(building.factionId) + case None => // TODO this happens during testing, need a way to not always persist during tests + } + + } + case Failure(e) => log.error(e.getMessage) + } + + override def onMessage(msg: Command): Behavior[Command] = { + msg match { + case GetZone(replyTo) => + replyTo ! ZoneResponse(zone) + + case AddPlayer(player) => + players.addOne(player) + + case RemovePlayer(player) => + players.filterInPlace(p => p.CharId == player.CharId) + + case DropItem(item, position, orientation) => + zone.Ground ! Zone.Ground.DropItem(item, position, orientation) + + case PickupItem(guid) => + zone.Ground ! Zone.Ground.PickupItem(guid) + + case BuildDeployable(obj, tool) => + zone.Deployables ! Zone.Deployable.Build(obj, tool) + + case DismissDeployable(obj) => + zone.Deployables ! Zone.Deployable.Dismiss(obj) + + case SpawnVehicle(vehicle) => + zone.Transport ! Zone.Vehicle.Spawn(vehicle) + + case DespawnVehicle(vehicle) => + zone.Transport ! Zone.Vehicle.Despawn(vehicle) + + case HotSpotActivity(defender, attacker, location) => + zone.Activity ! Zone.HotSpot.Activity(defender, attacker, location) + + case ZoneMapUpdate() => + zone.Buildings + .filter(_._2.BuildingType == StructureType.Facility) + .values + .foreach(_.Actor ! BuildingActor.MapUpdate()) + } + + this + } +} diff --git a/common/src/main/scala/net/psforever/login/LoginSessionActor.scala b/common/src/main/scala/net/psforever/login/LoginSessionActor.scala index ef42e3f5..1c472e8c 100644 --- a/common/src/main/scala/net/psforever/login/LoginSessionActor.scala +++ b/common/src/main/scala/net/psforever/login/LoginSessionActor.scala @@ -140,7 +140,7 @@ class LoginSessionActor extends Actor with MDCContextAware { def accountLogin(username: String, password: String): Unit = { import ctx._ val newToken = this.generateToken() - + log.info("accountLogin") val result = for { // backwards compatibility: prefer exact match first, then try lowercase accountsExact <- ctx.run(query[persistence.Account].filter(_.username == lift(username))) @@ -171,6 +171,7 @@ class LoginSessionActor extends Actor with MDCContextAware { } login <- accountOption match { case Some(account) => + log.info(s"$account") (account.inactive, password.isBcrypted(account.passhash)) match { case (false, true) => accountIntermediary ! StoreAccountData(newToken, new Account(account.id, account.username, account.gm)) diff --git a/common/src/main/scala/net/psforever/login/psadmin/CmdListPlayers.scala b/common/src/main/scala/net/psforever/login/psadmin/CmdListPlayers.scala index e6af302e..0cf48e5b 100644 --- a/common/src/main/scala/net/psforever/login/psadmin/CmdListPlayers.scala +++ b/common/src/main/scala/net/psforever/login/psadmin/CmdListPlayers.scala @@ -1,27 +1,40 @@ package net.psforever.login.psadmin +import akka.actor.typed.receptionist.Receptionist import akka.actor.{Actor, ActorRef} -import net.psforever.objects.zones.InterstellarCluster - +import services.{InterstellarClusterService, ServiceManager} import scala.collection.mutable.Map +import akka.actor.typed.scaladsl.adapter._ class CmdListPlayers(args: Array[String], services: Map[String, ActorRef]) extends Actor { private[this] val log = org.log4s.getLogger(self.path.name) override def preStart = { - services { "cluster" } ! InterstellarCluster.ListPlayers() + ServiceManager.receptionist ! Receptionist.Find( + InterstellarClusterService.InterstellarClusterServiceKey, + context.self + ) } override def receive = { - case InterstellarCluster.PlayerList(players) => + case InterstellarClusterService.InterstellarClusterServiceKey.Listing(listings) => + listings.head ! InterstellarClusterService.GetPlayers(context.self) + + case InterstellarClusterService.PlayersResponse(players) => val data = Map[String, Any]() - data { "player_count" } = players.size - data { "player_list" } = Array[String]() + data { + "player_count" + } = players.size + data { + "player_list" + } = Array[String]() if (players.isEmpty) { context.parent ! CommandGoodResponse("No players currently online!", data) } else { - data { "player_list" } = players + data { + "player_list" + } = players context.parent ! CommandGoodResponse(s"${players.length} players online\n", data) } case default => log.error(s"Unexpected message $default") diff --git a/common/src/main/scala/net/psforever/objects/Ntu.scala b/common/src/main/scala/net/psforever/objects/Ntu.scala index 0d2abe51..e6d88795 100644 --- a/common/src/main/scala/net/psforever/objects/Ntu.scala +++ b/common/src/main/scala/net/psforever/objects/Ntu.scala @@ -2,6 +2,7 @@ package net.psforever.objects import akka.actor.{Actor, ActorRef} +import net.psforever.actors.commands.NtuCommand import net.psforever.objects.serverobject.transfer.{TransferBehavior, TransferContainer} object Ntu { @@ -9,77 +10,81 @@ object Ntu { /** * Message for a `sender` announcing it has nanites it can offer the recipient. + * * @param src the nanite container recognized as the sender */ - final case class Offer(src : NtuContainer) + final case class Offer(src: NtuContainer) /** * Message for a `sender` asking for nanites from the recipient. + * * @param min a minimum amount of nanites requested; * if 0, the `sender` has no expectations * @param max the amount of nanites required to not make further requests; * if 0, the `sender` is full and the message is for clean up operations */ - final case class Request(min : Int, max : Int) + final case class Request(min: Int, max: Int) /** * Message for transferring nanites to a recipient. - * @param src the nanite container recognized as the sender + * + * @param src the nanite container recognized as the sender * @param amount the nanites transferred in this package */ - final case class Grant(src : NtuContainer, amount : Int) + final case class Grant(src: NtuContainer, amount: Int) } trait NtuContainer extends TransferContainer { - def NtuCapacitor : Int + def NtuCapacitor: Int - def NtuCapacitor_=(value: Int) : Int + def NtuCapacitor_=(value: Int): Int - def Definition : NtuContainerDefinition + def Definition: NtuContainerDefinition } trait CommonNtuContainer extends NtuContainer { - private var ntuCapacitor : Int = 0 + private var ntuCapacitor: Int = 0 - def NtuCapacitor : Int = ntuCapacitor + def NtuCapacitor: Int = ntuCapacitor - def NtuCapacitor_=(value: Int) : Int = { + def NtuCapacitor_=(value: Int): Int = { ntuCapacitor = scala.math.max(0, scala.math.min(value, Definition.MaxNtuCapacitor)) NtuCapacitor } - def Definition : NtuContainerDefinition + def Definition: NtuContainerDefinition } trait NtuContainerDefinition { - private var maxNtuCapacitor : Int = 0 + private var maxNtuCapacitor: Int = 0 - def MaxNtuCapacitor : Int = maxNtuCapacitor + def MaxNtuCapacitor: Int = maxNtuCapacitor - def MaxNtuCapacitor_=(max: Int) : Int = { + def MaxNtuCapacitor_=(max: Int): Int = { maxNtuCapacitor = max MaxNtuCapacitor } } trait NtuStorageBehavior extends Actor { - def NtuStorageObject : NtuContainer = null + def NtuStorageObject: NtuContainer = null - def storageBehavior : Receive = { + def storageBehavior: Receive = { case Ntu.Offer(src) => HandleNtuOffer(sender, src) case Ntu.Grant(_, 0) | Ntu.Request(0, 0) | TransferBehavior.Stopping() => StopNtuBehavior(sender) case Ntu.Request(min, max) => HandleNtuRequest(sender, min, max) - case Ntu.Grant(src, amount) => HandleNtuGrant(sender, src, amount) + case Ntu.Grant(src, amount) => HandleNtuGrant(sender, src, amount) + case NtuCommand.Grant(src, amount) => HandleNtuGrant(sender, src, amount) } - def HandleNtuOffer(sender : ActorRef, src : NtuContainer) : Unit + def HandleNtuOffer(sender: ActorRef, src: NtuContainer): Unit - def StopNtuBehavior(sender : ActorRef) : Unit + def StopNtuBehavior(sender: ActorRef): Unit - def HandleNtuRequest(sender : ActorRef, min : Int, max : Int) : Unit + def HandleNtuRequest(sender: ActorRef, min: Int, max: Int): Unit - def HandleNtuGrant(sender : ActorRef, src : NtuContainer, amount : Int) : Unit + def HandleNtuGrant(sender: ActorRef, src: NtuContainer, amount: Int): Unit } diff --git a/common/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala b/common/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala index 09920cc7..eaf6f674 100644 --- a/common/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala +++ b/common/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala @@ -498,7 +498,7 @@ class PlayerControl(player: Player) extends Actor with JammableBehavior with Dam val (interface, slotNumber) = player.VehicleSeated match { case Some(mech_guid) => ( - zone.Map.TerminalToInterface.get(mech_guid.guid), + zone.map.TerminalToInterface.get(mech_guid.guid), if (!player.Implants.exists({ case (implantType, _, _) => implantType == implant_type })) { //no duplicates player.InstallImplant(implant) @@ -547,7 +547,7 @@ class PlayerControl(player: Player) extends Actor with JammableBehavior with Dam val (interface, slotNumber) = player.VehicleSeated match { case Some(mech_guid) => ( - zone.Map.TerminalToInterface.get(mech_guid.guid), + zone.map.TerminalToInterface.get(mech_guid.guid), player.UninstallImplant(implant_type) ) case None => diff --git a/common/src/main/scala/net/psforever/objects/serverobject/implantmech/ImplantTerminalMechControl.scala b/common/src/main/scala/net/psforever/objects/serverobject/implantmech/ImplantTerminalMechControl.scala index 548525f3..9f35c60a 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/implantmech/ImplantTerminalMechControl.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/implantmech/ImplantTerminalMechControl.scala @@ -60,7 +60,7 @@ class ImplantTerminalMechControl(mech: ImplantTerminalMech) player: Player ): Boolean = { val zone = obj.Zone - zone.Map.TerminalToInterface.get(obj.GUID.guid) match { + zone.map.TerminalToInterface.get(obj.GUID.guid) match { case Some(interface_guid) => (zone.GUID(interface_guid) match { case Some(interface) => !interface.Destroyed diff --git a/common/src/main/scala/net/psforever/objects/serverobject/resourcesilo/ResourceSiloControl.scala b/common/src/main/scala/net/psforever/objects/serverobject/resourcesilo/ResourceSiloControl.scala index f62ef881..2299db4c 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/resourcesilo/ResourceSiloControl.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/resourcesilo/ResourceSiloControl.scala @@ -3,6 +3,7 @@ package net.psforever.objects.serverobject.resourcesilo import akka.actor.{Actor, ActorRef} import net.psforever.objects.serverobject.CommonMessages +import net.psforever.actors.zone.{BuildingActor, ZoneActor} import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior} import net.psforever.objects.serverobject.transfer.TransferBehavior import net.psforever.objects.serverobject.structures.Building @@ -10,7 +11,6 @@ import net.psforever.objects.{Ntu, NtuContainer, NtuStorageBehavior} import net.psforever.types.PlanetSideEmpire import services.Service import services.avatar.{AvatarAction, AvatarServiceMessage} -import services.local.{LocalAction, LocalServiceMessage} import services.vehicle.{VehicleAction, VehicleServiceMessage} import scala.concurrent.ExecutionContext.Implicits.global @@ -18,59 +18,69 @@ import scala.concurrent.duration._ /** * An `Actor` that handles messages being dispatched to a specific `Resource Silo`. + * * @param resourceSilo the `Resource Silo` object being governed */ -class ResourceSiloControl(resourceSilo: ResourceSilo) extends Actor with FactionAffinityBehavior.Check with NtuStorageBehavior { +class ResourceSiloControl(resourceSilo: ResourceSilo) + extends Actor + with FactionAffinityBehavior.Check + with NtuStorageBehavior { def FactionObject: FactionAffinity = resourceSilo - private[this] val log = org.log4s.getLogger - var panelAnimationFunc : Int=>Unit = PanelAnimation + + private[this] val log = org.log4s.getLogger + var panelAnimationFunc: Int => Unit = PanelAnimation def receive: Receive = { case "startup" => // todo: This is just a temporary solution to drain NTU over time. When base object destruction is properly implemented NTU should be deducted when base objects repair themselves -// context.system.scheduler.schedule(5 second, 5 second, self, ResourceSilo.UpdateChargeLevel(-1)) + // context.system.scheduler.schedule(5 second, 5 second, self, ResourceSilo.UpdateChargeLevel(-1)) context.become(Processing) case _ => ; } - def Processing : Receive = checkBehavior - .orElse(storageBehavior) - .orElse { - case CommonMessages.Use(player, _) => - if(resourceSilo.Faction == PlanetSideEmpire.NEUTRAL || player.Faction == resourceSilo.Faction) { - resourceSilo.Zone.Vehicles.find(v => v.PassengerInSeat(player).contains(0)) match { - case Some(vehicle) => - context.system.scheduler.scheduleOnce(delay = 1000 milliseconds, vehicle.Actor, TransferBehavior.Discharging(Ntu.Nanites)) - case _ => + def Processing: Receive = + checkBehavior + .orElse(storageBehavior) + .orElse { + case CommonMessages.Use(player, _) => + if (resourceSilo.Faction == PlanetSideEmpire.NEUTRAL || player.Faction == resourceSilo.Faction) { + resourceSilo.Zone.Vehicles.find(v => v.PassengerInSeat(player).contains(0)) match { + case Some(vehicle) => + context.system.scheduler.scheduleOnce( + delay = 1000 milliseconds, + vehicle.Actor, + TransferBehavior.Discharging(Ntu.Nanites) + ) + case _ => + } } - } - case ResourceSilo.LowNtuWarning(enabled: Boolean) => - LowNtuWarning(enabled) + case ResourceSilo.LowNtuWarning(enabled: Boolean) => + LowNtuWarning(enabled) - case ResourceSilo.UpdateChargeLevel(amount: Int) => - UpdateChargeLevel(amount) + case ResourceSilo.UpdateChargeLevel(amount: Int) => + UpdateChargeLevel(amount) - case _ => ; - } + case _ => ; + } - def LowNtuWarning(enabled : Boolean) : Unit = { + def LowNtuWarning(enabled: Boolean): Unit = { resourceSilo.LowNtuWarningOn = enabled log.trace(s"LowNtuWarning: Silo ${resourceSilo.GUID} low ntu warning set to $enabled") val building = resourceSilo.Owner - val zone = building.Zone + val zone = building.Zone building.Zone.AvatarEvents ! AvatarServiceMessage( zone.Id, - AvatarAction.PlanetsideAttribute(building.GUID, 47, if(resourceSilo.LowNtuWarningOn) 1 else 0) + AvatarAction.PlanetsideAttribute(building.GUID, 47, if (resourceSilo.LowNtuWarningOn) 1 else 0) ) } - def UpdateChargeLevel(amount: Int) : Unit = { - val siloChargeBeforeChange = resourceSilo.NtuCapacitor + def UpdateChargeLevel(amount: Int): Unit = { + val siloChargeBeforeChange = resourceSilo.NtuCapacitor val siloDisplayBeforeChange = resourceSilo.CapacitorDisplay - val building = resourceSilo.Owner.asInstanceOf[Building] - val zone = building.Zone + val building = resourceSilo.Owner.asInstanceOf[Building] + val zone = building.Zone // Increase if positive passed in or decrease charge level if negative number is passed in resourceSilo.NtuCapacitor += amount @@ -80,67 +90,65 @@ class ResourceSiloControl(resourceSilo: ResourceSilo) extends Actor with Faction // Only send updated capacitor display value to all clients if it has actually changed if (resourceSilo.CapacitorDisplay != siloDisplayBeforeChange) { - log.trace(s"Silo ${resourceSilo.GUID} NTU bar level has changed from $siloDisplayBeforeChange to ${resourceSilo.CapacitorDisplay}") - resourceSilo.Owner.Actor ! Building.SendMapUpdate(all_clients = true) + log.trace( + s"Silo ${resourceSilo.GUID} NTU bar level has changed from $siloDisplayBeforeChange to ${resourceSilo.CapacitorDisplay}" + ) + resourceSilo.Owner.Actor ! BuildingActor.MapUpdate() zone.AvatarEvents ! AvatarServiceMessage( zone.Id, AvatarAction.PlanetsideAttribute(resourceSilo.GUID, 45, resourceSilo.CapacitorDisplay) ) - building.Actor ! Building.SendMapUpdate(all_clients = true) + building.Actor ! BuildingActor.MapUpdate() } val ntuIsLow = resourceSilo.NtuCapacitor.toFloat / resourceSilo.Definition.MaxNtuCapacitor.toFloat < 0.2f if (resourceSilo.LowNtuWarningOn && !ntuIsLow) { LowNtuWarning(enabled = false) - } - else if (!resourceSilo.LowNtuWarningOn && ntuIsLow) { + } else if (!resourceSilo.LowNtuWarningOn && ntuIsLow) { LowNtuWarning(enabled = true) } if (resourceSilo.NtuCapacitor == 0 && siloChargeBeforeChange > 0) { // Oops, someone let the base run out of power. Shut it all down. zone.AvatarEvents ! AvatarServiceMessage(zone.Id, AvatarAction.PlanetsideAttribute(building.GUID, 48, 1)) - building.Faction = PlanetSideEmpire.NEUTRAL - zone.LocalEvents ! LocalServiceMessage(zone.Id, LocalAction.SetEmpire(building.GUID, PlanetSideEmpire.NEUTRAL)) - building.TriggerZoneMapUpdate() - } - else if (siloChargeBeforeChange == 0 && resourceSilo.NtuCapacitor > 0) { + building.Actor ! BuildingActor.SetFaction(PlanetSideEmpire.NEUTRAL) + } else if (siloChargeBeforeChange == 0 && resourceSilo.NtuCapacitor > 0) { // Power restored. Reactor Online. Sensors Online. Weapons Online. All systems nominal. //todo: Check generator is online before starting up zone.AvatarEvents ! AvatarServiceMessage( zone.Id, AvatarAction.PlanetsideAttribute(building.GUID, 48, 0) ) - building.TriggerZoneMapUpdate() + building.Zone.actor ! ZoneActor.ZoneMapUpdate() } } /** * The silo will agree to offers until its nanite capacitor is completely full. */ - def HandleNtuOffer(sender : ActorRef, src : NtuContainer) : Unit = { - sender ! (if(resourceSilo.NtuCapacitor < resourceSilo.MaxNtuCapacitor) { - Ntu.Request(0, resourceSilo.MaxNtuCapacitor - resourceSilo.NtuCapacitor) - } - else { - StopNtuBehavior(sender) - Ntu.Request(0, 0) - }) + def HandleNtuOffer(sender: ActorRef, src: NtuContainer): Unit = { + sender ! (if (resourceSilo.NtuCapacitor < resourceSilo.MaxNtuCapacitor) { + Ntu.Request(0, resourceSilo.MaxNtuCapacitor - resourceSilo.NtuCapacitor) + } else { + StopNtuBehavior(sender) + Ntu.Request(0, 0) + }) } /** * Reset the animation trigger and attempt the stop animation. */ - def StopNtuBehavior(sender : ActorRef) : Unit = { + def StopNtuBehavior(sender: ActorRef): Unit = { panelAnimationFunc = PanelAnimation panelAnimationFunc(0) } /** * na + * * @param sender na - * @param min a minimum amount of nanites requested; - * @param max the amount of nanites required to not make further requests; + * @param min a minimum amount of nanites requested; + * @param max the amount of nanites required to not make further requests; */ - def HandleNtuRequest(sender : ActorRef, min : Int, max : Int) : Unit = { + def HandleNtuRequest(sender: ActorRef, min: Int, max: Int): Unit = { val originalAmount = resourceSilo.NtuCapacitor UpdateChargeLevel(-min) sender ! Ntu.Grant(resourceSilo, originalAmount - resourceSilo.NtuCapacitor) @@ -149,8 +157,8 @@ class ResourceSiloControl(resourceSilo: ResourceSilo) extends Actor with Faction /** * Accept nanites into the silo capacitor and set the animation state. */ - def HandleNtuGrant(sender : ActorRef, src : NtuContainer, amount : Int) : Unit = { - if(amount != 0) { + def HandleNtuGrant(sender: ActorRef, src: NtuContainer, amount: Int): Unit = { + if (amount != 0) { val originalAmount = resourceSilo.NtuCapacitor UpdateChargeLevel(amount) panelAnimationFunc(resourceSilo.NtuCapacitor - originalAmount) @@ -162,19 +170,20 @@ class ResourceSiloControl(resourceSilo: ResourceSilo) extends Actor with Faction * When charging from another source of nanites, the silo's panels will glow * and a particle affect will traverse towards the panels from about ten meters in front of the silo. * These effects are both controlled by thee same packet. + * * @param trigger if positive, activate the animation; * if negative or zero, disable the animation */ - def PanelAnimation(trigger : Int) : Unit = { + def PanelAnimation(trigger: Int): Unit = { val zone = resourceSilo.Zone zone.VehicleEvents ! VehicleServiceMessage( zone.Id, - VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, resourceSilo.GUID, 49, if(trigger > 0) 1 else 0) + VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, resourceSilo.GUID, 49, if (trigger > 0) 1 else 0) ) // panel glow on & orb particles on } /** * Do nothing this turn. */ - def SkipPanelAnimation(trigger : Int) : Unit = { } + def SkipPanelAnimation(trigger: Int): Unit = {} } diff --git a/common/src/main/scala/net/psforever/objects/serverobject/structures/Building.scala b/common/src/main/scala/net/psforever/objects/serverobject/structures/Building.scala index 25a10575..02a59ffb 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/structures/Building.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/structures/Building.scala @@ -4,6 +4,7 @@ package net.psforever.objects.serverobject.structures import java.util.concurrent.TimeUnit import akka.actor.ActorContext +import net.psforever.actors.zone.{BuildingActor, ZoneActor} import net.psforever.objects.{Default, GlobalDefinitions, Player} import net.psforever.objects.definition.ObjectDefinition import net.psforever.objects.serverobject.generator.Generator @@ -13,18 +14,19 @@ import net.psforever.objects.serverobject.resourcesilo.ResourceSilo import net.psforever.objects.serverobject.terminals.CaptureTerminal import net.psforever.objects.serverobject.tube.SpawnTube import net.psforever.objects.zones.Zone -import net.psforever.packet.game.{Additional1, Additional2, Additional3} +import net.psforever.packet.game.BuildingInfoUpdateMessage import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID, PlanetSideGeneratorState, Vector3} import scalax.collection.{Graph, GraphEdge} import services.Service import services.local.{LocalAction, LocalServiceMessage} +import akka.actor.typed.scaladsl.adapter._ class Building( private val name: String, private val building_guid: Int, private val map_id: Int, private val zone: Zone, - private val buildingType: StructureType.Value, + private val buildingType: StructureType, private val buildingDefinition: BuildingDefinition ) extends AmenityOwner { @@ -71,7 +73,8 @@ class Building( } else if (IsCapitol) { UpdateForceDomeStatus() } - TriggerZoneMapUpdate() + // FIXME null check is a bad idea but tests rely on it + if (Zone.actor != null) Zone.actor ! ZoneActor.ZoneMapUpdate() Faction } @@ -126,10 +129,6 @@ class Building( } } - def TriggerZoneMapUpdate(): Unit = { - if (Actor != Default.Actor) Actor ! Building.TriggerZoneMapUpdate(Zone.Number) - } - def UpdateForceDomeStatus(): Unit = { if (IsCapitol) { val originalStatus = ForceDomeActive @@ -155,7 +154,7 @@ class Building( Zone.Id, LocalAction.UpdateForceDomeStatus(Service.defaultPlayerGUID, GUID, ForceDomeActive) ) - Actor ! Building.SendMapUpdate(all_clients = true) + Actor ! BuildingActor.MapUpdate() } } } @@ -171,27 +170,7 @@ class Building( } } - def Info: ( - Int, - Boolean, - PlanetSideEmpire.Value, - Long, - PlanetSideEmpire.Value, - Long, - Option[Additional1], - PlanetSideGeneratorState.Value, - Boolean, - Boolean, - Int, - Int, - List[Additional2], - Long, - Boolean, - Int, - Option[Additional3], - Boolean, - Boolean - ) = { + def infoUpdateMessage(): BuildingInfoUpdateMessage = { val ntuLevel: Int = NtuLevel //if we have a capture terminal, get the hack status & time (in milliseconds) from control console if it exists val (hacking, hackingFaction, hackTime): (Boolean, PlanetSideEmpire.Value, Long) = CaptureTerminal match { @@ -264,31 +243,33 @@ class Building( } } } - //out - ( + + BuildingInfoUpdateMessage( + Zone.Number, + MapId, ntuLevel, hacking, hackingFaction, hackTime, if (ntuLevel > 0) Faction else PlanetSideEmpire.NEUTRAL, - 0, //!! Field != 0 will cause malformed packet. See class def. + 0, // Field != 0 will cause malformed packet None, generatorState, spawnTubesNormal, - ForceDomeActive, + forceDomeActive, latticeBenefit, - 48, //cavern_benefit; !! Field > 0 will cause malformed packet. See class def. - Nil, //unk4 - 0, //unk5 - false, //unk6 - 8, //!! unk7 Field != 8 will cause malformed packet. See class def. - None, //unk7x - boostSpawnPain, //boost_spawn_pain - boostGeneratorPain //boost_generator_pain + 48, // cavern benefit + Nil, // unk4, + 0, // unk5 + false, // unk6 + 8, // unk7 Field != 8 will cause malformed packet + None, // unk7x + boostSpawnPain, + boostGeneratorPain ) } - def BuildingType: StructureType.Value = buildingType + def BuildingType: StructureType = buildingType override def Zone_=(zone: Zone): Zone = Zone //building never leaves zone after being set in constructor @@ -307,58 +288,51 @@ object Building { GUID = net.psforever.types.PlanetSideGUID(0) } - def apply(name: String, guid: Int, map_id: Int, zone: Zone, buildingType: StructureType.Value): Building = { + def apply(name: String, guid: Int, map_id: Int, zone: Zone, buildingType: StructureType): Building = { new Building(name, guid, map_id, zone, buildingType, GlobalDefinitions.building) } def Structure( - buildingType: StructureType.Value, + buildingType: StructureType, location: Vector3, rotation: Vector3, definition: BuildingDefinition )(name: String, guid: Int, map_id: Int, zone: Zone, context: ActorContext): Building = { - import akka.actor.Props val obj = new Building(name, guid, map_id, zone, buildingType, definition) obj.Position = location obj.Orientation = rotation - obj.Actor = context.actorOf(Props(classOf[BuildingControl], obj), s"$map_id-$buildingType-building") + obj.Actor = context.spawn(BuildingActor(zone, obj), s"$map_id-$buildingType-building").toClassic obj } def Structure( - buildingType: StructureType.Value, + buildingType: StructureType, location: Vector3 )(name: String, guid: Int, map_id: Int, zone: Zone, context: ActorContext): Building = { - import akka.actor.Props - val obj = new Building(name, guid, map_id, zone, buildingType, GlobalDefinitions.building) obj.Position = location - obj.Actor = context.actorOf(Props(classOf[BuildingControl], obj), s"$map_id-$buildingType-building") + obj.Actor = context.spawn(BuildingActor(zone, obj), s"$map_id-$buildingType-building").toClassic obj } def Structure( - buildingType: StructureType.Value + buildingType: StructureType )(name: String, guid: Int, map_id: Int, zone: Zone, context: ActorContext): Building = { - import akka.actor.Props val obj = new Building(name, guid, map_id, zone, buildingType, GlobalDefinitions.building) - obj.Actor = context.actorOf(Props(classOf[BuildingControl], obj), s"$map_id-$buildingType-building") + obj.Actor = context.spawn(BuildingActor(zone, obj), s"$map_id-$buildingType-building").toClassic obj } def Structure( - buildingType: StructureType.Value, + buildingType: StructureType, buildingDefinition: BuildingDefinition, location: Vector3 )(name: String, guid: Int, id: Int, zone: Zone, context: ActorContext): Building = { - import akka.actor.Props val obj = new Building(name, guid, id, zone, buildingType, buildingDefinition) obj.Position = location - obj.Actor = context.actorOf(Props(classOf[BuildingControl], obj), s"$id-$buildingType-building") + context.spawn(BuildingActor(zone, obj), s"$id-$buildingType-building").toClassic obj } final case class AmenityStateChange(obj: Amenity) - final case class SendMapUpdate(all_clients: Boolean) - final case class TriggerZoneMapUpdate(zone_num: Int) } diff --git a/common/src/main/scala/net/psforever/objects/serverobject/structures/BuildingControl.scala b/common/src/main/scala/net/psforever/objects/serverobject/structures/BuildingControl.scala deleted file mode 100644 index 47de6830..00000000 --- a/common/src/main/scala/net/psforever/objects/serverobject/structures/BuildingControl.scala +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright (c) 2017 PSForever -package net.psforever.objects.serverobject.structures - -import akka.actor.{Actor, ActorRef} -import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior} -import net.psforever.objects.serverobject.generator.Generator -import net.psforever.objects.serverobject.tube.SpawnTube -import net.psforever.objects.zones.InterstellarCluster -import net.psforever.packet.game.BuildingInfoUpdateMessage -import services.ServiceManager -import services.ServiceManager.Lookup -import services.galaxy.{GalaxyAction, GalaxyResponse, GalaxyServiceMessage, GalaxyServiceResponse} - -class BuildingControl(building: Building) extends Actor with FactionAffinityBehavior.Check { - def FactionObject: FactionAffinity = building - var galaxyService: ActorRef = ActorRef.noSender - var interstellarCluster: ActorRef = ActorRef.noSender - private[this] val log = org.log4s.getLogger - - override def preStart = { - log.trace(s"Starting BuildingControl for ${building.GUID} / ${building.MapId}") - ServiceManager.serviceManager ! Lookup("galaxy") - ServiceManager.serviceManager ! Lookup("cluster") - } - - def receive: Receive = - checkBehavior.orElse { - case ServiceManager.LookupResult("galaxy", endpoint) => - galaxyService = endpoint - log.trace("BuildingControl: Building " + building.GUID + " Got galaxy service " + endpoint) - case ServiceManager.LookupResult("cluster", endpoint) => - interstellarCluster = endpoint - log.trace("BuildingControl: Building " + building.GUID + " Got interstellar cluster service " + endpoint) - - case FactionAffinity.ConvertFactionAffinity(faction) => - val originalAffinity = building.Faction - if (originalAffinity != (building.Faction = faction)) { - building.Amenities.foreach(_.Actor forward FactionAffinity.ConfirmFactionAffinity()) - } - sender ! FactionAffinity.AssertFactionAffinity(building, faction) - - case Building.AmenityStateChange(obj: SpawnTube) => - if (building.Amenities.contains(obj)) { - SendMapUpdate(allClients = true) - } - - case Building.AmenityStateChange(obj: Generator) => - if (building.Amenities.contains(obj)) { - SendMapUpdate(allClients = true) - } - - case Building.TriggerZoneMapUpdate(zone_num: Int) => - if (interstellarCluster != ActorRef.noSender) interstellarCluster ! InterstellarCluster.ZoneMapUpdate(zone_num) - - case Building.SendMapUpdate(all_clients: Boolean) => - SendMapUpdate(all_clients) - - case _ => - } - - /** - * na - * @param allClients na - */ - def SendMapUpdate(allClients: Boolean): Unit = { - val zoneNumber = building.Zone.Number - val buildingNumber = building.MapId - log.trace(s"sending BuildingInfoUpdateMessage update - zone=$zoneNumber, building=$buildingNumber") - val ( - ntuLevel, - isHacked, - empireHack, - hackTimeRemaining, - controllingEmpire, - unk1, - unk1x, - generatorState, - spawnTubesNormal, - forceDomeActive, - latticeBenefit, - cavernBenefit, - unk4, - unk5, - unk6, - unk7, - unk7x, - boostSpawnPain, - boostGeneratorPain - ) = building.Info - val msg = BuildingInfoUpdateMessage( - zoneNumber, - buildingNumber, - ntuLevel, - isHacked, - empireHack, - hackTimeRemaining, - controllingEmpire, - unk1, - unk1x, - generatorState, - spawnTubesNormal, - forceDomeActive, - latticeBenefit, - cavernBenefit, - unk4, - unk5, - unk6, - unk7, - unk7x, - boostSpawnPain, - boostGeneratorPain - ) - - if (allClients) { - if (galaxyService != ActorRef.noSender) galaxyService ! GalaxyServiceMessage(GalaxyAction.MapUpdate(msg)) - } else { - // Fake a GalaxyServiceResponse response back to just the sender - sender ! GalaxyServiceResponse("", GalaxyResponse.MapUpdate(msg)) - } - } -} diff --git a/common/src/main/scala/net/psforever/objects/serverobject/structures/StructureType.scala b/common/src/main/scala/net/psforever/objects/serverobject/structures/StructureType.scala index 68115071..a5a2623d 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/structures/StructureType.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/structures/StructureType.scala @@ -1,19 +1,19 @@ // Copyright (c) 2017 PSForever package net.psforever.objects.serverobject.structures -/** - * An `Enumeration` of the kinds of building structures found in the game. - * This is merely a kludge for more a future, more complicated internal object that handles base operations. - */ -object StructureType extends Enumeration { - type Type = Value +import enumeratum.{EnumEntry, Enum} - val Bridge, // technically, a "bridge section" - Building, // generic - Bunker, // low accessible ground cover - Facility, // large base - Platform, // outdoor amenities disconnected from a proper base like the vehicle spawn pads in sanctuary - Tower, // also called field towers: watchtower, air tower, gun tower - WarpGate // transport point between zones - = Value +sealed trait StructureType extends EnumEntry + +object StructureType extends Enum[StructureType] { + val values = findValues + + case object Bridge extends StructureType // technically, a "bridge section" + case object Building extends StructureType // generic + case object Bunker extends StructureType // low accessible ground cover + case object Facility extends StructureType // large base + case object Platform + extends StructureType // outdoor amenities disconnected from a proper base like the vehicle spawn pads in sanctuary + case object Tower extends StructureType // also called field towers: watchtower, air tower, gun tower + case object WarpGate extends StructureType // transport point between zones } diff --git a/common/src/main/scala/net/psforever/objects/serverobject/structures/WarpGate.scala b/common/src/main/scala/net/psforever/objects/serverobject/structures/WarpGate.scala index d8174bb8..9e9444bf 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/structures/WarpGate.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/structures/WarpGate.scala @@ -5,8 +5,10 @@ import akka.actor.ActorContext import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.{GlobalDefinitions, NtuContainer, SpawnPoint} import net.psforever.objects.zones.Zone -import net.psforever.packet.game.{Additional1, Additional2, Additional3} +import net.psforever.packet.game.BuildingInfoUpdateMessage import net.psforever.types.{PlanetSideEmpire, PlanetSideGeneratorState, Vector3} +import akka.actor.typed.scaladsl.adapter._ +import net.psforever.actors.zone.BuildingActor import scala.collection.mutable @@ -20,28 +22,10 @@ class WarpGate(name: String, building_guid: Int, map_id: Int, zone: Zone, buildi /** what faction views this warp gate as a broadcast gate */ private var broadcast: mutable.Set[PlanetSideEmpire.Value] = mutable.Set.empty[PlanetSideEmpire.Value] - override def Info: ( - Int, - Boolean, - PlanetSideEmpire.Value, - Long, - PlanetSideEmpire.Value, - Long, - Option[Additional1], - PlanetSideGeneratorState.Value, - Boolean, - Boolean, - Int, - Int, - List[Additional2], - Long, - Boolean, - Int, - Option[Additional3], - Boolean, - Boolean - ) = { - ( + override def infoUpdateMessage(): BuildingInfoUpdateMessage = { + BuildingInfoUpdateMessage( + Zone.Number, + MapId, 0, false, PlanetSideEmpire.NEUTRAL, @@ -179,19 +163,17 @@ object WarpGate { } def Structure(name: String, guid: Int, map_id: Int, zone: Zone, context: ActorContext): WarpGate = { - import akka.actor.Props val obj = new WarpGate(name, guid, map_id, zone, GlobalDefinitions.warpgate) - obj.Actor = context.actorOf(Props(classOf[WarpGateControl], obj), name = s"$map_id-$guid-gate") + obj.Actor = context.spawn(BuildingActor(zone, obj), name = s"$map_id-$guid-gate").toClassic obj } def Structure( location: Vector3 )(name: String, guid: Int, map_id: Int, zone: Zone, context: ActorContext): WarpGate = { - import akka.actor.Props val obj = new WarpGate(name, guid, map_id, zone, GlobalDefinitions.warpgate) obj.Position = location - obj.Actor = context.actorOf(Props(classOf[WarpGateControl], obj), name = s"$map_id-$guid-gate") + obj.Actor = context.spawn(BuildingActor(zone, obj), name = s"$map_id-$guid-gate").toClassic obj } @@ -199,10 +181,9 @@ object WarpGate { location: Vector3, buildingDefinition: WarpGateDefinition )(name: String, guid: Int, map_id: Int, zone: Zone, context: ActorContext): WarpGate = { - import akka.actor.Props val obj = new WarpGate(name, guid, map_id, zone, buildingDefinition) obj.Position = location - obj.Actor = context.actorOf(Props(classOf[WarpGateControl], obj), name = s"$map_id-$guid-gate") + obj.Actor = context.spawn(BuildingActor(zone, obj), name = s"$map_id-$guid-gate").toClassic obj } } diff --git a/common/src/main/scala/net/psforever/objects/serverobject/structures/WarpGateControl.scala b/common/src/main/scala/net/psforever/objects/serverobject/structures/WarpGateControl.scala deleted file mode 100644 index a2e5e78d..00000000 --- a/common/src/main/scala/net/psforever/objects/serverobject/structures/WarpGateControl.scala +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) 2020 PSForever -package net.psforever.objects.serverobject.structures - -import akka.actor.ActorRef -import net.psforever.objects.{Ntu, NtuContainer, NtuStorageBehavior} - -class WarpGateControl(gate : WarpGate) extends BuildingControl(gate) - with NtuStorageBehavior { - override def receive : Receive = storageBehavior.orElse(super.receive) - - /** - * Warp gates don't need to respond to offers. - */ - def HandleNtuOffer(sender : ActorRef, src : NtuContainer) : Unit = {} - - /** - * Warp gates don't need to stop. - */ - def StopNtuBehavior(sender : ActorRef) : Unit = {} - - /** - * When processing a request, the only important consideration is whether the warp gate is active. - * @param sender na - * @param min a minimum amount of nanites requested; - * @param max the amount of nanites required to not make further requests; - */ - def HandleNtuRequest(sender : ActorRef, min : Int, max : Int) : Unit = { - sender ! Ntu.Grant(gate, if (gate.Active) min else 0) - } - - /** - * Warp gates doesn't need additional nanites. - * For the sake of not letting any go to waste, it will give back those nanites for free. - */ - def HandleNtuGrant(sender : ActorRef, src : NtuContainer, amount : Int) : Unit = { - sender ! Ntu.Grant(gate, amount) - } -} diff --git a/common/src/main/scala/net/psforever/objects/vehicles/AntTransferBehavior.scala b/common/src/main/scala/net/psforever/objects/vehicles/AntTransferBehavior.scala index db68f39c..2faf8d13 100644 --- a/common/src/main/scala/net/psforever/objects/vehicles/AntTransferBehavior.scala +++ b/common/src/main/scala/net/psforever/objects/vehicles/AntTransferBehavior.scala @@ -2,6 +2,8 @@ package net.psforever.objects.vehicles import akka.actor.{ActorRef, Cancellable} +import net.psforever.actors.commands.NtuCommand +import net.psforever.actors.zone.BuildingActor import net.psforever.objects.serverobject.deploy.Deployment import net.psforever.objects.serverobject.resourcesilo.ResourceSilo import net.psforever.objects.serverobject.structures.WarpGate @@ -10,21 +12,22 @@ import net.psforever.objects.{NtuContainer, _} import net.psforever.types.DriveState import services.Service import services.vehicle.{VehicleAction, VehicleServiceMessage} +import akka.actor.typed.scaladsl.adapter._ import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.duration._ -trait AntTransferBehavior extends TransferBehavior - with NtuStorageBehavior { - var ntuChargingTick : Cancellable = Default.Cancellable - var panelAnimationFunc : ()=>Unit = NoCharge +trait AntTransferBehavior extends TransferBehavior with NtuStorageBehavior { + var ntuChargingTick: Cancellable = Default.Cancellable + var panelAnimationFunc: () => Unit = NoCharge def TransferMaterial = Ntu.Nanites - def ChargeTransferObject : Vehicle with NtuContainer - def antBehavior : Receive = storageBehavior.orElse(transferBehavior) + def ChargeTransferObject: Vehicle with NtuContainer - def ActivatePanelsForChargingEvent(vehicle : NtuContainer) : Unit = { + def antBehavior: Receive = storageBehavior.orElse(transferBehavior) + + def ActivatePanelsForChargingEvent(vehicle: NtuContainer): Unit = { val zone = vehicle.Zone zone.VehicleEvents ! VehicleServiceMessage( zone.Id, @@ -33,7 +36,7 @@ trait AntTransferBehavior extends TransferBehavior } /** Charging */ - def StartNtuChargingEvent(vehicle : NtuContainer) : Unit = { + def StartNtuChargingEvent(vehicle: NtuContainer): Unit = { val zone = vehicle.Zone zone.VehicleEvents ! VehicleServiceMessage( zone.Id, @@ -41,8 +44,8 @@ trait AntTransferBehavior extends TransferBehavior ) // orb particle effect on } - def UpdateNtuUI(vehicle : Vehicle with NtuContainer) : Unit = { - if(vehicle.Seats.values.exists(_.isOccupied)) { + def UpdateNtuUI(vehicle: Vehicle with NtuContainer): Unit = { + if (vehicle.Seats.values.exists(_.isOccupied)) { val display = scala.math.ceil(vehicle.NtuCapacitorScaled).toLong vehicle.Zone.VehicleEvents ! VehicleServiceMessage( vehicle.Actor.toString, @@ -51,76 +54,82 @@ trait AntTransferBehavior extends TransferBehavior } } - def HandleChargingEvent(target : TransferContainer) : Boolean = { + def HandleChargingEvent(target: TransferContainer): Boolean = { ntuChargingTick.cancel val obj = ChargeTransferObject //log.trace(s"NtuCharging: Vehicle $guid is charging NTU capacitor.") - if(obj.NtuCapacitor < obj.Definition.MaxNtuCapacitor) { + if (obj.NtuCapacitor < obj.Definition.MaxNtuCapacitor) { //charging panelAnimationFunc = InitialCharge transferTarget = Some(target) transferEvent = TransferBehavior.Event.Charging - val (min, max) = target match { - case _ : WarpGate => + target match { + case _: WarpGate => //ANTs would charge from 0-100% in roughly 75s on live (https://www.youtube.com/watch?v=veOWToR2nSk&feature=youtu.be&t=1194) - val ntuMax = obj.Definition.MaxNtuCapacitor - obj.NtuCapacitor - val ntuMin = scala.math.min(obj.Definition.MaxNtuCapacitor/75, ntuMax) - (ntuMin, ntuMax) + val max = obj.Definition.MaxNtuCapacitor - obj.NtuCapacitor + target.Actor ! BuildingActor.Ntu( + NtuCommand.Request(scala.math.min(obj.Definition.MaxNtuCapacitor / 75, max), context.self) + ) case _ => - (0, 0) } - target.Actor ! Ntu.Request(min, max) - ntuChargingTick = context.system.scheduler.scheduleOnce(delay = 1000 milliseconds, self, TransferBehavior.Charging(TransferMaterial)) // Repeat until fully charged, or minor delay + + ntuChargingTick = context.system.scheduler.scheduleOnce( + delay = 1000 milliseconds, + self, + TransferBehavior.Charging(TransferMaterial) + ) // Repeat until fully charged, or minor delay true - } - else { + } else { // Fully charged TryStopChargingEvent(obj) false } } - def ReceiveAndDepositUntilFull(vehicle : Vehicle, amount : Int) : Boolean = { + def ReceiveAndDepositUntilFull(vehicle: Vehicle, amount: Int): Boolean = { val isNotFull = (vehicle.NtuCapacitor += amount) < vehicle.Definition.MaxNtuCapacitor UpdateNtuUI(vehicle) isNotFull } /** Discharging */ - def HandleDischargingEvent(target : TransferContainer) : Boolean = { + def HandleDischargingEvent(target: TransferContainer): Boolean = { //log.trace(s"NtuDischarging: Vehicle $guid is discharging NTU into silo $silo_guid") val obj = ChargeTransferObject - if(obj.NtuCapacitor > 0) { + if (obj.NtuCapacitor > 0) { panelAnimationFunc = InitialDischarge transferTarget = Some(target) transferEvent = TransferBehavior.Event.Discharging target.Actor ! Ntu.Offer(obj) ntuChargingTick.cancel - ntuChargingTick = context.system.scheduler.scheduleOnce(delay = 1000 milliseconds, self, TransferBehavior.Discharging(TransferMaterial)) + ntuChargingTick = context.system.scheduler.scheduleOnce( + delay = 1000 milliseconds, + self, + TransferBehavior.Discharging(TransferMaterial) + ) true - } - else { + } else { TryStopChargingEvent(obj) false } } - def NoCharge() : Unit = {} + def NoCharge(): Unit = {} - def InitialCharge() : Unit = { + def InitialCharge(): Unit = { panelAnimationFunc = NoCharge val obj = ChargeTransferObject ActivatePanelsForChargingEvent(obj) StartNtuChargingEvent(obj) } - def InitialDischarge() : Unit = { + def InitialDischarge(): Unit = { panelAnimationFunc = NoCharge ActivatePanelsForChargingEvent(ChargeTransferObject) } - def WithdrawAndTransmit(vehicle : Vehicle, maxRequested : Int) : Any = { - val chargeable = ChargeTransferObject + def WithdrawAndTransmit(vehicle: Vehicle, maxRequested: Int): Any = { + val chargeable = ChargeTransferObject var chargeToDeposit = Math.min(Math.min(chargeable.NtuCapacitor, 100), maxRequested) chargeable.NtuCapacitor -= chargeToDeposit UpdateNtuUI(chargeable) @@ -128,27 +137,34 @@ trait AntTransferBehavior extends TransferBehavior } /** Stopping */ - override def TryStopChargingEvent(container : TransferContainer) : Unit = { + override def TryStopChargingEvent(container: TransferContainer): Unit = { val vehicle = ChargeTransferObject ntuChargingTick.cancel - if(transferEvent != TransferBehavior.Event.None) { - if(vehicle.DeploymentState == DriveState.Deployed) { + if (transferEvent != TransferBehavior.Event.None) { + if (vehicle.DeploymentState == DriveState.Deployed) { //turning off glow/orb effects on ANT doesn't seem to work when deployed. Try to undeploy ANT first vehicle.Actor ! Deployment.TryUndeploy(DriveState.Undeploying) ntuChargingTick = context.system.scheduler.scheduleOnce(250 milliseconds, self, TransferBehavior.Stopping()) - } - else { + } else { //vehicle is not deployed; just do cleanup - val vguid = vehicle.GUID - val zone = vehicle.Zone + val vguid = vehicle.GUID + val zone = vehicle.Zone val zoneId = zone.Id val events = zone.VehicleEvents - if(transferEvent == TransferBehavior.Event.Charging) { - events ! VehicleServiceMessage(zoneId, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, vguid, 52, 0L)) // panel glow off - events ! VehicleServiceMessage(zoneId, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, vguid, 49, 0L)) // orb particle effect off - } - else if(transferEvent == TransferBehavior.Event.Discharging) { - events ! VehicleServiceMessage(zoneId, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, vguid, 52, 0L)) // panel glow off + if (transferEvent == TransferBehavior.Event.Charging) { + events ! VehicleServiceMessage( + zoneId, + VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, vguid, 52, 0L) + ) // panel glow off + events ! VehicleServiceMessage( + zoneId, + VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, vguid, 49, 0L) + ) // orb particle effect off + } else if (transferEvent == TransferBehavior.Event.Discharging) { + events ! VehicleServiceMessage( + zoneId, + VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, vguid, 52, 0L) + ) // panel glow off } } panelAnimationFunc = NoCharge @@ -156,39 +172,37 @@ trait AntTransferBehavior extends TransferBehavior } } - def StopNtuBehavior(sender : ActorRef) : Unit = TryStopChargingEvent(ChargeTransferObject) + def StopNtuBehavior(sender: ActorRef): Unit = TryStopChargingEvent(ChargeTransferObject) - def HandleNtuOffer(sender : ActorRef, src : NtuContainer) : Unit = { } + def HandleNtuOffer(sender: ActorRef, src: NtuContainer): Unit = {} - def HandleNtuRequest(sender : ActorRef, min : Int, max : Int) : Unit = { - if(transferEvent == TransferBehavior.Event.Discharging) { + def HandleNtuRequest(sender: ActorRef, min: Int, max: Int): Unit = { + if (transferEvent == TransferBehavior.Event.Discharging) { val chargeable = ChargeTransferObject - val chargeToDeposit = if(min == 0) { + val chargeToDeposit = if (min == 0) { transferTarget match { - case Some(silo : ResourceSilo) => + case Some(silo: ResourceSilo) => // Silos would charge from 0-100% in roughly 105s on live (~20%-100% https://youtu.be/veOWToR2nSk?t=1402) scala.math.min(scala.math.min(silo.MaxNtuCapacitor / 105, chargeable.NtuCapacitor), max) case _ => 0 } - } - else { + } else { scala.math.min(min, chargeable.NtuCapacitor) } -// var chargeToDeposit = Math.min(Math.min(chargeable.NtuCapacitor, 100), max) + // var chargeToDeposit = Math.min(Math.min(chargeable.NtuCapacitor, 100), max) chargeable.NtuCapacitor -= chargeToDeposit UpdateNtuUI(chargeable) sender ! Ntu.Grant(chargeable, chargeToDeposit) } } - def HandleNtuGrant(sender : ActorRef, src : NtuContainer, amount : Int) : Unit = { - if(transferEvent == TransferBehavior.Event.Charging) { + def HandleNtuGrant(sender: ActorRef, src: NtuContainer, amount: Int): Unit = { + if (transferEvent == TransferBehavior.Event.Charging) { val obj = ChargeTransferObject - if(ReceiveAndDepositUntilFull(obj, amount)) { + if (ReceiveAndDepositUntilFull(obj, amount)) { panelAnimationFunc() - } - else { + } else { TryStopChargingEvent(obj) sender ! Ntu.Request(0, 0) } diff --git a/common/src/main/scala/net/psforever/objects/zones/InterstellarCluster.scala b/common/src/main/scala/net/psforever/objects/zones/InterstellarCluster.scala deleted file mode 100644 index 50f2a126..00000000 --- a/common/src/main/scala/net/psforever/objects/zones/InterstellarCluster.scala +++ /dev/null @@ -1,228 +0,0 @@ -// Copyright (c) 2017 PSForever -package net.psforever.objects.zones - -import akka.actor.{Actor, Props} -import net.psforever.objects.serverobject.structures.{Building, StructureType} -import net.psforever.types.Vector3 - -import scala.annotation.tailrec -import scala.util.Random - -/** - * The root of the universe of one-continent planets, codified by the game's "Interstellar Map." - * Constructs each zone and thus instigates the construction of every server object in the game world. - * The nanite flow connecting all of these `Zone`s is called the "Intercontinental Lattice."
- *
- * The process of "construction" and "initialization" and "configuration" are referenced at this level. - * These concepts are not the same thing; - * the distinction is important. - * "Construction" and "instantiation" of the cluster merely produces the "facade" of the different `Zone` entities. - * In such a `List`, every built `Zone` is capable of being a destination on the "Intercontinental lattice." - * "Initialization" and "configuration" of the cluster refers to the act of completing the "Intercontinental Lattice" - * by connecting different terminus warp gates together. - * Other activities involve event management and managing wide-reaching and factional attributes. - * @param zones a `List` of continental `Zone` arenas - */ -class InterstellarCluster(zones: List[Zone]) extends Actor { - private[this] val log = org.log4s.getLogger - val recallRandom = new Random() - log.info("Starting interplanetary cluster ...") - - /** - * Create a `ZoneActor` for each `Zone`. - * That `Actor` is sent a packet that would start the construction of the `Zone`'s server objects. - * The process is maintained this way to allow every planet to be created and configured in separate stages. - */ - override def preStart(): Unit = { - super.preStart() - for (zone <- zones) { - log.info(s"Built continent ${zone.Id}") - zone.Actor = context.actorOf(Props(classOf[ZoneActor], zone), s"${zone.Id}-actor") - zone.Actor ! Zone.Init() - } - } - - def receive: Receive = { - case InterstellarCluster.GetWorld(zoneId) => - log.info(s"Asked to find $zoneId") - recursiveFindWorldInCluster(zones.iterator, _.Id == zoneId) match { - case Some(continent) => - sender ! InterstellarCluster.GiveWorld(zoneId, continent) - case None => - log.error(s"Requested zone $zoneId could not be found") - } - - case InterstellarCluster.RequestClientInitialization() => - zones.foreach(zone => { sender ! Zone.ClientInitialization(zone.ClientInitialization()) }) - sender ! InterstellarCluster.ClientInitializationComplete() //will be processed after all Zones - - case msg @ Zone.Lattice.RequestSpawnPoint(zone_number, _, _, _) => - recursiveFindWorldInCluster(zones.iterator, _.Number == zone_number) match { - case Some(zone) => - zone.Actor forward msg - - case None => //zone_number does not exist - sender ! Zone.Lattice.NoValidSpawnPoint(zone_number, None) - } - case InterstellarCluster.ListPlayers() => - var players: List[String] = List() - - for (zone <- zones) { - val zonePlayers = zone.Players - for (player <- zonePlayers) { - players ::= player.name - } - } - sender ! InterstellarCluster.PlayerList(players) - - case InterstellarCluster.GetZoneIds() => - sender ! InterstellarCluster.ZoneIds(zones.map(_.Number)) - - case msg @ Zone.Lattice.RequestSpecificSpawnPoint(zone_number, _, _, _) => - recursiveFindWorldInCluster(zones.iterator, _.Number == zone_number) match { - case Some(zone) => - zone.Actor forward msg - - case None => //zone_number does not exist - sender ! Zone.Lattice.NoValidSpawnPoint(zone_number, None) - } - case InterstellarCluster.ZoneMapUpdate(zone_num: Int) => - val zone = zones.find(x => x.Number == zone_num).get - zone.Buildings - .filter(_._2.BuildingType == StructureType.Facility) - .values - .foreach(b => b.Actor ! Building.SendMapUpdate(all_clients = true)) - - case Zoning.InstantAction.Request(faction) => - val interests = zones.flatMap { zone => - //TODO zone.Locked.contains(faction) - zone.HotSpotData - .collect { case spot if zone.Players.nonEmpty => (zone, spot) } - } /* ignore zones without existing population */ - if (interests.nonEmpty) { - val (withAllies, onlyEnemies) = interests - .map { - case (zone, spot) => - ( - zone, - spot, - ZoneActor.FindLocalSpawnPointsInZone(zone, spot.DisplayLocation, faction, 0).getOrElse(Nil) - ) - } /* pair hotspots and spawn points */ - .filter { case (_, _, spawns) => spawns.nonEmpty } /* faction spawns must exist */ - .sortBy({ case (_, spot, _) => spot.Activity.values.foldLeft(0)(_ + _.Heat) })( - Ordering[Int].reverse - ) /* greatest > least */ - .partition { case (_, spot, _) => spot.ActivityBy().contains(faction) } /* us versus them */ - withAllies.headOption.orElse(onlyEnemies.headOption) match { - case Some((zone, info, List(spawnPoint))) => - //one spawn - val pos = info.DisplayLocation - sender ! Zoning.InstantAction.Located(zone, pos, spawnPoint) - case Some((zone, info, spawns)) => - //multiple spawn options - val pos = info.DisplayLocation - val spawnPoint = spawns.minBy(point => Vector3.DistanceSquared(point.Position, pos)) - sender ! Zoning.InstantAction.Located(zone, pos, spawnPoint) - case None => - //no actionable hot spots - sender ! Zoning.InstantAction.NotLocated() - } - } else { - //never had any actionable hot spots - sender ! Zoning.InstantAction.NotLocated() - } - - case Zoning.Recall.Request(faction, sanctuary_id) => - recursiveFindWorldInCluster(zones.iterator, _.Id.equals(sanctuary_id)) match { - case Some(zone) => - //TODO zone full - val width = zone.Map.Scale.width - val height = zone.Map.Scale.height - //xy-coordinates indicate sanctuary spawn bias: - val spot = math.abs(scala.util.Random.nextInt() % sender.toString.hashCode % 4) match { - case 0 => Vector3(width, height, 0) //NE - case 1 => Vector3(width, 0, 0) //SE - case 2 => Vector3.Zero //SW - case 3 => Vector3(0, height, 0) //NW - } - ZoneActor.FindLocalSpawnPointsInZone(zone, spot, faction, 7).getOrElse(Nil) match { - case Nil => - //no spawns - sender ! Zoning.Recall.Denied("unavailable") - case List(spawnPoint) => - //one spawn - sender ! Zoning.Recall.Located(zone, spawnPoint) - case spawnPoints => - //multiple spawn options - val spawnPoint = spawnPoints(recallRandom.nextInt(spawnPoints.length)) - sender ! Zoning.Recall.Located(zone, spawnPoint) - } - case None => - sender ! Zoning.Recall.Denied("unavailable") - } - - case _ => - log.warn(s"InterstellarCluster received unknown message"); - } - - /** - * Search through the `List` of `Zone` entities and find the one with the matching designation. - * @param iter an `Iterator` of `Zone` entities - * @param predicate a condition to check against to determine when the appropriate `Zone` is discovered - * @return the discovered `Zone` - */ - @tailrec private def recursiveFindWorldInCluster(iter: Iterator[Zone], predicate: Zone => Boolean): Option[Zone] = { - if (!iter.hasNext) { - None - } else { - val cont = iter.next - if (predicate.apply(cont)) { - Some(cont) - } else { - recursiveFindWorldInCluster(iter, predicate) - } - } - } -} - -object InterstellarCluster { - - /** - * Request a hard reference to a `Zone`. - * @param zoneId the name of the `Zone` - */ - final case class GetWorld(zoneId: String) - - /** - * Provide a hard reference to a `Zone`. - * @param zoneId the name of the `Zone` - * @param zone the `Zone` - */ - final case class GiveWorld(zoneId: String, zone: Zone) - - final case class ListPlayers() - final case class PlayerList(players: List[String]) - - /** - * Signal to the cluster that a new client needs to be initialized for all listed `Zone` destinations. - * @see `Zone` - */ - final case class RequestClientInitialization() - - /** - * Return signal intended to inform the original sender that all `Zone`s have finished being initialized. - * @see `WorldSessionActor` - */ - final case class ClientInitializationComplete() - - /** - * Requests that all buildings within a zone send a map update for the purposes of refreshing lattice benefits, such as when a base is hacked, changes faction or loses power - * @see `BuildingInfoUpdateMessage` - * @param zone_num the zone number to request building map updates for - */ - final case class ZoneMapUpdate(zone_num: Int) - - final case class GetZoneIds() - final case class ZoneIds(zoneIds: List[Int]) -} diff --git a/common/src/main/scala/net/psforever/objects/zones/Zone.scala b/common/src/main/scala/net/psforever/objects/zones/Zone.scala index a35f5559..88ab3c0b 100644 --- a/common/src/main/scala/net/psforever/objects/zones/Zone.scala +++ b/common/src/main/scala/net/psforever/objects/zones/Zone.scala @@ -16,10 +16,11 @@ import net.psforever.objects.guid.source.LimitedNumberSource import net.psforever.objects.inventory.Container import net.psforever.objects.serverobject.painbox.{Painbox, PainboxDefinition} import net.psforever.objects.serverobject.resourcesilo.ResourceSilo -import net.psforever.objects.serverobject.structures.{Amenity, Building, WarpGate} +import net.psforever.objects.serverobject.structures.{Amenity, AmenityOwner, Building, StructureType, WarpGate} import net.psforever.objects.serverobject.turret.FacilityTurret import net.psforever.objects.serverobject.zipline.ZipLinePath -import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID, Vector3} +import net.psforever.types.{DriveState, PlanetSideEmpire, PlanetSideGUID, SpawnGroup, Vector3} +import org.log4s.Logger import services.avatar.AvatarService import services.local.LocalService import services.vehicle.VehicleService @@ -33,6 +34,10 @@ import scalax.collection.GraphPredef._ import scalax.collection.GraphEdge._ import scala.util.Try +import akka.actor.typed +import net.psforever.actors.zone.ZoneActor +import net.psforever.objects.serverobject.tube.SpawnTube +import net.psforever.objects.vehicles.UtilityType /** * A server object representing the one-landmass planets as well as the individual subterranean caverns.
@@ -45,20 +50,21 @@ import scala.util.Try * Static server objects originate from the `ZoneMap`. * Dynamic game objects originate from player characters. * (Write more later.) - * @param zoneId the privileged name that can be used as the second parameter in the packet `LoadMapMessage` - * @param zoneMap the map of server objects upon which this `Zone` is based + * + * @param zoneId the privileged name that can be used as the second parameter in the packet `LoadMapMessage` + * @param map the map of server objects upon which this `Zone` is based * @param zoneNumber the numerical index of the `Zone` as it is recognized in a variety of packets; * also used by `LivePlayerList` to indicate a specific `Zone` * @see `ZoneMap`
* `LoadMapMessage`
* `LivePlayerList` */ -class Zone(private val zoneId: String, zoneMap: ZoneMap, zoneNumber: Int) { +class Zone(private val zoneId: String, val map: ZoneMap, zoneNumber: Int) { /** Governs general synchronized external requests. */ - private var actor = Default.Actor + var actor: typed.ActorRef[ZoneActor.Command] = _ - /** Actor that handles SOI related functionality, for example if a player is in a SOI * */ + /** Actor that handles SOI related functionality, for example if a player is in a SOI */ private var soi = Default.Actor /** Used by the globally unique identifier system to coordinate requests. */ @@ -79,8 +85,7 @@ class Zone(private val zoneId: String, zoneMap: ZoneMap, zoneNumber: Int) { /** */ - private val constructions: ListBuffer[PlanetSideGameObject with Deployable] = - ListBuffer[PlanetSideGameObject with Deployable]() + private val constructions: ListBuffer[PlanetSideGameObject with Deployable] = ListBuffer() /** */ @@ -106,8 +111,6 @@ class Zone(private val zoneId: String, zoneMap: ZoneMap, zoneNumber: Int) { private var lattice: Graph[Building, UnDiEdge] = Graph() - private var zipLinePaths: List[ZipLinePath] = List() - /** key - spawn zone id, value - buildings belonging to spawn zone */ private var spawnGroups: Map[Building, List[SpawnPoint]] = PairMap[Building, List[SpawnPoint]]() @@ -155,31 +158,32 @@ class Zone(private val zoneId: String, zoneMap: ZoneMap, zoneNumber: Int) { * Execution of this operation should be fail-safe. * The chances of failure should be mitigated or skipped. * A testing routine should be run after the fact on the results of the process. + * * @see `ZoneActor.ZoneSetupCheck` * @param context a reference to an `ActorContext` necessary for `Props` */ - def Init(implicit context: ActorContext): Unit = { + def init(implicit context: ActorContext): Unit = { if (accessor == Default.Actor) { SetupNumberPools() accessor = context.actorOf( RandomPool(25).props( Props(classOf[UniqueNumberSystem], this.guid, UniqueNumberSystem.AllocateNumberPoolActors(this.guid)) ), - s"$Id-uns" + s"zone-$Id-uns" ) - ground = context.actorOf(Props(classOf[ZoneGroundActor], this, equipmentOnGround), s"$Id-ground") - deployables = context.actorOf(Props(classOf[ZoneDeployableActor], this, constructions), s"$Id-deployables") - transport = context.actorOf(Props(classOf[ZoneVehicleActor], this, vehicles), s"$Id-vehicles") - population = context.actorOf(Props(classOf[ZonePopulationActor], this, players, corpses), s"$Id-players") + ground = context.actorOf(Props(classOf[ZoneGroundActor], this, equipmentOnGround), s"zone-$Id-ground") + deployables = context.actorOf(Props(classOf[ZoneDeployableActor], this, constructions), s"zone-$Id-deployables") + transport = context.actorOf(Props(classOf[ZoneVehicleActor], this, vehicles), s"zone-$Id-vehicles") + population = context.actorOf(Props(classOf[ZonePopulationActor], this, players, corpses), s"zone-$Id-players") projector = context.actorOf( Props(classOf[ZoneHotSpotDisplay], this, hotspots, 15 seconds, hotspotHistory, 60 seconds), - s"$Id-hotspots" + s"zone-$Id-hotspots" ) - soi = context.actorOf(Props(classOf[SphereOfInfluenceActor], this), s"$Id-soi") + soi = context.actorOf(Props(classOf[SphereOfInfluenceActor], this), s"zone-$Id-soi") - avatarEvents = context.actorOf(Props(classOf[AvatarService], this), s"$Id-avatar-events") - localEvents = context.actorOf(Props(classOf[LocalService], this), s"$Id-local-events") - vehicleEvents = context.actorOf(Props(classOf[VehicleService], this), s"$Id-vehicle-events") + avatarEvents = context.actorOf(Props(classOf[AvatarService], this), s"zone-$Id-avatar-events") + localEvents = context.actorOf(Props(classOf[LocalService], this), s"zone-$Id-local-events") + vehicleEvents = context.actorOf(Props(classOf[VehicleService], this), s"zone-$Id-vehicle-events") implicit val guid: NumberPoolHub = this.guid //passed into builderObject.Build implicitly BuildLocalObjects(context, guid) @@ -189,7 +193,118 @@ class Zone(private val zoneId: String, zoneMap: ZoneMap, zoneNumber: Int) { AssignAmenities() CreateSpawnGroups() - zipLinePaths = Map.ZipLinePaths + validate() + } + } + + def validate(): Unit = { + implicit val log: Logger = org.log4s.getLogger(s"zone/$Id/sanity") + + //check bases + map.ObjectToBuilding.values + .toSet[Int] + .foreach(building_id => { + val target = Building(building_id) + if (target.isEmpty) { + log.error(s"expected a building for id #$building_id") + } else if (!target.get.HasGUID) { + log.error(s"building #$building_id was not registered") + } + }) + + //check base to object associations + map.ObjectToBuilding.keys.foreach(object_guid => + if (guid(object_guid).isEmpty) { + log.error(s"expected object id $object_guid to exist, but it did not") + } + ) + + //check door to lock association + map.DoorToLock.foreach({ + case (doorGuid, lockGuid) => + validateObject(doorGuid, (x: PlanetSideGameObject) => x.isInstanceOf[serverobject.doors.Door], "door") + validateObject(lockGuid, (x: PlanetSideGameObject) => x.isInstanceOf[serverobject.locks.IFFLock], "IFF lock") + }) + + //check vehicle terminal to spawn pad association + map.TerminalToSpawnPad.foreach({ + case (termGuid, padGuid) => + validateObject( + termGuid, + (x: PlanetSideGameObject) => x.isInstanceOf[serverobject.terminals.Terminal], + "vehicle terminal" + ) + validateObject( + padGuid, + (x: PlanetSideGameObject) => x.isInstanceOf[serverobject.pad.VehicleSpawnPad], + "vehicle spawn pad" + ) + }) + + //check implant terminal mech to implant terminal interface association + map.TerminalToInterface.foreach({ + case (mechGuid, interfaceGuid) => + validateObject( + mechGuid, + (x: PlanetSideGameObject) => x.isInstanceOf[serverobject.implantmech.ImplantTerminalMech], + "implant terminal mech" + ) + validateObject( + interfaceGuid, + (o: PlanetSideGameObject) => o.isInstanceOf[serverobject.terminals.Terminal], + "implant terminal interface" + ) + }) + + //check manned turret to weapon association + map.TurretToWeapon.foreach({ + case (turretGuid, weaponGuid) => + validateObject( + turretGuid, + (o: PlanetSideGameObject) => o.isInstanceOf[serverobject.turret.FacilityTurret], + "facility turret mount" + ) + if ( + validateObject( + weaponGuid, + (o: PlanetSideGameObject) => o.isInstanceOf[net.psforever.objects.Tool], + "facility turret weapon" + ) + ) { + if (GUID(weaponGuid).get.asInstanceOf[Tool].AmmoSlots.count(!_.Box.HasGUID) > 0) { + log.error(s"expected weapon $weaponGuid has an unregistered ammunition unit") + } + } + }) + } + + /** + * Recover an object from a collection and perform any number of validating tests upon it. + * If the object fails any tests, log an error. + * + * @param objectGuid the unique indentifier being checked against the `guid` access point + * @param test a test for the discovered object; + * expects at least `Type` checking + * @param description an explanation of how the object, if not discovered, should be identified + * @return `true` if the object was discovered and validates correctly; + * `false` if the object failed any tests + */ + def validateObject( + objectGuid: Int, + test: PlanetSideGameObject => Boolean, + description: String + )(implicit log: Logger): Boolean = { + try { + if (!test(GUID(objectGuid).get)) { + log.error(s"expected id $objectGuid to be a $description, but it was not") + false + } else { + true + } + } catch { + case e: Exception => + log.error(s"expected a $description at id $objectGuid but no object is initialized - $e") + false } } @@ -213,42 +328,89 @@ class Zone(private val zoneId: String, zoneMap: ZoneMap, zoneNumber: Int) { //guid.AddPool("l", (60001 to 65535).toList).Selector = new RandomSelector } - /** - * A reference to the primary `Actor` that governs this `Zone`. - * @return an `ActorRef` - * @see `ZoneActor`
- * `Zone.Init` - */ - def Actor: ActorRef = actor - - /** - * Give this `Zone` an `Actor` that will govern its interactions sequentially. - * @param zoneActor an `ActorRef` for this `Zone`; - * will not overwrite any existing governance unless `noSender` - * @return an `ActorRef` - * @see `ZoneActor` - */ - def Actor_=(zoneActor: ActorRef): ActorRef = { - if (actor == Default.Actor) { - actor = zoneActor + def findSpawns( + faction: PlanetSideEmpire.Value, + spawnGroups: Seq[SpawnGroup] + ): List[(AmenityOwner, Iterable[SpawnPoint])] = { + val ams = spawnGroups.contains(SpawnGroup.AMS) + val structures = spawnGroups.collect { + case SpawnGroup.Facility => + StructureType.Facility + case SpawnGroup.Tower => + StructureType.Tower + case SpawnGroup.WarpGate => + StructureType.WarpGate + case SpawnGroup.Sanctuary => + StructureType.Building } - Actor + + SpawnGroups() + .filter { + case (building, spawns) => + spawns.nonEmpty && + spawns.exists(_.Offline == false) && + structures.contains(building.BuildingType) + } + .filter { + case (building, _) => + building match { + case warpGate: WarpGate => + warpGate.Faction == faction || warpGate.Faction == PlanetSideEmpire.NEUTRAL || warpGate.Broadcast + case building => + building.Faction == faction + } + } + .map { + case (building, spawns) => + (building, spawns.filter(!_.Offline)) + } + .concat( + (if (ams) Vehicles else List()) + .filter(vehicle => + vehicle.Definition == GlobalDefinitions.ams && + !vehicle.Destroyed && + vehicle.DeploymentState == DriveState.Deployed && + vehicle.Faction == faction + ) + .map(vehicle => + ( + vehicle, + vehicle.Utilities.values + .filter(util => util.UtilType == UtilityType.ams_respawn_tube) + .map(_().asInstanceOf[SpawnTube]) + ) + ) + ) + .toList + + } + + def findNearestSpawnPoints( + faction: PlanetSideEmpire.Value, + location: Vector3, + spawnGroups: Seq[SpawnGroup] + ): Option[List[SpawnPoint]] = { + findSpawns(faction, spawnGroups) + .sortBy { + case (spawn, _) => + Vector3.DistanceSquared(location, spawn.Position.xy) + } + .collectFirst { + case (_, spawnPoints) if spawnPoints.nonEmpty => + spawnPoints.toList + } } /** * The privileged name that can be used as the second parameter in the packet `LoadMapMessage`. + * * @return the name */ def Id: String = zoneId - /** - * The map of server objects upon which this `Zone` is based - * @return the map - */ - def Map: ZoneMap = zoneMap - /** * The numerical index of the `Zone` as it is recognized in a variety of packets. + * * @return the abstract index position of this `Zone` */ def Number: Int = zoneNumber @@ -268,7 +430,7 @@ class Zone(private val zoneId: String, zoneMap: ZoneMap, zoneNumber: Int) { * @return synchronized reference to the globally unique identifier system */ def GUID(hub: NumberPoolHub): Boolean = { - if (actor == Default.Actor && guid.Pools.values.foldLeft(0)(_ + _.Count) == 0) { + if (actor == null && guid.Pools.values.foldLeft(0)(_ + _.Count) == 0) { import org.fusesource.jansi.Ansi.Color.RED import org.fusesource.jansi.Ansi.ansi println( @@ -407,12 +569,12 @@ class Zone(private val zoneId: String, zoneMap: ZoneMap, zoneNumber: Int) { lattice } - def ZipLinePaths: List[ZipLinePath] = { - zipLinePaths + def zipLinePaths: List[ZipLinePath] = { + map.ZipLinePaths } private def BuildLocalObjects(implicit context: ActorContext, guid: NumberPoolHub): Unit = { - Map.LocalObjects.foreach({ builderObject => + map.LocalObjects.foreach({ builderObject => builderObject.Build val obj = guid(builderObject.Id) @@ -426,7 +588,7 @@ class Zone(private val zoneId: String, zoneMap: ZoneMap, zoneNumber: Int) { //guard against errors here, but don't worry about specifics; let ZoneActor.ZoneSetupCheck complain about problems val other: ListBuffer[IdentifiableEntity] = new ListBuffer[IdentifiableEntity]() //turret to weapon - Map.TurretToWeapon.foreach({ + map.TurretToWeapon.foreach({ case (turret_guid, weapon_guid) => ((GUID(turret_guid) match { case Some(obj: FacilityTurret) => @@ -456,7 +618,7 @@ class Zone(private val zoneId: String, zoneMap: ZoneMap, zoneNumber: Int) { } private def MakeBuildings(implicit context: ActorContext): PairMap[Int, Building] = { - val buildingList = Map.LocalBuildings + val buildingList = map.LocalBuildings val registrationKeys: Map[Int, Try[LoanedKey]] = buildingList.map { case ((_, building_guid: Int, _), _) => (building_guid, guid.register(building_guid)) @@ -471,7 +633,7 @@ class Zone(private val zoneId: String, zoneMap: ZoneMap, zoneNumber: Int) { } private def AssignAmenities(): Unit = { - Map.ObjectToBuilding.foreach({ + map.ObjectToBuilding.foreach({ case (object_guid, building_id) => (buildings.get(building_id), guid(object_guid)) match { case (Some(building), Some(amenity)) => @@ -498,7 +660,7 @@ class Zone(private val zoneId: String, zoneMap: ZoneMap, zoneNumber: Int) { } private def MakeLattice(): Unit = { - lattice ++= Map.LatticeLink.map { + lattice ++= map.LatticeLink.map { case (source, target) => val (sourceBuilding, targetBuilding) = (Building(source), Building(target)) match { case (Some(sBuilding), Some(tBuilding)) => (sBuilding, tBuilding) @@ -650,12 +812,6 @@ object Zone { new Zone(id, map, number) } - /** - * Message to initialize the `Zone`. - * @see `Zone.Init(implicit ActorContext)` - */ - final case class Init() - object Population { /** @@ -730,79 +886,6 @@ object Zone { final case class Remove(player: Player) } - object Lattice { - - /** - * Message requesting that the current zone determine where a `player` can spawn. - * @param zone_number this zone's numeric identifier - * @param position the locality that the result should adhere - * @param faction which empire's spawn options should be available - * @param spawn_group the category of spawn points the request wants searched - */ - final case class RequestSpawnPoint( - zone_number: Int, - position: Vector3, - faction: PlanetSideEmpire.Value, - spawn_group: Int - ) - - object RequestSpawnPoint { - - /** - * Overloaded constructor for `RequestSpawnPoint`. - * @param zone_number this zone's numeric identifier - * @param player the `Player` object - * @param spawn_group the category of spawn points the request wants searched - */ - def apply(zone_number: Int, player: Player, spawn_group: Int): RequestSpawnPoint = { - RequestSpawnPoint(zone_number, player.Position, player.Faction, spawn_group) - } - } - - /** - * Message requesting a particular spawn point in the current zone. - * @param zone_number this zone's numeric identifier - * @param position the locality that the result should adhere - * @param faction which empire's spawn options should be available - * @param target the identifier of the spawn object - */ - final case class RequestSpecificSpawnPoint( - zone_number: Int, - position: Vector3, - faction: PlanetSideEmpire.Value, - target: PlanetSideGUID - ) - - object RequestSpecificSpawnPoint { - - /** - * Overloaded constructor for `RequestSpecificSpawnPoint`. - * @param zone_number this zone's numeric identifier - * @param player the `Player` object - * @param target the identifier of the spawn object - */ - def apply(zone_number: Int, player: Player, target: PlanetSideGUID): RequestSpecificSpawnPoint = { - RequestSpecificSpawnPoint(zone_number, player.Position, player.Faction, target) - } - } - - /** - * Message that returns a discovered spawn point to a request source. - * @param zone_id the zone's text identifier - * @param spawn_point the spawn point holding object - */ - final case class SpawnPoint(zone_id: String, spawn_point: net.psforever.objects.SpawnPoint) - - /** - * Message that informs a request source that a spawn point could not be discovered with the previous criteria. - * @param zone_number this zone's numeric identifier - * @param spawn_group the spawn point holding object; - * if `None`, then the previous `zone_number` could not be found; - * otherwise, no spawn points could be found in the zone - */ - final case class NoValidSpawnPoint(zone_number: Int, spawn_group: Option[Int]) - } - object Ground { final case class DropItem(item: Equipment, pos: Vector3, orient: Vector3) final case class ItemOnGround(item: Equipment, pos: Vector3, orient: Vector3) diff --git a/common/src/main/scala/net/psforever/objects/zones/ZoneActor.scala b/common/src/main/scala/net/psforever/objects/zones/ZoneActor.scala deleted file mode 100644 index a67b8ea9..00000000 --- a/common/src/main/scala/net/psforever/objects/zones/ZoneActor.scala +++ /dev/null @@ -1,395 +0,0 @@ -// Copyright (c) 2017 PSForever -package net.psforever.objects.zones - -import java.util.concurrent.atomic.AtomicInteger - -import akka.actor.Actor -import net.psforever.objects.{GlobalDefinitions, PlanetSideGameObject, SpawnPoint, Tool} -import net.psforever.objects.serverobject.structures.{AmenityOwner, StructureType, WarpGate} -import net.psforever.objects.serverobject.tube.SpawnTube -import net.psforever.objects.vehicles.UtilityType -import net.psforever.types.{DriveState, PlanetSideEmpire, Vector3} -import org.log4s.Logger - -/** - * na - * @param zone the `Zone` governed by this `Actor` - */ -class ZoneActor(zone: Zone) extends Actor { - private[this] val log = org.log4s.getLogger - - def receive: Receive = Init - - def Init: Receive = { - case Zone.Init() => - zone.Init - ZoneSetupCheck() - context.become(Processing) - - case _ => ; - } - - def Processing: Receive = { - //frwd to Population Actor - case msg @ Zone.Population.Join => - zone.Population forward msg - - case msg @ Zone.Population.Leave => - zone.Population forward msg - - case msg @ Zone.Population.Spawn => - zone.Population forward msg - - case msg @ Zone.Population.Release => - zone.Population forward msg - - case msg @ Zone.Corpse.Add => - zone.Population forward msg - - case msg @ Zone.Corpse.Remove => - zone.Population forward msg - - //frwd to Ground Actor - case msg @ Zone.Ground.DropItem => - zone.Ground forward msg - - case msg @ Zone.Ground.PickupItem => - zone.Ground forward msg - - //frwd to Deployable Actor - case msg @ Zone.Deployable.Build => - zone.Deployables forward msg - - case msg @ Zone.Deployable.Dismiss => - zone.Deployables forward msg - - //frwd to Vehicle Actor - case msg @ Zone.Vehicle.Spawn => - zone.Transport forward msg - - case msg @ Zone.Vehicle.Despawn => - zone.Transport forward msg - - //frwd to Projector actor - case msg @ Zone.HotSpot.Activity => - zone.Activity forward msg - - case msg @ Zone.HotSpot.UpdateNow => - zone.Activity forward msg - - case msg @ Zone.HotSpot.ClearAll => - zone.Activity forward msg - - //own - case Zone.Lattice.RequestSpawnPoint(zone_number, position, faction, spawn_group) => - if (zone_number == zone.Number) { - ZoneActor.FindLocalSpawnPointsInZone(zone, position, faction, spawn_group) match { - case Some(Nil) | None => - sender ! Zone.Lattice.NoValidSpawnPoint(zone_number, Some(spawn_group)) - - case Some(List(tube)) => - sender ! Zone.Lattice.SpawnPoint(zone.Id, tube) - - case Some(tubes) => - val tube = scala.util.Random.shuffle(tubes).head - sender ! Zone.Lattice.SpawnPoint(zone.Id, tube) - } - } else { //wrong zone_number - sender ! Zone.Lattice.NoValidSpawnPoint(zone_number, None) - } - - case Zone.Lattice.RequestSpecificSpawnPoint(zone_number, _, faction, target) => - if (zone_number == zone.Number) { - //is our spawn point some other privileged vehicle? - zone.Vehicles - .collectFirst({ - case vehicle: SpawnPoint if vehicle.Faction == faction && vehicle.GUID == target => - Some(vehicle) //the vehicle itself is the spawn point - case vehicle if vehicle.Faction == faction && vehicle.GUID == target => - vehicle.Utilities.values.find { util => - util().isInstanceOf[SpawnPoint] - } match { - case None => - None - case Some(util) => - Some(util().asInstanceOf[SpawnTube]) //the vehicle's utility is the spawn point - } - }) - .orElse({ - //is our spawn point a building itself (like a warp gate)? - val friendlySpawnGroups = zone.SpawnGroups().filter { - case (building, _) => - building match { - case wg: WarpGate => - building.Faction == faction || building.Faction == PlanetSideEmpire.NEUTRAL || wg.Broadcast - case _ => - building.Faction == faction - } - } - friendlySpawnGroups - .collectFirst({ - case (building, points) if building.MapId == target.guid && points.nonEmpty => - scala.util.Random.shuffle(points).head - }) - .orElse { - //is our spawn a conventional amenity? - friendlySpawnGroups.values.flatten.find { point => point.GUID == target } - } - }) match { - case Some(point: SpawnPoint) => - sender ! Zone.Lattice.SpawnPoint(zone.Id, point) - - case _ => - sender ! Zone.Lattice.NoValidSpawnPoint(zone_number, Some(target.guid)) - } - } else { //wrong zone_number - sender ! Zone.Lattice.NoValidSpawnPoint(zone_number, None) - } - - case msg => - log.warn(s"Received unexpected message - $msg") - } - - def ZoneSetupCheck(): Int = { - import ZoneActor._ - val map = zone.Map - def guid(id: Int) = zone.GUID(id) - val slog = org.log4s.getLogger(s"zone/${zone.Id}/sanity") - val errors = new AtomicInteger(0) - val validateObject: (Int, PlanetSideGameObject => Boolean, String) => Boolean = ValidateObject(guid, slog, errors) - - //check bases - map.ObjectToBuilding.values - .toSet[Int] - .foreach(building_id => { - val target = zone.Building(building_id) - if (target.isEmpty) { - slog.error(s"expected a building for id #$building_id") - errors.incrementAndGet() - } else if (!target.get.HasGUID) { - slog.error(s"building #$building_id was not registered") - errors.incrementAndGet() - } - }) - - //check base to object associations - map.ObjectToBuilding.keys.foreach(object_guid => - if (guid(object_guid).isEmpty) { - slog.error(s"expected object id $object_guid to exist, but it did not") - errors.incrementAndGet() - } - ) - - //check door to lock association - map.DoorToLock.foreach({ - case (door_guid, lock_guid) => - validateObject(door_guid, DoorCheck, "door") - validateObject(lock_guid, LockCheck, "IFF lock") - }) - - //check vehicle terminal to spawn pad association - map.TerminalToSpawnPad.foreach({ - case (term_guid, pad_guid) => - validateObject(term_guid, TerminalCheck, "vehicle terminal") - validateObject(pad_guid, VehicleSpawnPadCheck, "vehicle spawn pad") - }) - - //check implant terminal mech to implant terminal interface association - map.TerminalToInterface.foreach({ - case (mech_guid, interface_guid) => - validateObject(mech_guid, ImplantMechCheck, "implant terminal mech") - validateObject(interface_guid, TerminalCheck, "implant terminal interface") - }) - - //check manned turret to weapon association - map.TurretToWeapon.foreach({ - case (turret_guid, weapon_guid) => - validateObject(turret_guid, FacilityTurretCheck, "facility turret mount") - if (validateObject(weapon_guid, WeaponCheck, "facility turret weapon")) { - if (guid(weapon_guid).get.asInstanceOf[Tool].AmmoSlots.count(!_.Box.HasGUID) > 0) { - slog.error(s"expected weapon $weapon_guid has an unregistered ammunition unit") - errors.incrementAndGet() - } - } - }) - - //output number of errors - errors.intValue() - } -} - -object ZoneActor { - - /** - * na - * @param zone na - * @param position na - * @param faction na - * @param spawn_group na - * @return na - */ - def FindLocalSpawnPointsInZone( - zone: Zone, - position: Vector3, - faction: PlanetSideEmpire.Value, - spawn_group: Int - ): Option[List[SpawnPoint]] = { - (if (spawn_group == 2) { - FindVehicleSpawnPointsInZone(zone, position, faction) - } else { - FindBuildingSpawnPointsInZone(zone, position, faction, spawn_group) - }).headOption match { - case None | Some((_, Nil)) => - None - case Some((_, tubes)) => - Some(tubes toList) - } - } - - /** - * na - * @param zone na - * @param position na - * @param faction na - * @return na - */ - private def FindVehicleSpawnPointsInZone( - zone: Zone, - position: Vector3, - faction: PlanetSideEmpire.Value - ): List[(AmenityOwner, Iterable[SpawnTube])] = { - val xy = position.xy - //ams - zone.Vehicles - .filter(veh => - veh.Definition == GlobalDefinitions.ams && - !veh.Destroyed && - veh.DeploymentState == DriveState.Deployed && - veh.Faction == faction - ) - .sortBy(veh => Vector3.DistanceSquared(xy, veh.Position.xy)) - .map(veh => - ( - veh, - veh.Utilities.values - .filter(util => util.UtilType == UtilityType.ams_respawn_tube) - .map(_().asInstanceOf[SpawnTube]) - ) - ) - } - - /** - * na - * @param zone na - * @param position na - * @param faction na - * @param spawn_group na - * @return na - */ - private def FindBuildingSpawnPointsInZone( - zone: Zone, - position: Vector3, - faction: PlanetSideEmpire.Value, - spawn_group: Int - ): List[(AmenityOwner, Iterable[SpawnPoint])] = { - val xy = position.xy - //facilities, towers, warp gates, and other buildings - val buildingTypeSet = if (spawn_group == 0) { - Set(StructureType.Facility, StructureType.Tower, StructureType.Building) - } else if (spawn_group == 6) { - Set(StructureType.Tower) - } else if (spawn_group == 7) { - Set(StructureType.Facility, StructureType.Building) - } else if (spawn_group == 12) { - Set(StructureType.WarpGate) - } else { - Set.empty[StructureType.Value] - } - zone - .SpawnGroups() - .collect({ - case (building, spawns) if buildingTypeSet.contains(building.BuildingType) && (building match { - case wg: WarpGate => - building.Faction == faction || building.Faction == PlanetSideEmpire.NEUTRAL || wg.Broadcast - case _ => - building.Faction == faction && spawns.nonEmpty && spawns.exists(_.Offline == false) - }) => - (building, spawns.filter(_.Offline == false)) - }) - .toSeq - .sortBy({ - case (building, _) => - Vector3.DistanceSquared(xy, building.Position.xy) - }) - .toList - } - - /** - * Recover an object from a collection and perform any number of validating tests upon it. - * If the object fails any tests, log an error. - * @param guid access to an association between unique numbers and objects using some of those unique numbers - * @param elog a contraction of "error log;" - * accepts `String` data - * @param object_guid the unique indentifier being checked against the `guid` access point - * @param test a test for the discovered object; - * expects at least `Type` checking - * @param description an explanation of how the object, if not discovered, should be identified - * @return `true` if the object was discovered and validates correctly; - * `false` if the object failed any tests - */ - def ValidateObject( - guid: Int => Option[PlanetSideGameObject], - elog: Logger, - errorCounter: AtomicInteger - )(object_guid: Int, test: PlanetSideGameObject => Boolean, description: String): Boolean = { - try { - if (!test(guid(object_guid).get)) { - elog.error(s"expected id $object_guid to be a $description, but it was not") - errorCounter.incrementAndGet() - false - } else { - true - } - } catch { - case e: Exception => - elog.error(s"expected a $description at id $object_guid but no object is initialized - $e") - errorCounter.incrementAndGet() - false - } - } - - def LockCheck(obj: PlanetSideGameObject): Boolean = { - import net.psforever.objects.serverobject.locks.IFFLock - obj.isInstanceOf[IFFLock] - } - - def DoorCheck(obj: PlanetSideGameObject): Boolean = { - import net.psforever.objects.serverobject.doors.Door - obj.isInstanceOf[Door] - } - - def TerminalCheck(obj: PlanetSideGameObject): Boolean = { - import net.psforever.objects.serverobject.terminals.Terminal - obj.isInstanceOf[Terminal] - } - - def ImplantMechCheck(obj: PlanetSideGameObject): Boolean = { - import net.psforever.objects.serverobject.implantmech.ImplantTerminalMech - obj.isInstanceOf[ImplantTerminalMech] - } - - def VehicleSpawnPadCheck(obj: PlanetSideGameObject): Boolean = { - import net.psforever.objects.serverobject.pad.VehicleSpawnPad - obj.isInstanceOf[VehicleSpawnPad] - } - - def FacilityTurretCheck(obj: PlanetSideGameObject): Boolean = { - import net.psforever.objects.serverobject.turret.FacilityTurret - obj.isInstanceOf[FacilityTurret] - } - - def WeaponCheck(obj: PlanetSideGameObject): Boolean = { - import net.psforever.objects.Tool - obj.isInstanceOf[Tool] - } -} diff --git a/common/src/main/scala/net/psforever/objects/zones/ZoneDeployableActor.scala b/common/src/main/scala/net/psforever/objects/zones/ZoneDeployableActor.scala index c782689e..bc5365d3 100644 --- a/common/src/main/scala/net/psforever/objects/zones/ZoneDeployableActor.scala +++ b/common/src/main/scala/net/psforever/objects/zones/ZoneDeployableActor.scala @@ -86,127 +86,4 @@ object ZoneDeployableActor { } } } - -// /** -// * Add an `avatar` as the key of an `Avatar` to `Player` object pair in the given collection. -// * @param avatar an `Avatar` object -// * @param playerMap the mapping of `Avatar` objects to `Player` objects -// * @return true, if the mapping is for a new key; -// * false, if the key already exists -// */ -// def PopulationJoin(avatar : Avatar, playerMap : TrieMap[Avatar, Option[Player]]) : Boolean = { -// playerMap.get(avatar) match { -// case Some(_) => -// false -// case None => -// playerMap += avatar -> None -// true -// } -// } -// /** -// * Remove an `avatar` from the key of an `Avatar` to `Player` object pair in the given collection. -// * If a `Player` object is associated at the time, return it safely. -// * @param avatar an `Avatar` object -// * @param playerMap the mapping of `Avatar` objects to `Player` objects -// * @return any `Player` object that was associated at the time the `avatar` was removed -// */ -// def PopulationLeave(avatar : Avatar, playerMap : TrieMap[Avatar, Option[Player]]) : Option[Player] = { -// playerMap.remove(avatar) match { -// case None => -// None -// case Some(tplayer) => -// tplayer -// } -// } -// -// /** -// * Associate a `Player` object as a value to an existing `Avatar` object that will be its key. -// * Do not overwrite players that are already associated. -// * @param avatar an `Avatar` object -// * @param player a `Player` object -// * @param playerMap the mapping of `Avatar` objects to `Player` objects -// * @return the `Player` object that is associated with the `Avatar` key -// */ -// def PopulationSpawn(avatar : Avatar, player : Player, playerMap : TrieMap[Avatar, Option[Player]]) : Option[Player] = { -// playerMap.get(avatar) match { -// case None => -// None -// case Some(tplayer) => -// tplayer match { -// case Some(aplayer) => -// Some(aplayer) -// case None => -// playerMap(avatar) = Some(player) -// Some(player) -// } -// } -// } -// -// /** -// * Disassociate a `Player` object from an existing `Avatar` object that was be its key. -// * @param avatar an `Avatar` object -// * @param playerMap the mapping of `Avatar` objects to `Player` objects -// * @return any `Player` object that is associated at the time -// */ -// def PopulationRelease(avatar : Avatar, playerMap : TrieMap[Avatar, Option[Player]]) : Option[Player] = { -// playerMap.get(avatar) match { -// case None => -// None -// case Some(tplayer) => -// playerMap(avatar) = None -// tplayer -// } -// } -// -// /** -// * If the given `player` passes a condition check, add it to the list. -// * @param player a `Player` object -// * @param corpseList a list of `Player` objects -// * @return true, if the `player` was added to the list; -// * false, otherwise -// */ -// def CorpseAdd(player : Player, corpseList : ListBuffer[Player]) : Boolean = { -// if(player.isBackpack) { -// corpseList += player -// true -// } -// else { -// false -// } -// } -// -// /** -// * Remove the given `player` from the list. -// * @param player a `Player` object -// * @param corpseList a list of `Player` objects -// */ -// def CorpseRemove(player : Player, corpseList : ListBuffer[Player]) : Unit = { -// recursiveFindCorpse(corpseList.iterator, player) match { -// case None => ; -// case Some(index) => -// corpseList.remove(index) -// } -// } -// -// /** -// * A recursive function that finds and removes a specific player from a list of players. -// * @param iter an `Iterator` of `Player` objects -// * @param player the target `Player` -// * @param index the index of the discovered `Player` object -// * @return the index of the `Player` object in the list to be removed; -// * `None`, otherwise -// */ -// @tailrec final def recursiveFindCorpse(iter : Iterator[Player], player : Player, index : Int = 0) : Option[Int] = { -// if(!iter.hasNext) { -// None -// } -// else { -// if(iter.next == player) { -// Some(index) -// } -// else { -// recursiveFindCorpse(iter, player, index + 1) -// } -// } -// } } diff --git a/common/src/main/scala/net/psforever/packet/PSPacket.scala b/common/src/main/scala/net/psforever/packet/PSPacket.scala index 385192f1..6b0bc0dc 100644 --- a/common/src/main/scala/net/psforever/packet/PSPacket.scala +++ b/common/src/main/scala/net/psforever/packet/PSPacket.scala @@ -127,8 +127,8 @@ object PacketHelpers { createEnumerationCodec(enum, storageCodec.xmap[Int](_.toInt, _.toLong)) } - /** Create a Codec for enumeratum's Enum type */ - def createEnumCodec[E <: IntEnumEntry](enum: IntEnum[E], storageCodec: Codec[Int]): Codec[E] = { + /** Create a Codec for enumeratum's IntEnum type */ + def createIntEnumCodec[E <: IntEnumEntry](enum: IntEnum[E], storageCodec: Codec[Int]): Codec[E] = { type Struct = Int :: HNil val struct: Codec[Struct] = storageCodec.hlist @@ -149,6 +149,10 @@ object PacketHelpers { struct.narrow[E](from, to) } + def createLongIntEnumCodec[E <: IntEnumEntry](enum: IntEnum[E], storageCodec: Codec[Long]): Codec[E] = { + createIntEnumCodec(enum, storageCodec.xmap[Int](_.toInt, _.toLong)) + } + /** Common codec for how PlanetSide stores string sizes * * When the first bit of the byte is set, the size can be between [0, 127]. diff --git a/common/src/main/scala/net/psforever/packet/game/BindPlayerMessage.scala b/common/src/main/scala/net/psforever/packet/game/BindPlayerMessage.scala index e7af3353..50fa0e47 100644 --- a/common/src/main/scala/net/psforever/packet/game/BindPlayerMessage.scala +++ b/common/src/main/scala/net/psforever/packet/game/BindPlayerMessage.scala @@ -67,7 +67,7 @@ final case class BindPlayerMessage( bind_desc: String, display_icon: Boolean, logging: Boolean, - spawn_group: SpawnGroup.Value, + spawn_group: SpawnGroup, zone_number: Long, unk4: Long, pos: Vector3 @@ -84,7 +84,7 @@ object BindPlayerMessage extends Marshallable[BindPlayerMessage] { */ val Standard = BindPlayerMessage(BindStatus.Unbind, "", false, false, SpawnGroup.BoundAMS, 0, 0, Vector3.Zero) - private val spawnGroupCodec = PacketHelpers.createEnumerationCodec(SpawnGroup, uint4) + private val spawnGroupCodec = PacketHelpers.createIntEnumCodec(SpawnGroup, uint4) implicit val codec: Codec[BindPlayerMessage] = ( ("action" | BindStatus.codec) :: diff --git a/common/src/main/scala/net/psforever/packet/game/SpawnRequestMessage.scala b/common/src/main/scala/net/psforever/packet/game/SpawnRequestMessage.scala index 3c3b2dba..40a0787f 100644 --- a/common/src/main/scala/net/psforever/packet/game/SpawnRequestMessage.scala +++ b/common/src/main/scala/net/psforever/packet/game/SpawnRequestMessage.scala @@ -8,22 +8,25 @@ import scodec.codecs._ /** * na - * @param unk1 when defined, na; - * non-zero when selecting the sanctuary option from a non-sanctuary continent deployment map - * @param spawn_type the type of spawn point destination - * @param unk3 na - * @param unk4 na - * @param zone_number when defined, the continent number + * + * @param unk1 when defined, na; + * non-zero when selecting the sanctuary option from a non-sanctuary continent deployment map + * @param spawn_type the type of spawn point destination + * @param unk3 na + * @param unk4 na + * @param zone_number when defined, the zone number */ -final case class SpawnRequestMessage(unk1: Int, spawn_type: SpawnGroup.Value, unk3: Int, unk4: Int, zone_number: Int) +final case class SpawnRequestMessage(unk1: Int, spawn_type: SpawnGroup, unk3: Int, unk4: Int, zone_number: Int) extends PlanetSideGamePacket { type Packet = SpawnRequestMessage + def opcode = GamePacketOpcode.SpawnRequestMessage + def encode = SpawnRequestMessage.encode(this) } object SpawnRequestMessage extends Marshallable[SpawnRequestMessage] { - private val spawnGroupCodec = PacketHelpers.createLongEnumerationCodec(SpawnGroup, uint32L) + private val spawnGroupCodec = PacketHelpers.createLongIntEnumCodec(SpawnGroup, uint32L) implicit val codec: Codec[SpawnRequestMessage] = ( ("unk1" | uint16L) :: diff --git a/common/src/main/scala/net/psforever/packet/game/VNLWorldStatusMessage.scala b/common/src/main/scala/net/psforever/packet/game/VNLWorldStatusMessage.scala index 4e01bdfc..0a0548c3 100644 --- a/common/src/main/scala/net/psforever/packet/game/VNLWorldStatusMessage.scala +++ b/common/src/main/scala/net/psforever/packet/game/VNLWorldStatusMessage.scala @@ -26,7 +26,7 @@ object ServerType extends IntEnum[ServerType] { case object ReleasedGemini extends ServerType(4, "released_gemini") val values = findValues - implicit val codec = PacketHelpers.createEnumCodec(this, uint8L) + implicit val codec = PacketHelpers.createIntEnumCodec(this, uint8L) } // This MUST be an IP address. The client DOES NOT do name resolution properly diff --git a/common/src/main/scala/net/psforever/persistence/Building.scala b/common/src/main/scala/net/psforever/persistence/Building.scala new file mode 100644 index 00000000..7a095a07 --- /dev/null +++ b/common/src/main/scala/net/psforever/persistence/Building.scala @@ -0,0 +1,7 @@ +package net.psforever.persistence + +case class Building( + localId: Int, // aka map id + zoneId: Int, // aka zone number + factionId: Int +) diff --git a/common/src/main/scala/net/psforever/types/SpawnGroup.scala b/common/src/main/scala/net/psforever/types/SpawnGroup.scala index f0113973..1cc7c8a0 100644 --- a/common/src/main/scala/net/psforever/types/SpawnGroup.scala +++ b/common/src/main/scala/net/psforever/types/SpawnGroup.scala @@ -1,6 +1,8 @@ // Copyright (c) 2017 PSForever package net.psforever.types +import enumeratum.values.{IntEnum, IntEnumEntry} + /** * The spawn group.
*
@@ -17,11 +19,37 @@ package net.psforever.types * The icons produced by the normal and the bound tower and facility groups are not detailed. * The ones that are not designated as "bound" also do not display icons when manually set. * The AMS spawn group icons have an overhead AMS glyph and are smaller in radius, identical otherwise. + * * @see `BindPlayerMessage` */ -object SpawnGroup extends Enumeration { - type Type = Value +sealed abstract class SpawnGroup(val value: Int) extends IntEnumEntry + +object SpawnGroup extends IntEnum[SpawnGroup] { + val values = findValues + + case object Sanctuary extends SpawnGroup(0) + + case object BoundAMS extends SpawnGroup(1) + + case object AMS extends SpawnGroup(2) + + case object Unknown3 extends SpawnGroup(3) + + case object BoundTower extends SpawnGroup(4) // unused? + case object BoundFacility extends SpawnGroup(5) + + case object Tower extends SpawnGroup(6) + + case object Facility extends SpawnGroup(7) + + case object Unknown8 extends SpawnGroup(8) + + case object Unknown9 extends SpawnGroup(9) + + case object Unknown10 extends SpawnGroup(10) + + case object Unknown11 extends SpawnGroup(11) + + case object WarpGate extends SpawnGroup(12) - val Sanctuary, BoundAMS, AMS, Unknown3, BoundTower, //unused? - BoundFacility, Tower, Facility, Unknown8, Unknown9, Unknown10, Unknown11, Unknown12 = Value } diff --git a/common/src/main/scala/net/psforever/util/Config.scala b/common/src/main/scala/net/psforever/util/Config.scala index e89600ce..d66007dc 100644 --- a/common/src/main/scala/net/psforever/util/Config.scala +++ b/common/src/main/scala/net/psforever/util/Config.scala @@ -57,6 +57,7 @@ case class AppConfig( world: WorldConfig, admin: AdminConfig, database: DatabaseConfig, + game: GameConfig, antiCheat: AntiCheatConfig, network: NetworkConfig, developer: DeveloperConfig, @@ -103,6 +104,10 @@ case class SessionConfig( outboundGraceTime: Duration ) +case class GameConfig( + instantActionAms: Boolean +) + case class DeveloperConfig( netSim: NetSimConfig ) diff --git a/common/src/main/scala/net/psforever/util/Database.scala b/common/src/main/scala/net/psforever/util/Database.scala index dce3d563..9cfcd2f3 100644 --- a/common/src/main/scala/net/psforever/util/Database.scala +++ b/common/src/main/scala/net/psforever/util/Database.scala @@ -1,20 +1,24 @@ package net.psforever.util import io.getquill.{PostgresJAsyncContext, SnakeCase} -import net.psforever.persistence +import net.psforever.persistence.{Account, Building, Loadout, Locker, Login, Character} object Database { - implicit val accountSchemaMeta = ctx.schemaMeta[persistence.Account]("accounts", _.id -> "id") - implicit val characterSchemaMeta = ctx.schemaMeta[persistence.Character]("characters", _.id -> "id") - implicit val loadoutSchemaMeta = ctx.schemaMeta[persistence.Loadout]("loadouts", _.id -> "id") - implicit val lockerSchemaMeta = ctx.schemaMeta[persistence.Locker]("lockers", _.id -> "id") - implicit val loginSchemaMeta = ctx.schemaMeta[persistence.Login]("logins", _.id -> "id") + val ctx = new PostgresJAsyncContext(SnakeCase, Config.config.getConfig("database")) + + implicit val accountSchemaMeta: ctx.SchemaMeta[Account] = ctx.schemaMeta[Account]("accounts") + implicit val characterSchemaMeta: ctx.SchemaMeta[Character] = ctx.schemaMeta[Character]("characters") + implicit val loadoutSchemaMeta: ctx.SchemaMeta[Loadout] = ctx.schemaMeta[Loadout]("loadouts") + implicit val lockerSchemaMeta: ctx.SchemaMeta[Locker] = ctx.schemaMeta[Locker]("lockers") + implicit val loginSchemaMeta: ctx.SchemaMeta[Login] = ctx.schemaMeta[Login]("logins") + implicit val buildingSchemaMeta: ctx.SchemaMeta[Building] = ctx.schemaMeta[Building]("buildings") // TODO remove if this gets merged https://github.com/getquill/quill/pull/1765 implicit class ILike(s1: String) { + import ctx._ + def ilike(s2: String) = quote(infix"$s1 ilike $s2".as[Boolean]) } - val ctx = new PostgresJAsyncContext(SnakeCase, Config.config.getConfig("database")) } diff --git a/common/src/main/scala/net/psforever/zones/Zones.scala b/common/src/main/scala/net/psforever/zones/Zones.scala index d2cccbc5..04142aad 100644 --- a/common/src/main/scala/net/psforever/zones/Zones.scala +++ b/common/src/main/scala/net/psforever/zones/Zones.scala @@ -14,10 +14,10 @@ object Zones { val zones: HashMap[String, Zone] = HashMap( ( "z1", - new Zone("z1", Await.result(Maps.map01, 30 seconds), 1) { - override def Init(implicit context: ActorContext): Unit = { - super.Init(context) - HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(Map.Scale, 80, 80) + new Zone("z1", Await.result(Maps.map01, 60 seconds), 1) { + override def init(implicit context: ActorContext): Unit = { + super.init(context) + HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(map.Scale, 80, 80) HotSpotTimeFunction = Zones.HotSpots.StandardTimeRules InitZoneAmenities(zone = this) @@ -26,10 +26,10 @@ object Zones { ), ( "z2", - new Zone("z2", Await.result(Maps.map02, 30 seconds), 2) { - override def Init(implicit context: ActorContext): Unit = { - super.Init(context) - HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(Map.Scale, 80, 80) + new Zone("z2", Await.result(Maps.map02, 60 seconds), 2) { + override def init(implicit context: ActorContext): Unit = { + super.init(context) + HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(map.Scale, 80, 80) HotSpotTimeFunction = Zones.HotSpots.StandardTimeRules InitZoneAmenities(zone = this) @@ -38,10 +38,10 @@ object Zones { ), ( "z3", - new Zone("z3", Await.result(Maps.map03, 30 seconds), 3) { - override def Init(implicit context: ActorContext): Unit = { - super.Init(context) - HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(Map.Scale, 80, 80) + new Zone("z3", Await.result(Maps.map03, 60 seconds), 3) { + override def init(implicit context: ActorContext): Unit = { + super.init(context) + HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(map.Scale, 80, 80) HotSpotTimeFunction = Zones.HotSpots.StandardTimeRules InitZoneAmenities(zone = this) @@ -50,84 +50,22 @@ object Zones { ), ( "z4", - new Zone("z4", Await.result(Maps.map04, 30 seconds), 4) { - override def Init(implicit context: ActorContext): Unit = { - super.Init(context) - HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(Map.Scale, 80, 80) + new Zone("z4", Await.result(Maps.map04, 60 seconds), 4) { + override def init(implicit context: ActorContext): Unit = { + super.init(context) + HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(map.Scale, 80, 80) HotSpotTimeFunction = Zones.HotSpots.StandardTimeRules InitZoneAmenities(zone = this) - - BuildingByMapId(5).get.Faction = PlanetSideEmpire.TR //Akkan - BuildingByMapId(6).get.Faction = PlanetSideEmpire.TR //Baal - BuildingByMapId(7).get.Faction = PlanetSideEmpire.TR //Dagon - BuildingByMapId(8).get.Faction = PlanetSideEmpire.NC //Enkidu - BuildingByMapId(9).get.Faction = PlanetSideEmpire.VS //Girru - BuildingByMapId(10).get.Faction = PlanetSideEmpire.VS //Hanish - BuildingByMapId(11).get.Faction = PlanetSideEmpire.VS //Irkalla - BuildingByMapId(12).get.Faction = PlanetSideEmpire.VS //Kusag - BuildingByMapId(13).get.Faction = PlanetSideEmpire.VS //Lahar - BuildingByMapId(14).get.Faction = PlanetSideEmpire.NC //Marduk - BuildingByMapId(15).get.Faction = PlanetSideEmpire.NC //Neti - BuildingByMapId(16).get.Faction = PlanetSideEmpire.NC //Zaqar - BuildingByMapId(17).get.Faction = PlanetSideEmpire.NC //S_Marduk_Tower - BuildingByMapId(18).get.Faction = PlanetSideEmpire.NC //W_Neti_Tower - BuildingByMapId(19).get.Faction = PlanetSideEmpire.NC //W_Zaqar_Tower - BuildingByMapId(20).get.Faction = PlanetSideEmpire.NC //E_Zaqar_Tower - BuildingByMapId(21).get.Faction = PlanetSideEmpire.NC //NE_Neti_Tower - BuildingByMapId(22).get.Faction = PlanetSideEmpire.NC //SE_Ceryshen_Warpgate_Tower - BuildingByMapId(23).get.Faction = PlanetSideEmpire.VS //S_Kusag_Tower - BuildingByMapId(24).get.Faction = PlanetSideEmpire.VS //NW_Kusag_Tower - BuildingByMapId(25).get.Faction = PlanetSideEmpire.VS //N_Ceryshen_Warpgate_Tower - BuildingByMapId(26).get.Faction = PlanetSideEmpire.VS //SE_Irkalla_Tower - BuildingByMapId(27).get.Faction = PlanetSideEmpire.VS //S_Irkalla_Tower - BuildingByMapId(28).get.Faction = PlanetSideEmpire.TR //NE_Enkidu_Tower - BuildingByMapId(29).get.Faction = PlanetSideEmpire.NC //SE_Akkan_Tower - BuildingByMapId(30).get.Faction = PlanetSideEmpire.NC //SW_Enkidu_Tower - BuildingByMapId(31).get.Faction = PlanetSideEmpire.TR //E_Searhus_Warpgate_Tower - BuildingByMapId(32).get.Faction = PlanetSideEmpire.TR //N_Searhus_Warpgate_Tower - BuildingByMapId(33).get.Faction = PlanetSideEmpire.VS //E_Girru_Tower - BuildingByMapId(34).get.Faction = PlanetSideEmpire.VS //SE_Hanish_Tower - BuildingByMapId(35).get.Faction = PlanetSideEmpire.TR //SW_Hanish_Tower - BuildingByMapId(36).get.Faction = PlanetSideEmpire.VS //W_Girru_Tower - BuildingByMapId(37).get.Faction = PlanetSideEmpire.TR //E_Dagon_Tower - BuildingByMapId(38).get.Faction = PlanetSideEmpire.TR //NE_Baal_Tower - BuildingByMapId(39).get.Faction = PlanetSideEmpire.TR //SE_Baal_Tower - BuildingByMapId(40).get.Faction = PlanetSideEmpire.TR //S_Dagon_Tower - BuildingByMapId(41).get.Faction = PlanetSideEmpire.NC //W_Ceryshen_Warpgate_Tower - BuildingByMapId(42).get.Faction = PlanetSideEmpire.NEUTRAL //dagon bunker - BuildingByMapId(43).get.Faction = PlanetSideEmpire.NEUTRAL //Akkan North Bunker - BuildingByMapId(44).get.Faction = PlanetSideEmpire.NEUTRAL //Enkidu East Bunker - BuildingByMapId(45).get.Faction = PlanetSideEmpire.NEUTRAL //Neti bunker - BuildingByMapId(46).get.Faction = PlanetSideEmpire.NEUTRAL //Hanish West Bunker - BuildingByMapId(47).get.Faction = PlanetSideEmpire.NEUTRAL //Irkalla East Bunker - BuildingByMapId(48).get.Faction = PlanetSideEmpire.NEUTRAL //Zaqar bunker - BuildingByMapId(49).get.Faction = PlanetSideEmpire.NEUTRAL //Kusag West Bunker - BuildingByMapId(50).get.Faction = PlanetSideEmpire.NEUTRAL //marduk bunker - BuildingByMapId(51).get.Faction = PlanetSideEmpire.TR //baal bunker - BuildingByMapId(52).get.Faction = PlanetSideEmpire.NEUTRAL //girru bunker - BuildingByMapId(53).get.Faction = PlanetSideEmpire.NEUTRAL //lahar bunker - BuildingByMapId(54).get.Faction = PlanetSideEmpire.NEUTRAL //akkan bunker - BuildingByMapId(55).get.Faction = PlanetSideEmpire.VS //Irkalla_Tower - BuildingByMapId(56).get.Faction = PlanetSideEmpire.VS //Hanish_Tower - BuildingByMapId(57).get.Faction = PlanetSideEmpire.VS //E_Ceryshen_Warpgate_Tower - BuildingByMapId(58).get.Faction = PlanetSideEmpire.VS //Lahar_Tower - BuildingByMapId(59).get.Faction = PlanetSideEmpire.VS //VSSanc_Warpgate_Tower - BuildingByMapId(60).get.Faction = PlanetSideEmpire.TR //Akkan_Tower - BuildingByMapId(61).get.Faction = PlanetSideEmpire.NC //TRSanc_Warpgate_Tower - BuildingByMapId(62).get.Faction = PlanetSideEmpire.NC //Marduk_Tower - BuildingByMapId(63).get.Faction = PlanetSideEmpire.TR //NW_Dagon_Tower - BuildingByMapId(64).get.Faction = PlanetSideEmpire.NEUTRAL //E7 East Bunker (at north from bridge) - BuildingByMapId(65).get.Faction = PlanetSideEmpire.VS //W_Hanish_Tower } } ), ( "z5", - new Zone("z5", Await.result(Maps.map05, 30 seconds), 5) { - override def Init(implicit context: ActorContext): Unit = { - super.Init(context) - HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(Map.Scale, 80, 80) + new Zone("z5", Await.result(Maps.map05, 60 seconds), 5) { + override def init(implicit context: ActorContext): Unit = { + super.init(context) + HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(map.Scale, 80, 80) HotSpotTimeFunction = Zones.HotSpots.StandardTimeRules InitZoneAmenities(zone = this) @@ -136,27 +74,22 @@ object Zones { ), ( "z6", - new Zone("z6", Await.result(Maps.map06, 30 seconds), 6) { - override def Init(implicit context: ActorContext): Unit = { - super.Init(context) - HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(Map.Scale, 80, 80) + new Zone("z6", Await.result(Maps.map06, 60 seconds), 6) { + override def init(implicit context: ActorContext): Unit = { + super.init(context) + HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(map.Scale, 80, 80) HotSpotTimeFunction = Zones.HotSpots.StandardTimeRules - import net.psforever.types.PlanetSideEmpire - BuildingByMapId(2).get.Faction = PlanetSideEmpire.VS - BuildingByMapId(48).get.Faction = PlanetSideEmpire.VS - BuildingByMapId(49).get.Faction = PlanetSideEmpire.VS - InitZoneAmenities(zone = this) } } ), ( "z7", - new Zone("z7", Await.result(Maps.map07, 30 seconds), 7) { - override def Init(implicit context: ActorContext): Unit = { - super.Init(context) - HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(Map.Scale, 80, 80) + new Zone("z7", Await.result(Maps.map07, 60 seconds), 7) { + override def init(implicit context: ActorContext): Unit = { + super.init(context) + HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(map.Scale, 80, 80) HotSpotTimeFunction = Zones.HotSpots.StandardTimeRules InitZoneAmenities(zone = this) @@ -165,10 +98,10 @@ object Zones { ), ( "z8", - new Zone("z8", Await.result(Maps.map08, 30 seconds), 8) { - override def Init(implicit context: ActorContext): Unit = { - super.Init(context) - HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(Map.Scale, 80, 80) + new Zone("z8", Await.result(Maps.map08, 60 seconds), 8) { + override def init(implicit context: ActorContext): Unit = { + super.init(context) + HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(map.Scale, 80, 80) HotSpotTimeFunction = Zones.HotSpots.StandardTimeRules InitZoneAmenities(zone = this) @@ -177,10 +110,10 @@ object Zones { ), ( "z9", - new Zone("z9", Await.result(Maps.map09, 30 seconds), 9) { - override def Init(implicit context: ActorContext): Unit = { - super.Init(context) - HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(Map.Scale, 80, 80) + new Zone("z9", Await.result(Maps.map09, 60 seconds), 9) { + override def init(implicit context: ActorContext): Unit = { + super.init(context) + HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(map.Scale, 80, 80) HotSpotTimeFunction = Zones.HotSpots.StandardTimeRules InitZoneAmenities(zone = this) @@ -189,10 +122,10 @@ object Zones { ), ( "z10", - new Zone("z10", Await.result(Maps.map10, 30 seconds), 10) { - override def Init(implicit context: ActorContext): Unit = { - super.Init(context) - HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(Map.Scale, 80, 80) + new Zone("z10", Await.result(Maps.map10, 60 seconds), 10) { + override def init(implicit context: ActorContext): Unit = { + super.init(context) + HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(map.Scale, 80, 80) HotSpotTimeFunction = Zones.HotSpots.StandardTimeRules InitZoneAmenities(zone = this) @@ -201,12 +134,14 @@ object Zones { ), ( "home1", - new Zone("home1", Await.result(Maps.map11, 30 seconds), 11) { - override def Init(implicit context: ActorContext): Unit = { - super.Init(context) + new Zone("home1", Await.result(Maps.map11, 60 seconds), 11) { + override def init(implicit context: ActorContext): Unit = { + super.init(context) import net.psforever.types.PlanetSideEmpire - Buildings.values.foreach { _.Faction = PlanetSideEmpire.NC } + Buildings.values.foreach { + _.Faction = PlanetSideEmpire.NC + } InitZoneAmenities(zone = this) } @@ -214,12 +149,14 @@ object Zones { ), ( "home2", - new Zone("home2", Await.result(Maps.map12, 30 seconds), 12) { - override def Init(implicit context: ActorContext): Unit = { - super.Init(context) + new Zone("home2", Await.result(Maps.map12, 60 seconds), 12) { + override def init(implicit context: ActorContext): Unit = { + super.init(context) import net.psforever.types.PlanetSideEmpire - Buildings.values.foreach { _.Faction = PlanetSideEmpire.TR } + Buildings.values.foreach { + _.Faction = PlanetSideEmpire.TR + } InitZoneAmenities(zone = this) } @@ -227,12 +164,14 @@ object Zones { ), ( "home3", - new Zone("home3", Await.result(Maps.map13, 30 seconds), 13) { - override def Init(implicit context: ActorContext): Unit = { - super.Init(context) + new Zone("home3", Await.result(Maps.map13, 60 seconds), 13) { + override def init(implicit context: ActorContext): Unit = { + super.init(context) import net.psforever.types.PlanetSideEmpire - Buildings.values.foreach { _.Faction = PlanetSideEmpire.VS } + Buildings.values.foreach { + _.Faction = PlanetSideEmpire.VS + } InitZoneAmenities(zone = this) } @@ -276,10 +215,10 @@ object Zones { ), ( "c1", - new Zone("c1", Await.result(Maps.ugd01, 30 seconds), 23) { - override def Init(implicit context: ActorContext): Unit = { - super.Init(context) - HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(Map.Scale, 80, 80) + new Zone("c1", Await.result(Maps.ugd01, 60 seconds), 23) { + override def init(implicit context: ActorContext): Unit = { + super.init(context) + HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(map.Scale, 80, 80) HotSpotTimeFunction = Zones.HotSpots.StandardTimeRules InitZoneAmenities(zone = this) @@ -288,10 +227,10 @@ object Zones { ), ( "c2", - new Zone("c2", Await.result(Maps.ugd02, 30 seconds), 24) { - override def Init(implicit context: ActorContext): Unit = { - super.Init(context) - HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(Map.Scale, 80, 80) + new Zone("c2", Await.result(Maps.ugd02, 60 seconds), 24) { + override def init(implicit context: ActorContext): Unit = { + super.init(context) + HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(map.Scale, 80, 80) HotSpotTimeFunction = Zones.HotSpots.StandardTimeRules InitZoneAmenities(zone = this) @@ -300,10 +239,10 @@ object Zones { ), ( "c3", - new Zone("c3", Await.result(Maps.ugd03, 30 seconds), 25) { - override def Init(implicit context: ActorContext): Unit = { - super.Init(context) - HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(Map.Scale, 80, 80) + new Zone("c3", Await.result(Maps.ugd03, 60 seconds), 25) { + override def init(implicit context: ActorContext): Unit = { + super.init(context) + HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(map.Scale, 80, 80) HotSpotTimeFunction = Zones.HotSpots.StandardTimeRules InitZoneAmenities(zone = this) @@ -312,10 +251,10 @@ object Zones { ), ( "c4", - new Zone("c4", Await.result(Maps.ugd04, 30 seconds), 26) { - override def Init(implicit context: ActorContext): Unit = { - super.Init(context) - HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(Map.Scale, 80, 80) + new Zone("c4", Await.result(Maps.ugd04, 60 seconds), 26) { + override def init(implicit context: ActorContext): Unit = { + super.init(context) + HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(map.Scale, 80, 80) HotSpotTimeFunction = Zones.HotSpots.StandardTimeRules InitZoneAmenities(zone = this) @@ -324,10 +263,10 @@ object Zones { ), ( "c5", - new Zone("c5", Await.result(Maps.ugd05, 30 seconds), 27) { - override def Init(implicit context: ActorContext): Unit = { - super.Init(context) - HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(Map.Scale, 80, 80) + new Zone("c5", Await.result(Maps.ugd05, 60 seconds), 27) { + override def init(implicit context: ActorContext): Unit = { + super.init(context) + HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(map.Scale, 80, 80) HotSpotTimeFunction = Zones.HotSpots.StandardTimeRules InitZoneAmenities(zone = this) @@ -336,10 +275,10 @@ object Zones { ), ( "c6", - new Zone("c6", Await.result(Maps.ugd06, 30 seconds), 28) { - override def Init(implicit context: ActorContext): Unit = { - super.Init(context) - HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(Map.Scale, 80, 80) + new Zone("c6", Await.result(Maps.ugd06, 60 seconds), 28) { + override def init(implicit context: ActorContext): Unit = { + super.init(context) + HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(map.Scale, 80, 80) HotSpotTimeFunction = Zones.HotSpots.StandardTimeRules InitZoneAmenities(zone = this) @@ -348,10 +287,10 @@ object Zones { ), ( "i1", - new Zone("i1", Await.result(Maps.map99, 30 seconds), 29) { - override def Init(implicit context: ActorContext): Unit = { - super.Init(context) - HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(Map.Scale, 80, 80) + new Zone("i1", Await.result(Maps.map99, 60 seconds), 29) { + override def init(implicit context: ActorContext): Unit = { + super.init(context) + HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(map.Scale, 80, 80) HotSpotTimeFunction = Zones.HotSpots.StandardTimeRules InitZoneAmenities(zone = this) @@ -360,10 +299,10 @@ object Zones { ), ( "i2", - new Zone("i2", Await.result(Maps.map98, 30 seconds), 30) { - override def Init(implicit context: ActorContext): Unit = { - super.Init(context) - HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(Map.Scale, 80, 80) + new Zone("i2", Await.result(Maps.map98, 60 seconds), 30) { + override def init(implicit context: ActorContext): Unit = { + super.init(context) + HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(map.Scale, 80, 80) HotSpotTimeFunction = Zones.HotSpots.StandardTimeRules InitZoneAmenities(zone = this) @@ -372,10 +311,10 @@ object Zones { ), ( "i3", - new Zone("i3", Await.result(Maps.map97, 30 seconds), 31) { - override def Init(implicit context: ActorContext): Unit = { - super.Init(context) - HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(Map.Scale, 80, 80) + new Zone("i3", Await.result(Maps.map97, 60 seconds), 31) { + override def init(implicit context: ActorContext): Unit = { + super.init(context) + HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(map.Scale, 80, 80) HotSpotTimeFunction = Zones.HotSpots.StandardTimeRules InitZoneAmenities(zone = this) @@ -384,10 +323,10 @@ object Zones { ), ( "i4", - new Zone("i4", Await.result(Maps.map96, 30 seconds), 32) { - override def Init(implicit context: ActorContext): Unit = { - super.Init(context) - HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(Map.Scale, 80, 80) + new Zone("i4", Await.result(Maps.map96, 60 seconds), 32) { + override def init(implicit context: ActorContext): Unit = { + super.init(context) + HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(map.Scale, 80, 80) HotSpotTimeFunction = Zones.HotSpots.StandardTimeRules InitZoneAmenities(zone = this) @@ -560,9 +499,9 @@ object Zones { case t: ObjectSource if t.Definition == GlobalDefinitions.manned_turret => 60 seconds case _: DeployableSource => - 30 seconds + 60 seconds case _: ComplexDeployableSource => - 30 seconds + 60 seconds case _ => 0 seconds } diff --git a/common/src/main/scala/services/InterstellarClusterService.scala b/common/src/main/scala/services/InterstellarClusterService.scala new file mode 100644 index 00000000..805e498e --- /dev/null +++ b/common/src/main/scala/services/InterstellarClusterService.scala @@ -0,0 +1,218 @@ +package services + +import akka.actor.typed.receptionist.{Receptionist, ServiceKey} +import akka.actor.typed.scaladsl.{AbstractBehavior, ActorContext, Behaviors} +import akka.actor.typed.{ActorRef, Behavior, SupervisorStrategy} +import net.psforever.actors.zone.ZoneActor +import net.psforever.objects.{Avatar, Player, SpawnPoint, Vehicle} +import net.psforever.objects.serverobject.structures.Building +import net.psforever.objects.zones.Zone +import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID, SpawnGroup, Vector3} +import net.psforever.util.Config + +import scala.collection.mutable +import scala.util.Random + +object InterstellarClusterService { + val InterstellarClusterServiceKey: ServiceKey[Command] = + ServiceKey[InterstellarClusterService.Command]("interstellarCluster") + + def apply(zones: Iterable[Zone]): Behavior[Command] = + Behaviors + .supervise[Command] { + Behaviors.setup { context => + context.system.receptionist ! Receptionist.Register(InterstellarClusterServiceKey, context.self) + new InterstellarClusterService(context, zones) + } + } + .onFailure[Exception](SupervisorStrategy.restart) + + sealed trait Command + + final case class FindZoneActor(predicate: Zone => Boolean, replyTo: ActorRef[ZoneActorResponse]) extends Command + + final case class ZoneActorResponse(zoneActor: Option[ActorRef[ZoneActor.Command]]) + + final case class FindZone(predicate: Zone => Boolean, replyTo: ActorRef[ZoneResponse]) extends Command + + final case class ZoneResponse(zoneActor: Option[Zone]) + + final case class FilterZones(predicate: Zone => Boolean, replyTo: ActorRef[ZonesResponse]) extends Command + + final case class ZonesResponse(zoneActor: Iterable[Zone]) + + final case class GetInstantActionSpawnPoint(faction: PlanetSideEmpire.Value, replyTo: ActorRef[SpawnPointResponse]) + extends Command + + final case class GetSpawnPoint( + zoneNumber: Int, + player: Player, + target: PlanetSideGUID, + replyTo: ActorRef[SpawnPointResponse] + ) extends Command + + final case class GetNearbySpawnPoint( + zoneNumber: Int, + player: Player, + spawnGroups: Seq[SpawnGroup], + replyTo: ActorRef[SpawnPointResponse] + ) extends Command + + final case class GetRandomSpawnPoint( + zoneNumber: Int, + faction: PlanetSideEmpire.Value, + spawnGroups: Seq[SpawnGroup], + replyTo: ActorRef[SpawnPointResponse] + ) extends Command + + final case class SpawnPointResponse(response: Option[(Zone, SpawnPoint)]) + + final case class GetPlayers(replyTo: ActorRef[PlayersResponse]) extends Command + + final case class PlayersResponse(players: Seq[Avatar]) +} + +class InterstellarClusterService(context: ActorContext[InterstellarClusterService.Command], _zones: Iterable[Zone]) + extends AbstractBehavior[InterstellarClusterService.Command](context) { + + import InterstellarClusterService._ + + private[this] val log = org.log4s.getLogger + + val zoneActors: mutable.Map[String, (ActorRef[ZoneActor.Command], Zone)] = mutable.Map( + _zones.map { + case zone => + val zoneActor = context.spawn(ZoneActor(zone), s"zone-${zone.Id}") + (zone.Id, (zoneActor, zone)) + }.toSeq: _* + ) + + val zones = zoneActors.map { + case (id, (_, zone)) => zone + } + + override def onMessage(msg: Command): Behavior[Command] = { + log.info(s"$msg") + msg match { + case GetPlayers(replyTo) => + replyTo ! PlayersResponse(zones.map(_.Players).flatten.toSeq) + case FindZoneActor(predicate, replyTo) => + replyTo ! ZoneActorResponse( + zoneActors.collectFirst { + case (_, (actor, zone)) if predicate(zone) => actor + } + ) + + case FindZone(predicate, replyTo) => + replyTo ! ZoneResponse(zones.find(predicate)) + + case FilterZones(predicate, replyTo) => + replyTo ! ZonesResponse(zones.filter(predicate)) + + case GetInstantActionSpawnPoint(faction, replyTo) => + val res = zones + .filter(_.Players.nonEmpty) + .flatMap { zone => + zone.HotSpotData.collect { + case spot => (zone, spot) + } + } + .map { + case (zone, spot) => + ( + zone, + spot, + zone.findNearestSpawnPoints( + faction, + spot.DisplayLocation, + if (Config.app.game.instantActionAms) Seq(SpawnGroup.Tower, SpawnGroup.Facility, SpawnGroup.AMS) + else Seq(SpawnGroup.Tower, SpawnGroup.Facility) + ) + ) + } + .collect { + case (zone, info, Some(spawns)) => (zone, info, spawns) + } + .toList + .sortBy { case (_, spot, _) => spot.Activity.values.foldLeft(0)(_ + _.Heat) }( + Ordering[Int].reverse + ) // greatest > least + .sortWith { + case ((_, spot1, _), (_, spot2, _)) => + spot1.ActivityBy().contains(faction) // prefer own faction activity + } + .headOption + .flatMap { + case (zone, info, spawns) => + val pos = info.DisplayLocation + val spawnPoint = spawns.minBy(point => Vector3.DistanceSquared(point.Position, pos)) + //Some(zone, pos, spawnPoint) + Some(zone, spawnPoint) + case _ => None + } + replyTo ! SpawnPointResponse(res) + + case GetRandomSpawnPoint(zoneNumber, faction, spawnGroups, replyTo) => + val response = zones.find(_.Number == zoneNumber) match { + case Some(zone) => + /* + val location = math.abs(Random.nextInt() % 4) match { + case 0 => Vector3(sanctuary.map.Scale.width, sanctuary.map.Scale.height, 0) //NE + case 1 => Vector3(sanctuary.map.Scale.width, 0, 0) //SE + case 2 => Vector3.Zero //SW + case 3 => Vector3(0, sanctuary.map.Scale.height, 0) //NW + } + sanctuary.findNearestSpawnPoints( + faction, + location, + structures + ) */ + Random.shuffle(zone.findSpawns(faction, spawnGroups)).headOption match { + case Some((_, spawnPoints)) if spawnPoints.nonEmpty => + Some((zone, Random.shuffle(spawnPoints.toList).head)) + case None => + None + } + case None => + log.error(s"no zone $zoneNumber") + None + } + replyTo ! SpawnPointResponse(response) + + case GetSpawnPoint(zoneNumber, player, target, replyTo) => + zones.find(_.Number == zoneNumber) match { + case Some(zone) => + zone.findSpawns(player.Faction, SpawnGroup.values).find { + case (spawn: Building, spawnPoints) => + spawn.MapId == target.guid || spawnPoints.exists(_.GUID == target) + case (spawn: Vehicle, spawnPoints) => + spawn.GUID == target || spawnPoints.exists(_.GUID.guid == target.guid) + case _ => false + } match { + case Some((_, spawnPoints)) => + replyTo ! SpawnPointResponse(Some(zone, Random.shuffle(spawnPoints.toList).head)) + case _ => + replyTo ! SpawnPointResponse(None) + } + case None => + replyTo ! SpawnPointResponse(None) + } + + case GetNearbySpawnPoint(zoneNumber, player, spawnGroups, replyTo) => + zones.find(_.Number == zoneNumber) match { + case Some(zone) => + zone.findNearestSpawnPoints(player.Faction, player.Position, spawnGroups) match { + case None | Some(Nil) => + replyTo ! SpawnPointResponse(None) + case Some(spawnPoints) => + replyTo ! SpawnPointResponse(Some(zone, scala.util.Random.shuffle(spawnPoints).head)) + } + case None => + replyTo ! SpawnPointResponse(None) + } + } + + this + } + +} diff --git a/common/src/main/scala/services/ServiceManager.scala b/common/src/main/scala/services/ServiceManager.scala index 5a14c4cd..b82b3994 100644 --- a/common/src/main/scala/services/ServiceManager.scala +++ b/common/src/main/scala/services/ServiceManager.scala @@ -2,20 +2,31 @@ package services import akka.actor.{Actor, ActorIdentity, ActorRef, ActorSystem, Identify, Props} +import akka.actor.typed.scaladsl.adapter._ +import akka.actor.typed +import akka.actor.typed.receptionist.Receptionist import scala.collection.mutable object ServiceManager { var serviceManager = ActorRef.noSender + var receptionist: typed.ActorRef[Receptionist.Command] = null + def boot(implicit system: ActorSystem) = { serviceManager = system.actorOf(Props[ServiceManager], "service") + receptionist = system.toTyped.receptionist serviceManager } case class Register(props: Props, name: String) + case class Lookup(name: String) + + case class LookupFromTyped(name: String, replyTo: typed.ActorRef[LookupResult]) + case class LookupResult(request: String, endpoint: ActorRef) + } class ServiceManager extends Actor { @@ -38,6 +49,11 @@ class ServiceManager extends Actor { lookups += nextLookupId -> RequestEntry(name, sender()) nextLookupId += 1 + case LookupFromTyped(name, replyTo) => + context.actorSelection(name) ! Identify(nextLookupId) + lookups += nextLookupId -> RequestEntry(name, replyTo.toClassic) + nextLookupId += 1 + case ActorIdentity(id, Some(ref)) => val idNumber = id.asInstanceOf[Long] lookups.get(idNumber) match { diff --git a/common/src/main/scala/services/local/LocalService.scala b/common/src/main/scala/services/local/LocalService.scala index 91e24f52..40ae7421 100644 --- a/common/src/main/scala/services/local/LocalService.scala +++ b/common/src/main/scala/services/local/LocalService.scala @@ -2,6 +2,7 @@ package services.local import akka.actor.{Actor, ActorRef, Props} +import net.psforever.actors.zone.{BuildingActor, ZoneActor} import net.psforever.objects.ce.Deployable import net.psforever.objects.serverobject.structures.{Amenity, Building} import net.psforever.objects.serverobject.terminals.{CaptureTerminal, Terminal} @@ -131,7 +132,7 @@ class LocalService(zone: Zone) extends Actor { // If the owner of this capture terminal is on the lattice trigger a zone wide map update to update lattice benefits zone.Lattice find building match { - case Some(_) => building.TriggerZoneMapUpdate() + case Some(_) => building.Zone.actor ! ZoneActor.ZoneMapUpdate() case None => ; } case LocalAction.RouterTelepadTransport(player_guid, passenger_guid, src_guid, dest_guid) => @@ -247,9 +248,7 @@ class LocalService(zone: Zone) extends Actor { if (building.NtuLevel > 0) { log.info(s"Setting base ${building.GUID} / MapId: ${building.MapId} as owned by $hackedByFaction") - - building.Faction = hackedByFaction - self ! LocalServiceMessage(zone.Id, LocalAction.SetEmpire(building.GUID, hackedByFaction)) + building.Actor ! BuildingActor.SetFaction(hackedByFaction) } else { log.info("Base hack completed, but base was out of NTU.") } diff --git a/common/src/main/scala/services/local/support/HackCaptureActor.scala b/common/src/main/scala/services/local/support/HackCaptureActor.scala index 90a06f1f..4faa0409 100644 --- a/common/src/main/scala/services/local/support/HackCaptureActor.scala +++ b/common/src/main/scala/services/local/support/HackCaptureActor.scala @@ -1,6 +1,7 @@ package services.local.support import akka.actor.{Actor, Cancellable} +import net.psforever.actors.zone.ZoneActor import net.psforever.objects.Default import net.psforever.objects.serverobject.hackable.Hackable import net.psforever.objects.serverobject.structures.Building @@ -44,7 +45,9 @@ class HackCaptureActor extends Actor { // Restart the timer, in case this is the first object in the hacked objects list or the object was removed and re-added RestartTimer() - if (target.isInstanceOf[CaptureTerminal]) { target.Owner.asInstanceOf[Building].TriggerZoneMapUpdate() } + if (target.isInstanceOf[CaptureTerminal]) { + target.Owner.asInstanceOf[Building].Zone.actor ! ZoneActor.ZoneMapUpdate() + } case HackCaptureActor.ProcessCompleteHacks() => log.trace("Processing complete hacks") @@ -74,7 +77,9 @@ class HackCaptureActor extends Actor { case HackCaptureActor.ClearHack(target, _) => hackedObjects = hackedObjects.filterNot(x => x.target == target) - if (target.isInstanceOf[CaptureTerminal]) { target.Owner.asInstanceOf[Building].TriggerZoneMapUpdate() } + if (target.isInstanceOf[CaptureTerminal]) { + target.Owner.asInstanceOf[Building].Zone.actor ! ZoneActor.ZoneMapUpdate() + } // Restart the timer in case the object we just removed was the next one scheduled RestartTimer() diff --git a/common/src/main/scala/services/properties/PropertyOverrideManager.scala b/common/src/main/scala/services/properties/PropertyOverrideManager.scala index fcd4c43f..d2be223e 100644 --- a/common/src/main/scala/services/properties/PropertyOverrideManager.scala +++ b/common/src/main/scala/services/properties/PropertyOverrideManager.scala @@ -1,55 +1,29 @@ package services.properties -import akka.actor.{Actor, ActorRef, Stash} -import net.psforever.objects.zones.InterstellarCluster +import akka.actor.Actor import net.psforever.packet.game.{GamePropertyTarget, PropertyOverrideMessage} import net.psforever.packet.game.PropertyOverrideMessage.GamePropertyScope import net.psforever.packet.game.objectcreate.ObjectClass -import services.ServiceManager -import services.ServiceManager.Lookup - +import net.psforever.zones.Zones import scala.collection.mutable.ListBuffer -class PropertyOverrideManager extends Actor with Stash { +class PropertyOverrideManager extends Actor { private[this] val log = org.log4s.getLogger("PropertyOverrideManager") private var overrides: Map[Int, Map[String, List[(String, String)]]] = Map() private var gamePropertyScopes: List[PropertyOverrideMessage.GamePropertyScope] = List() - private var interstellarCluster: ActorRef = Actor.noSender - private var zoneIds: List[Int] = List() + lazy private val zoneIds: Iterable[Int] = Zones.zones.values.map(_.Number) override def preStart = { - log.info(s"Starting PropertyOverrideManager") - ServiceManager.serviceManager ! Lookup("cluster") + LoadOverridesFromFile(zoneId = 0) // Global overrides + for (zoneId <- zoneIds) { + LoadOverridesFromFile(zoneId) + } + + ProcessGamePropertyScopes() } - override def receive = ServiceLookup - - def ServiceLookup: Receive = { - case ServiceManager.LookupResult("cluster", endpoint) => - interstellarCluster = endpoint - - if (interstellarCluster != ActorRef.noSender) { - interstellarCluster ! InterstellarCluster.GetZoneIds() - } - - case InterstellarCluster.ZoneIds(zoneIds) => - this.zoneIds = zoneIds - - unstashAll() - LoadOverridesFromFile(zoneId = 0) // Global overrides - for (zoneId <- zoneIds) { - LoadOverridesFromFile(zoneId) - } - - ProcessGamePropertyScopes() - - context.become(ReceiveCommand) - - case _ => stash() - } - - def ReceiveCommand: Receive = { + override def receive: Receive = { case PropertyOverrideManager.GetOverridesMessage => { sender ! gamePropertyScopes } diff --git a/common/src/test/scala/objects/BuildingTest.scala b/common/src/test/scala/objects/BuildingTest.scala index cbf4274a..6e65b948 100644 --- a/common/src/test/scala/objects/BuildingTest.scala +++ b/common/src/test/scala/objects/BuildingTest.scala @@ -1,19 +1,15 @@ // Copyright (c) 2017 PSForever package objects -import akka.actor.Props import base.ActorTest +import net.psforever.actors.zone.BuildingActor import net.psforever.objects.{Default, GlobalDefinitions} -import net.psforever.objects.serverobject.affinity.FactionAffinity -import net.psforever.objects.serverobject.doors.{Door, DoorControl} +import net.psforever.objects.serverobject.doors.Door import net.psforever.objects.serverobject.structures._ import net.psforever.objects.zones.Zone -import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID} +import net.psforever.types.PlanetSideEmpire import org.specs2.mutable.Specification -import services.ServiceManager -import services.galaxy.GalaxyService - -import scala.concurrent.duration._ +import akka.actor.typed.scaladsl.adapter._ class AmenityTest extends Specification { val definition = new AmenityDefinition(0) { @@ -115,82 +111,12 @@ class WarpGateTest extends Specification { } } -class BuildingControl1Test extends ActorTest { +class BuildingActor1Test extends ActorTest { "Building Control" should { "construct" in { val bldg = Building("Building", 0, 10, Zone.Nowhere, StructureType.Building) - bldg.Actor = system.actorOf(Props(classOf[BuildingControl], bldg), "test") + bldg.Actor = system.spawn(BuildingActor(Zone.Nowhere, bldg), "test").toClassic assert(bldg.Actor != Default.Actor) } } } - -class BuildingControl2Test extends ActorTest { - ServiceManager.boot(system) ! ServiceManager.Register(Props[GalaxyService], "galaxy") - val bldg = Building("Building", 0, 10, Zone.Nowhere, StructureType.Building) - bldg.Faction = PlanetSideEmpire.TR - bldg.Actor = system.actorOf(Props(classOf[BuildingControl], bldg), "test") - bldg.Actor ! "startup" - - "Building Control" should { - "convert and assert faction affinity on convert request" in { - expectNoMessage(500 milliseconds) - - assert(bldg.Faction == PlanetSideEmpire.TR) - bldg.Actor ! FactionAffinity.ConvertFactionAffinity(PlanetSideEmpire.VS) - val reply = receiveOne(500 milliseconds) - assert(reply.isInstanceOf[FactionAffinity.AssertFactionAffinity]) - assert(reply.asInstanceOf[FactionAffinity.AssertFactionAffinity].obj == bldg) - assert(reply.asInstanceOf[FactionAffinity.AssertFactionAffinity].faction == PlanetSideEmpire.VS) - assert(bldg.Faction == PlanetSideEmpire.VS) - } - } -} - -class BuildingControl3Test extends ActorTest { - ServiceManager.boot(system) ! ServiceManager.Register(Props[GalaxyService], "galaxy") - val bldg = Building("Building", 0, 10, Zone.Nowhere, StructureType.Building) - bldg.Faction = PlanetSideEmpire.TR - bldg.Actor = system.actorOf(Props(classOf[BuildingControl], bldg), "test") - val door1 = Door(GlobalDefinitions.door) - door1.GUID = PlanetSideGUID(1) - door1.Actor = system.actorOf(Props(classOf[DoorControl], door1), "door1-test") - val door2 = Door(GlobalDefinitions.door) - door2.GUID = PlanetSideGUID(2) - door2.Actor = system.actorOf(Props(classOf[DoorControl], door2), "door2-test") - bldg.Amenities = door2 - bldg.Amenities = door1 - bldg.Actor ! "startup" - - "Building Control" should { - "convert and assert faction affinity on convert request, and for each of its amenities" in { - expectNoMessage(500 milliseconds) - - assert(bldg.Faction == PlanetSideEmpire.TR) - assert(bldg.Amenities.length == 2) - assert(bldg.Amenities.head == door2) - assert(bldg.Amenities(1) == door1) - - bldg.Actor ! FactionAffinity.ConvertFactionAffinity(PlanetSideEmpire.VS) - val reply = ActorTest.receiveMultiple(3, 500 milliseconds, this) - //val reply = receiveN(3, Duration.create(5000, "ms")) - assert(reply.length == 3) - var building_count = 0 - var door_count = 0 - reply.foreach(item => { - assert(item.isInstanceOf[FactionAffinity.AssertFactionAffinity]) - val item2 = item.asInstanceOf[FactionAffinity.AssertFactionAffinity] - item2.obj match { - case _: Building => - building_count += 1 - case _: Door => - door_count += 1 - case _ => - assert(false) - } - assert(item2.faction == PlanetSideEmpire.VS) - }) - assert(building_count == 1 && door_count == 2) - } - } -} diff --git a/common/src/test/scala/objects/ResourceSiloTest.scala b/common/src/test/scala/objects/ResourceSiloTest.scala index 76134f01..37a54ecc 100644 --- a/common/src/test/scala/objects/ResourceSiloTest.scala +++ b/common/src/test/scala/objects/ResourceSiloTest.scala @@ -5,6 +5,7 @@ import akka.actor.{Actor, Props} import akka.routing.RandomPool import akka.testkit.TestProbe import base.ActorTest +import net.psforever.actors.zone.{BuildingActor, ZoneActor} import net.psforever.objects.guid.{NumberPoolHub, TaskResolver} import net.psforever.objects.guid.source.LimitedNumberSource import net.psforever.objects.serverobject.CommonMessages @@ -12,12 +13,13 @@ import net.psforever.objects.{Avatar, GlobalDefinitions, Ntu, Player, Vehicle} import net.psforever.objects.serverobject.resourcesilo.{ResourceSilo, ResourceSiloControl, ResourceSiloDefinition} import net.psforever.objects.serverobject.structures.{Building, StructureType} import net.psforever.objects.serverobject.transfer.TransferBehavior -import net.psforever.objects.zones.{Zone, ZoneActor, ZoneMap} +import net.psforever.objects.zones.{Zone, ZoneMap} import net.psforever.packet.game.UseItemMessage import net.psforever.types._ import org.specs2.mutable.Specification import services.ServiceManager import services.avatar.{AvatarAction, AvatarServiceMessage} +import akka.actor.typed.scaladsl.adapter._ import scala.concurrent.duration._ @@ -97,8 +99,7 @@ class ResourceSiloControlUseTest extends ActorTest { override def SetupNumberPools() = {} GUID(guid) } - zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), "test-zone-actor") - zone.Actor ! Zone.Init() + zone.actor = system.spawnAnonymous(ZoneActor(zone)) val building = new Building( "Building", building_guid = 0, @@ -117,7 +118,7 @@ class ResourceSiloControlUseTest extends ActorTest { new Avatar(0L, "TestCharacter", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute) ) //guid=3 val vehicle = Vehicle(GlobalDefinitions.ant) //guid=4 - val probe = new TestProbe(system) + val probe = new TestProbe(system) guid.register(building, 1) guid.register(obj, 2) @@ -139,7 +140,7 @@ class ResourceSiloControlUseTest extends ActorTest { val reply = probe.receiveOne(2000 milliseconds) assert(reply match { case TransferBehavior.Discharging(Ntu.Nanites) => true - case _ => false + case _ => false }) } } @@ -221,21 +222,21 @@ class ResourceSiloControlUpdate1Test extends ActorTest { assert(obj.CapacitorDisplay == 4) assert(reply1 match { case AvatarServiceMessage("nowhere", AvatarAction.PlanetsideAttribute(PlanetSideGUID(1), 45, 4)) => true - case _ => false + case _ => false }) - assert(reply2.isInstanceOf[Building.SendMapUpdate]) + assert(reply2.isInstanceOf[BuildingActor.MapUpdate]) val reply3 = zoneEvents.receiveOne(500 milliseconds) assert(!obj.LowNtuWarningOn) assert(reply3 match { case AvatarServiceMessage("nowhere", AvatarAction.PlanetsideAttribute(PlanetSideGUID(6), 47, 0)) => true - case _ => false + case _ => false }) val reply4 = zoneEvents.receiveOne(500 milliseconds) assert(reply4 match { case AvatarServiceMessage("nowhere", AvatarAction.PlanetsideAttribute(PlanetSideGUID(6), 48, 0)) => true - case _ => false + case _ => false }) } } @@ -295,7 +296,7 @@ class ResourceSiloControlUpdate2Test extends ActorTest { .attribute_value == 3 ) - assert(reply2.isInstanceOf[Building.SendMapUpdate]) + assert(reply2.isInstanceOf[BuildingActor.MapUpdate]) val reply3 = zoneEvents.receiveOne(500 milliseconds) assert(!obj.LowNtuWarningOn) @@ -355,7 +356,9 @@ class ResourceSiloControlNoUpdateTest extends ActorTest { expectNoMessage(500 milliseconds) zoneEvents.expectNoMessage(500 milliseconds) buildingEvents.expectNoMessage(500 milliseconds) - assert(obj.NtuCapacitor == 299 || obj.NtuCapacitor == 300) // Just in case the capacitor level drops while waiting for the message check 299 & 300 + assert( + obj.NtuCapacitor == 299 || obj.NtuCapacitor == 300 + ) // Just in case the capacitor level drops while waiting for the message check 299 & 300 assert(obj.CapacitorDisplay == 3) assert(!obj.LowNtuWarningOn) } diff --git a/common/src/test/scala/objects/VehicleTest.scala b/common/src/test/scala/objects/VehicleTest.scala index 689f6c7a..0755fea2 100644 --- a/common/src/test/scala/objects/VehicleTest.scala +++ b/common/src/test/scala/objects/VehicleTest.scala @@ -11,7 +11,7 @@ import net.psforever.objects.guid.source.LimitedNumberSource import net.psforever.objects.serverobject.mount.Mountable import net.psforever.objects.vehicles._ import net.psforever.objects.vital.VehicleShieldCharge -import net.psforever.objects.zones.{Zone, ZoneActor, ZoneMap} +import net.psforever.objects.zones.{Zone, ZoneMap} import net.psforever.packet.game.{CargoMountPointStatusMessage, ObjectDetachMessage, PlanetsideAttributeMessage} import net.psforever.types.{PlanetSideGUID, _} import org.specs2.mutable._ @@ -19,8 +19,11 @@ import services.{RemoverActor, ServiceManager} import services.vehicle.{VehicleAction, VehicleServiceMessage} import scala.concurrent.duration._ +import akka.actor.typed.scaladsl.adapter._ +import net.psforever.actors.zone.ZoneActor class VehicleTest extends Specification { + import VehicleTest._ "SeatDefinition" should { @@ -408,10 +411,12 @@ class VehicleControlPrepareForDeletionMountedInTest extends FreedContextActorTes val guid = new NumberPoolHub(new LimitedNumberSource(10)) val zone = new Zone("test", new ZoneMap("test"), 0) { GUID(guid) + override def SetupNumberPools(): Unit = {} } - zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), "test-zone-actor") - zone.Init(context) + zone.actor = system.spawn(ZoneActor(zone), "test-zone-actor") + // crappy workaround but without it the zone doesn't get initialized in time + expectNoMessage(400 milliseconds) val vehicle = Vehicle(GlobalDefinitions.two_man_assault_buggy) vehicle.Faction = PlanetSideEmpire.TR @@ -533,10 +538,12 @@ class VehicleControlPrepareForDeletionMountedCargoTest extends FreedContextActor ServiceManager.boot val zone = new Zone("test", new ZoneMap("test"), 0) { GUID(guid) + override def SetupNumberPools(): Unit = {} } - zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), "test-zone-actor") - zone.Init(context) + zone.actor = system.spawn(ZoneActor(zone), "test-zone-actor") + // crappy workaround but without it the zone doesn't get initialized in time + expectNoMessage(200 milliseconds) val vehicle = Vehicle(GlobalDefinitions.two_man_assault_buggy) vehicle.Faction = PlanetSideEmpire.TR diff --git a/common/src/test/scala/objects/ZoneTest.scala b/common/src/test/scala/objects/ZoneTest.scala index 92694351..bbfdbba7 100644 --- a/common/src/test/scala/objects/ZoneTest.scala +++ b/common/src/test/scala/objects/ZoneTest.scala @@ -3,7 +3,7 @@ package objects import java.util.concurrent.atomic.AtomicInteger -import akka.actor.{ActorContext, ActorRef, Props} +import akka.actor.ActorContext import base.ActorTest import net.psforever.objects.entity.IdentifiableEntity import net.psforever.objects.equipment.Equipment @@ -14,14 +14,18 @@ import net.psforever.objects.serverobject.tube.SpawnTube import net.psforever.objects._ import net.psforever.types._ import net.psforever.objects.serverobject.structures.{Building, FoundationBuilder, StructureType} -import net.psforever.objects.zones.{Zone, ZoneActor, ZoneMap} +import net.psforever.objects.zones.{Zone, ZoneMap} import net.psforever.objects.Vehicle import org.specs2.mutable.Specification +import akka.actor.typed.scaladsl.adapter._ +import net.psforever.actors.zone.ZoneActor import scala.concurrent.duration._ class ZoneTest extends Specification { - def test(a: String, b: Int, c: Int, d: Zone, e: ActorContext): Building = { Building.NoBuilding } + def test(a: String, b: Int, c: Int, d: Zone, e: ActorContext): Building = { + Building.NoBuilding + } "ZoneMap" should { "construct" in { @@ -116,29 +120,26 @@ class ZoneTest extends Specification { class ZoneActorTest extends ActorTest { "Zone" should { - "have an Actor" in { - val zone = new Zone("test", new ZoneMap("map6"), 1) { override def SetupNumberPools() = {} } - zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), "test-actor") - expectNoMessage(Duration.create(100, "ms")) - assert(zone.Actor != ActorRef.noSender) - } - "create new number pools before the Actor is started" in { - val zone = new Zone("test", new ZoneMap("map6"), 1) { override def SetupNumberPools() = {} } + val zone = new Zone("test", new ZoneMap("map6"), 1) { + override def SetupNumberPools() = {} + } zone.GUID(new NumberPoolHub(new LimitedNumberSource(10))) assert(zone.AddPool("test1", 1 to 2)) - zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), "test-add-pool-actor") //note: not Init'd yet + zone.actor = system.spawn(ZoneActor(zone), "test-add-pool-actor") //note: not Init'd yet assert(zone.AddPool("test2", 3 to 4)) } "remove existing number pools before the Actor is started" in { - val zone = new Zone("test", new ZoneMap("map6"), 1) { override def SetupNumberPools() = {} } + val zone = new Zone("test", new ZoneMap("map6"), 1) { + override def SetupNumberPools() = {} + } zone.GUID(new NumberPoolHub(new LimitedNumberSource(10))) assert(zone.AddPool("test1", 1 to 2)) assert(zone.RemovePool("test1")) - zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), "test-remove-pool-actor") //note: not Init'd yet + zone.actor = system.spawn(ZoneActor(zone), "test-remove-pool-actor") //note: not Init'd yet assert(zone.AddPool("test2", 3 to 4)) assert(zone.RemovePool("test2")) } @@ -146,8 +147,7 @@ class ZoneActorTest extends ActorTest { "refuse new number pools after the Actor is started" in { val zone = new Zone("test", new ZoneMap("map6"), 1) { override def SetupNumberPools() = {} } zone.GUID(new NumberPoolHub(new LimitedNumberSource(40150))) - zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), "test-add-pool-actor-init") - zone.Actor ! Zone.Init() + zone.actor = system.spawn(ZoneActor(zone), "test-add-pool-actor-init") expectNoMessage(Duration.create(500, "ms")) assert(!zone.AddPool("test1", 1 to 2)) @@ -158,8 +158,7 @@ class ZoneActorTest extends ActorTest { zone.GUID(new NumberPoolHub(new LimitedNumberSource(10))) zone.AddPool("test", 1 to 2) - zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), "test-remove-pool-actor-init") - zone.Actor ! Zone.Init() + zone.actor = system.spawn(ZoneActor(zone), "test-remove-pool-actor-init") expectNoMessage(Duration.create(300, "ms")) assert(!zone.RemovePool("test")) @@ -203,8 +202,7 @@ class ZoneActorTest extends ActorTest { ObjectToBuilding(10, 7) } val zone = new Zone("test", map6, 1) { override def SetupNumberPools() = {} } - zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), "test-init") - zone.Actor ! Zone.Init() + zone.actor = system.spawn(ZoneActor(zone), "test-init") expectNoMessage(Duration.create(1, "seconds")) val groups = zone.SpawnGroups() @@ -230,80 +228,6 @@ class ZoneActorTest extends ActorTest { } }) } - - "select spawn points based on the position of the player in reference to buildings" in { - val map6 = new ZoneMap("map6") { - LocalBuilding( - "Building", - building_guid = 1, - map_id = 1, - FoundationBuilder(Building.Structure(StructureType.Building, Vector3(1, 1, 1))) - ) - LocalObject(2, SpawnTube.Constructor(Vector3(1, 0, 0), Vector3.Zero)) - ObjectToBuilding(2, 1) - - LocalBuilding( - "Building", - building_guid = 3, - map_id = 3, - FoundationBuilder(Building.Structure(StructureType.Building, Vector3(4, 4, 4))) - ) - LocalObject(4, SpawnTube.Constructor(Vector3(1, 0, 0), Vector3.Zero)) - ObjectToBuilding(4, 3) - } - val zone = new Zone("test", map6, 1) { override def SetupNumberPools() = {} } - zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), "test-spawn") - zone.Actor ! Zone.Init() - expectNoMessage(Duration.create(1, "seconds")) - val player = Player(Avatar("Chord", PlanetSideEmpire.NEUTRAL, CharacterGender.Male, 0, CharacterVoice.Voice5)) - - val bldg1 = zone.Building(1).get - val bldg3 = zone.Building(3).get - player.Position = Vector3(1, 1, 1) //closer to bldg1 - zone.Actor ! Zone.Lattice.RequestSpawnPoint(1, player, 7) - val reply1 = receiveOne(Duration.create(200, "ms")) - assert(reply1.isInstanceOf[Zone.Lattice.SpawnPoint]) - assert(reply1.asInstanceOf[Zone.Lattice.SpawnPoint].zone_id == "test") - assert(reply1.asInstanceOf[Zone.Lattice.SpawnPoint].spawn_point.Owner == bldg1) - - player.Position = Vector3(3, 3, 3) //closer to bldg3 - zone.Actor ! Zone.Lattice.RequestSpawnPoint(1, player, 7) - val reply3 = receiveOne(Duration.create(200, "ms")) - assert(reply3.isInstanceOf[Zone.Lattice.SpawnPoint]) - assert(reply3.asInstanceOf[Zone.Lattice.SpawnPoint].zone_id == "test") - assert(reply3.asInstanceOf[Zone.Lattice.SpawnPoint].spawn_point.Owner == bldg3) - } - - "will report if no spawn points have been found in a zone" in { - val map6 = new ZoneMap("map6") { - LocalBuilding( - "Building", - building_guid = 1, - map_id = 1, - FoundationBuilder(Building.Structure(StructureType.Building, Vector3(1, 1, 1))) - ) - - LocalBuilding( - "Building", - building_guid = 3, - map_id = 3, - FoundationBuilder(Building.Structure(StructureType.Tower, Vector3(4, 4, 4))) - ) - LocalObject(5, SpawnTube.Constructor(Vector3.Zero, Vector3.Zero)) - ObjectToBuilding(5, 3) - } - val zone = new Zone("test", map6, 1) { override def SetupNumberPools() = {} } - zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), "test-no-spawn") - zone.Actor ! Zone.Init() - expectNoMessage(Duration.create(300, "ms")) - val player = Player(Avatar("Chord", PlanetSideEmpire.NEUTRAL, CharacterGender.Male, 0, CharacterVoice.Voice5)) - - zone.Actor ! Zone.Lattice.RequestSpawnPoint(1, player, 7) - val reply = receiveOne(Duration.create(200, "ms")) - assert(reply.isInstanceOf[Zone.Lattice.NoValidSpawnPoint]) - assert(reply.asInstanceOf[Zone.Lattice.NoValidSpawnPoint].zone_number == 1) - assert(reply.asInstanceOf[Zone.Lattice.NoValidSpawnPoint].spawn_group.contains(7)) - } } } @@ -312,8 +236,7 @@ class ZonePopulationTest extends ActorTest { "add new user to zones" in { val zone = new Zone("test", new ZoneMap(""), 0) { override def SetupNumberPools() = {} } val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5) - zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), ZoneTest.TestName) - zone.Actor ! Zone.Init() + zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName) expectNoMessage(200 milliseconds) assert(zone.Players.isEmpty) @@ -328,8 +251,7 @@ class ZonePopulationTest extends ActorTest { "remove user from zones" in { val zone = new Zone("test", new ZoneMap(""), 0) { override def SetupNumberPools() = {} } val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5) - zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), ZoneTest.TestName) - zone.Actor ! Zone.Init() + zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName) receiveOne(Duration.create(200, "ms")) //consume zone.Population ! Zone.Population.Join(avatar) expectNoMessage(Duration.create(100, "ms")) @@ -345,8 +267,7 @@ class ZonePopulationTest extends ActorTest { val zone = new Zone("test", new ZoneMap(""), 0) { override def SetupNumberPools() = {} } val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5) val player = Player(avatar) - zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), ZoneTest.TestName) - zone.Actor ! Zone.Init() + zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName) expectNoMessage(200 milliseconds) zone.Population ! Zone.Population.Join(avatar) expectNoMessage(Duration.create(100, "ms")) @@ -366,8 +287,7 @@ class ZonePopulationTest extends ActorTest { val zone = new Zone("test", new ZoneMap(""), 0) { override def SetupNumberPools() = {} } val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5) val player = Player(avatar) - zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), ZoneTest.TestName) - zone.Actor ! Zone.Init() + zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName) expectNoMessage(200 milliseconds) zone.Population ! Zone.Population.Join(avatar) expectNoMessage(Duration.create(100, "ms")) @@ -390,8 +310,7 @@ class ZonePopulationTest extends ActorTest { val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5) val player = Player(avatar) player.GUID = PlanetSideGUID(1) - zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), ZoneTest.TestName) - zone.Actor ! Zone.Init() + zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName) expectNoMessage(200 milliseconds) zone.Population ! Zone.Population.Join(avatar) expectNoMessage(Duration.create(100, "ms")) @@ -416,8 +335,7 @@ class ZonePopulationTest extends ActorTest { val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5) val player1 = Player(avatar) val player2 = Player(avatar) - zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), ZoneTest.TestName) - zone.Actor ! Zone.Init() + zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName) expectNoMessage(200 milliseconds) zone.Population ! Zone.Population.Join(avatar) expectNoMessage(Duration.create(100, "ms")) @@ -442,8 +360,7 @@ class ZonePopulationTest extends ActorTest { val zone = new Zone("test", new ZoneMap(""), 0) { override def SetupNumberPools() = {} } val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5) val player = Player(avatar) - zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), ZoneTest.TestName) - zone.Actor ! Zone.Init() + zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName) expectNoMessage(200 milliseconds) assert(zone.Players.isEmpty) @@ -460,8 +377,7 @@ class ZonePopulationTest extends ActorTest { "user tries to Release a character, but did not Spawn a character first" in { val zone = new Zone("test", new ZoneMap(""), 0) { override def SetupNumberPools() = {} } val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5) - zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), ZoneTest.TestName) - zone.Actor ! Zone.Init() + zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName) expectNoMessage(200 milliseconds) zone.Population ! Zone.Population.Join(avatar) expectNoMessage(Duration.create(100, "ms")) @@ -483,8 +399,7 @@ class ZonePopulationTest extends ActorTest { val zone = new Zone("test", new ZoneMap(""), 0) { override def SetupNumberPools() = {} } val player = Player(Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)) player.Release - zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), ZoneTest.TestName) - zone.Actor ! Zone.Init() + zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName) expectNoMessage(200 milliseconds) assert(zone.Corpses.isEmpty) @@ -498,8 +413,7 @@ class ZonePopulationTest extends ActorTest { val zone = new Zone("test", new ZoneMap(""), 0) { override def SetupNumberPools() = {} } val player = Player(Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)) player.Release - zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), ZoneTest.TestName) - zone.Actor ! Zone.Init() + zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName) expectNoMessage(200 milliseconds) zone.Population ! Zone.Corpse.Add(player) expectNoMessage(Duration.create(500, "ms")) @@ -519,8 +433,7 @@ class ZonePopulationTest extends ActorTest { player2.Release val player3 = Player(Avatar("Chord3", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)) player3.Release - zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), ZoneTest.TestName) - zone.Actor ! Zone.Init() + zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName) expectNoMessage(200 milliseconds) zone.Population ! Zone.Corpse.Add(player1) zone.Population ! Zone.Corpse.Add(player2) @@ -542,8 +455,7 @@ class ZonePopulationTest extends ActorTest { val zone = new Zone("test", new ZoneMap(""), 0) { override def SetupNumberPools() = {} } val player = Player(Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)) //player.Release !!important - zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), ZoneTest.TestName) - zone.Actor ! Zone.Init() + zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName) expectNoMessage(200 milliseconds) assert(zone.Corpses.isEmpty) @@ -560,8 +472,7 @@ class ZoneGroundDropItemTest extends ActorTest { hub.register(item, 10) val zone = new Zone("test", new ZoneMap("test-map"), 0) { override def SetupNumberPools() = {} } zone.GUID(hub) - zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), ZoneTest.TestName) - zone.Actor ! Zone.Init() + zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName) expectNoMessage(200 milliseconds) "DropItem" should { @@ -586,8 +497,7 @@ class ZoneGroundCanNotDropItem1Test extends ActorTest { //hub.register(item, 10) //!important val zone = new Zone("test", new ZoneMap("test-map"), 0) { override def SetupNumberPools() = {} } zone.GUID(hub) - zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), ZoneTest.TestName) - zone.Actor ! Zone.Init() + zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName) expectNoMessage(200 milliseconds) "DropItem" should { @@ -612,8 +522,7 @@ class ZoneGroundCanNotDropItem2Test extends ActorTest { hub.register(item, 10) //!important val zone = new Zone("test", new ZoneMap("test-map"), 0) { override def SetupNumberPools() = {} } //zone.GUID(hub) //!important - zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), ZoneTest.TestName) - zone.Actor ! Zone.Init() + zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName) expectNoMessage(200 milliseconds) "DropItem" should { @@ -638,8 +547,7 @@ class ZoneGroundCanNotDropItem3Test extends ActorTest { hub.register(item, 10) //!important val zone = new Zone("test", new ZoneMap("test-map"), 0) { override def SetupNumberPools() = {} } zone.GUID(hub) //!important - zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), ZoneTest.TestName) - zone.Actor ! Zone.Init() + zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName) expectNoMessage(200 milliseconds) "DropItem" should { @@ -672,8 +580,7 @@ class ZoneGroundPickupItemTest extends ActorTest { hub.register(item, 10) val zone = new Zone("test", new ZoneMap("test-map"), 0) { override def SetupNumberPools() = {} } zone.GUID(hub) - zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), ZoneTest.TestName) - zone.Actor ! Zone.Init() + zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName) expectNoMessage(200 milliseconds) "PickupItem" should { @@ -701,8 +608,7 @@ class ZoneGroundCanNotPickupItemTest extends ActorTest { hub.register(item, 10) val zone = new Zone("test", new ZoneMap("test-map"), 0) { override def SetupNumberPools() = {} } zone.GUID(hub) //still registered to this zone - zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), ZoneTest.TestName) - zone.Actor ! Zone.Init() + zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName) expectNoMessage(200 milliseconds) "PickupItem" should { @@ -726,8 +632,7 @@ class ZoneGroundRemoveItemTest extends ActorTest { hub.register(item, 10) val zone = new Zone("test", new ZoneMap("test-map"), 0) { override def SetupNumberPools() = {} } zone.GUID(hub) //still registered to this zone - zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), ZoneTest.TestName) - zone.Actor ! Zone.Init() + zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName) expectNoMessage(200 milliseconds) "RemoveItem" should { diff --git a/common/src/test/scala/objects/terminal/ProximityTest.scala b/common/src/test/scala/objects/terminal/ProximityTest.scala index 8646b79e..7c92c3fc 100644 --- a/common/src/test/scala/objects/terminal/ProximityTest.scala +++ b/common/src/test/scala/objects/terminal/ProximityTest.scala @@ -4,6 +4,7 @@ package objects.terminal import akka.actor.Props import akka.testkit.TestProbe import base.ActorTest +import net.psforever.actors.zone.ZoneActor import net.psforever.objects.serverobject.CommonMessages import net.psforever.objects.serverobject.structures.{Building, StructureType} import net.psforever.objects.serverobject.terminals.{ @@ -12,14 +13,14 @@ import net.psforever.objects.serverobject.terminals.{ ProximityUnit, Terminal } -import net.psforever.objects.zones.{Zone, ZoneActor, ZoneMap} +import net.psforever.objects.zones.{Zone, ZoneMap} import net.psforever.objects.{Avatar, GlobalDefinitions, Player} import net.psforever.types.{CharacterGender, CharacterVoice, PlanetSideEmpire, PlanetSideGUID} import org.specs2.mutable.Specification import services.Service import services.local.LocalService - import scala.concurrent.duration._ +import akka.actor.typed.scaladsl.adapter._ class ProximityTest extends Specification { "ProximityUnit" should { @@ -106,7 +107,7 @@ class ProximityTerminalControlStartTest extends ActorTest { "ProximityTerminalControl" should { //setup val zone: Zone = new Zone("test", new ZoneMap("test-map"), 0) { - Actor = system.actorOf(Props(classOf[ZoneActor], this), "test-zone") + actor = system.spawn(ZoneActor(this), "test-zone") override def SetupNumberPools() = { AddPool("dynamic", 1 to 10) } @@ -146,7 +147,7 @@ class ProximityTerminalControlTwoUsersTest extends ActorTest { "ProximityTerminalControl" should { //setup val zone: Zone = new Zone("test", new ZoneMap("test-map"), 0) { - Actor = system.actorOf(Props(classOf[ZoneActor], this), "test-zone") + actor = system.spawn(ZoneActor(this), "test-zone") override def SetupNumberPools() = { AddPool("dynamic", 1 to 10) } @@ -199,7 +200,7 @@ class ProximityTerminalControlStopTest extends ActorTest { "ProximityTerminalControl" should { //setup val zone: Zone = new Zone("test", new ZoneMap("test-map"), 0) { - Actor = system.actorOf(Props(classOf[ZoneActor], this), "test-zone") + actor = system.spawn(ZoneActor(this), "test-zone") override def SetupNumberPools() = { AddPool("dynamic", 1 to 10) } @@ -242,7 +243,7 @@ class ProximityTerminalControlNotStopTest extends ActorTest { "ProximityTerminalControl" should { //setup val zone: Zone = new Zone("test", new ZoneMap("test-map"), 0) { - Actor = system.actorOf(Props(classOf[ZoneActor], this), "test-zone") + actor = system.spawn(ZoneActor(this), "test-zone") override def SetupNumberPools() = { AddPool("dynamic", 1 to 10) } diff --git a/config/logback.xml b/config/logback.xml index 7fd13ebe..4e81de88 100644 --- a/config/logback.xml +++ b/config/logback.xml @@ -26,8 +26,8 @@ %date{ISO8601} [%thread] %5level "%X" %logger{35} - %msg%n - OFF - + + TRACE diff --git a/pslogin/src/main/resources/db/migration/V002__Buildings.sql b/pslogin/src/main/resources/db/migration/V002__Buildings.sql new file mode 100644 index 00000000..d4b4c2c4 --- /dev/null +++ b/pslogin/src/main/resources/db/migration/V002__Buildings.sql @@ -0,0 +1,6 @@ +CREATE TABLE IF NOT EXISTS "buildings" ( + local_id INT NOT NULL, + zone_id INT NOT NULL, + faction_id INT NOT NULL, + PRIMARY KEY (local_id, zone_id) +); \ No newline at end of file diff --git a/pslogin/src/main/scala/PsLogin.scala b/pslogin/src/main/scala/PsLogin.scala index e7a834e8..435004ce 100644 --- a/pslogin/src/main/scala/PsLogin.scala +++ b/pslogin/src/main/scala/PsLogin.scala @@ -4,7 +4,7 @@ import java.net.InetAddress import java.util.Locale import akka.{actor => classic} -import akka.actor.typed.ActorSystem +import akka.actor.typed.scaladsl.adapter._ import akka.routing.RandomPool import ch.qos.logback.classic.LoggerContext import ch.qos.logback.classic.joran.JoranConfigurator @@ -15,7 +15,7 @@ import net.psforever.objects.guid.TaskResolver import org.slf4j import org.fusesource.jansi.Ansi._ import org.fusesource.jansi.Ansi.Color._ -import services.ServiceManager +import services.{InterstellarClusterService, ServiceManager} import services.account.{AccountIntermediaryService, AccountPersistenceService} import services.chat.ChatService import services.galaxy.GalaxyService @@ -27,7 +27,6 @@ import org.flywaydb.core.Flyway import java.nio.file.Paths import scopt.OParser -import akka.actor.typed.scaladsl.adapter._ import net.psforever.actors.session.SessionActor import net.psforever.login.psadmin.PsAdminActor import net.psforever.login.{ @@ -94,8 +93,6 @@ object PsLogin { implicit val system = classic.ActorSystem("PsLogin") Default(system) - val typedSystem: ActorSystem[Nothing] = system.toTyped - /** Create pipelines for the login and world servers * * The first node in the pipe is an Actor that handles the crypto for protecting packets. @@ -130,16 +127,16 @@ object PsLogin { None } - val continents = Zones.zones.values ++ Seq(Zone.Nowhere) + val zones = Zones.zones.values ++ Seq(Zone.Nowhere) - system.spawnAnonymous(ChatService()) + system.spawn(ChatService(), ChatService.ChatServiceKey.id) + system.spawn(InterstellarClusterService(zones), InterstellarClusterService.InterstellarClusterServiceKey.id) val serviceManager = ServiceManager.boot serviceManager ! ServiceManager.Register(classic.Props[AccountIntermediaryService], "accountIntermediary") serviceManager ! ServiceManager.Register(RandomPool(150).props(classic.Props[TaskResolver]), "taskResolver") serviceManager ! ServiceManager.Register(classic.Props[GalaxyService], "galaxy") serviceManager ! ServiceManager.Register(classic.Props[SquadService], "squad") - serviceManager ! ServiceManager.Register(classic.Props(classOf[InterstellarCluster], continents), "cluster") serviceManager ! ServiceManager.Register(classic.Props[AccountPersistenceService], "accountPersistence") serviceManager ! ServiceManager.Register(classic.Props[PropertyOverrideManager], "propertyOverrideManager") diff --git a/pslogin/src/test/scala/actor/objects/VehicleSpawnPadTest.scala b/pslogin/src/test/scala/actor/objects/VehicleSpawnPadTest.scala index 390a81bc..ceda584b 100644 --- a/pslogin/src/test/scala/actor/objects/VehicleSpawnPadTest.scala +++ b/pslogin/src/test/scala/actor/objects/VehicleSpawnPadTest.scala @@ -11,6 +11,8 @@ import net.psforever.objects.zones.Zone import net.psforever.types.{PlanetSideGUID, _} import services.RemoverActor import services.vehicle.{VehicleAction, VehicleServiceMessage} +import akka.actor.typed.scaladsl.adapter._ +import net.psforever.actors.zone.ZoneActor import scala.concurrent.duration._ @@ -213,7 +215,6 @@ object VehicleSpawnPadControlTest { import net.psforever.objects.guid.source.LimitedNumberSource import net.psforever.objects.serverobject.structures.Building import net.psforever.objects.vehicles.VehicleControl - import net.psforever.objects.zones.ZoneActor import net.psforever.objects.Tool import net.psforever.types.CharacterGender @@ -228,8 +229,7 @@ object VehicleSpawnPadControlTest { override def SetupNumberPools(): Unit = {} } zone.GUID(guid) - zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), s"test-zone-${System.nanoTime()}") - zone.Actor ! Zone.Init() + zone.actor = system.spawn(ZoneActor(zone), s"test-zone-${System.nanoTime()}") // Hack: Wait for the Zone to finish booting, otherwise later tests will fail randomly due to race conditions // with actor probe setting diff --git a/pslogin/src/test/scala/actor/service/AvatarServiceTest.scala b/pslogin/src/test/scala/actor/service/AvatarServiceTest.scala index 1f10445a..8822a76e 100644 --- a/pslogin/src/test/scala/actor/service/AvatarServiceTest.scala +++ b/pslogin/src/test/scala/actor/service/AvatarServiceTest.scala @@ -6,7 +6,7 @@ import akka.routing.RandomPool import actor.base.ActorTest import net.psforever.objects._ import net.psforever.objects.guid.{GUIDTask, TaskResolver} -import net.psforever.objects.zones.{Zone, ZoneActor, ZoneMap} +import net.psforever.objects.zones.{Zone, ZoneMap} import net.psforever.packet.game.objectcreate.{DroppedItemData, ObjectClass, ObjectCreateMessageParent, PlacementData} import net.psforever.packet.game.{ObjectCreateMessage, PlayerStateMessageUpstream} import net.psforever.types._ @@ -14,6 +14,8 @@ import services.{RemoverActor, Service, ServiceManager} import services.avatar._ import scala.concurrent.duration._ +import akka.actor.typed.scaladsl.adapter._ +import net.psforever.actors.zone.ZoneActor class AvatarService1Test extends ActorTest { "AvatarService" should { @@ -510,8 +512,7 @@ class AvatarReleaseTest extends ActorTest { } val service = system.actorOf(Props(classOf[AvatarService], zone), "release-test-service") val taskResolver = system.actorOf(Props[TaskResolver], "release-test-resolver") - zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), "release-test-zone") - zone.Actor ! Zone.Init() + zone.actor = system.spawn(ZoneActor(zone), "release-test-zone") val obj = Player(Avatar("TestCharacter", PlanetSideEmpire.VS, CharacterGender.Female, 1, CharacterVoice.Voice1)) obj.Continent = "test" obj.Release @@ -561,8 +562,7 @@ class AvatarReleaseEarly1Test extends ActorTest { } val service = system.actorOf(Props(classOf[AvatarService], zone), "release-test-service") val taskResolver = system.actorOf(Props[TaskResolver], "release-test-resolver") - zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), "release-test-zone") - zone.Actor ! Zone.Init() + zone.actor = system.spawn(ZoneActor(zone), "release-test-zone") val obj = Player(Avatar("TestCharacter1", PlanetSideEmpire.VS, CharacterGender.Female, 1, CharacterVoice.Voice1)) obj.Continent = "test" obj.Release @@ -613,8 +613,7 @@ class AvatarReleaseEarly2Test extends ActorTest { } val service = system.actorOf(Props(classOf[AvatarService], zone), "release-test-service") val taskResolver = system.actorOf(Props[TaskResolver], "release-test-resolver") - zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), "release-test-zone") - zone.Actor ! Zone.Init() + zone.actor = system.spawn(ZoneActor(zone), "release-test-zone") val objAlt = Player( Avatar("TestCharacter2", PlanetSideEmpire.NC, CharacterGender.Male, 1, CharacterVoice.Voice1) diff --git a/scripts/format-switch.py b/scripts/format-switch.py deleted file mode 100644 index e47b57dd..00000000 --- a/scripts/format-switch.py +++ /dev/null @@ -1,51 +0,0 @@ -import re - -def getmap(): - data = open("tmp").read() - lines = data.split("\n") - lines_stripped = [l.strip().rstrip() for l in lines] - - datalines = [] - for i in lines_stripped: - m = re.findall(r'^[A-Z0-9a-z_]+', i) - if len(m): - datalines.append(m[0]) - - return datalines - -def top(): - datalines = getmap() - - for i in range(0, len(datalines), 16): - print("// OPCODES 0x%02x-%02x" % (i, i+15)) - print("\n".join([d+"," for d in datalines[i:min(i+8, len(datalines))]])) - print("// 0x%02x" % (i+8)) - print("\n".join([d+"," for d in datalines[min(i+8,len(datalines)):min(i+16, len(datalines))]])) - print("") - -def bot(): - data = open("tmp2").read() - lines = data.split("\n") - lines_stripped = [l.strip().rstrip() for l in lines] - - datalinesMap = getmap() - datalines = [] - for i in lines_stripped: - m = re.findall(r'^case ([0-9]+)', i) - if len(m): - num = int(m[0]) - m = re.findall(r'=> (.*)', i)[0] - - if m.startswith("noDecoder"): - datalines.append((num, "noDecoder(%s)" % datalinesMap[num])) - else: - datalines.append((num, m)) - - for i in range(0, len(datalines), 16): - print("// OPCODES 0x%02x-%02x" % (i, i+15)) - print("\n".join(["case 0x%02x => %s" % (n,d) for n,d in datalines[i:min(i+8, len(datalines))]])) - print("// 0x%02x" % (i+8)) - print("\n".join(["case 0x%02x => %s" % (n,d) for n,d in datalines[i+8:min(i+16, len(datalines))]])) - print("") - -top()