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) }