diff --git a/src/main/scala/net/psforever/actors/session/ChatActor.scala b/src/main/scala/net/psforever/actors/session/ChatActor.scala
deleted file mode 100644
index a05dea1ec..000000000
--- a/src/main/scala/net/psforever/actors/session/ChatActor.scala
+++ /dev/null
@@ -1,1759 +0,0 @@
-package net.psforever.actors.session
-
-import akka.actor.Cancellable
-import akka.actor.typed.{ActorRef, Behavior, PostStop, SupervisorStrategy}
-import akka.actor.typed.receptionist.Receptionist
-import akka.actor.typed.scaladsl.{ActorContext, Behaviors, StashBuffer}
-import akka.actor.typed.scaladsl.adapter._
-import net.psforever.actors.session.normal.{NormalMode => SessionNormalMode}
-import net.psforever.actors.session.spectator.{SpectatorMode => SessionSpectatorMode}
-import net.psforever.actors.zone.ZoneActor
-import net.psforever.objects.sourcing.PlayerSource
-import net.psforever.objects.zones.ZoneInfo
-import net.psforever.services.chat.{DefaultChannel, SquadChannel}
-import net.psforever.services.local.{LocalAction, LocalServiceMessage}
-import net.psforever.types.ChatMessageType.CMT_QUIT
-import org.log4s.Logger
-
-import scala.annotation.unused
-import scala.collection.{Seq, mutable}
-import scala.concurrent.ExecutionContextExecutor
-import scala.concurrent.duration._
-//
-import net.psforever.actors.zone.BuildingActor
-import net.psforever.login.WorldSession
-import net.psforever.objects.{Default, Player, Session}
-import net.psforever.objects.avatar.{BattleRank, Certification, CommandRank, Shortcut => AvatarShortcut}
-import net.psforever.objects.definition.ImplantDefinition
-import net.psforever.objects.serverobject.pad.{VehicleSpawnControl, VehicleSpawnPad}
-import net.psforever.objects.serverobject.resourcesilo.ResourceSilo
-import net.psforever.objects.serverobject.structures.{Amenity, Building}
-import net.psforever.objects.serverobject.turret.{FacilityTurret, TurretUpgrade, WeaponTurrets}
-import net.psforever.objects.zones.Zoning
-import net.psforever.packet.game.objectcreate.DrawnSlot
-import net.psforever.packet.game.{ChatMsg, CreateShortcutMessage, DeadState, RequestDestroyMessage, Shortcut}
-import net.psforever.services.{CavernRotationService, InterstellarClusterService}
-import net.psforever.services.chat.ChatService
-import net.psforever.services.chat.ChatChannel
-import net.psforever.types.ChatMessageType.{CMT_GMOPEN, UNK_227, UNK_229}
-import net.psforever.types.{ChatMessageType, Cosmetic, ExperienceType, ImplantType, PlanetSideEmpire, PlanetSideGUID, Vector3}
-import net.psforever.util.{Config, PointOfInterest}
-import net.psforever.zones.Zones
-
-object ChatActor {
- def apply(
- sessionActor: ActorRef[SessionActor.Command],
- avatarActor: ActorRef[AvatarActor.Command]
- ): Behavior[Command] =
- Behaviors
- .supervise[Command] {
- Behaviors.withStash(100) { buffer =>
- Behaviors.setup(context => new ChatActor(context, buffer, sessionActor, avatarActor).start())
- }
- }
- .onFailure[Exception](SupervisorStrategy.restart)
-
- sealed trait Command
-
- final case class JoinChannel(channel: ChatChannel) extends Command
- final case class LeaveChannel(channel: ChatChannel) extends Command
- final case class Message(message: ChatMsg) extends Command
- final case class SetSession(session: Session) extends Command
- final case class SetMode(mode: String) extends Command
-
- private case class ListingResponse(listing: Receptionist.Listing) extends Command
- private case class IncomingMessage(session: Session, message: ChatMsg, channel: ChatChannel) extends Command
-
- trait ChatFuncs {
- def chatService: ActorRef[ChatService.Command]
- def cluster: ActorRef[InterstellarClusterService.Command]
-
- def message(session: Session, message: ChatMsg): Unit
-
- def incoming(session: Session, message: ChatMsg, fromSession: Session): Unit
- }
-
- trait ChatMode {
- def init(chatService: ActorRef[ChatService.Command], cluster: ActorRef[InterstellarClusterService.Command]): ChatFuncs
- }
-
- /**
- * For a provided number of facility nanite transfer unit resource silos,
- * charge the facility's silo with an expected amount of nanite transfer units.
- * @see `Amenity`
- * @see `ChatMsg`
- * @see `ResourceSilo`
- * @see `ResourceSilo.UpdateChargeLevel`
- * @see `SessionActor.Command`
- * @see `SessionActor.SendResponse`
- * @param session messaging reference back tothe target session
- * @param resources the optional number of resources to set to each silo;
- * different values provide different resources as indicated below;
- * an undefined value also has a condition
- * @param silos where to deposit the resources
- * @param debugContent something for log output context
- */
- private def setBaseResources(
- session: ActorRef[SessionActor.Command],
- resources: Option[Int],
- silos: Iterable[Amenity],
- debugContent: String
- ): Unit = {
- if (silos.isEmpty) {
- session ! SessionActor.SendResponse(
- ChatMsg(UNK_229, wideContents=true, "Server", s"no targets for ntu found with parameters $debugContent", None)
- )
- }
- resources match {
- // x = n0% of maximum capacitance
- case Some(value) if value > -1 && value < 11 =>
- silos.collect {
- case silo: ResourceSilo =>
- silo.Actor ! ResourceSilo.UpdateChargeLevel(
- value * silo.MaxNtuCapacitor * 0.1f - silo.NtuCapacitor
- )
- }
- // capacitance set to x (where x > 10) exactly, within limits
- case Some(value) =>
- silos.collect {
- case silo: ResourceSilo =>
- silo.Actor ! ResourceSilo.UpdateChargeLevel(value - silo.NtuCapacitor)
- }
- case None =>
- // x >= n0% of maximum capacitance and x <= maximum capacitance
- val rand = new scala.util.Random
- silos.collect {
- case silo: ResourceSilo =>
- val a = 7
- val b = 10 - a
- val tenth = silo.MaxNtuCapacitor * 0.1f
- silo.Actor ! ResourceSilo.UpdateChargeLevel(
- a * tenth + rand.nextFloat() * b * tenth - silo.NtuCapacitor
- )
- }
- }
- }
-
- /**
- * Create a medkit shortcut if there is no medkit shortcut on the hotbar.
- * Bounce the packet to the client and the client will bounce it back to the server to continue the setup,
- * or cancel / invalidate the shortcut creation.
- * @see `Array::indexWhere`
- * @see `CreateShortcutMessage`
- * @see `net.psforever.objects.avatar.Shortcut`
- * @see `net.psforever.packet.game.Shortcut.Medkit`
- * @see `SessionActor.SendResponse`
- * @param guid current player unique identifier for the target client
- * @param shortcuts list of all existing shortcuts, used for early validation
- */
- private def medkitSanityTest(
- guid: PlanetSideGUID,
- shortcuts: Array[Option[AvatarShortcut]],
- sendTo: ActorRef[SessionActor.Command]
- ): Unit = {
- if (!shortcuts.exists {
- case Some(a) => a.purpose == 0
- case None => false
- }) {
- shortcuts.indexWhere(_.isEmpty) match {
- case -1 => ()
- case index =>
- //new shortcut
- sendTo ! SessionActor.SendResponse(CreateShortcutMessage(
- guid,
- index + 1,
- Some(Shortcut.Medkit)
- ))
- }
- }
- }
-
- /**
- * Create all implant macro shortcuts for all implants whose shortcuts have been removed from the hotbar.
- * Bounce the packet to the client and the client will bounce it back to the server to continue the setup,
- * or cancel / invalidate the shortcut creation.
- * @see `CreateShortcutMessage`
- * @see `ImplantDefinition`
- * @see `net.psforever.objects.avatar.Shortcut`
- * @see `SessionActor.SendResponse`
- * @param guid current player unique identifier for the target client
- * @param haveImplants list of implants the player possesses
- * @param shortcuts list of all existing shortcuts, used for early validation
- */
- private def implantSanityTest(
- guid: PlanetSideGUID,
- haveImplants: Iterable[ImplantDefinition],
- shortcuts: Array[Option[AvatarShortcut]],
- sendTo: ActorRef[SessionActor.Command]
- ): Unit = {
- val haveImplantShortcuts = shortcuts.collect {
- case Some(shortcut) if shortcut.purpose == 2 => shortcut.tile
- }
- var start: Int = 0
- haveImplants.filterNot { imp => haveImplantShortcuts.contains(imp.Name) }
- .foreach { implant =>
- shortcuts.indexWhere(_.isEmpty, start) match {
- case -1 => ()
- case index =>
- //new shortcut
- start = index + 1
- sendTo ! SessionActor.SendResponse(CreateShortcutMessage(
- guid,
- start,
- Some(implant.implantType.shortcut)
- ))
- }
- }
- }
-
- /**
- * Create a text chat macro shortcut if it doesn't already exist.
- * Bounce the packet to the client and the client will bounce it back to the server to continue the setup,
- * or cancel / invalidate the shortcut creation.
- * @see `Array::indexWhere`
- * @see `CreateShortcutMessage`
- * @see `net.psforever.objects.avatar.Shortcut`
- * @see `net.psforever.packet.game.Shortcut.Macro`
- * @see `SessionActor.SendResponse`
- * @param guid current player unique identifier for the target client
- * @param acronym three letters emblazoned on the shortcut icon
- * @param msg the message published to text chat
- * @param shortcuts a list of all existing shortcuts, used for early validation
- */
- private def macroSanityTest(
- guid: PlanetSideGUID,
- acronym: String,
- msg: String,
- shortcuts: Array[Option[AvatarShortcut]],
- sendTo: ActorRef[SessionActor.Command]
- ): Unit = {
- shortcuts.indexWhere(_.isEmpty) match {
- case -1 => ()
- case index =>
- //new shortcut
- sendTo ! SessionActor.SendResponse(CreateShortcutMessage(
- guid,
- index + 1,
- Some(Shortcut.Macro(acronym, msg))
- ))
- }
- }
-
- private def setBattleRank(
- session: Session,
- params: Seq[String],
- msgFunc: Long => AvatarActor.Command,
- sendTo: ActorRef[AvatarActor.Command]
- ): Boolean = {
- val (target, rank) = (params.headOption, params.lift(1)) match {
- case (Some(target), Some(rank)) if target == session.avatar.name =>
- rank.toIntOption match {
- case Some(rank) => (None, BattleRank.withValueOpt(rank))
- case None => (None, None)
- }
- case (Some("-h"), _) | (Some("-help"), _) =>
- (None, Some(BattleRank.BR1))
- case (Some(_), Some(_)) =>
- // picking other targets is not supported for now
- (None, None)
- case (Some(rank), None) =>
- rank.toIntOption match {
- case Some(rank) => (None, BattleRank.withValueOpt(rank))
- case None => (None, None)
- }
- case _ => (None, None)
- }
- (target, rank) match {
- case (_, Some(rank)) if rank.value <= Config.app.game.maxBattleRank =>
- sendTo ! msgFunc(rank.experience)
- true
- case _ =>
- false
- }
- }
-
- private def setCommandRank(
- contents: String,
- session: Session,
- sendTo: ActorRef[AvatarActor.Command]
- ): Boolean = {
- val buffer = cliTokenization(contents)
- val (target, rank) = (buffer.headOption, buffer.lift(1)) match {
- case (Some(target), Some(rank)) if target == session.avatar.name =>
- rank.toIntOption match {
- case Some(rank) => (None, CommandRank.withValueOpt(rank))
- case None => (None, None)
- }
- case (Some(_), Some(_)) =>
- // picking other targets is not supported for now
- (None, None)
- case (Some(rank), None) =>
- rank.toIntOption match {
- case Some(rank) => (None, CommandRank.withValueOpt(rank))
- case None => (None, None)
- }
- case _ => (None, None)
- }
- (target, rank) match {
- case (_, Some(rank)) =>
- sendTo ! AvatarActor.SetCep(rank.experience)
- true
- case _ =>
- false
- }
- }
-
- private def captureBaseParamFacilities(
- session: Session,
- token: Option[String]
- ): Option[Seq[Building]] = {
- token.collect {
- case "curr" =>
- val list = captureBaseCurrSoi(session)
- if (list.nonEmpty) {
- Some(list.toSeq)
- } else {
- None
- }
- case "all" =>
- val list = session.zone.Buildings.values.filter(_.CaptureTerminal.isDefined)
- if (list.nonEmpty) {
- Some(list.toSeq)
- } else {
- None
- }
- case name =>
- val trueName = ZoneInfo
- .values
- .find(_.id.equals(session.zone.id))
- .flatMap { info =>
- info.aliases
- .facilities
- .collectFirst { case (key, internalName) if key.equalsIgnoreCase(name) => internalName }
- }
- .getOrElse(name)
- session.zone.Buildings
- .values
- .find {
- building => trueName.equalsIgnoreCase(building.Name) && building.CaptureTerminal.isDefined
- }
- .map(b => Seq(b))
- }
- .flatten
- }
-
- private def captureBaseCurrSoi(
- session: Session
- ): Iterable[Building] = {
- val charId = session.player.CharId
- session.zone.Buildings.values.filter { building =>
- building.PlayersInSOI.exists(_.CharId == charId)
- }
- }
-
- private def captureBaseParamFaction(
- @unused session: Session,
- token: Option[String]
- ): Option[PlanetSideEmpire.Value] = {
- token.collect {
- case faction =>
- faction.toLowerCase() match {
- case "tr" => Some(PlanetSideEmpire.TR)
- case "nc" => Some(PlanetSideEmpire.NC)
- case "vs" => Some(PlanetSideEmpire.VS)
- case "none" => Some(PlanetSideEmpire.NEUTRAL)
- case "bo" => Some(PlanetSideEmpire.NEUTRAL)
- case "neutral" => Some(PlanetSideEmpire.NEUTRAL)
- case _ => None
- }
- }.flatten
- }
-
- private def captureBaseParamTimer(
- @unused session: Session,
- token: Option[String]
- ): Option[Int] = {
- token.flatMap(_.toIntOption)
- }
-
-
-
- private def customCommandWhitetext(
- session: Session,
- content: Seq[String],
- sendTo: ActorRef[ChatService.Command]
- ): Boolean = {
- sendTo ! ChatService.Message(
- session,
- ChatMsg(UNK_227, wideContents=true, "", content.mkString(" "), None),
- DefaultChannel
- )
- true
- }
-
- private def customCommandLoc(
- session: Session,
- message: ChatMsg,
- sendTo: ActorRef[SessionActor.Command]
- ): Boolean = {
- val continent = session.zone
- val player = session.player
- val loc =
- s"zone=${continent.id} pos=${player.Position.x},${player.Position.y},${player.Position.z}; ori=${player.Orientation.x},${player.Orientation.y},${player.Orientation.z}"
- sendTo ! SessionActor.SendResponse(message.copy(contents = loc))
- true
- }
-
- private def customCommandList(
- session: Session,
- params: Seq[String],
- message: ChatMsg,
- sendTo: ActorRef[SessionActor.Command]
- ): Boolean = {
- val zone = params.headOption match {
- case Some("") | None =>
- Some(session.zone)
- case Some(id) =>
- Zones.zones.find(_.id == id)
- }
- zone match {
- case Some(inZone) =>
- sendTo ! SessionActor.SendResponse(
- ChatMsg(
- CMT_GMOPEN,
- message.wideContents,
- "Server",
- "\\#8Name (Faction) [ID] at PosX PosY PosZ",
- message.note
- )
- )
- (inZone.LivePlayers ++ inZone.Corpses)
- .filter(_.CharId != session.player.CharId)
- .sortBy(p => (p.Name, !p.isAlive))
- .foreach(player => {
- val color = if (!player.isAlive) "\\#7" else ""
- sendTo ! SessionActor.SendResponse(
- ChatMsg(
- CMT_GMOPEN,
- message.wideContents,
- "Server",
- s"$color${player.Name} (${player.Faction}) [${player.CharId}] at ${player.Position.x.toInt} ${player.Position.y.toInt} ${player.Position.z.toInt}",
- message.note
- )
- )
- })
- case None =>
- sendTo ! SessionActor.SendResponse(
- ChatMsg(
- CMT_GMOPEN,
- message.wideContents,
- "Server",
- "Invalid zone ID",
- message.note
- )
- )
- }
- true
- }
-
- private def customCommandNtu(
- session: Session,
- params: Seq[String],
- sendTo: ActorRef[SessionActor.Command]
- ): Boolean = {
- val (facility, customNtuValue) = (params.headOption, params.lift(1)) match {
- case (Some(x), Some(y)) if y.toIntOption.nonEmpty => (Some(x), Some(y.toInt))
- case (Some(x), None) if x.toIntOption.nonEmpty => (None, Some(x.toInt))
- case _ => (None, None)
- }
- val silos = (facility match {
- case Some(cur) if cur.toLowerCase().startsWith("curr") =>
- val position = session.player.Position
- session.zone.Buildings.values
- .filter { building =>
- val soi2 = building.Definition.SOIRadius * building.Definition.SOIRadius
- Vector3.DistanceSquared(building.Position, position) < soi2
- }
- case Some(all) if all.toLowerCase.startsWith("all") =>
- session.zone.Buildings.values
- case Some(x) =>
- session.zone.Buildings.values.find(_.Name.equalsIgnoreCase(x)).toList
- case _ =>
- session.zone.Buildings.values
- })
- .flatMap { building =>
- building.Amenities.filter(_.isInstanceOf[ResourceSilo])
- }
- ChatActor.setBaseResources(sendTo, customNtuValue, silos, debugContent = s"$facility")
- true
- }
-
- private def customCommandZonerotate(
- params: Seq[String],
- sendTo: ActorRef[InterstellarClusterService.Command],
- replyTo: ActorRef[SessionActor.Command]
- ): Boolean = {
- sendTo ! InterstellarClusterService.CavernRotation(params.headOption match {
- case Some("-list") | Some("-l") =>
- CavernRotationService.ReportRotationOrder(replyTo.toClassic)
- case _ =>
- CavernRotationService.HurryNextRotation
- })
- true
- }
-
- private def customCommandSuicide(
- session: Session
- ): Boolean = {
- //this is like CMT_SUICIDE but it ignores checks and forces a suicide state
- val tplayer = session.player
- tplayer.Revive
- tplayer.Actor ! Player.Die()
- true
- }
-
- private def customCommandGrenade(
- session: Session,
- log: Logger
- ): Boolean = {
- WorldSession.QuickSwapToAGrenade(session.player, DrawnSlot.Pistol1.id, log)
- true
- }
-
- private def customCommandMacro(
- session: Session,
- params: Seq[String],
- sendTo: ActorRef[SessionActor.Command]
- ): Boolean = {
- val avatar = session.avatar
- (params.headOption, params.lift(1)) match {
- case (Some(cmd), other) =>
- cmd.toLowerCase() match {
- case "medkit" =>
- medkitSanityTest(session.player.GUID, avatar.shortcuts, sendTo)
- true
-
- case "implants" =>
- //implant shortcut sanity test
- implantSanityTest(
- session.player.GUID,
- avatar.implants.collect {
- case Some(implant) if implant.definition.implantType != ImplantType.None => implant.definition
- },
- avatar.shortcuts,
- sendTo
- )
- true
-
- case name
- if ImplantType.values.exists { a => a.shortcut.tile.equals(name) } =>
- avatar.implants.find {
- case Some(implant) => implant.definition.Name.equalsIgnoreCase(name)
- case None => false
- } match {
- case Some(Some(implant)) =>
- //specific implant shortcut sanity test
- implantSanityTest(session.player.GUID, Seq(implant.definition), avatar.shortcuts, sendTo)
- true
- case _ if other.nonEmpty =>
- //add macro?
- macroSanityTest(session.player.GUID, name, params.drop(2).mkString(" "), avatar.shortcuts, sendTo)
- true
- case _ =>
- false
- }
-
- case name
- if name.nonEmpty && other.nonEmpty =>
- //add macro
- macroSanityTest(session.player.GUID, name, params.drop(2).mkString(" "), avatar.shortcuts, sendTo)
- true
-
- case _ =>
- false
- }
- case _ =>
- false
- }
- }
-
- private def customCommandProgress(
- session: Session,
- params: Seq[String],
- sendTo: ActorRef[AvatarActor.Command]
- ): Boolean = {
- val ourRank = BattleRank.withExperience(session.avatar.bep).value
- if (!session.account.gm &&
- (ourRank <= Config.app.game.promotion.broadcastBattleRank ||
- ourRank > Config.app.game.promotion.resetBattleRank && ourRank < Config.app.game.promotion.maxBattleRank + 1)) {
- setBattleRank(session, params, AvatarActor.Progress, sendTo)
- true
- } else {
- setBattleRank(session, Seq("1"), AvatarActor.Progress, sendTo)
- false
- }
- }
-
- private def customCommandNearby(
- session: Session,
- sendTo: ActorRef[SessionActor.Command]
- ): Boolean = {
- val playerPos = session.player.Position.xy
- val closest = session.zone
- .Buildings
- .values
- .toSeq
- .minByOption(base => Vector3.DistanceSquared(playerPos, base.Position.xy))
- .map(base => s"${base.Name} - ${base.Definition.Name}")
- sendTo ! SessionActor.SendResponse(
- ChatMsg(CMT_GMOPEN, wideContents = false, "Server", s"closest facility: $closest", None)
- )
- true
- }
-
- private def firstParam[T](
- session: Session,
- buffer: Iterable[String],
- func: (Session, Option[String])=>Option[T]
- ): (Option[T], Option[String], Iterable[String]) = {
- val tokenOpt = buffer.headOption
- val valueOpt = func(session, tokenOpt)
- val outBuffer = if (valueOpt.nonEmpty) {
- buffer.drop(1)
- } else {
- buffer
- }
- (valueOpt, tokenOpt, outBuffer)
- }
-
- private def cliTokenization(str: String): List[String] = {
- str.replaceAll("\\s+", " ").toLowerCase.trim.split("\\s").toList
- }
-}
-
-class ChatActor(
- context: ActorContext[ChatActor.Command],
- buffer: StashBuffer[ChatActor.Command],
- sessionActor: ActorRef[SessionActor.Command],
- avatarActor: ActorRef[AvatarActor.Command]
-) {
-
- import ChatActor._
-
- implicit val ec: ExecutionContextExecutor = context.executionContext
-
- private[this] val log = org.log4s.getLogger
- var channels: List[ChatChannel] = List()
- var session: Option[Session] = None
- var chatService: Option[ActorRef[ChatService.Command]] = None
- var cluster: Option[ActorRef[InterstellarClusterService.Command]] = None
- var silenceTimer: Cancellable = Default.Cancellable
- /**
- * when another player is listed as one of our ignored players,
- * and that other player sends an emote,
- * that player is assigned a cooldown and only one emote per period will be seen
- * key - character unique avatar identifier, value - when the current cooldown period will end
- */
- var ignoredEmoteCooldown: mutable.LongMap[Long] = mutable.LongMap[Long]()
-
- val chatServiceAdapter: ActorRef[ChatService.MessageResponse] = context.messageAdapter[ChatService.MessageResponse] {
- case ChatService.MessageResponse(_session, message, channel) => IncomingMessage(_session, message, channel)
- }
- var logic: ChatFuncs = _
-
- context.system.receptionist ! Receptionist.Find(
- ChatService.ChatServiceKey,
- context.messageAdapter[Receptionist.Listing](ListingResponse)
- )
-
- context.system.receptionist ! Receptionist.Find(
- InterstellarClusterService.InterstellarClusterServiceKey,
- context.messageAdapter[Receptionist.Listing](ListingResponse)
- )
-
- def start(): Behavior[Command] = {
- Behaviors
- .receiveMessage[Command] {
- case ListingResponse(InterstellarClusterService.InterstellarClusterServiceKey.Listing(listings)) =>
- listings.headOption match {
- case Some(ref) =>
- cluster = Some(ref)
- postStartBehaviour()
- case None =>
- context.system.receptionist ! Receptionist.Find(
- InterstellarClusterService.InterstellarClusterServiceKey,
- context.messageAdapter[Receptionist.Listing](ListingResponse)
- )
- Behaviors.same
- }
-
- case ListingResponse(ChatService.ChatServiceKey.Listing(listings)) =>
- chatService = Some(listings.head)
- channels ++= List(DefaultChannel)
- postStartBehaviour()
-
- case SetSession(newSession) =>
- session = Some(newSession)
- postStartBehaviour()
-
- case other =>
- buffer.stash(other)
- Behaviors.same
- }
- }
-
- def postStartBehaviour(): Behavior[Command] = {
- (session, chatService, cluster) match {
- case (Some(_session), Some(_chatService), Some(_cluster)) if _session.player != null =>
- _chatService ! ChatService.JoinChannel(chatServiceAdapter, null, DefaultChannel)
- logic = NormalMode.init(_chatService, _cluster)
- buffer.unstashAll(active(_session, _chatService, _cluster))
- case _ =>
- Behaviors.same
- }
- }
-
- def active(
- session: Session,
- chatService: ActorRef[ChatService.Command],
- cluster: ActorRef[InterstellarClusterService.Command]
- ): Behavior[Command] = {
- Behaviors
- .receiveMessagePartial[Command] {
- case SetSession(newSession) =>
- this.session = Some(newSession)
- active(newSession, chatService, cluster)
-
- case JoinChannel(channel) =>
- chatService ! ChatService.JoinChannel(chatServiceAdapter, null, channel)
- channels ++= List(channel)
- Behaviors.same
-
- case LeaveChannel(channel) =>
- chatService ! ChatService.LeaveChannel(chatServiceAdapter, channel)
- channels = channels.filterNot(_ == channel)
- Behaviors.same
-
- case SetMode("normal") =>
- logic = NormalMode.init(logic.chatService, logic.cluster)
- Behaviors.same
-
- case SetMode("spectator") =>
- logic = SpectatorMode.init(logic.chatService, logic.cluster)
- Behaviors.same
-
- case Message(message) =>
- logic.message(session, message)
- Behaviors.same
-
- case IncomingMessage(fromSession, message, _) =>
- logic.incoming(session, message, fromSession)
- Behaviors.same
- }
- .receiveSignal {
- case (_, _: PostStop) =>
- silenceTimer.cancel()
- chatService ! ChatService.LeaveAllChannels(chatServiceAdapter)
- Behaviors.same
- case _ =>
- Behaviors.same
- }
- }
-
- def commandFly(contents: String, recipient: String, sendTo: ActorRef[SessionActor.Command]): Unit = {
- val (token, flying) = contents match {
- case "on" => (contents, true)
- case "off" => (contents, false)
- case _ => ("off", false)
- }
- sendTo ! SessionActor.SetFlying(flying)
- sendTo ! SessionActor.SendResponse(ChatMsg(ChatMessageType.CMT_FLY, wideContents=false, recipient, token, None))
- }
-
- def commandWatermark(contents: String, sendTo: ActorRef[SessionActor.Command]): Unit = {
- val connectionState =
- if (contents.contains("40 80")) 100
- else if (contents.contains("120 200")) 25
- else 50
- sendTo ! SessionActor.SetConnectionState(connectionState)
- }
-
- def commandSpeed(message: ChatMsg, contents: String, sendTo: ActorRef[SessionActor.Command]): Unit = {
- val speed =
- try {
- contents.toFloat
- } catch {
- case _: Throwable =>
- 1f
- }
- sendTo ! SessionActor.SetSpeed(speed)
- sendTo ! SessionActor.SendResponse(message.copy(contents = f"$speed%.3f"))
- }
-
- def commandToggleSpectatorMode(session: Session, contents: String, sendTo: ActorRef[SessionActor.Command]): Unit = {
- val currentSpectatorActivation = session.player.spectator
- contents.toLowerCase() match {
- case "on" | "o" | "" if !currentSpectatorActivation =>
- sendTo ! SessionActor.SetMode(SessionSpectatorMode)
- case "off" | "of" if currentSpectatorActivation =>
- sendTo ! SessionActor.SetMode(SessionNormalMode)
- case _ => ()
- }
- }
-
- def commandRecall(session: Session, sendTo: ActorRef[SessionActor.Command]): Unit = {
- val player = session.player
- 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(player.Faction) =>
- Some("You can't recall to your sanctuary when you are already in your sanctuary")
- case _ if !player.isAlive || session.deadState != DeadState.Alive =>
- Some(if (player.isAlive) "@norecall_deconstructing" else "@norecall_dead")
- case _ if player.VehicleSeated.nonEmpty =>
- Some("@norecall_invehicle")
- case _ =>
- None
- }
- errorMessage match {
- case Some(errorMessage) =>
- sendTo ! SessionActor.SendResponse(ChatMsg(CMT_QUIT, errorMessage))
- case None =>
- sendTo ! SessionActor.Recall()
- }
- }
-
- def commandInstantAction(session: Session, sendTo: ActorRef[SessionActor.Command]): Unit = {
- val player = session.player
- if (session.zoningType == Zoning.Method.Quit) {
- sendTo ! SessionActor.SendResponse(ChatMsg(CMT_QUIT, "You can't instant action while quitting."))
- } else if (session.zoningType == Zoning.Method.InstantAction) {
- sendTo ! SessionActor.SendResponse(ChatMsg(CMT_QUIT, "@noinstantaction_instantactionting"))
- } else if (session.zoningType == Zoning.Method.Recall) {
- sendTo ! SessionActor.SendResponse(
- ChatMsg(CMT_QUIT, "You won't instant action. You already requested to recall to your sanctuary continent")
- )
- } else if (!player.isAlive || session.deadState != DeadState.Alive) {
- if (player.isAlive) {
- sendTo ! SessionActor.SendResponse(ChatMsg(CMT_QUIT, "@noinstantaction_deconstructing"))
- } else {
- sendTo ! SessionActor.SendResponse(ChatMsg(CMT_QUIT, "@noinstantaction_dead"))
- }
- } else if (player.VehicleSeated.nonEmpty) {
- sendTo ! SessionActor.SendResponse(ChatMsg(CMT_QUIT, "@noinstantaction_invehicle"))
- } else {
- sendTo ! SessionActor.InstantAction()
- }
- }
-
- def commandQuit(session: Session, sendTo: ActorRef[SessionActor.Command]): Unit = {
- val player = session.player
- if (session.zoningType == Zoning.Method.Quit) {
- sendTo ! SessionActor.SendResponse(ChatMsg(CMT_QUIT, "@noquit_quitting"))
- } else if (!player.isAlive || session.deadState != DeadState.Alive) {
- if (player.isAlive) {
- sendTo ! SessionActor.SendResponse(ChatMsg(CMT_QUIT, "@noquit_deconstructing"))
- } else {
- sendTo ! SessionActor.SendResponse(ChatMsg(CMT_QUIT, "@noquit_dead"))
- }
- } else if (player.VehicleSeated.nonEmpty) {
- sendTo ! SessionActor.SendResponse(ChatMsg(CMT_QUIT, "@noquit_invehicle"))
- } else {
- sendTo ! SessionActor.Quit()
- }
- }
-
- def commandSuicide(session: Session, sendTo: ActorRef[SessionActor.Command]): Unit = {
- if (session.player.isAlive && session.deadState != DeadState.Release) {
- sendTo ! SessionActor.Suicide()
- }
- }
-
- def commandDestroy(session: Session, message: ChatMsg, contents: String, sendTo: ActorRef[SessionActor.Command]): Unit = {
- 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 _ =>
- // FIXME we shouldn't do it like that
- sendTo.toClassic ! RequestDestroyMessage(PlanetSideGUID(guid))
- }
- sendTo ! SessionActor.SendResponse(message)
- }
-
- def commandSetBaseResources(session: Session, contents: String, sendTo: ActorRef[SessionActor.Command]): Unit = {
- val buffer = cliTokenization(contents)
- val customNtuValue = buffer.lift(1) match {
- case Some(x) if x.toIntOption.nonEmpty => Some(x.toInt)
- case _ => None
- }
- val silos = {
- val position = session.player.Position
- session.zone.Buildings.values
- .filter { building =>
- val soi2 = building.Definition.SOIRadius * building.Definition.SOIRadius
- Vector3.DistanceSquared(building.Position, position) < soi2
- }
- }
- .flatMap { building => building.Amenities.filter { _.isInstanceOf[ResourceSilo] } }
- ChatActor.setBaseResources(sendTo, customNtuValue, silos, debugContent="")
- }
-
- def commandZoneLock(contents: String, cluster: ActorRef[InterstellarClusterService.Command]): Unit = {
- val buffer = cliTokenization(contents)
- val (zoneOpt, lockVal) = (buffer.lift(1), buffer.lift(2)) match {
- case (Some(x), Some(y)) =>
- val zone = if (x.toIntOption.nonEmpty) {
- val xInt = x.toInt
- Zones.zones.find(_.Number == xInt)
- } else {
- Zones.zones.find(z => z.id.equals(x))
- }
- val value = if (y.toIntOption.nonEmpty && y.toInt == 0) {
- 0
- } else {
- 1
- }
- (zone, Some(value))
- case _ =>
- (None, None)
- }
- (zoneOpt, lockVal) match {
- case (Some(zone), Some(lock)) if zone.map.cavern =>
- //caverns must be rotated in an order
- if (lock == 0) {
- cluster ! InterstellarClusterService.CavernRotation(CavernRotationService.HurryRotationToZoneUnlock(zone.id))
- } else {
- cluster ! InterstellarClusterService.CavernRotation(CavernRotationService.HurryRotationToZoneLock(zone.id))
- }
- case (Some(_), Some(_)) =>
- //normal zones can lock when all facilities and towers on it belong to the same faction
- //normal zones can lock when ???
- case _ => ()
- }
- }
-
- def commandZoneRotate(cluster: ActorRef[InterstellarClusterService.Command]): Unit = {
- cluster ! InterstellarClusterService.CavernRotation(CavernRotationService.HurryNextRotation)
- }
-
- def commandCaptureBase(session: Session, message: ChatMsg, contents: String, sendTo: ActorRef[SessionActor.Command]): Unit = {
- val buffer = cliTokenization(contents).take(3)
- //walk through the param buffer
- val (foundFacilities, foundFacilitiesTag, factionBuffer) = firstParam(session, buffer, captureBaseParamFacilities)
- val (foundFaction, foundFactionTag, timerBuffer) = firstParam(session, factionBuffer, captureBaseParamFaction)
- val (foundTimer, foundTimerTag, _) = firstParam(session, timerBuffer, captureBaseParamTimer)
- //resolve issues with the initial params
- var facilityError: Int = 0
- var factionError: Boolean = false
- var timerError: Boolean = false
- var usageMessage: Boolean = false
- val resolvedFacilities = foundFacilities
- .orElse {
- if (foundFacilitiesTag.nonEmpty) {
- if (foundFaction.isEmpty) {
- /* /capturebase OR /capturebase */
- //malformed facility tag error
- facilityError = 2
- None
- } else if (!foundFacilitiesTag.contains("curr")) { //did we do this next check already
- /* /capturebase , potentially */
- val buildings = captureBaseCurrSoi(session)
- if (buildings.nonEmpty) {
- //convert facilities to faction
- Some(buildings.toSeq)
- } else {
- //no facilities error
- facilityError = 1
- None
- }
- } else {
- //no facilities error
- facilityError = 1
- None
- }
- } else {
- //no params; post command usage reminder
- usageMessage = true
- None
- }
- }
- val resolvedFaction = foundFaction
- .orElse {
- if (resolvedFacilities.nonEmpty) {
- /* /capturebase OR /capturebase */
- if (foundFactionTag.isEmpty || foundTimer.nonEmpty) {
- //convert facilities to OUR PLAYER'S faction
- Some(session.player.Faction)
- } else {
- //malformed faction tag error
- factionError = true
- None
- }
- } else {
- //incorrect params; already posted an error message
- None
- }
- }
- val resolvedTimer = foundTimer
- .orElse {
- //todo stop command execution? post command usage reminder?
- if (resolvedFaction.nonEmpty && foundTimerTag.nonEmpty) {
- /* /capturebase > > */
- //malformed timer tag error
- timerError = true
- None
- } else {
- //eh
- Some(1)
- }
- }
- //evaluate results
- (resolvedFacilities, resolvedFaction, resolvedTimer) match {
- case (Some(buildings), Some(faction), Some(_)) =>
- buildings.foreach { building =>
- //TODO implement timer
- val terminal = building.CaptureTerminal.get
- val zone = building.Zone
- val zoneActor = zone.actor
- val buildingActor = building.Actor
- //clear any previous hack
- if (building.CaptureTerminalIsHacked) {
- zone.LocalEvents ! LocalServiceMessage(
- zone.id,
- LocalAction.ResecureCaptureTerminal(terminal, PlayerSource.Nobody)
- )
- }
- //push any updates this might cause
- zoneActor ! ZoneActor.ZoneMapUpdate()
- //convert faction affiliation
- buildingActor ! BuildingActor.SetFaction(faction)
- buildingActor ! BuildingActor.AmenityStateChange(terminal, Some(false))
- //push for map updates again
- zoneActor ! ZoneActor.ZoneMapUpdate()
- }
- case _ =>
- if (usageMessage) {
- sendTo ! SessionActor.SendResponse(
- message.copy(messageType = UNK_229, contents = "@CMT_CAPTUREBASE_usage")
- )
- } else {
- val msg = if (facilityError == 1) { "can not contextually determine building target" }
- else if (facilityError == 2) { s"\'${foundFacilitiesTag.get}\' is not a valid building name" }
- else if (factionError) { s"\'${foundFactionTag.get}\' is not a valid faction designation" }
- else if (timerError) { s"\'${foundTimerTag.get}\' is not a valid timer value" }
- else { "malformed params; check usage" }
- sendTo ! SessionActor.SendResponse(ChatMsg(UNK_229, wideContents=true, "", s"\\#FF4040ERROR - $msg", None))
- }
- }
- }
-
- def commandVoice(session: Session, message: ChatMsg, contents: String, chatService: ActorRef[ChatService.Command]): Unit = {
- // SH prefix are tactical voice macros only sent to squad
- if (contents.startsWith("SH")) {
- channels.foreach {
- case _/*channel*/: SquadChannel =>
- commandSendToRecipient(session, message, chatService)
- case _ => ()
- }
- } else {
- commandSendToRecipient(session, message, chatService)
- }
- }
-
- def commandTellOrIgnore(session: Session, message: ChatMsg, chatService: ActorRef[ChatService.Command], sendTo: ActorRef[SessionActor.Command]): Unit = {
- if (AvatarActor.onlineIfNotIgnored(message.recipient, session.avatar.name)) {
- commandSend(session, message, chatService)
- } else if (AvatarActor.getLiveAvatarForFunc(message.recipient, (_,_,_)=>{}).isEmpty) {
- sendTo ! SessionActor.SendResponse(
- ChatMsg(ChatMessageType.UNK_45, wideContents=false, "none", "@notell_target", None)
- )
- } else {
- sendTo ! SessionActor.SendResponse(
- ChatMsg(ChatMessageType.UNK_45, wideContents=false, "none", "@notell_ignore", None)
- )
- }
- }
-
- def commandSquad(session: Session, message: ChatMsg, chatService: ActorRef[ChatService.Command]): Unit = {
- channels.foreach {
- case _/*channel*/: SquadChannel =>
- commandSendToRecipient(session, message, chatService)
- case _ => ()
- }
- }
-
- def commandWho(session: Session, sendTo: ActorRef[SessionActor.Command]): Unit = {
- 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)
- if (popNC + popTR + popVS == 0) {
- sendTo ! SessionActor.SendResponse(ChatMsg(ChatMessageType.CMT_WHO, "@Nomatches"))
- } else {
- val contName = session.zone.map.name
- sendTo ! SessionActor.SendResponse(
- ChatMsg(ChatMessageType.CMT_WHO, wideContents=true, "", "That command doesn't work for now, but : ", None)
- )
- sendTo ! SessionActor.SendResponse(
- ChatMsg(ChatMessageType.CMT_WHO, wideContents=true, "", "NC online : " + popNC + " on " + contName, None)
- )
- sendTo ! SessionActor.SendResponse(
- ChatMsg(ChatMessageType.CMT_WHO, wideContents=true, "", "TR online : " + popTR + " on " + contName, None)
- )
- sendTo ! SessionActor.SendResponse(
- ChatMsg(ChatMessageType.CMT_WHO, wideContents=true, "", "VS online : " + popVS + " on " + contName, None)
- )
- }
- }
-
- def commandZone(message: ChatMsg, contents: String, sendTo: ActorRef[SessionActor.Command]): Unit = {
- val buffer = cliTokenization(contents)
- val (zone, gate, list) = (buffer.headOption, 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) =>
- sendTo ! SessionActor.SendResponse(ChatMsg(UNK_229, wideContents=true, "", PointOfInterest.list, None))
- case (Some(zone), None, true) =>
- sendTo ! SessionActor.SendResponse(
- ChatMsg(UNK_229, wideContents=true, "", PointOfInterest.listWarpgates(zone), None)
- )
- case (Some(zone), Some(gate), false) =>
- sendTo ! SessionActor.SetZone(zone.zonename, gate)
- case (_, None, false) =>
- sendTo ! SessionActor.SendResponse(
- ChatMsg(UNK_229, wideContents=true, "", "Gate id not defined (use '/zone -list')", None)
- )
- case (_, _, _) if buffer.isEmpty || buffer.headOption.contains("-help") =>
- sendTo ! SessionActor.SendResponse(
- message.copy(messageType = UNK_229, contents = "@CMT_ZONE_usage")
- )
- case _ => ()
- }
- }
-
- def commandWarp(session: Session, message: ChatMsg, contents: String, sendTo: ActorRef[SessionActor.Command]): Unit = {
- val buffer = cliTokenization(contents)
- val (coordinates, waypoint) = (buffer.headOption, 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) if waypoint.nonEmpty => (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
- } =>
- sendTo ! SessionActor.SetPosition(Vector3(x.toFloat, y.toFloat, z.toFloat))
- case (None, Some(waypoint)) if waypoint == "-list" =>
- val zone = PointOfInterest.get(session.player.Zone.id)
- zone match {
- case Some(zone: PointOfInterest) =>
- sendTo ! SessionActor.SendResponse(
- ChatMsg(UNK_229, wideContents=true, "", PointOfInterest.listAll(zone), None)
- )
- case _ =>
- sendTo ! SessionActor.SendResponse(
- ChatMsg(UNK_229, wideContents=true, "", s"unknown player zone '${session.player.Zone.id}'", None)
- )
- }
- case (None, Some(waypoint)) if waypoint != "-help" =>
- PointOfInterest.getWarpLocation(session.zone.id, waypoint) match {
- case Some(location) =>
- sendTo ! SessionActor.SetPosition(location)
- case None =>
- sendTo ! SessionActor.SendResponse(
- ChatMsg(UNK_229, wideContents=true, "", s"unknown location '$waypoint'", None)
- )
- }
- case _ =>
- sendTo ! SessionActor.SendResponse(
- message.copy(messageType = UNK_229, contents = "@CMT_WARP_usage")
- )
- }
- }
-
- def commandSetBattleRank(session: Session, message: ChatMsg, contents: String, sendTo: ActorRef[SessionActor.Command]): Unit = {
- if (!setBattleRank(session, cliTokenization(contents), AvatarActor.SetBep, avatarActor)) {
- sendTo ! SessionActor.SendResponse(
- message.copy(messageType = UNK_229, contents = "@CMT_SETBATTLERANK_usage")
- )
- }
- }
-
- def commandSetCommandRank(session: Session, message: ChatMsg, contents: String, sendTo: ActorRef[SessionActor.Command]): Unit = {
- if (!setCommandRank(contents, session, avatarActor)) {
- sendTo ! SessionActor.SendResponse(
- message.copy(messageType = UNK_229, contents = "@CMT_SETCOMMANDRANK_usage")
- )
- }
- }
-
- def commandAddBattleExperience(message: ChatMsg, contents: String, sendTo: ActorRef[SessionActor.Command]): Unit = {
- contents.toIntOption match {
- case Some(bep) => avatarActor ! AvatarActor.AwardBep(bep, ExperienceType.Normal)
- case None =>
- sendTo ! SessionActor.SendResponse(
- message.copy(messageType = UNK_229, contents = "@CMT_ADDBATTLEEXPERIENCE_usage")
- )
- }
- }
-
- def commandAddCommandExperience(message: ChatMsg, contents: String, sendTo: ActorRef[SessionActor.Command]): Unit = {
- contents.toIntOption match {
- case Some(cep) => avatarActor ! AvatarActor.AwardCep(cep)
- case None =>
- sendTo ! SessionActor.SendResponse(
- message.copy(messageType = UNK_229, contents = "@CMT_ADDCOMMANDEXPERIENCE_usage")
- )
- }
- }
-
- def commandToggleHat(session: Session, message: ChatMsg, contents: String, avatarSendTo: ActorRef[AvatarActor.Command], sendTo: ActorRef[SessionActor.Command]): Unit = {
- val cosmetics = session.avatar.decoration.cosmetics.getOrElse(Set())
- val nextCosmetics = contents match {
- case "off" =>
- cosmetics.diff(Set(Cosmetic.BrimmedCap, Cosmetic.Beret))
- case _ =>
- if (cosmetics.contains(Cosmetic.BrimmedCap)) {
- cosmetics.diff(Set(Cosmetic.BrimmedCap)) + Cosmetic.Beret
- } else if (cosmetics.contains(Cosmetic.Beret)) {
- cosmetics.diff(Set(Cosmetic.BrimmedCap, Cosmetic.Beret))
- } else {
- cosmetics + Cosmetic.BrimmedCap
- }
- }
- val on = nextCosmetics.contains(Cosmetic.BrimmedCap) || nextCosmetics.contains(Cosmetic.Beret)
- avatarSendTo ! AvatarActor.SetCosmetics(nextCosmetics)
- sendTo ! SessionActor.SendResponse(
- message.copy(
- messageType = UNK_229,
- contents = s"@CMT_TOGGLE_HAT_${if (on) "on" else "off"}"
- )
- )
- }
-
- def commandToggleCosmetics(session: Session, message: ChatMsg, contents: String, avatarSendTo: ActorRef[AvatarActor.Command], sendTo: ActorRef[SessionActor.Command]): Unit = {
- val cosmetics = session.avatar.decoration.cosmetics.getOrElse(Set())
- val cosmetic = message.messageType match {
- case ChatMessageType.CMT_HIDE_HELMET => Cosmetic.NoHelmet
- case ChatMessageType.CMT_TOGGLE_SHADES => Cosmetic.Sunglasses
- case ChatMessageType.CMT_TOGGLE_EARPIECE => Cosmetic.Earpiece
- case _ => null
- }
- val on = contents match {
- case "on" => true
- case "off" => false
- case _ => !cosmetics.contains(cosmetic)
- }
- avatarSendTo ! AvatarActor.SetCosmetics(
- if (on) cosmetics + cosmetic
- else cosmetics.diff(Set(cosmetic))
- )
- sendTo ! SessionActor.SendResponse(
- message.copy(
- messageType = UNK_229,
- contents = s"@${message.messageType.toString}_${if (on) "on" else "off"}"
- )
- )
- }
-
- def commandAddCertification(session: Session, message: ChatMsg, contents: String, avatarSendTo: ActorRef[AvatarActor.Command], sendTo: ActorRef[SessionActor.Command]): Unit = {
- val certs = cliTokenization(contents).map(name => Certification.values.find(_.name == name))
- val result = if (certs.nonEmpty) {
- if (certs.contains(None)) {
- s"@AckErrorCertifications"
- } else {
- avatarSendTo ! AvatarActor.SetCertifications(session.avatar.certifications ++ certs.flatten)
- s"@AckSuccessCertifications"
- }
- } else {
- if (session.avatar.certifications.size < Certification.values.size) {
- avatarSendTo ! AvatarActor.SetCertifications(Certification.values.toSet)
- } else {
- avatarSendTo ! AvatarActor.SetCertifications(Certification.values.filter(_.cost == 0).toSet)
- }
- s"@AckSuccessCertifications"
- }
- sendTo ! SessionActor.SendResponse(message.copy(messageType = UNK_229, contents = result))
- }
-
- def commandKick(session: Session, message: ChatMsg, contents: String, sendTo: ActorRef[SessionActor.Command]): Unit = {
- val inputs = cliTokenization(contents)
- inputs.headOption match {
- case Some(input) =>
- val determination: Player => Boolean = input.toLongOption match {
- case Some(id) => _.CharId == id
- case _ => _.Name.equals(input)
- }
- session.zone.LivePlayers
- .find(determination)
- .orElse(session.zone.Corpses.find(determination)) match {
- case Some(player) =>
- inputs.lift(1).map(_.toLongOption) match {
- case Some(Some(time)) =>
- sendTo ! SessionActor.Kick(player, Some(time))
- case _ =>
- sendTo ! SessionActor.Kick(player)
- }
- sendTo ! SessionActor.SendResponse(message.copy(messageType = UNK_229, recipient = "Server", contents = "@kick_i"))
- case None =>
- sendTo ! SessionActor.SendResponse(message.copy(messageType = UNK_229, recipient = "Server", contents = "@kick_o"))
- }
- case None =>
- sendTo ! SessionActor.SendResponse(message.copy(messageType = UNK_229, recipient = "Server", contents = "@kick_o"))
- }
- }
-
- def commandIncomingSendAllIfOnline(session: Session, message: ChatMsg, sendTo: ActorRef[SessionActor.Command]): Unit = {
- if (AvatarActor.onlineIfNotIgnored(session.avatar, message.recipient)) {
- sendTo ! SessionActor.SendResponse(message)
- }
- }
-
- def commandIncomingSendToLocalIfOnline(session: Session, fromSession: Session, message: ChatMsg, sendTo: ActorRef[SessionActor.Command]): Unit = {
- if (
- session.zone == fromSession.zone &&
- Vector3.DistanceSquared(session.player.Position, fromSession.player.Position) < 625 &&
- session.player.Faction == fromSession.player.Faction &&
- AvatarActor.onlineIfNotIgnored(session.avatar, message.recipient)
- ) {
- sendTo ! SessionActor.SendResponse(message)
- }
- }
-
- def commandIncomingVoice(session: Session, fromSession: Session, message: ChatMsg, sendTo: ActorRef[SessionActor.Command]): Unit = {
- if (
- (session.zone == fromSession.zone || message.contents.startsWith("SH")) && /*tactical squad voice macro*/
- Vector3.DistanceSquared(session.player.Position, fromSession.player.Position) < 1600
- ) {
- val name = fromSession.avatar.name
- if (!session.avatar.people.ignored.exists { f => f.name.equals(name) } ||
- {
- val id = fromSession.avatar.id.toLong
- val curr = System.currentTimeMillis()
- ignoredEmoteCooldown.get(id) match {
- case None =>
- ignoredEmoteCooldown.put(id, curr + 15000L)
- true
- case Some(time) if time < curr =>
- ignoredEmoteCooldown.put(id, curr + 15000L)
- true
- case _ =>
- false
- }}
- ) {
- sendTo ! SessionActor.SendResponse(message)
- }
- }
- }
-
- def commandIncomingSilence(session: Session, message: ChatMsg, sendTo: ActorRef[SessionActor.Command]): Unit = {
- val args = cliTokenization(message.contents)
- val (name, time) = (args.headOption, 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) {
- sendTo ! SessionActor.SetSilenced(false)
- sendTo ! SessionActor.SendResponse(
- ChatMsg(ChatMessageType.UNK_229, wideContents=true, "", "@silence_off", None)
- )
- if (!silenceTimer.isCancelled) silenceTimer.cancel()
- } else {
- sendTo ! SessionActor.SetSilenced(true)
- sendTo ! SessionActor.SendResponse(
- ChatMsg(ChatMessageType.UNK_229, wideContents=true, "", "@silence_on", None)
- )
- silenceTimer = context.system.scheduler.scheduleOnce(
- time minutes,
- () => {
- sendTo ! SessionActor.SetSilenced(false)
- sendTo ! SessionActor.SendResponse(
- ChatMsg(ChatMessageType.UNK_229, wideContents=true, "", "@silence_timeout", None)
- )
- }
- )
- }
- case (name, time) =>
- log.warn(s"Bad silence args $name $time")
- }
- }
-
- def commandIncomingSend(message: ChatMsg, sendTo: ActorRef[SessionActor.Command]): Unit = {
- sendTo ! SessionActor.SendResponse(message)
- }
-
- def commandSend(session: Session, message: ChatMsg, chatService: ActorRef[ChatService.Command]): Unit = {
- chatService ! ChatService.Message(
- session,
- message,
- DefaultChannel
- )
- }
-
- def commandSendToRecipient(session: Session, message: ChatMsg, chatService: ActorRef[ChatService.Command]): Unit = {
- chatService ! ChatService.Message(
- session,
- message.copy(recipient = session.player.Name),
- DefaultChannel
- )
- }
-
- final case class NormalFuncs(
- chatService: ActorRef[ChatService.Command],
- cluster: ActorRef[InterstellarClusterService.Command]
- ) extends ChatFuncs {
- def message(session: Session, message: ChatMsg): Unit = {
- import ChatMessageType._
- val gmCommandAllowed =
- session.account.gm || Config.app.development.unprivilegedGmCommands.contains(message.messageType)
- (message.messageType, message.recipient.trim, message.contents.trim) match {
- /** Messages starting with ! are custom chat commands */
- case (_, _, contents) if contents.startsWith("!") &&
- customCommandMessages(message, session) => ()
-
- case (CMT_FLY, recipient, contents) if gmCommandAllowed =>
- commandFly(contents, recipient, sessionActor)
-
- case (CMT_ANONYMOUS, _, _) =>
- // ?
-
- case (CMT_TOGGLE_GM, _, _) =>
- // ?
-
- case (CMT_CULLWATERMARK, _, contents) =>
- commandWatermark(contents, sessionActor)
-
- case (CMT_SPEED, _, contents) if gmCommandAllowed =>
- commandSpeed(message, contents, sessionActor)
-
- case (CMT_TOGGLESPECTATORMODE, _, contents) if gmCommandAllowed =>
- commandToggleSpectatorMode(session, contents, sessionActor)
-
- case (CMT_RECALL, _, _) =>
- commandRecall(session, sessionActor)
-
- case (CMT_INSTANTACTION, _, _) =>
- commandInstantAction(session, sessionActor)
-
- case (CMT_QUIT, _, _) =>
- commandQuit(session, sessionActor)
-
- case (CMT_SUICIDE, _, _) =>
- commandSuicide(session, sessionActor)
-
- case (CMT_DESTROY, _, contents) if contents.matches("\\d+") =>
- commandDestroy(session, message, contents, sessionActor)
-
- case (CMT_SETBASERESOURCES, _, contents) if gmCommandAllowed =>
- commandSetBaseResources(session, contents, sessionActor)
-
- case (CMT_ZONELOCK, _, contents) if gmCommandAllowed =>
- commandZoneLock(contents, cluster)
-
- case (U_CMT_ZONEROTATE, _, _) if gmCommandAllowed =>
- commandZoneRotate(cluster)
-
- case (CMT_CAPTUREBASE, _, contents) if gmCommandAllowed =>
- commandCaptureBase(session, message, contents, sessionActor)
-
- case (CMT_GMBROADCAST | CMT_GMBROADCAST_NC | CMT_GMBROADCAST_VS | CMT_GMBROADCAST_TR, _, _)
- if gmCommandAllowed =>
- commandSendToRecipient(session, message, chatService)
-
- case (CMT_GMTELL, _, _) if gmCommandAllowed =>
- commandSend(session, message, chatService)
-
- case (CMT_GMBROADCASTPOPUP, _, _) if gmCommandAllowed =>
- commandSendToRecipient(session, message, chatService)
-
- case (CMT_OPEN, _, _) if !session.player.silenced =>
- commandSendToRecipient(session, message, chatService)
-
- case (CMT_VOICE, _, contents) =>
- commandVoice(session, message, contents, chatService)
-
- case (CMT_TELL, _, _) if !session.player.silenced =>
- commandTellOrIgnore(session, message, chatService, sessionActor)
-
- case (CMT_BROADCAST, _, _) if !session.player.silenced =>
- commandSendToRecipient(session, message, chatService)
-
- case (CMT_PLATOON, _, _) if !session.player.silenced =>
- commandSendToRecipient(session, message, chatService)
-
- case (CMT_COMMAND, _, _) if gmCommandAllowed =>
- commandSendToRecipient(session, message, chatService)
-
- case (CMT_NOTE, _, _) =>
- commandSend(session, message, chatService)
-
- case (CMT_SILENCE, _, _) if gmCommandAllowed =>
- commandSend(session, message, chatService)
-
- case (CMT_SQUAD, _, _) =>
- commandSquad(session, message, chatService)
-
- case (CMT_WHO | CMT_WHO_CSR | CMT_WHO_CR | CMT_WHO_PLATOONLEADERS | CMT_WHO_SQUADLEADERS | CMT_WHO_TEAMS, _, _) =>
- commandWho(session, sessionActor)
-
- case (CMT_ZONE, _, contents) if gmCommandAllowed =>
- commandZone(message, contents, sessionActor)
-
- case (CMT_WARP, _, contents) if gmCommandAllowed =>
- commandWarp(session, message, contents, sessionActor)
-
- case (CMT_SETBATTLERANK, _, contents) if gmCommandAllowed =>
- commandSetBattleRank(session, message, contents, sessionActor)
-
- case (CMT_SETCOMMANDRANK, _, contents) if gmCommandAllowed =>
- commandSetCommandRank(session, message, contents, sessionActor)
-
- case (CMT_ADDBATTLEEXPERIENCE, _, contents) if gmCommandAllowed =>
- commandAddBattleExperience(message, contents, sessionActor)
-
- case (CMT_ADDCOMMANDEXPERIENCE, _, contents) if gmCommandAllowed =>
- commandAddCommandExperience(message, contents, sessionActor)
-
- case (CMT_TOGGLE_HAT, _, contents) =>
- commandToggleHat(session, message, contents, avatarActor, sessionActor)
-
- case (CMT_HIDE_HELMET | CMT_TOGGLE_SHADES | CMT_TOGGLE_EARPIECE, _, contents) =>
- commandToggleCosmetics(session, message, contents, avatarActor, sessionActor)
-
- case (CMT_ADDCERTIFICATION, _, contents) if gmCommandAllowed =>
- commandAddCertification(session, message, contents, avatarActor, sessionActor)
-
- case (CMT_KICK, _, contents) if gmCommandAllowed =>
- commandKick(session, message, contents, sessionActor)
-
- case _ =>
- log.warn(s"Unhandled chat message $message")
- }
- }
-
- def incoming(session: Session, message: ChatMsg, fromSession: Session): Unit = {
- import ChatMessageType._
- message.messageType match {
- case CMT_BROADCAST | CMT_SQUAD | CMT_PLATOON | CMT_COMMAND | CMT_NOTE =>
- commandIncomingSendAllIfOnline(session, message, sessionActor)
-
- case CMT_OPEN =>
- commandIncomingSendToLocalIfOnline(session, fromSession, message, sessionActor)
-
- case CMT_TELL | U_CMT_TELLFROM |
- CMT_GMOPEN | CMT_GMBROADCAST | CMT_GMBROADCAST_NC | CMT_GMBROADCAST_TR | CMT_GMBROADCAST_VS |
- CMT_GMBROADCASTPOPUP | CMT_GMTELL | U_CMT_GMTELLFROM | UNK_45 | UNK_71 | UNK_227 | UNK_229 =>
- commandIncomingSend(message, sessionActor)
-
- case CMT_VOICE =>
- commandIncomingVoice(session, fromSession, message, sessionActor)
-
- case CMT_SILENCE =>
- commandIncomingSilence(session, message, sessionActor)
-
- case _ =>
- log.warn(s"Unexpected messageType $message")
- }
- }
-
- private def customCommandMessages(
- message: ChatMsg,
- session: Session
- ): Boolean = {
- val contents = message.contents
- if (contents.startsWith("!")) {
- val (command, params) = cliTokenization(contents.drop(1)) match {
- case a :: b => (a, b)
- case _ => ("", Seq(""))
- }
- val gmBangCommandAllowed = session.account.gm || Config.app.development.unprivilegedGmBangCommands.contains(command)
- //try gm commands
- val tryGmCommandResult = if (gmBangCommandAllowed) {
- command match {
- case "whitetext" => Some(customCommandWhitetext(session, params, chatService))
- case "list" => Some(customCommandList(session, params, message, sessionActor))
- case "ntu" => Some(customCommandNtu(session, params, sessionActor))
- case "zonerotate" => Some(customCommandZonerotate(params, cluster, sessionActor))
- case "nearby" => Some(customCommandNearby(session, sessionActor))
- case _ => None
- }
- } else {
- None
- }
- //try commands for all players if not caught as a gm command
- val result = tryGmCommandResult match {
- case None =>
- command match {
- case "loc" => customCommandLoc(session, message, sessionActor)
- case "suicide" => customCommandSuicide(session)
- case "grenade" => customCommandGrenade(session, log)
- case "macro" => customCommandMacro(session, params, sessionActor)
- case "progress" => customCommandProgress(session, params, avatarActor)
- case _ => false
- }
- case Some(out) =>
- out
- }
- if (!result) {
- // command was not handled
- sessionActor ! SessionActor.SendResponse(
- ChatMsg(
- CMT_GMOPEN, // CMT_GMTELL
- message.wideContents,
- "Server",
- s"Unknown command !$command",
- message.note
- )
- )
- }
- result
- } else {
- false // not a handled command
- }
- }
- }
-
- final case class SpectatorFuncs(
- chatService: ActorRef[ChatService.Command],
- cluster: ActorRef[InterstellarClusterService.Command]
- ) extends ChatFuncs {
- def message(session: Session, message: ChatMsg): Unit = {
- import ChatMessageType._
- (message.messageType, message.recipient.trim, message.contents.trim) match {
- /** Messages starting with ! are custom chat commands */
- case (_, _, contents) if contents.startsWith("!") &&
- customCommandMessages(message, session) => ()
-
- case (CMT_FLY, recipient, contents) =>
- commandFly(contents, recipient, sessionActor)
-
- case (CMT_ANONYMOUS, _, _) =>
- // ?
-
- case (CMT_TOGGLE_GM, _, _) =>
- // ?
-
- case (CMT_CULLWATERMARK, _, contents) =>
- commandWatermark(contents, sessionActor)
-
- case (CMT_SPEED, _, contents) =>
- commandSpeed(message, contents, sessionActor)
-
- case (CMT_TOGGLESPECTATORMODE, _, contents) =>
- commandToggleSpectatorMode(session, contents, sessionActor)
-
- case (CMT_RECALL, _, _) =>
- commandRecall(session, sessionActor)
-
- case (CMT_QUIT, _, _) =>
- commandQuit(session, sessionActor)
-
- case (CMT_SUICIDE, _, _) =>
- commandSuicide(session, sessionActor)
-
- case (CMT_GMTELL, _, _) =>
- commandSend(session, message, chatService)
-
- case (CMT_NOTE, _, _) =>
- commandSend(session, message, chatService)
-
- case (CMT_WHO | CMT_WHO_CSR | CMT_WHO_CR | CMT_WHO_PLATOONLEADERS | CMT_WHO_SQUADLEADERS | CMT_WHO_TEAMS, _, _) =>
- commandWho(session, sessionActor)
-
- case (CMT_ZONE, _, contents) =>
- commandZone(message, contents, sessionActor)
-
- case (CMT_WARP, _, contents) =>
- commandWarp(session, message, contents, sessionActor)
-
- case _ => ()
- }
- }
-
- def incoming(session: Session, message: ChatMsg, fromSession: Session): Unit = {
- import ChatMessageType._
- message.messageType match {
- case CMT_BROADCAST | CMT_SQUAD | CMT_PLATOON | CMT_COMMAND | CMT_NOTE =>
- commandIncomingSendAllIfOnline(session, message, sessionActor)
-
- case CMT_OPEN =>
- commandIncomingSendToLocalIfOnline(session, fromSession, message, sessionActor)
-
- case CMT_TELL | U_CMT_TELLFROM |
- CMT_GMOPEN | CMT_GMBROADCAST | CMT_GMBROADCAST_NC | CMT_GMBROADCAST_TR | CMT_GMBROADCAST_VS |
- CMT_GMBROADCASTPOPUP | CMT_GMTELL | U_CMT_GMTELLFROM | UNK_45 | UNK_71 | UNK_227 | UNK_229 =>
- commandIncomingSend(message, sessionActor)
-
- case CMT_SILENCE =>
- commandIncomingSilence(session, message, sessionActor)
-
- case _ => ()
- }
- }
-
- private def customCommandMessages(
- message: ChatMsg,
- session: Session
- ): Boolean = {
- val contents = message.contents
- if (contents.startsWith("!")) {
- val (command, params) = cliTokenization(contents.drop(1)) match {
- case a :: b => (a, b)
- case _ => ("", Seq(""))
- }
- command match {
- case "list" => customCommandList(session, params, message, sessionActor)
- case "nearby" => customCommandNearby(session, sessionActor)
- case "loc" => customCommandLoc(session, message, sessionActor)
- case "macro" => customCommandMacro(session, params, sessionActor)
- case _ => false
- }
- } else {
- false
- }
- }
- }
-
- case object NormalMode extends ChatMode {
- def init(chatService: ActorRef[ChatService.Command], cluster: ActorRef[InterstellarClusterService.Command]): ChatFuncs = {
- NormalFuncs(chatService, cluster)
- }
- }
-
- case object SpectatorMode extends ChatMode {
- def init(chatService: ActorRef[ChatService.Command], cluster: ActorRef[InterstellarClusterService.Command]): ChatFuncs = {
- SpectatorFuncs(chatService, cluster)
- }
- }
-}
-
diff --git a/src/main/scala/net/psforever/actors/session/normal/ChatLogic.scala b/src/main/scala/net/psforever/actors/session/normal/ChatLogic.scala
index 419dadd8c..dc599e017 100644
--- a/src/main/scala/net/psforever/actors/session/normal/ChatLogic.scala
+++ b/src/main/scala/net/psforever/actors/session/normal/ChatLogic.scala
@@ -45,7 +45,7 @@ class ChatLogic(val ops: ChatOperations, implicit val context: ActorContext) ext
case (CMT_SPEED, _, contents) if gmCommandAllowed =>
ops.commandSpeed(message, contents)
- case (CMT_TOGGLESPECTATORMODE, _, contents) if isAlive /*&& (gmCommandAllowed || perms.canSpectate)*/ =>
+ case (CMT_TOGGLESPECTATORMODE, _, contents) if isAlive && (gmCommandAllowed || perms.canSpectate) =>
ops.commandToggleSpectatorMode(session, contents)
case (CMT_RECALL, _, _) =>
diff --git a/src/main/scala/net/psforever/actors/session/spectator/SpectatorMode.scala b/src/main/scala/net/psforever/actors/session/spectator/SpectatorMode.scala
index c1e720492..0c5fd9308 100644
--- a/src/main/scala/net/psforever/actors/session/spectator/SpectatorMode.scala
+++ b/src/main/scala/net/psforever/actors/session/spectator/SpectatorMode.scala
@@ -58,8 +58,6 @@ class SpectatorModeLogic(data: SessionData) extends ModeLogic {
val pguid = player.GUID
val sendResponse: PlanetSidePacket=>Unit = data.sendResponse
//
- sendResponse(ChatMsg(ChatMessageType.CMT_TOGGLESPECTATORMODE, "on"))
- sendResponse(ChatMsg(ChatMessageType.UNK_227, "@SpectatorEnabled"))
continent.actor ! ZoneActor.RemoveFromBlockMap(player)
continent
.GUID(data.terminals.usingMedicalTerminal)
@@ -95,10 +93,12 @@ class SpectatorModeLogic(data: SessionData) extends ModeLogic {
case (Some(obj: Vehicle), Some(seatNum)) if seatNum == 0 =>
data.vehicles.ServerVehicleOverrideStop(obj)
obj.Actor ! ServerObject.AttributeMsg(10, 3) //faction-accessible driver seat
- obj.Actor ! Mountable.TryDismount(player, seatNum)
+ obj.Seat(seatNum).foreach(_.unmount(player))
+ player.VehicleSeated = None
Some(ObjectCreateMessageParent(obj.GUID, seatNum))
case (Some(obj), Some(seatNum)) =>
- obj.Actor ! Mountable.TryDismount(player, seatNum)
+ obj.Seat(seatNum).foreach(_.unmount(player))
+ player.VehicleSeated = None
Some(ObjectCreateMessageParent(obj.GUID, seatNum))
case _ =>
None
@@ -148,6 +148,8 @@ class SpectatorModeLogic(data: SessionData) extends ModeLogic {
handheld.Definition.Packet.DetailedConstructorData(handheld).get
))
data.zoning.spawn.HandleSetCurrentAvatar(newPlayer)
+ sendResponse(ChatMsg(ChatMessageType.CMT_TOGGLESPECTATORMODE, "on"))
+ sendResponse(ChatMsg(ChatMessageType.UNK_227, "@SpectatorEnabled"))
data.session = session.copy(player = player)
}