mirror of
https://github.com/psforever/PSF-LoginServer.git
synced 2026-01-19 18:44:45 +00:00
ChatActor
This removes roughly 1k LOC from WorldSessionActor and moves them to a new ChatActor. That was the initial goal anyway, but it wasn't that simple. There was no clear location to put this new actor, I didn't want to put it in pslogin since it isn't part of the "login server" (and neither is WSA). But since the new actor would have to talk to WSA and common does not depend on pslogin, I had a choice of putting more actors in pslogin or putting everything in common. I chose the latter. ChatActor and SessionActor (formerly WorldSessionActor) now live in `common/actors/session`. Since WSA also depends on other actors in pslogin, most of the pslogin code was moved to either common/login or common/util. PsLogin as the main entry point remains in pslogin since having the main code compile to a library has some advantages, and it will allow us to produce binaries for distinct login/world servers in the future if desired. For a second take, I'd suggest moving common to /src in the root directory. This change is enabled by a new immutable `Zone` object that is passed from SessionActor to ChatActor. Most of its members are still mutable references, and the code at the moment does depend on this being the case. Changes to the session object in SessionActor are forwarded through a SetZone message to ChatActor. As we split out more code into actors, we could use EventBus or typed Topic's instead. Also included is a reworked ChatService that was converted to a typed actor and uses the built-in Receptionist facility for service discovery. By receiving the session object from ChatActor, it can be much smarter about who to send messages to, rather than sending all messages to everyone and having them figure it out. But as this session object is not updated, it can only use static properties like player name and faction and not fluid properties like position. The following chat commands were added: command, note, gmbroadcast, [nc|tr|vs|broadcast, gmtell, gmpopup and !whitetext
This commit is contained in:
parent
144804139f
commit
4634dffe00
|
|
@ -4,6 +4,7 @@ lazy val commonSettings = Seq(
|
|||
organization := "net.psforever",
|
||||
version := "1.0.2-SNAPSHOT",
|
||||
scalaVersion := "2.13.2",
|
||||
Global / cancelable := false,
|
||||
semanticdbEnabled := true,
|
||||
semanticdbVersion := scalafixSemanticdb.revision,
|
||||
scalacOptions := Seq(
|
||||
|
|
@ -110,7 +111,7 @@ lazy val pslogin = (project in file("pslogin"))
|
|||
// ActorTests have specific timing requirements and will be flaky if run in parallel
|
||||
parallelExecution in Test := false,
|
||||
// TODO(chord): remove exclusion when WorldSessionActor is refactored: https://github.com/psforever/PSF-LoginServer/issues/279
|
||||
coverageExcludedPackages := "net.psforever.pslogin.WorldSessionActor.*;net.psforever.pslogin.zonemaps.*",
|
||||
coverageExcludedPackages := "net.psforever.actors.session.SessionActor.*;net.psforever.zones.zonemaps.*",
|
||||
// Copy all tests from Test -> QuietTest (we're only changing the run options)
|
||||
inConfig(QuietTest)(Defaults.testTasks)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,755 @@
|
|||
package net.psforever.actors.session
|
||||
|
||||
import akka.actor.Cancellable
|
||||
import akka.actor.typed.receptionist.Receptionist
|
||||
import akka.actor.typed.{ActorRef, Behavior, PostStop, Signal}
|
||||
import akka.actor.typed.scaladsl.{AbstractBehavior, ActorContext, Behaviors}
|
||||
import net.psforever.objects.serverobject.pad.{VehicleSpawnControl, VehicleSpawnPad}
|
||||
import net.psforever.objects.{Default, GlobalDefinitions, Player, Session}
|
||||
import net.psforever.objects.serverobject.resourcesilo.ResourceSilo
|
||||
import net.psforever.objects.serverobject.turret.{FacilityTurret, TurretUpgrade, WeaponTurrets}
|
||||
import net.psforever.objects.zones.Zoning
|
||||
import net.psforever.packet.PacketCoding
|
||||
import net.psforever.packet.game.{ChatMsg, DeadState, RequestDestroyMessage, ZonePopulationUpdateMessage}
|
||||
import net.psforever.types.{ChatMessageType, PlanetSideEmpire, PlanetSideGUID, Vector3}
|
||||
import net.psforever.util.PointOfInterest
|
||||
import net.psforever.zones.Zones
|
||||
import services.chat.ChatService
|
||||
import services.chat.ChatService.ChatChannel
|
||||
import services.local.{LocalAction, LocalServiceMessage}
|
||||
|
||||
import scala.concurrent.duration._
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
|
||||
object ChatActor {
|
||||
def apply(sessionActor: ActorRef[SessionActor.Command]): Behavior[Command] =
|
||||
Behaviors.setup(context => new ChatActor(context, sessionActor))
|
||||
|
||||
sealed trait Command
|
||||
|
||||
final case class JoinChannel(channel: ChatChannel) extends Command
|
||||
final case class LeaveChannel(channel: ChatChannel) extends Command
|
||||
final case class Message(message: ChatMsg) extends Command
|
||||
final case class SetSession(session: Session) extends Command
|
||||
|
||||
private case class ListingResponse(listing: Receptionist.Listing) extends Command
|
||||
private case class IncomingMessage(session: Session, message: ChatMsg, channel: ChatChannel) extends Command
|
||||
}
|
||||
|
||||
class ChatActor(context: ActorContext[ChatActor.Command], sessionActor: ActorRef[SessionActor.Command])
|
||||
extends AbstractBehavior[ChatActor.Command](context) {
|
||||
import ChatActor._
|
||||
|
||||
private[this] val log = org.log4s.getLogger
|
||||
var channels: List[ChatChannel] = List()
|
||||
var session: Option[Session] = None
|
||||
var chatService: Option[ActorRef[ChatService.Command]] = None
|
||||
var silenceTimer: Cancellable = Default.Cancellable
|
||||
|
||||
val chatServiceAdapter: ActorRef[ChatService.MessageResponse] = context.messageAdapter[ChatService.MessageResponse] {
|
||||
case ChatService.MessageResponse(session, message, channel) => IncomingMessage(session, message, channel)
|
||||
}
|
||||
|
||||
override def onSignal: PartialFunction[Signal, Behavior[Command]] = {
|
||||
case PostStop =>
|
||||
silenceTimer.cancel()
|
||||
if (chatService.isDefined) chatService.get ! ChatService.LeaveAllChannels(chatServiceAdapter)
|
||||
this
|
||||
}
|
||||
|
||||
override def onMessage(msg: Command): Behavior[Command] = {
|
||||
import ChatMessageType._
|
||||
|
||||
msg match {
|
||||
case ListingResponse(ChatService.ChatServiceKey.Listing(listings)) =>
|
||||
chatService = Some(listings.head)
|
||||
chatService.get ! ChatService.JoinChannel(chatServiceAdapter, session.get, ChatChannel.Default())
|
||||
channels ++= List(ChatChannel.Default())
|
||||
this
|
||||
|
||||
case SetSession(newSession) =>
|
||||
session = Some(newSession)
|
||||
if (chatService.isEmpty && newSession.player != null) { // TODO the player check sucks...
|
||||
context.system.receptionist ! Receptionist.Find(
|
||||
ChatService.ChatServiceKey,
|
||||
context.messageAdapter[Receptionist.Listing](ListingResponse)
|
||||
)
|
||||
}
|
||||
this
|
||||
|
||||
case JoinChannel(channel) =>
|
||||
chatService.get ! ChatService.JoinChannel(chatServiceAdapter, session.get, channel)
|
||||
channels ++= List(channel)
|
||||
this
|
||||
|
||||
case LeaveChannel(channel) =>
|
||||
chatService.get ! ChatService.LeaveChannel(chatServiceAdapter, channel)
|
||||
channels = channels.filter(_ == channel)
|
||||
this
|
||||
|
||||
/** Some messages are sent during login so we handle them prematurely because main message handler requires the
|
||||
* session object and chat service and they may not be set yet
|
||||
*/
|
||||
case Message(ChatMsg(CMT_CULLWATERMARK, _, _, contents, _)) =>
|
||||
val connectionState =
|
||||
if (contents.contains("40 80")) 100
|
||||
else if (contents.contains("120 200")) 25
|
||||
else 50
|
||||
sessionActor ! SessionActor.SetConnectionState(connectionState)
|
||||
this
|
||||
|
||||
case Message(ChatMsg(CMT_ANONYMOUS, _, _, _, _)) =>
|
||||
// ??
|
||||
this
|
||||
|
||||
case Message(ChatMsg(CMT_TOGGLE_GM, _, _, _, _)) =>
|
||||
// ??
|
||||
this
|
||||
|
||||
case Message(message) =>
|
||||
log.info("Chat: " + message)
|
||||
|
||||
(session, chatService) match {
|
||||
case (Some(session), Some(chatService)) =>
|
||||
(message.messageType, message.recipient.trim, message.contents.trim) match {
|
||||
case (CMT_FLY, recipient, contents) if session.admin =>
|
||||
val flying = contents match {
|
||||
case "on" => true
|
||||
case "off" => false
|
||||
case _ => !session.flying
|
||||
}
|
||||
sessionActor ! SessionActor.SetFlying(flying)
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
ChatMsg(CMT_FLY, false, recipient, if (flying) "on" else "off", None)
|
||||
)
|
||||
|
||||
case (CMT_SPEED, recipient, contents) =>
|
||||
val speed =
|
||||
try {
|
||||
contents.toFloat
|
||||
} catch {
|
||||
case _: Throwable =>
|
||||
1f
|
||||
}
|
||||
sessionActor ! SessionActor.SetSpeed(speed)
|
||||
sessionActor ! SessionActor.SendResponse(message.copy(contents = f"$speed%.3f"))
|
||||
|
||||
case (CMT_TOGGLESPECTATORMODE, _, contents) if session.admin =>
|
||||
val spectator = contents match {
|
||||
case "on" => true
|
||||
case "off" => false
|
||||
case _ => !session.player.spectator
|
||||
}
|
||||
sessionActor ! SessionActor.SetSpectator(spectator)
|
||||
sessionActor ! SessionActor.SendResponse(message.copy(contents = if (spectator) "on" else "off"))
|
||||
|
||||
case (CMT_RECALL, _, _) =>
|
||||
val sanctuary = Zones.SanctuaryZoneId(session.player.Faction)
|
||||
val errorMessage = session.zoningType match {
|
||||
case Zoning.Method.Quit => Some("You can't recall to your sanctuary continent while quitting")
|
||||
case Zoning.Method.InstantAction =>
|
||||
Some("You can't recall to your sanctuary continent while instant actioning")
|
||||
case Zoning.Method.Recall => Some("You already requested to recall to your sanctuary continent")
|
||||
case _ if session.zone.Id == sanctuary =>
|
||||
Some("You can't recall to your sanctuary when you are already in your sanctuary")
|
||||
case _ if !session.player.isAlive || session.deadState != DeadState.Alive =>
|
||||
Some(if (session.player.isAlive) "@norecall_deconstructing" else "@norecall_dead")
|
||||
case _ if session.player.VehicleSeated.nonEmpty => Some("@norecall_invehicle")
|
||||
case _ => None
|
||||
}
|
||||
errorMessage match {
|
||||
case Some(errorMessage) =>
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
ChatMsg(
|
||||
CMT_QUIT,
|
||||
false,
|
||||
"",
|
||||
errorMessage,
|
||||
None
|
||||
)
|
||||
)
|
||||
case None =>
|
||||
sessionActor ! SessionActor.Recall(sanctuary)
|
||||
}
|
||||
|
||||
case (CMT_INSTANTACTION, _, _) =>
|
||||
if (session.zoningType == Zoning.Method.Quit) {
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
ChatMsg(CMT_QUIT, false, "", "You can't instant action while quitting.", None)
|
||||
)
|
||||
} else if (session.zoningType == Zoning.Method.InstantAction) {
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
ChatMsg(CMT_QUIT, false, "", "@noinstantaction_instantactionting", None)
|
||||
)
|
||||
} else if (session.zoningType == Zoning.Method.Recall) {
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
ChatMsg(
|
||||
CMT_QUIT,
|
||||
false,
|
||||
"",
|
||||
"You won't instant action. You already requested to recall to your sanctuary continent",
|
||||
None
|
||||
)
|
||||
)
|
||||
} else if (!session.player.isAlive || session.deadState != DeadState.Alive) {
|
||||
if (session.player.isAlive) {
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
ChatMsg(CMT_QUIT, false, "", "@noinstantaction_deconstructing", None)
|
||||
)
|
||||
} else {
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
ChatMsg(CMT_QUIT, false, "", "@noinstantaction_dead", None)
|
||||
)
|
||||
}
|
||||
} else if (session.player.VehicleSeated.nonEmpty) {
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
ChatMsg(CMT_QUIT, false, "", "@noinstantaction_invehicle", None)
|
||||
)
|
||||
} else {
|
||||
sessionActor ! SessionActor.InstantAction()
|
||||
}
|
||||
|
||||
case (CMT_QUIT, _, _) =>
|
||||
if (session.zoningType == Zoning.Method.Quit) {
|
||||
sessionActor ! SessionActor.SendResponse(ChatMsg(CMT_QUIT, false, "", "@noquit_quitting", None))
|
||||
} else if (!session.player.isAlive || session.deadState != DeadState.Alive) {
|
||||
if (session.player.isAlive) {
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
ChatMsg(CMT_QUIT, false, "", "@noquit_deconstructing", None)
|
||||
)
|
||||
} else {
|
||||
sessionActor ! SessionActor.SendResponse(ChatMsg(CMT_QUIT, false, "", "@noquit_dead", None))
|
||||
}
|
||||
} else if (session.player.VehicleSeated.nonEmpty) {
|
||||
sessionActor ! SessionActor.SendResponse(ChatMsg(CMT_QUIT, false, "", "@noquit_invehicle", None))
|
||||
} else {
|
||||
sessionActor ! SessionActor.Quit()
|
||||
}
|
||||
|
||||
case (CMT_SUICIDE, _, _) =>
|
||||
if (session.player.isAlive && session.deadState != DeadState.Release) {
|
||||
sessionActor ! SessionActor.Suicide()
|
||||
}
|
||||
|
||||
case (CMT_DESTROY, recipient, contents) =>
|
||||
val guid = contents.toInt
|
||||
session.zone.GUID(session.zone.Map.TerminalToSpawnPad.getOrElse(guid, guid)) match {
|
||||
case Some(pad: VehicleSpawnPad) =>
|
||||
pad.Actor ! VehicleSpawnControl.ProcessControl.Flush
|
||||
case Some(turret: FacilityTurret) if turret.isUpgrading =>
|
||||
WeaponTurrets.FinishUpgradingMannedTurret(turret, TurretUpgrade.None)
|
||||
case _ =>
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
PacketCoding.CreateGamePacket(0, RequestDestroyMessage(PlanetSideGUID(guid))).packet
|
||||
)
|
||||
}
|
||||
sessionActor ! SessionActor.SendResponse(message)
|
||||
|
||||
case (_, _, "!loc") =>
|
||||
val continent = session.zone
|
||||
val player = session.player
|
||||
val loc =
|
||||
s"zone=${continent.Id} pos=${player.Position.x},${player.Position.y},${player.Position.z}; ori=${player.Orientation.x},${player.Orientation.y},${player.Orientation.z}"
|
||||
log.info(loc)
|
||||
sessionActor ! SessionActor.SendResponse(message.copy(contents = loc))
|
||||
|
||||
case (_, _, contents) if contents.startsWith("!list") =>
|
||||
val zone = contents.split(" ").lift(1) match {
|
||||
case None =>
|
||||
Some(session.zone)
|
||||
case Some(id) =>
|
||||
Zones.zones.get(id)
|
||||
}
|
||||
|
||||
zone match {
|
||||
case Some(zone) =>
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
ChatMsg(
|
||||
CMT_GMOPEN,
|
||||
message.wideContents,
|
||||
"Server",
|
||||
"\\#8Name (Faction) [ID] at PosX PosY PosZ",
|
||||
message.note
|
||||
)
|
||||
)
|
||||
|
||||
(zone.LivePlayers ++ zone.Corpses)
|
||||
.filter(_.CharId != session.player.CharId)
|
||||
.sortBy(_.Name)
|
||||
.foreach(player => {
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
ChatMsg(
|
||||
CMT_GMOPEN,
|
||||
message.wideContents,
|
||||
"Server",
|
||||
s"\\#7${player.Name} (${player.Faction}) [${player.CharId}] at ${player.Position.x.toInt} ${player.Position.y.toInt} ${player.Position.z.toInt}",
|
||||
message.note
|
||||
)
|
||||
)
|
||||
})
|
||||
case None =>
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
ChatMsg(
|
||||
CMT_GMOPEN,
|
||||
message.wideContents,
|
||||
"Server",
|
||||
"Invalid zone ID",
|
||||
message.note
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
case (_, _, contents) if session.admin && contents.startsWith("!kick") =>
|
||||
val input = contents.split("\\s+").drop(1)
|
||||
if (input.length > 0) {
|
||||
val numRegex = raw"(\d+)".r
|
||||
val id = input(0)
|
||||
val determination: Player => Boolean = id match {
|
||||
case numRegex(_) => _.CharId == id.toLong
|
||||
case _ => _.Name.equals(id)
|
||||
}
|
||||
session.zone.LivePlayers
|
||||
.find(determination)
|
||||
.orElse(session.zone.Corpses.find(determination)) match {
|
||||
case Some(player) =>
|
||||
input.lift(1) match {
|
||||
case Some(numRegex(time)) =>
|
||||
sessionActor ! SessionActor.Kick(player, Some(time.toLong))
|
||||
case _ =>
|
||||
sessionActor ! SessionActor.Kick(player)
|
||||
}
|
||||
case None =>
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
ChatMsg(
|
||||
CMT_GMOPEN,
|
||||
message.wideContents,
|
||||
"Server",
|
||||
"Invalid player",
|
||||
message.note
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
case (CMT_CAPTUREBASE, _, contents) if session.admin =>
|
||||
val args = contents.split(" ").filter(_ != "")
|
||||
|
||||
val (faction, factionPos) = args.zipWithIndex
|
||||
.map { case (faction, pos) => (faction.toLowerCase, pos) }
|
||||
.flatMap {
|
||||
case ("tr", pos) => Some(PlanetSideEmpire.TR, pos)
|
||||
case ("nc", pos) => Some(PlanetSideEmpire.NC, pos)
|
||||
case ("vs", pos) => Some(PlanetSideEmpire.VS, pos)
|
||||
case ("none", pos) => Some(PlanetSideEmpire.NEUTRAL, pos)
|
||||
case _ => None
|
||||
}
|
||||
.headOption match {
|
||||
case Some((faction, pos)) => (faction, Some(pos))
|
||||
case None => (session.player.Faction, None)
|
||||
}
|
||||
|
||||
val (buildingsOption, buildingPos) = args.zipWithIndex.flatMap {
|
||||
case (_, pos) if factionPos.isDefined && factionPos.get == pos => None
|
||||
case ("all", pos) =>
|
||||
Some(
|
||||
Some(
|
||||
session.zone.Buildings
|
||||
.filter {
|
||||
case (_, building) => building.CaptureTerminal.isDefined
|
||||
}
|
||||
.values
|
||||
.toSeq
|
||||
),
|
||||
Some(pos)
|
||||
)
|
||||
case (name, pos) =>
|
||||
session.zone.Buildings.find {
|
||||
case (_, building) => name.equalsIgnoreCase(building.Name) && building.CaptureTerminal.isDefined
|
||||
} match {
|
||||
case Some((_, building)) => Some(Some(Seq(building)), Some(pos))
|
||||
case None =>
|
||||
try {
|
||||
// check if we have a timer
|
||||
name.toInt
|
||||
None
|
||||
} catch {
|
||||
case _: Throwable =>
|
||||
Some(None, Some(pos))
|
||||
}
|
||||
}
|
||||
}.headOption match {
|
||||
case Some((buildings, pos)) => (buildings, pos)
|
||||
case None => (None, None)
|
||||
}
|
||||
|
||||
val (timerOption, timerPos) = args.zipWithIndex.flatMap {
|
||||
case (_, pos)
|
||||
if factionPos.isDefined && factionPos.get == pos || buildingPos.isDefined && buildingPos.get == pos =>
|
||||
None
|
||||
case (timer, pos) =>
|
||||
try {
|
||||
val t = timer.toInt // TODO what is the timer format supposed to be?
|
||||
Some(Some(t), Some(pos))
|
||||
} catch {
|
||||
case _: Throwable =>
|
||||
Some(None, Some(pos))
|
||||
}
|
||||
}.headOption match {
|
||||
case Some((timer, posOption)) => (timer, posOption)
|
||||
case None => (None, None)
|
||||
}
|
||||
|
||||
(factionPos, buildingPos, timerPos, buildingsOption, timerOption) match {
|
||||
case // [[<empire>|none [<timer>]]
|
||||
(Some(0), None, Some(1), None, Some(_)) | (Some(0), None, None, None, None) |
|
||||
(None, None, None, None, None) |
|
||||
// [<building name> [<empire>|none [timer]]]
|
||||
(None | Some(1), Some(0), None, Some(_), None) | (Some(1), Some(0), Some(2), Some(_), Some(_)) |
|
||||
// [all [<empire>|none]]
|
||||
(Some(1) | None, Some(0), None, Some(_), None) =>
|
||||
val buildings = buildingsOption.getOrElse(
|
||||
session.zone.Buildings
|
||||
.filter {
|
||||
case (_, building) =>
|
||||
building.PlayersInSOI.exists { soiPlayer =>
|
||||
session.player.CharId == soiPlayer.CharId
|
||||
}
|
||||
}
|
||||
.map { case (_, building) => building }
|
||||
)
|
||||
buildings foreach { building =>
|
||||
// TODO implement timer
|
||||
building.Faction = faction
|
||||
session.zone.LocalEvents ! LocalServiceMessage(
|
||||
session.zone.Id,
|
||||
LocalAction.SetEmpire(building.GUID, faction)
|
||||
)
|
||||
}
|
||||
case (_, Some(0), _, None, _) =>
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
ChatMsg(
|
||||
UNK_229,
|
||||
true,
|
||||
"",
|
||||
s"\\#FF4040ERROR - \'${args(0)}\' is not a valid building name.",
|
||||
None
|
||||
)
|
||||
)
|
||||
case (Some(0), _, Some(1), _, None) | (Some(1), Some(0), Some(2), _, None) =>
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
ChatMsg(
|
||||
UNK_229,
|
||||
true,
|
||||
"",
|
||||
s"\\#FF4040ERROR - \'${args(timerPos.get)}\' is not a valid timer value.",
|
||||
None
|
||||
)
|
||||
)
|
||||
case _ =>
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
ChatMsg(
|
||||
UNK_229,
|
||||
true,
|
||||
"",
|
||||
"usage: /capturebase [[<empire>|none [<timer>]] | [<building name> [<empire>|none [timer]]] | [all [<empire>|none]]",
|
||||
None
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
case (CMT_GMBROADCAST | CMT_GMBROADCAST_NC | CMT_GMBROADCAST_VS | CMT_GMBROADCAST_TR, _, _)
|
||||
if session.admin =>
|
||||
chatService ! ChatService.Message(
|
||||
session,
|
||||
message.copy(recipient = session.player.Name),
|
||||
ChatChannel.Default()
|
||||
)
|
||||
|
||||
case (CMT_GMTELL, _, _) if session.admin =>
|
||||
chatService ! ChatService.Message(
|
||||
session,
|
||||
message.copy(recipient = session.player.Name),
|
||||
ChatChannel.Default()
|
||||
)
|
||||
|
||||
case (CMT_GMBROADCASTPOPUP, _, _) if session.admin =>
|
||||
chatService ! ChatService.Message(
|
||||
session,
|
||||
message.copy(recipient = session.player.Name),
|
||||
ChatChannel.Default()
|
||||
)
|
||||
|
||||
case (_, _, contents) if contents.startsWith("!whitetext ") && session.admin =>
|
||||
chatService ! ChatService.Message(
|
||||
session,
|
||||
ChatMsg(UNK_227, true, "", contents.replace("!whitetext ", ""), None),
|
||||
ChatChannel.Default()
|
||||
)
|
||||
|
||||
case (_, "tr", contents) =>
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
ZonePopulationUpdateMessage(4, 414, 138, contents.toInt, 138, contents.toInt / 2, 138, 0, 138, 0)
|
||||
)
|
||||
|
||||
case (_, "nc", contents) =>
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
ZonePopulationUpdateMessage(4, 414, 138, 0, 138, contents.toInt, 138, contents.toInt / 3, 138, 0)
|
||||
)
|
||||
|
||||
case (_, "vs", contents) =>
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
ZonePopulationUpdateMessage(4, 414, 138, contents.toInt * 2, 138, 0, 138, contents.toInt, 138, 0)
|
||||
)
|
||||
|
||||
case (_, "bo", contents) =>
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
ZonePopulationUpdateMessage(4, 414, 138, 0, 138, 0, 138, 0, 138, contents.toInt)
|
||||
)
|
||||
|
||||
case (_, _, contents) if contents.startsWith("!ntu") && session.admin =>
|
||||
session.zone.Buildings.values.foreach(building =>
|
||||
building.Amenities.foreach(amenity =>
|
||||
amenity.Definition match {
|
||||
case GlobalDefinitions.resource_silo =>
|
||||
val r = new scala.util.Random
|
||||
val silo = amenity.asInstanceOf[ResourceSilo]
|
||||
val ntu: Int = 900 + r.nextInt(100) - silo.ChargeLevel
|
||||
// val ntu: Int = 0 + r.nextInt(100) - silo.ChargeLevel
|
||||
silo.Actor ! ResourceSilo.UpdateChargeLevel(ntu)
|
||||
|
||||
case _ => ;
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
case (CMT_OPEN, _, _) if !session.player.silenced =>
|
||||
chatService ! ChatService.Message(
|
||||
session,
|
||||
message.copy(recipient = session.player.Name),
|
||||
ChatChannel.Default()
|
||||
)
|
||||
|
||||
case (CMT_VOICE, _, _) =>
|
||||
chatService ! ChatService.Message(
|
||||
session,
|
||||
message.copy(recipient = session.player.Name),
|
||||
ChatChannel.Default()
|
||||
)
|
||||
|
||||
case (CMT_TELL, _, _) if !session.player.silenced =>
|
||||
chatService ! ChatService.Message(
|
||||
session,
|
||||
message,
|
||||
ChatChannel.Default()
|
||||
)
|
||||
|
||||
case (CMT_BROADCAST, _, _) if !session.player.silenced =>
|
||||
chatService ! ChatService.Message(
|
||||
session,
|
||||
message.copy(recipient = session.player.Name),
|
||||
ChatChannel.Default()
|
||||
)
|
||||
|
||||
case (CMT_PLATOON, _, _) if !session.player.silenced =>
|
||||
chatService ! ChatService.Message(
|
||||
session,
|
||||
message.copy(recipient = session.player.Name),
|
||||
ChatChannel.Default()
|
||||
)
|
||||
|
||||
case (CMT_COMMAND, _, _) if session.admin =>
|
||||
chatService ! ChatService.Message(
|
||||
session,
|
||||
message.copy(recipient = session.player.Name),
|
||||
ChatChannel.Default()
|
||||
)
|
||||
|
||||
case (CMT_NOTE, _, _) =>
|
||||
chatService ! ChatService.Message(session, message, ChatChannel.Default())
|
||||
|
||||
case (CMT_SILENCE, _, _) if session.admin =>
|
||||
chatService ! ChatService.Message(session, message, ChatChannel.Default())
|
||||
|
||||
case (CMT_SQUAD, _, _) =>
|
||||
channels.foreach {
|
||||
case channel: ChatChannel.Squad =>
|
||||
chatService ! ChatService.Message(session, message.copy(recipient = session.player.Name), channel)
|
||||
case _ =>
|
||||
}
|
||||
|
||||
case (
|
||||
CMT_WHO | CMT_WHO_CSR | CMT_WHO_CR | CMT_WHO_PLATOONLEADERS | CMT_WHO_SQUADLEADERS | CMT_WHO_TEAMS,
|
||||
_,
|
||||
_
|
||||
) =>
|
||||
val players = session.zone.Players
|
||||
val popTR = players.count(_.faction == PlanetSideEmpire.TR)
|
||||
val popNC = players.count(_.faction == PlanetSideEmpire.NC)
|
||||
val popVS = players.count(_.faction == PlanetSideEmpire.VS)
|
||||
val contName = session.zone.Map.Name
|
||||
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
ChatMsg(ChatMessageType.CMT_WHO, true, "", "That command doesn't work for now, but : ", None)
|
||||
)
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
ChatMsg(ChatMessageType.CMT_WHO, true, "", "NC online : " + popNC + " on " + contName, None)
|
||||
)
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
ChatMsg(ChatMessageType.CMT_WHO, true, "", "TR online : " + popTR + " on " + contName, None)
|
||||
)
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
ChatMsg(ChatMessageType.CMT_WHO, true, "", "VS online : " + popVS + " on " + contName, None)
|
||||
)
|
||||
|
||||
case (CMT_ZONE, _, contents) if session.admin =>
|
||||
val buffer = contents.toLowerCase.split("\\s+")
|
||||
val (zone, gate, list) = (buffer.lift(0), buffer.lift(1)) match {
|
||||
case (Some("-list"), None) =>
|
||||
(None, None, true)
|
||||
case (Some(zoneId), Some("-list")) =>
|
||||
(PointOfInterest.get(zoneId), None, true)
|
||||
case (Some(zoneId), gateId) =>
|
||||
val zone = PointOfInterest.get(zoneId)
|
||||
val gate = (zone, gateId) match {
|
||||
case (Some(zone), Some(gateId)) => PointOfInterest.getWarpgate(zone, gateId)
|
||||
case (Some(zone), None) => Some(PointOfInterest.selectRandom(zone))
|
||||
case _ => None
|
||||
}
|
||||
(zone, gate, false)
|
||||
case _ =>
|
||||
(None, None, false)
|
||||
}
|
||||
(zone, gate, list) match {
|
||||
case (None, None, true) =>
|
||||
sessionActor ! SessionActor.SendResponse(ChatMsg(UNK_229, true, "", PointOfInterest.list, None))
|
||||
case (Some(zone), None, true) =>
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
ChatMsg(UNK_229, true, "", PointOfInterest.listWarpgates(zone), None)
|
||||
)
|
||||
case (Some(zone), Some(gate), false) =>
|
||||
sessionActor ! SessionActor.SetZone(zone.zonename, gate)
|
||||
case (_, None, false) =>
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
ChatMsg(UNK_229, true, "", "Gate id not defined (use '/zone <zone> -list')", None)
|
||||
)
|
||||
case (_, _, _) if buffer.isEmpty || buffer(0).equals("-help") =>
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
ChatMsg(UNK_229, true, "", "usage: /zone <zone> [gatename] | [-list]", None)
|
||||
)
|
||||
}
|
||||
|
||||
case (CMT_WARP, _, contents) if session.admin =>
|
||||
val buffer = contents.toLowerCase.split("\\s+")
|
||||
val (coordinates, waypoint) = (buffer.lift(0), buffer.lift(1), buffer.lift(2)) match {
|
||||
case (Some(x), Some(y), Some(z)) => (Some(x, y, z), None)
|
||||
case (Some("to"), Some(character), None) => (None, None) // TODO not implemented
|
||||
case (Some("near"), Some(objectName), None) => (None, None) // TODO not implemented
|
||||
case (Some(waypoint), None, None) => (None, Some(waypoint))
|
||||
case _ => (None, None)
|
||||
}
|
||||
(coordinates, waypoint) match {
|
||||
case (Some((x, y, z)), None) if List(x, y, z).forall { str =>
|
||||
val coordinate = str.toFloatOption
|
||||
coordinate.isDefined && coordinate.get >= 0 && coordinate.get <= 8191
|
||||
} =>
|
||||
sessionActor ! SessionActor.SetPosition(Vector3(x.toFloat, y.toFloat, z.toFloat))
|
||||
case (None, Some(waypoint)) if waypoint != "-help" =>
|
||||
PointOfInterest.getWarpLocation(session.zone.Id, waypoint) match {
|
||||
case Some(location) => sessionActor ! SessionActor.SetPosition(location)
|
||||
case None =>
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
ChatMsg(UNK_229, true, "", s"unknown location '$waypoint", None)
|
||||
)
|
||||
}
|
||||
case _ =>
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
ChatMsg(
|
||||
UNK_229,
|
||||
true,
|
||||
"",
|
||||
s"usage: /warp <x><y><z> OR /warp to <character> OR /warp near <object> OR /warp above <object> OR /warp waypoint",
|
||||
None
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
case _ =>
|
||||
log.info("unhandled chat message $message")
|
||||
}
|
||||
case (None, _) | (_, None) =>
|
||||
log.error("failed to handle message because dependencies are missing")
|
||||
}
|
||||
this
|
||||
|
||||
case IncomingMessage(fromSession, message, channel) =>
|
||||
(session) match {
|
||||
case Some(session) =>
|
||||
message.messageType match {
|
||||
case CMT_TELL | U_CMT_TELLFROM | CMT_BROADCAST | CMT_SQUAD | CMT_PLATOON | CMT_COMMAND | UNK_45 | UNK_71 |
|
||||
CMT_NOTE | CMT_GMBROADCAST | CMT_GMBROADCAST_NC | CMT_GMBROADCAST_TR | CMT_GMBROADCAST_VS |
|
||||
CMT_GMBROADCASTPOPUP | CMT_GMTELL | U_CMT_GMTELLFROM | UNK_227 =>
|
||||
sessionActor ! SessionActor.SendResponse(message)
|
||||
case CMT_OPEN =>
|
||||
if (
|
||||
session.zone == fromSession.zone &&
|
||||
Vector3.Distance(session.player.Position, fromSession.player.Position) < 25 &&
|
||||
session.player.Faction == fromSession.player.Faction
|
||||
) {
|
||||
sessionActor ! SessionActor.SendResponse(message)
|
||||
}
|
||||
case CMT_VOICE =>
|
||||
if (
|
||||
session.zone == fromSession.zone &&
|
||||
Vector3.Distance(session.player.Position, fromSession.player.Position) < 25
|
||||
) {
|
||||
sessionActor ! SessionActor.SendResponse(message)
|
||||
}
|
||||
case CMT_SILENCE =>
|
||||
val args = message.contents.split(" ")
|
||||
val (name, time) = (args.lift(0), args.lift(1)) match {
|
||||
case (Some(name), _) if name != session.player.Name =>
|
||||
log.error("received silence message for other player")
|
||||
(None, None)
|
||||
case (Some(name), None) => (Some(name), Some(5))
|
||||
case (Some(name), Some(time)) if time.toIntOption.isDefined => (Some(name), Some(time.toInt))
|
||||
case _ => (None, None)
|
||||
}
|
||||
(name, time) match {
|
||||
case (Some(_), Some(time)) =>
|
||||
if (session.player.silenced) {
|
||||
sessionActor ! SessionActor.SetSilenced(false)
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
ChatMsg(ChatMessageType.UNK_71, true, "", "@silence_off", None)
|
||||
)
|
||||
if (!silenceTimer.isCancelled) silenceTimer.cancel()
|
||||
} else {
|
||||
sessionActor ! SessionActor.SetSilenced(true)
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
ChatMsg(ChatMessageType.UNK_71, true, "", "@silence_on", None)
|
||||
)
|
||||
silenceTimer = context.system.scheduler.scheduleOnce(
|
||||
time minutes,
|
||||
() => {
|
||||
sessionActor ! SessionActor.SetSilenced(false)
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
ChatMsg(ChatMessageType.UNK_71, true, "", "@silence_timeout", None)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
case (name, time) =>
|
||||
log.error(s"bad silence args $name $time")
|
||||
}
|
||||
case _ =>
|
||||
log.error(s"unexpected messageType $message")
|
||||
}
|
||||
case None =>
|
||||
log.error("failed to handle incoming message because dependencies are missing")
|
||||
}
|
||||
this
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,18 +1,18 @@
|
|||
package net.psforever.pslogin
|
||||
package net.psforever.login
|
||||
|
||||
import akka.actor.{Actor, ActorRef, MDCContextAware}
|
||||
import net.psforever.crypto.CryptoInterface.CryptoStateWithMAC
|
||||
import net.psforever.crypto.CryptoInterface
|
||||
import net.psforever.packet._
|
||||
import scodec.Attempt.{Failure, Successful}
|
||||
import scodec.bits._
|
||||
import java.security.SecureRandom
|
||||
|
||||
import akka.actor.MDCContextAware.Implicits._
|
||||
import akka.actor.{Actor, ActorRef, MDCContextAware}
|
||||
import net.psforever.crypto.CryptoInterface
|
||||
import net.psforever.crypto.CryptoInterface.CryptoStateWithMAC
|
||||
import net.psforever.packet._
|
||||
import net.psforever.packet.control._
|
||||
import net.psforever.packet.crypto._
|
||||
import net.psforever.packet.game.PingMsg
|
||||
import org.log4s.MDC
|
||||
import MDCContextAware.Implicits._
|
||||
import scodec.Attempt.{Failure, Successful}
|
||||
import scodec.bits._
|
||||
|
||||
sealed trait CryptoSessionAPI
|
||||
final case class DropCryptoSession() extends CryptoSessionAPI
|
||||
|
|
@ -1,27 +1,28 @@
|
|||
package net.psforever.pslogin
|
||||
package net.psforever.login
|
||||
|
||||
import java.net.InetSocketAddress
|
||||
import java.net.{InetAddress, InetSocketAddress}
|
||||
|
||||
import akka.actor.MDCContextAware.Implicits._
|
||||
import akka.actor.{Actor, ActorRef, Cancellable, MDCContextAware}
|
||||
import net.psforever.packet.{PlanetSideGamePacket, _}
|
||||
import com.github.t3hnar.bcrypt._
|
||||
import net.psforever.objects.{Account, Default}
|
||||
import net.psforever.packet.control._
|
||||
import net.psforever.packet.game.LoginRespMessage.{LoginError, StationError, StationSubscriptionStatus}
|
||||
import net.psforever.packet.game._
|
||||
import net.psforever.packet.{PlanetSideGamePacket, _}
|
||||
import net.psforever.persistence
|
||||
import net.psforever.types.PlanetSideEmpire
|
||||
import net.psforever.util.Config
|
||||
import net.psforever.util.Database._
|
||||
import org.log4s.MDC
|
||||
import scodec.bits._
|
||||
import MDCContextAware.Implicits._
|
||||
import net.psforever.objects.Account
|
||||
import net.psforever.objects.Default
|
||||
import net.psforever.types.PlanetSideEmpire
|
||||
import services.ServiceManager
|
||||
import services.ServiceManager.Lookup
|
||||
import services.account.{ReceiveIPAddress, RetrieveIPAddress, StoreAccountData}
|
||||
import com.github.t3hnar.bcrypt._
|
||||
import net.psforever.packet.game.LoginRespMessage.{LoginError, StationError, StationSubscriptionStatus}
|
||||
import net.psforever.persistence
|
||||
|
||||
import scala.concurrent.Future
|
||||
import scala.concurrent.duration._
|
||||
import scala.util.{Failure, Success}
|
||||
import scala.concurrent.Future
|
||||
import Database._
|
||||
import java.net.InetAddress
|
||||
|
||||
class LoginSessionActor extends Actor with MDCContextAware {
|
||||
private[this] val log = org.log4s.getLogger
|
||||
|
|
@ -1,13 +1,13 @@
|
|||
package net.psforever.pslogin
|
||||
package net.psforever.login
|
||||
|
||||
import akka.actor.MDCContextAware.Implicits._
|
||||
import akka.actor.{Actor, ActorRef, Cancellable, MDCContextAware}
|
||||
import net.psforever.objects.Default
|
||||
import net.psforever.packet._
|
||||
import net.psforever.packet.control.{HandleGamePacket, _}
|
||||
import org.log4s.MDC
|
||||
import scodec.Attempt.{Failure, Successful}
|
||||
import scodec.bits._
|
||||
import org.log4s.MDC
|
||||
import MDCContextAware.Implicits._
|
||||
import net.psforever.objects.Default
|
||||
import net.psforever.packet.control.{HandleGamePacket, _}
|
||||
|
||||
import scala.annotation.tailrec
|
||||
import scala.collection.mutable
|
||||
|
|
@ -1,13 +1,11 @@
|
|||
package net.psforever.pslogin
|
||||
package net.psforever.login
|
||||
|
||||
import java.net.InetSocketAddress
|
||||
|
||||
import akka.actor._
|
||||
import scodec.bits._
|
||||
|
||||
import akka.actor.{ActorContext, ActorRef, PoisonPill}
|
||||
import akka.actor.MDCContextAware.Implicits._
|
||||
import akka.actor.{ActorContext, ActorRef, PoisonPill, _}
|
||||
import com.github.nscala_time.time.Imports._
|
||||
import MDCContextAware.Implicits._
|
||||
import scodec.bits._
|
||||
|
||||
sealed trait SessionState
|
||||
final case class New() extends SessionState
|
||||
|
|
@ -1,19 +1,18 @@
|
|||
package net.psforever.pslogin
|
||||
package net.psforever.login
|
||||
|
||||
import java.net.InetSocketAddress
|
||||
|
||||
import akka.actor._
|
||||
import org.log4s.MDC
|
||||
import scodec.bits._
|
||||
|
||||
import scala.collection.mutable
|
||||
import akka.actor.SupervisorStrategy.Stop
|
||||
import akka.actor._
|
||||
import net.psforever.packet.PacketCoding
|
||||
import net.psforever.packet.control.ConnectionClose
|
||||
import net.psforever.util.Config
|
||||
import org.log4s.MDC
|
||||
import scodec.bits._
|
||||
import services.ServiceManager
|
||||
import services.ServiceManager.Lookup
|
||||
import services.account.{IPAddress, StoreIPAddress}
|
||||
|
||||
import scala.collection.mutable
|
||||
import scala.concurrent.duration._
|
||||
|
||||
sealed trait SessionRouterAPI
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package net.psforever.pslogin
|
||||
package net.psforever.login
|
||||
|
||||
import java.net.{InetAddress, InetSocketAddress}
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package net.psforever.pslogin
|
||||
package net.psforever.login
|
||||
|
||||
import java.net.{InetAddress, InetSocketAddress}
|
||||
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
package net.psforever.pslogin
|
||||
package net.psforever.login
|
||||
|
||||
import akka.actor.{Actor, ActorRef}
|
||||
import akka.io._
|
||||
|
||||
import scala.util.Random
|
||||
import scala.collection.mutable
|
||||
import scala.concurrent.duration._
|
||||
import scala.util.Random
|
||||
|
||||
/** Parameters for the Network simulator
|
||||
*
|
||||
|
|
@ -1,15 +1,15 @@
|
|||
package net.psforever.pslogin
|
||||
package net.psforever.login
|
||||
|
||||
import akka.actor.ActorRef
|
||||
import akka.pattern.{AskTimeoutException, ask}
|
||||
import akka.util.Timeout
|
||||
import net.psforever.objects.{AmmoBox, GlobalDefinitions, Player, Tool}
|
||||
import net.psforever.objects.equipment.{Ammo, Equipment}
|
||||
import net.psforever.objects.guid.{GUIDTask, Task, TaskResolver}
|
||||
import net.psforever.objects.inventory.{Container, InventoryItem}
|
||||
import net.psforever.objects.serverobject.PlanetSideServerObject
|
||||
import net.psforever.objects.serverobject.containable.Containable
|
||||
import net.psforever.objects.zones.Zone
|
||||
import net.psforever.objects.{AmmoBox, GlobalDefinitions, Player, Tool}
|
||||
import net.psforever.packet.game.ObjectHeldMessage
|
||||
import net.psforever.types.{PlanetSideGUID, TransactionType, Vector3}
|
||||
import services.Service
|
||||
|
|
@ -19,7 +19,7 @@ import scala.concurrent.ExecutionContext.Implicits.global
|
|||
import scala.concurrent.Future
|
||||
import scala.concurrent.duration._
|
||||
import scala.language.implicitConversions
|
||||
import scala.util.{Success, Failure}
|
||||
import scala.util.{Failure, Success}
|
||||
|
||||
object WorldSession {
|
||||
|
||||
|
|
@ -1,8 +1,7 @@
|
|||
package net.psforever.pslogin.psadmin
|
||||
package net.psforever.login.psadmin
|
||||
|
||||
import net.psforever.util.Config
|
||||
import scala.collection.mutable
|
||||
import net.psforever.pslogin.Config
|
||||
|
||||
import scala.jdk.CollectionConverters._
|
||||
|
||||
object CmdInternal {
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
package net.psforever.pslogin.psadmin
|
||||
package net.psforever.login.psadmin
|
||||
|
||||
import akka.actor.ActorRef
|
||||
import akka.actor.Actor
|
||||
import scala.collection.mutable.Map
|
||||
import akka.actor.{Actor, ActorRef}
|
||||
import net.psforever.objects.zones.InterstellarCluster
|
||||
|
||||
import scala.collection.mutable.Map
|
||||
|
||||
class CmdListPlayers(args: Array[String], services: Map[String, ActorRef]) extends Actor {
|
||||
private[this] val log = org.log4s.getLogger(self.path.name)
|
||||
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package net.psforever.pslogin.psadmin
|
||||
package net.psforever.login.psadmin
|
||||
|
||||
import akka.actor.{Actor, ActorRef}
|
||||
|
||||
import scala.collection.mutable.Map
|
||||
|
||||
class CmdShutdown(args: Array[String], services: Map[String, ActorRef]) extends Actor {
|
||||
|
|
@ -1,20 +1,19 @@
|
|||
package net.psforever.pslogin.psadmin
|
||||
package net.psforever.login.psadmin
|
||||
|
||||
import java.net.InetSocketAddress
|
||||
import akka.actor.{ActorRef, Props}
|
||||
import akka.actor.{Actor, Stash}
|
||||
import akka.io.Tcp
|
||||
import scodec.bits._
|
||||
import scodec.interop.akka._
|
||||
import scala.collection.mutable.Map
|
||||
import akka.util.ByteString
|
||||
|
||||
import akka.actor.{Actor, ActorRef, Props, Stash}
|
||||
import akka.io.Tcp
|
||||
import akka.util.ByteString
|
||||
import org.json4s._
|
||||
import org.json4s.native.Serialization.write
|
||||
|
||||
import scodec.bits._
|
||||
import scodec.interop.akka._
|
||||
import services.ServiceManager.Lookup
|
||||
import services._
|
||||
|
||||
import scala.collection.mutable.Map
|
||||
|
||||
object PsAdminActor {
|
||||
val whiteSpaceRegex = """\s+""".r
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package net.psforever.pslogin.psadmin
|
||||
package net.psforever.login.psadmin
|
||||
|
||||
import scala.collection.mutable
|
||||
|
||||
17
common/src/main/scala/net/psforever/objects/Session.scala
Normal file
17
common/src/main/scala/net/psforever/objects/Session.scala
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
package net.psforever.objects
|
||||
|
||||
import net.psforever.objects.zones.{Zone, Zoning}
|
||||
import net.psforever.packet.game.DeadState
|
||||
|
||||
case class Session(
|
||||
id: Long = 0,
|
||||
zone: Zone = Zone.Nowhere,
|
||||
account: Account = null,
|
||||
player: Player = null,
|
||||
avatar: Avatar = null,
|
||||
admin: Boolean = false,
|
||||
zoningType: Zoning.Method.Value = Zoning.Method.None,
|
||||
deadState: DeadState.Value = DeadState.Alive,
|
||||
speed: Float = 1.0f,
|
||||
flying: Boolean = false
|
||||
)
|
||||
|
|
@ -63,8 +63,8 @@ object ChatMessageType extends Enumeration {
|
|||
CMT_GMTELL, // /gmtell (actually causes normal /tell 0x20 when not a gm???)
|
||||
CMT_NOTE, // /note
|
||||
CMT_GMBROADCASTPOPUP, // /gmpopup
|
||||
U_CMT_GMTELLFROM, // ??? Recipient of /gmtell?
|
||||
U_CMT_TELLFROM, // ??? Recipient of /t?
|
||||
U_CMT_GMTELLFROM, // Acknowledgement of /gmtell for sender
|
||||
U_CMT_TELLFROM, // Acknowledgement of /tell for sender
|
||||
UNK_45, // ??? empty
|
||||
CMT_CULLWATERMARK, // ??? This actually causes the client to ping back to the server with some stringified numbers "80 120" (with the same 46 chatmsg causing infinite loop?) - may be incorrect decoding
|
||||
CMT_INSTANTACTION, // /instantaction OR /ia
|
||||
|
|
|
|||
|
|
@ -1,25 +1,24 @@
|
|||
package net.psforever.pslogin
|
||||
package net.psforever.util
|
||||
|
||||
import java.nio.file.Paths
|
||||
|
||||
import com.typesafe.config.{Config => TypesafeConfig}
|
||||
|
||||
import scala.concurrent.duration._
|
||||
import net.psforever.packet.game.ServerType
|
||||
import pureconfig.{ConfigConvert, ConfigSource}
|
||||
import pureconfig.ConfigConvert.{viaNonEmptyStringOpt}
|
||||
import enumeratum.values.{IntEnum, IntEnumEntry}
|
||||
import pureconfig.generic.auto._ // intellij: this is not unused
|
||||
|
||||
import net.psforever.packet.game.ServerType
|
||||
import pureconfig.ConfigConvert.viaNonEmptyStringOpt
|
||||
import pureconfig.ConfigReader.Result
|
||||
import pureconfig.{ConfigConvert, ConfigSource}
|
||||
import scala.concurrent.duration._
|
||||
import scala.reflect.ClassTag
|
||||
import pureconfig.generic.auto._ // intellij: this is not unused
|
||||
|
||||
object Config {
|
||||
// prog.home is defined when we are running from SBT pack
|
||||
val directory: String = System.getProperty("prog.home") match {
|
||||
case null =>
|
||||
Paths.get("config").toAbsolutePath().toString()
|
||||
Paths.get("config").toAbsolutePath.toString
|
||||
case home =>
|
||||
Paths.get(home, "config").toAbsolutePath().toString()
|
||||
Paths.get(home, "config").toAbsolutePath.toString
|
||||
}
|
||||
|
||||
implicit def enumeratumIntConfigConvert[A <: IntEnumEntry](implicit
|
||||
|
|
@ -42,7 +41,7 @@ object Config {
|
|||
ConfigSource.defaultApplication
|
||||
}
|
||||
|
||||
val result = source.load[AppConfig]
|
||||
val result: Result[AppConfig] = source.load[AppConfig]
|
||||
|
||||
// Raw config object - prefer app when possible
|
||||
lazy val config: TypesafeConfig = source.config().toOption.get
|
||||
|
|
@ -88,7 +87,7 @@ case class DatabaseConfig(
|
|||
database: String,
|
||||
sslmode: String
|
||||
) {
|
||||
def toJdbc = s"jdbc:postgresql://${host}:${port}/${database}"
|
||||
def toJdbc = s"jdbc:postgresql://$host:$port/$database"
|
||||
}
|
||||
|
||||
case class AntiCheatConfig(
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
package net.psforever.pslogin
|
||||
package net.psforever.util
|
||||
|
||||
import io.getquill.PostgresJAsyncContext
|
||||
import io.getquill.SnakeCase
|
||||
import io.getquill.{PostgresJAsyncContext, SnakeCase}
|
||||
import net.psforever.persistence
|
||||
|
||||
object Database {
|
||||
|
|
@ -1,13 +1,10 @@
|
|||
package net.psforever.pslogin.csr
|
||||
package net.psforever.util
|
||||
|
||||
import net.psforever.types.Vector3
|
||||
|
||||
import scala.collection.mutable
|
||||
import scala.util.Random
|
||||
|
||||
/*
|
||||
The following is STILL for development and fun.
|
||||
*/
|
||||
/**
|
||||
* A crude representation of the information needed to describe a continent (hitherto, a "zone").
|
||||
* The information is mainly catered to the simulation of the CSR commands `/zone` and `/warp`.
|
||||
|
|
@ -16,7 +13,7 @@ The following is STILL for development and fun.
|
|||
* @param map the map name of the zone (this map is loaded)
|
||||
* @param zonename the zone's internal name
|
||||
*/
|
||||
class CSRZoneImpl(val alias: String, val map: String, val zonename: String) {
|
||||
class PointOfInterest(val alias: String, val map: String, val zonename: String) {
|
||||
|
||||
/**
|
||||
* A listing of warpgates, geowarps, and island warpgates in this zone.
|
||||
|
|
@ -33,49 +30,49 @@ class CSRZoneImpl(val alias: String, val map: String, val zonename: String) {
|
|||
private val locations: mutable.HashMap[String, Vector3] = mutable.HashMap()
|
||||
}
|
||||
|
||||
object CSRZoneImpl {
|
||||
object PointOfInterest {
|
||||
|
||||
/**
|
||||
* A listing of all zones that can be visited by their internal name.
|
||||
* The keys in this map should be directly usable by the `/zone` command.
|
||||
*/
|
||||
private val zones = Map[String, CSRZoneImpl](
|
||||
"z1" -> CSRZoneImpl("Solsar", "map01", "z1"),
|
||||
"z2" -> CSRZoneImpl("Hossin", "map02", "z2"),
|
||||
"z3" -> CSRZoneImpl("Cyssor", "map03", "z3"),
|
||||
"z4" -> CSRZoneImpl("Ishundar", "map04", "z4"),
|
||||
"z5" -> CSRZoneImpl("Forseral", "map05", "z5"),
|
||||
"z6" -> CSRZoneImpl("Ceryshen", "map06", "z6"),
|
||||
"z7" -> CSRZoneImpl("Esamir", "map07", "z7"),
|
||||
"z8" -> CSRZoneImpl("Oshur", "map08", "z8"),
|
||||
"z9" -> CSRZoneImpl("Searhus", "map09", "z9"),
|
||||
"z10" -> CSRZoneImpl("Amerish", "map10", "z10"),
|
||||
"home1" -> CSRZoneImpl("NC Sanctuary", "map11", "home1"),
|
||||
"home2" -> CSRZoneImpl("TR Sanctuary", "map12", "home2"),
|
||||
"home3" -> CSRZoneImpl("VS Sanctuary", "map13", "home3"),
|
||||
"tzshtr" -> CSRZoneImpl("VR Shooting Range TR", "map14", "tzshtr"),
|
||||
"tzdrtr" -> CSRZoneImpl("VR Driving Range TR", "map15", "tzdrtr"),
|
||||
"tzcotr" -> CSRZoneImpl("VR Combat Zone TR", "map16", "tzcotr"),
|
||||
"tzshvs" -> CSRZoneImpl("VR Shooting Range VS", "map14", "tzshvs"),
|
||||
"tzdrvs" -> CSRZoneImpl("VR Driving Range VS", "map15", "tzdrvs"),
|
||||
"tzcovs" -> CSRZoneImpl("VR Combat Zone VS", "map16", "tzcovs"),
|
||||
"tzshnc" -> CSRZoneImpl("VR Shooting Range NC", "map14", "tzshnc"),
|
||||
"tzdrnc" -> CSRZoneImpl("VR Driving Range NC", "map15", "tzdrnc"),
|
||||
"tzconc" -> CSRZoneImpl("VR Combat Zone NC", "map16", "tzconc"),
|
||||
"c1" -> CSRZoneImpl("Supai", "ugd01", "c1"),
|
||||
"c2" -> CSRZoneImpl("Hunhau", "ugd02", "c2"),
|
||||
"c3" -> CSRZoneImpl("Adlivun", "ugd03", "c3"),
|
||||
"c4" -> CSRZoneImpl("Byblos", "ugd04", "c4"),
|
||||
"c5" -> CSRZoneImpl("Annwn", "ugd05", "c5"),
|
||||
"c6" -> CSRZoneImpl("Drugaskan", "ugd06", "c6"),
|
||||
"i4" -> CSRZoneImpl("Nexus", "map96", "i4"),
|
||||
"i3" -> CSRZoneImpl("Desolation", "map97", "i3"),
|
||||
"i2" -> CSRZoneImpl("Ascension", "map98", "i2"),
|
||||
"i1" -> CSRZoneImpl("Extinction", "map99", "i1"),
|
||||
"homebo" -> CSRZoneImpl("Black_ops_hq", "Black_ops_hq", "homebo"),
|
||||
"station1" -> CSRZoneImpl("TR Station", "Station1", "station1"),
|
||||
"station2" -> CSRZoneImpl("NC Station", "Station2", "station2"),
|
||||
"station3" -> CSRZoneImpl("VS Station", "Station3", "station3")
|
||||
private val zones = Map[String, PointOfInterest](
|
||||
"z1" -> PointOfInterest("Solsar", "map01", "z1"),
|
||||
"z2" -> PointOfInterest("Hossin", "map02", "z2"),
|
||||
"z3" -> PointOfInterest("Cyssor", "map03", "z3"),
|
||||
"z4" -> PointOfInterest("Ishundar", "map04", "z4"),
|
||||
"z5" -> PointOfInterest("Forseral", "map05", "z5"),
|
||||
"z6" -> PointOfInterest("Ceryshen", "map06", "z6"),
|
||||
"z7" -> PointOfInterest("Esamir", "map07", "z7"),
|
||||
"z8" -> PointOfInterest("Oshur", "map08", "z8"),
|
||||
"z9" -> PointOfInterest("Searhus", "map09", "z9"),
|
||||
"z10" -> PointOfInterest("Amerish", "map10", "z10"),
|
||||
"home1" -> PointOfInterest("NC Sanctuary", "map11", "home1"),
|
||||
"home2" -> PointOfInterest("TR Sanctuary", "map12", "home2"),
|
||||
"home3" -> PointOfInterest("VS Sanctuary", "map13", "home3"),
|
||||
"tzshtr" -> PointOfInterest("VR Shooting Range TR", "map14", "tzshtr"),
|
||||
"tzdrtr" -> PointOfInterest("VR Driving Range TR", "map15", "tzdrtr"),
|
||||
"tzcotr" -> PointOfInterest("VR Combat Zone TR", "map16", "tzcotr"),
|
||||
"tzshvs" -> PointOfInterest("VR Shooting Range VS", "map14", "tzshvs"),
|
||||
"tzdrvs" -> PointOfInterest("VR Driving Range VS", "map15", "tzdrvs"),
|
||||
"tzcovs" -> PointOfInterest("VR Combat Zone VS", "map16", "tzcovs"),
|
||||
"tzshnc" -> PointOfInterest("VR Shooting Range NC", "map14", "tzshnc"),
|
||||
"tzdrnc" -> PointOfInterest("VR Driving Range NC", "map15", "tzdrnc"),
|
||||
"tzconc" -> PointOfInterest("VR Combat Zone NC", "map16", "tzconc"),
|
||||
"c1" -> PointOfInterest("Supai", "ugd01", "c1"),
|
||||
"c2" -> PointOfInterest("Hunhau", "ugd02", "c2"),
|
||||
"c3" -> PointOfInterest("Adlivun", "ugd03", "c3"),
|
||||
"c4" -> PointOfInterest("Byblos", "ugd04", "c4"),
|
||||
"c5" -> PointOfInterest("Annwn", "ugd05", "c5"),
|
||||
"c6" -> PointOfInterest("Drugaskan", "ugd06", "c6"),
|
||||
"i4" -> PointOfInterest("Nexus", "map96", "i4"),
|
||||
"i3" -> PointOfInterest("Desolation", "map97", "i3"),
|
||||
"i2" -> PointOfInterest("Ascension", "map98", "i2"),
|
||||
"i1" -> PointOfInterest("Extinction", "map99", "i1"),
|
||||
"homebo" -> PointOfInterest("Black_ops_hq", "Black_ops_hq", "homebo"),
|
||||
"station1" -> PointOfInterest("TR Station", "Station1", "station1"),
|
||||
"station2" -> PointOfInterest("NC Station", "Station2", "station2"),
|
||||
"station3" -> PointOfInterest("VS Station", "Station3", "station3")
|
||||
)
|
||||
|
||||
/**
|
||||
|
|
@ -134,14 +131,14 @@ object CSRZoneImpl {
|
|||
* @param map the map name of the zone (this map is loaded)
|
||||
* @param zonename the zone's internal name
|
||||
*/
|
||||
def apply(alias: String, map: String, zonename: String): CSRZoneImpl = new CSRZoneImpl(alias, map, zonename)
|
||||
def apply(alias: String, map: String, zonename: String): PointOfInterest = new PointOfInterest(alias, map, zonename)
|
||||
|
||||
/**
|
||||
* Get a valid `CSRZone`'s information.
|
||||
* @param zoneId a name that describes the zone and should be searchable
|
||||
* @return the `CSRZone`, or `None`
|
||||
*/
|
||||
def get(zoneId: String): Option[CSRZoneImpl] = {
|
||||
def get(zoneId: String): Option[PointOfInterest] = {
|
||||
var zId = zoneId.toLowerCase
|
||||
if (alias.get(zId).isDefined)
|
||||
zId = alias(zId)
|
||||
|
|
@ -151,16 +148,19 @@ object CSRZoneImpl {
|
|||
/**
|
||||
* Get a location within the `CSRZone`.
|
||||
* The location should be a facility or a warpgate or interesting.
|
||||
* @param zone the `CSRZone`
|
||||
* @param locId a name that describes a known location in the provided `CSRZone` and is searchable
|
||||
* @param zoneId the `CSRZone`
|
||||
* @param locationId a name that describes a known location in the provided `CSRZone` and is searchable
|
||||
* @return the coordinates of that location, or None
|
||||
*/
|
||||
def getWarpLocation(zone: CSRZoneImpl, locId: String): Option[Vector3] = {
|
||||
val low_locId = locId.toLowerCase
|
||||
var location = zone.locations.get(low_locId)
|
||||
if (location.isEmpty)
|
||||
location = zone.gates.get(low_locId)
|
||||
location
|
||||
def getWarpLocation(zoneId: String, locationId: String): Option[Vector3] = {
|
||||
get(zoneId) match {
|
||||
case Some(poi) =>
|
||||
poi.locations.get(locationId) match {
|
||||
case Some(position) => Some(position)
|
||||
case None => poi.gates.get(locationId)
|
||||
}
|
||||
case None => None
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -169,7 +169,7 @@ object CSRZoneImpl {
|
|||
* @param gateId a name that describes a known warpgate in the provided `CSRZone` and is searchable
|
||||
* @return the coordinates of that warpgate, or None
|
||||
*/
|
||||
def getWarpgate(zone: CSRZoneImpl, gateId: String): Option[Vector3] = {
|
||||
def getWarpgate(zone: PointOfInterest, gateId: String): Option[Vector3] = {
|
||||
zone.gates.get(gateId.toLowerCase)
|
||||
}
|
||||
|
||||
|
|
@ -178,7 +178,7 @@ object CSRZoneImpl {
|
|||
* @return all of the zonenames
|
||||
*/
|
||||
def list: String = {
|
||||
"zonenames: z1 - z10, home1 - home3, tzshnc, tzdrnc, tzconc, tzshtr, tzdrtr, tzcotr, tzshvs, tzdrvs, tzcovs, c1 - c6, i1 - i4; zones are also aliased to their continent name"
|
||||
"zone names: z1 - z10, home1 - home3, tzshnc, tzdrnc, tzconc, tzshtr, tzdrtr, tzcotr, tzshvs, tzdrvs, tzcovs, c1 - c6, i1 - i4; zones are also aliased to their continent name"
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -186,7 +186,7 @@ object CSRZoneImpl {
|
|||
* @param zone the `CSRZone`
|
||||
* @return all of the location keys
|
||||
*/
|
||||
def listLocations(zone: CSRZoneImpl): String = {
|
||||
def listLocations(zone: PointOfInterest): String = {
|
||||
var out: String = "warps: "
|
||||
if (zone.locations.nonEmpty) {
|
||||
out += zone.locations.keys.toArray.sorted.mkString(", ")
|
||||
|
|
@ -200,7 +200,7 @@ object CSRZoneImpl {
|
|||
* @param zone the `CSRZone`
|
||||
* @return all of the warpgate keys
|
||||
*/
|
||||
def listWarpgates(zone: CSRZoneImpl): String = {
|
||||
def listWarpgates(zone: PointOfInterest): String = {
|
||||
var out: String = "gatenames: "
|
||||
if (zone.gates.isEmpty)
|
||||
out += "none"
|
||||
|
|
@ -214,7 +214,7 @@ object CSRZoneImpl {
|
|||
* @param zone the `CSRZone`
|
||||
* @return the coordinates of the spawn point
|
||||
*/
|
||||
def selectRandom(zone: CSRZoneImpl): Vector3 = {
|
||||
def selectRandom(zone: PointOfInterest): Vector3 = {
|
||||
var outlets = zone.locations //random location?
|
||||
if (outlets.nonEmpty) {
|
||||
return outlets.values.toArray.apply(rand.nextInt(outlets.size))
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
package net.psforever.pslogin
|
||||
package net.psforever.zones
|
||||
|
||||
import net.psforever.objects.LocalProjectile
|
||||
import net.psforever.objects.ballistics.Projectile
|
||||
import net.psforever.objects.zones.ZoneMap
|
||||
import zonemaps._
|
||||
import net.psforever.zones.zonemaps._
|
||||
|
||||
import scala.concurrent.Future
|
||||
import scala.util.{Failure, Success}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package net.psforever.pslogin
|
||||
package net.psforever.zones
|
||||
|
||||
import akka.actor.ActorContext
|
||||
import net.psforever.objects.GlobalDefinitions
|
||||
|
|
@ -6,10 +6,9 @@ import net.psforever.objects.serverobject.resourcesilo.ResourceSilo
|
|||
import net.psforever.objects.serverobject.structures.WarpGate
|
||||
import net.psforever.objects.zones.Zone
|
||||
import net.psforever.types.PlanetSideEmpire
|
||||
|
||||
import scala.concurrent.duration._
|
||||
import scala.concurrent.Await
|
||||
import scala.collection.immutable.HashMap
|
||||
import scala.concurrent.Await
|
||||
|
||||
object Zones {
|
||||
val zones: HashMap[String, Zone] = HashMap(
|
||||
|
|
@ -547,8 +546,8 @@ object Zones {
|
|||
* @return the duration
|
||||
*/
|
||||
def StandardTimeRules(defender: SourceEntry, attacker: SourceEntry): FiniteDuration = {
|
||||
import net.psforever.objects.ballistics._
|
||||
import net.psforever.objects.GlobalDefinitions
|
||||
import net.psforever.objects.ballistics._
|
||||
if (attacker.Faction == defender.Faction) {
|
||||
0 seconds
|
||||
} else {
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package net.psforever.pslogin.zonemaps
|
||||
package net.psforever.zones.zonemaps
|
||||
|
||||
import net.psforever.objects.GlobalDefinitions._
|
||||
import net.psforever.objects.serverobject.doors.Door
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package net.psforever.pslogin.zonemaps
|
||||
package net.psforever.zones.zonemaps
|
||||
|
||||
import net.psforever.objects.GlobalDefinitions._
|
||||
import net.psforever.objects.serverobject.doors.Door
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package net.psforever.pslogin.zonemaps
|
||||
package net.psforever.zones.zonemaps
|
||||
|
||||
import net.psforever.objects.GlobalDefinitions._
|
||||
import net.psforever.objects.serverobject.doors.Door
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package net.psforever.pslogin.zonemaps
|
||||
package net.psforever.zones.zonemaps
|
||||
|
||||
import net.psforever.objects.GlobalDefinitions._
|
||||
import net.psforever.objects.serverobject.doors.Door
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package net.psforever.pslogin.zonemaps
|
||||
package net.psforever.zones.zonemaps
|
||||
|
||||
import net.psforever.objects.GlobalDefinitions._
|
||||
import net.psforever.objects.serverobject.doors.Door
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package net.psforever.pslogin.zonemaps
|
||||
package net.psforever.zones.zonemaps
|
||||
|
||||
import net.psforever.objects.GlobalDefinitions._
|
||||
import net.psforever.objects.serverobject.doors.Door
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package net.psforever.pslogin.zonemaps
|
||||
package net.psforever.zones.zonemaps
|
||||
|
||||
import net.psforever.objects.GlobalDefinitions._
|
||||
import net.psforever.objects.serverobject.doors.Door
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package net.psforever.pslogin.zonemaps
|
||||
package net.psforever.zones.zonemaps
|
||||
|
||||
import net.psforever.objects.GlobalDefinitions._
|
||||
import net.psforever.objects.serverobject.doors.Door
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package net.psforever.pslogin.zonemaps
|
||||
package net.psforever.zones.zonemaps
|
||||
|
||||
import net.psforever.objects.GlobalDefinitions._
|
||||
import net.psforever.objects.serverobject.doors.Door
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package net.psforever.pslogin.zonemaps
|
||||
package net.psforever.zones.zonemaps
|
||||
|
||||
import net.psforever.objects.GlobalDefinitions._
|
||||
import net.psforever.objects.serverobject.doors.Door
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package net.psforever.pslogin.zonemaps
|
||||
package net.psforever.zones.zonemaps
|
||||
|
||||
import net.psforever.objects.GlobalDefinitions._
|
||||
import net.psforever.objects.serverobject.doors.Door
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package net.psforever.pslogin.zonemaps
|
||||
package net.psforever.zones.zonemaps
|
||||
|
||||
import net.psforever.objects.GlobalDefinitions._
|
||||
import net.psforever.objects.serverobject.doors.Door
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package net.psforever.pslogin.zonemaps
|
||||
package net.psforever.zones.zonemaps
|
||||
|
||||
import net.psforever.objects.GlobalDefinitions._
|
||||
import net.psforever.objects.serverobject.doors.Door
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package net.psforever.pslogin.zonemaps
|
||||
package net.psforever.zones.zonemaps
|
||||
|
||||
import net.psforever.objects.GlobalDefinitions._
|
||||
import net.psforever.objects.serverobject.doors.Door
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package net.psforever.pslogin.zonemaps
|
||||
package net.psforever.zones.zonemaps
|
||||
|
||||
import net.psforever.objects.GlobalDefinitions._
|
||||
import net.psforever.objects.serverobject.doors.Door
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package net.psforever.pslogin.zonemaps
|
||||
package net.psforever.zones.zonemaps
|
||||
|
||||
import net.psforever.objects.GlobalDefinitions._
|
||||
import net.psforever.objects.serverobject.doors.Door
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package net.psforever.pslogin.zonemaps
|
||||
package net.psforever.zones.zonemaps
|
||||
|
||||
import net.psforever.objects.GlobalDefinitions._
|
||||
import net.psforever.objects.serverobject.doors.Door
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package net.psforever.pslogin.zonemaps
|
||||
package net.psforever.zones.zonemaps
|
||||
|
||||
import net.psforever.objects.GlobalDefinitions._
|
||||
import net.psforever.objects.serverobject.doors.Door
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package net.psforever.pslogin.zonemaps
|
||||
package net.psforever.zones.zonemaps
|
||||
|
||||
import net.psforever.objects.GlobalDefinitions._
|
||||
import net.psforever.objects.serverobject.doors.Door
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package net.psforever.pslogin.zonemaps
|
||||
package net.psforever.zones.zonemaps
|
||||
|
||||
import net.psforever.objects.GlobalDefinitions._
|
||||
import net.psforever.objects.serverobject.doors.Door
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package net.psforever.pslogin.zonemaps
|
||||
package net.psforever.zones.zonemaps
|
||||
|
||||
import net.psforever.objects.GlobalDefinitions._
|
||||
import net.psforever.objects.serverobject.doors.Door
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package net.psforever.pslogin.zonemaps
|
||||
package net.psforever.zones.zonemaps
|
||||
|
||||
import net.psforever.objects.GlobalDefinitions._
|
||||
import net.psforever.objects.serverobject.doors.Door
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package net.psforever.pslogin.zonemaps
|
||||
package net.psforever.zones.zonemaps
|
||||
|
||||
import net.psforever.objects.GlobalDefinitions._
|
||||
import net.psforever.objects.serverobject.doors.Door
|
||||
|
|
@ -15,14 +15,14 @@ object Service {
|
|||
}
|
||||
|
||||
trait GenericEventBusMsg {
|
||||
def toChannel: String
|
||||
def channel: String
|
||||
}
|
||||
|
||||
class GenericEventBus[A <: GenericEventBusMsg] extends ActorEventBus with SubchannelClassification {
|
||||
type Event = A
|
||||
type Classifier = String
|
||||
|
||||
protected def classify(event: Event): Classifier = event.toChannel
|
||||
protected def classify(event: Event): Classifier = event.channel
|
||||
|
||||
protected def subclassification =
|
||||
new Subclassification[Classifier] {
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import net.psforever.types.{ExoSuitType, PlanetSideEmpire, PlanetSideGUID, Trans
|
|||
import services.GenericEventBusMsg
|
||||
|
||||
final case class AvatarServiceResponse(
|
||||
toChannel: String,
|
||||
channel: String,
|
||||
avatar_guid: PlanetSideGUID,
|
||||
replyMessage: AvatarResponse.Response
|
||||
) extends GenericEventBusMsg
|
||||
|
|
|
|||
|
|
@ -1,62 +0,0 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package services.chat
|
||||
|
||||
import net.psforever.objects.zones.Zone
|
||||
import net.psforever.packet.game.ChatMsg
|
||||
import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID, Vector3}
|
||||
|
||||
object ChatAction {
|
||||
sealed trait Action
|
||||
|
||||
final case class Local(
|
||||
player_guid: PlanetSideGUID,
|
||||
player_name: String,
|
||||
continent: Zone,
|
||||
player_pos: Vector3,
|
||||
player_faction: PlanetSideEmpire.Value,
|
||||
msg: ChatMsg
|
||||
) extends Action
|
||||
final case class Tell(player_guid: PlanetSideGUID, player_name: String, msg: ChatMsg) extends Action
|
||||
final case class Broadcast(
|
||||
player_guid: PlanetSideGUID,
|
||||
player_name: String,
|
||||
continent: Zone,
|
||||
player_pos: Vector3,
|
||||
player_faction: PlanetSideEmpire.Value,
|
||||
msg: ChatMsg
|
||||
) extends Action
|
||||
final case class Voice(
|
||||
player_guid: PlanetSideGUID,
|
||||
player_name: String,
|
||||
continent: Zone,
|
||||
player_pos: Vector3,
|
||||
player_faction: PlanetSideEmpire.Value,
|
||||
msg: ChatMsg
|
||||
) extends Action
|
||||
final case class Note(player_guid: PlanetSideGUID, player_name: String, msg: ChatMsg) extends Action
|
||||
final case class Squad(
|
||||
player_guid: PlanetSideGUID,
|
||||
player_name: String,
|
||||
continent: Zone,
|
||||
player_pos: Vector3,
|
||||
player_faction: PlanetSideEmpire.Value,
|
||||
msg: ChatMsg
|
||||
) extends Action
|
||||
final case class Platoon(
|
||||
player_guid: PlanetSideGUID,
|
||||
player_name: String,
|
||||
continent: Zone,
|
||||
player_pos: Vector3,
|
||||
player_faction: PlanetSideEmpire.Value,
|
||||
msg: ChatMsg
|
||||
) extends Action
|
||||
final case class Command(
|
||||
player_guid: PlanetSideGUID,
|
||||
player_name: String,
|
||||
continent: Zone,
|
||||
player_pos: Vector3,
|
||||
player_faction: PlanetSideEmpire.Value,
|
||||
msg: ChatMsg
|
||||
) extends Action
|
||||
final case class GM(player_guid: PlanetSideGUID, player_name: String, msg: ChatMsg) extends Action
|
||||
}
|
||||
|
|
@ -1,90 +0,0 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package services.chat
|
||||
|
||||
import net.psforever.types.{ChatMessageType, PlanetSideGUID}
|
||||
|
||||
object ChatResponse {
|
||||
sealed trait Response
|
||||
|
||||
final case class Local(
|
||||
sender: String,
|
||||
messageType: ChatMessageType.Value,
|
||||
wideContents: Boolean,
|
||||
recipient: String,
|
||||
contents: String,
|
||||
note: Option[String]
|
||||
) extends Response
|
||||
final case class Tell(
|
||||
sender: String,
|
||||
messageType: ChatMessageType.Value,
|
||||
wideContents: Boolean,
|
||||
recipient: String,
|
||||
contents: String,
|
||||
note: Option[String]
|
||||
) extends Response
|
||||
final case class UTell(
|
||||
sender: String,
|
||||
messageType: ChatMessageType.Value,
|
||||
wideContents: Boolean,
|
||||
recipient: String,
|
||||
contents: String,
|
||||
note: Option[String]
|
||||
) extends Response
|
||||
final case class Broadcast(
|
||||
messageType: ChatMessageType.Value,
|
||||
wideContents: Boolean,
|
||||
recipient: String,
|
||||
contents: String,
|
||||
note: Option[String]
|
||||
) extends Response
|
||||
final case class Voice(
|
||||
messageType: ChatMessageType.Value,
|
||||
wideContents: Boolean,
|
||||
recipient: String,
|
||||
contents: String,
|
||||
note: Option[String]
|
||||
) extends Response
|
||||
final case class Unk45(
|
||||
sender: String,
|
||||
messageType: ChatMessageType.Value,
|
||||
wideContents: Boolean,
|
||||
recipient: String,
|
||||
contents: String,
|
||||
note: Option[String]
|
||||
) extends Response
|
||||
final case class Squad(
|
||||
sender: String,
|
||||
messageType: ChatMessageType.Value,
|
||||
wideContents: Boolean,
|
||||
recipient: String,
|
||||
contents: String,
|
||||
note: Option[String]
|
||||
) extends Response
|
||||
final case class Platoon(
|
||||
sender: String,
|
||||
messageType: ChatMessageType.Value,
|
||||
wideContents: Boolean,
|
||||
recipient: String,
|
||||
contents: String,
|
||||
note: Option[String]
|
||||
) extends Response
|
||||
final case class Command(
|
||||
sender: String,
|
||||
messageType: ChatMessageType.Value,
|
||||
wideContents: Boolean,
|
||||
recipient: String,
|
||||
contents: String,
|
||||
note: Option[String]
|
||||
) extends Response
|
||||
|
||||
final case class Text(
|
||||
toChannel: String,
|
||||
avatar_guid: PlanetSideGUID,
|
||||
personal: Int,
|
||||
messageType: ChatMessageType.Value,
|
||||
wideContents: Boolean,
|
||||
recipient: String,
|
||||
contents: String,
|
||||
note: Option[String]
|
||||
)
|
||||
}
|
||||
|
|
@ -1,227 +1,188 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package services.chat
|
||||
|
||||
import akka.actor.Actor
|
||||
import net.psforever.objects.LivePlayerList
|
||||
import akka.actor.typed.receptionist.{Receptionist, ServiceKey}
|
||||
import akka.actor.typed.{ActorRef, Behavior}
|
||||
import akka.actor.typed.scaladsl.{AbstractBehavior, ActorContext, Behaviors}
|
||||
import net.psforever.objects.Session
|
||||
import net.psforever.packet.game.ChatMsg
|
||||
import net.psforever.types.ChatMessageType
|
||||
import services.{GenericEventBus, Service}
|
||||
import net.psforever.types.{ChatMessageType, PlanetSideEmpire, PlanetSideGUID}
|
||||
|
||||
class ChatService extends Actor {
|
||||
private[this] val log = org.log4s.getLogger
|
||||
object ChatService {
|
||||
val ChatServiceKey: ServiceKey[Command] = ServiceKey[ChatService.Command]("chatService")
|
||||
|
||||
override def preStart = {
|
||||
log.info("Starting....")
|
||||
def apply(): Behavior[Command] =
|
||||
Behaviors.setup { context =>
|
||||
context.system.receptionist ! Receptionist.Register(ChatServiceKey, context.self)
|
||||
new ChatService(context)
|
||||
}
|
||||
|
||||
sealed trait Command
|
||||
|
||||
final case class JoinChannel(actor: ActorRef[MessageResponse], session: Session, channel: ChatChannel) extends Command
|
||||
final case class LeaveChannel(actor: ActorRef[MessageResponse], channel: ChatChannel) extends Command
|
||||
final case class LeaveAllChannels(actor: ActorRef[MessageResponse]) extends Command
|
||||
|
||||
final case class Message(session: Session, message: ChatMsg, channel: ChatChannel) extends Command
|
||||
final case class MessageResponse(session: Session, message: ChatMsg, channel: ChatChannel)
|
||||
|
||||
trait ChatChannel
|
||||
object ChatChannel {
|
||||
// one of the default channels that the player is always subscribed to (local, broadcast, command...)
|
||||
final case class Default() extends ChatChannel
|
||||
final case class Squad(guid: PlanetSideGUID) extends ChatChannel
|
||||
}
|
||||
|
||||
val ChatEvents = new GenericEventBus[ChatServiceResponse]
|
||||
}
|
||||
|
||||
def receive = {
|
||||
case Service.Join(channel) =>
|
||||
val path = s"/Chat/$channel"
|
||||
val who = sender()
|
||||
log.info(s"$who has joined $path")
|
||||
ChatEvents.subscribe(who, path)
|
||||
case Service.Leave(None) =>
|
||||
ChatEvents.unsubscribe(sender())
|
||||
case Service.Leave(Some(channel)) =>
|
||||
val path = s"/Chat/$channel"
|
||||
val who = sender()
|
||||
log.info(s"$who has left $path")
|
||||
ChatEvents.unsubscribe(who, path)
|
||||
case Service.LeaveAll() =>
|
||||
ChatEvents.unsubscribe(sender())
|
||||
class ChatService(context: ActorContext[ChatService.Command]) extends AbstractBehavior[ChatService.Command](context) {
|
||||
|
||||
case ChatServiceMessage(forChannel, action) =>
|
||||
action match {
|
||||
case ChatAction.Local(player_guid, player_name, cont, player_pos, player_faction, msg) => // local
|
||||
ChatEvents.publish(
|
||||
ChatServiceResponse(
|
||||
s"/Chat/$forChannel",
|
||||
player_guid,
|
||||
player_name,
|
||||
cont,
|
||||
player_pos,
|
||||
player_faction,
|
||||
2,
|
||||
ChatMsg(ChatMessageType.CMT_OPEN, msg.wideContents, player_name, msg.contents, None)
|
||||
)
|
||||
)
|
||||
case ChatAction.Tell(player_guid, player_name, msg) => // tell
|
||||
var good: Boolean = false
|
||||
LivePlayerList
|
||||
.WorldPopulation(_ => true)
|
||||
.foreach(char => {
|
||||
if (char.name.equalsIgnoreCase(msg.recipient)) {
|
||||
good = true
|
||||
}
|
||||
})
|
||||
if (good) {
|
||||
ChatEvents.publish(
|
||||
ChatServiceResponse(
|
||||
s"/Chat/$forChannel",
|
||||
player_guid,
|
||||
player_name,
|
||||
target = 0,
|
||||
replyMessage = ChatMsg(ChatMessageType.CMT_TELL, msg.wideContents, msg.recipient, msg.contents, None)
|
||||
)
|
||||
)
|
||||
ChatEvents.publish(
|
||||
ChatServiceResponse(
|
||||
s"/Chat/$forChannel",
|
||||
player_guid,
|
||||
player_name,
|
||||
target = 1,
|
||||
replyMessage =
|
||||
ChatMsg(ChatMessageType.U_CMT_TELLFROM, msg.wideContents, msg.recipient, msg.contents, None)
|
||||
)
|
||||
)
|
||||
} else {
|
||||
ChatEvents.publish(
|
||||
ChatServiceResponse(
|
||||
s"/Chat/$forChannel",
|
||||
player_guid,
|
||||
player_name,
|
||||
target = 1,
|
||||
replyMessage =
|
||||
ChatMsg(ChatMessageType.U_CMT_TELLFROM, msg.wideContents, msg.recipient, msg.contents, None)
|
||||
)
|
||||
)
|
||||
ChatEvents.publish(
|
||||
ChatServiceResponse(
|
||||
s"/Chat/$forChannel",
|
||||
player_guid,
|
||||
player_name,
|
||||
target = 1,
|
||||
replyMessage = ChatMsg(ChatMessageType.UNK_45, msg.wideContents, "", "@NoTell_Target", None)
|
||||
)
|
||||
)
|
||||
}
|
||||
case ChatAction.Broadcast(player_guid, player_name, cont, player_pos, player_faction, msg) => // broadcast
|
||||
ChatEvents.publish(
|
||||
ChatServiceResponse(
|
||||
s"/Chat/$forChannel",
|
||||
player_guid,
|
||||
player_name,
|
||||
cont,
|
||||
player_pos,
|
||||
player_faction,
|
||||
2,
|
||||
ChatMsg(msg.messageType, msg.wideContents, player_name, msg.contents, None)
|
||||
)
|
||||
)
|
||||
case ChatAction.Voice(player_guid, player_name, cont, player_pos, player_faction, msg) => // voice
|
||||
ChatEvents.publish(
|
||||
ChatServiceResponse(
|
||||
s"/Chat/$forChannel",
|
||||
player_guid,
|
||||
player_name,
|
||||
cont,
|
||||
player_pos,
|
||||
player_faction,
|
||||
2,
|
||||
ChatMsg(ChatMessageType.CMT_VOICE, false, player_name, msg.contents, None)
|
||||
)
|
||||
)
|
||||
import ChatService._
|
||||
import ChatMessageType._
|
||||
|
||||
case ChatAction.Note(player_guid, player_name, msg) => // note
|
||||
ChatEvents.publish(
|
||||
ChatServiceResponse(
|
||||
s"/Chat/$forChannel",
|
||||
player_guid,
|
||||
player_name,
|
||||
target = 1,
|
||||
replyMessage = ChatMsg(ChatMessageType.U_CMT_GMTELLFROM, true, msg.recipient, msg.contents, None)
|
||||
)
|
||||
)
|
||||
ChatEvents.publish(
|
||||
ChatServiceResponse(
|
||||
s"/Chat/$forChannel",
|
||||
player_guid,
|
||||
player_name,
|
||||
target = 1,
|
||||
replyMessage = ChatMsg(
|
||||
ChatMessageType.CMT_GMTELL,
|
||||
true,
|
||||
"Server",
|
||||
"Why do you try to /note ? That's a GM command ! ... Or not, nobody can /note",
|
||||
None
|
||||
)
|
||||
)
|
||||
)
|
||||
case ChatAction.Squad(player_guid, player_name, cont, player_pos, player_faction, msg) => // squad
|
||||
ChatEvents.publish(
|
||||
ChatServiceResponse(
|
||||
s"/Chat/$forChannel",
|
||||
player_guid,
|
||||
player_name,
|
||||
cont,
|
||||
player_pos,
|
||||
player_faction,
|
||||
2,
|
||||
ChatMsg(ChatMessageType.CMT_SQUAD, msg.wideContents, player_name, msg.contents, None)
|
||||
)
|
||||
)
|
||||
case ChatAction.Platoon(player_guid, player_name, cont, player_pos, player_faction, msg) => // platoon
|
||||
ChatEvents.publish(
|
||||
ChatServiceResponse(
|
||||
s"/Chat/$forChannel",
|
||||
player_guid,
|
||||
player_name,
|
||||
cont,
|
||||
player_pos,
|
||||
player_faction,
|
||||
2,
|
||||
ChatMsg(ChatMessageType.CMT_PLATOON, msg.wideContents, player_name, msg.contents, None)
|
||||
)
|
||||
)
|
||||
case ChatAction.Command(player_guid, player_name, cont, player_pos, player_faction, msg) => // command
|
||||
ChatEvents.publish(
|
||||
ChatServiceResponse(
|
||||
s"/Chat/$forChannel",
|
||||
player_guid,
|
||||
player_name,
|
||||
cont,
|
||||
player_pos,
|
||||
player_faction,
|
||||
2,
|
||||
ChatMsg(ChatMessageType.CMT_COMMAND, msg.wideContents, player_name, msg.contents, None)
|
||||
)
|
||||
)
|
||||
case ChatAction.GM(player_guid, player_name, msg) => // GM
|
||||
msg.messageType match {
|
||||
case ChatMessageType.CMT_SILENCE =>
|
||||
ChatEvents.publish(
|
||||
ChatServiceResponse(
|
||||
s"/Chat/$forChannel",
|
||||
player_guid,
|
||||
msg.contents,
|
||||
target = 0,
|
||||
replyMessage = ChatMsg(ChatMessageType.CMT_SILENCE, true, "", "", None)
|
||||
private[this] val log = org.log4s.getLogger
|
||||
var subscriptions: List[JoinChannel] = List[JoinChannel]()
|
||||
|
||||
override def onMessage(msg: Command): Behavior[Command] = {
|
||||
msg match {
|
||||
case subscription: JoinChannel =>
|
||||
subscriptions ++= List(subscription)
|
||||
this
|
||||
|
||||
case LeaveChannel(actor, channel) =>
|
||||
subscriptions = subscriptions.filter {
|
||||
case JoinChannel(a, _, c) => actor != a && channel != c
|
||||
}
|
||||
this
|
||||
|
||||
case LeaveAllChannels(actor) =>
|
||||
subscriptions = subscriptions.filter {
|
||||
case JoinChannel(a, _, _) => actor != a
|
||||
}
|
||||
this
|
||||
|
||||
case Message(session, message, channel) =>
|
||||
(channel, message.messageType) match {
|
||||
case (ChatChannel.Squad(_), CMT_SQUAD) =>
|
||||
case (ChatChannel.Default(), messageType) if messageType != CMT_SQUAD =>
|
||||
case _ =>
|
||||
log.error(s"invalid chat channel $channel for messageType ${message.messageType}")
|
||||
return this
|
||||
}
|
||||
val subs = subscriptions.filter(_.channel == channel)
|
||||
message.messageType match {
|
||||
case CMT_TELL | CMT_GMTELL =>
|
||||
subs.find(_.session.player.Name == session.player.Name).foreach {
|
||||
case JoinChannel(sender, _, _) =>
|
||||
sender ! MessageResponse(
|
||||
session,
|
||||
message.copy(messageType = if (message.messageType == CMT_TELL) U_CMT_TELLFROM else U_CMT_GMTELLFROM),
|
||||
channel
|
||||
)
|
||||
)
|
||||
// if(player_guid != PlanetSideGUID(0)) {
|
||||
//
|
||||
// val args = msg.contents.split(" ")
|
||||
// var silence_name : String = ""
|
||||
// var silence_time : Int = 5
|
||||
// if (args.length == 1) {
|
||||
// silence_name = args(0)
|
||||
// }
|
||||
// else if (args.length == 2) {
|
||||
// silence_name = args(0)
|
||||
// silence_time = args(1).toInt
|
||||
// }
|
||||
// ChatEvents.publish(
|
||||
// ChatServiceResponse(s"/Chat/$forChannel", player_guid, player_name, target = 1, replyMessage = ChatMsg(ChatMessageType.UNK_45, true, "", silence_name + " silenced for " + silence_time + " min(s)", None))
|
||||
// )
|
||||
// }
|
||||
case _ => ;
|
||||
}
|
||||
case _ => ;
|
||||
}
|
||||
subs.find(_.session.player.Name == message.recipient) match {
|
||||
case Some(JoinChannel(receiver, _, _)) =>
|
||||
receiver ! MessageResponse(session, message, channel)
|
||||
case None =>
|
||||
sender ! MessageResponse(
|
||||
session,
|
||||
ChatMsg(ChatMessageType.UNK_45, false, "", "@NoTell_Target", None),
|
||||
channel
|
||||
)
|
||||
}
|
||||
|
||||
case msg =>
|
||||
log.info(s"Unhandled message $msg from $sender")
|
||||
}
|
||||
|
||||
case CMT_SILENCE =>
|
||||
val args = message.contents.split(" ")
|
||||
val (name, time, error) = (args.lift(0), args.lift(1)) match {
|
||||
case (Some(name), None) => (Some(name), Some(5), None)
|
||||
case (Some(name), Some(time)) =>
|
||||
time.toIntOption match {
|
||||
case Some(time) =>
|
||||
(Some(name), Some(time), None)
|
||||
case None =>
|
||||
(None, None, Some("bad time format"))
|
||||
}
|
||||
case _ => (None, None, None)
|
||||
}
|
||||
|
||||
val sender = subs.find(_.session.player.Name == session.player.Name)
|
||||
|
||||
(sender, name, time, error) match {
|
||||
case (Some(sender), Some(name), Some(_), None) =>
|
||||
val recipient = subs.find(_.session.player.Name == name)
|
||||
recipient match {
|
||||
case Some(recipient) =>
|
||||
if (recipient.session.player.silenced) {
|
||||
sender.actor ! MessageResponse(
|
||||
session,
|
||||
ChatMsg(UNK_71, true, "", "@silence_disabled_ack", None),
|
||||
channel
|
||||
)
|
||||
} else {
|
||||
sender.actor ! MessageResponse(
|
||||
session,
|
||||
ChatMsg(UNK_71, true, "", "@silence_enabled_ack", None),
|
||||
channel
|
||||
)
|
||||
}
|
||||
recipient.actor ! MessageResponse(session, message, channel)
|
||||
case None =>
|
||||
sender.actor ! MessageResponse(
|
||||
session,
|
||||
ChatMsg(UNK_71, true, "", s"unknown player '$name'", None),
|
||||
channel
|
||||
)
|
||||
}
|
||||
case (Some(sender), _, _, error) =>
|
||||
sender.actor ! MessageResponse(
|
||||
session,
|
||||
ChatMsg(UNK_71, false, "", error.getOrElse("usage: /silence <name> [<time>]"), None),
|
||||
channel
|
||||
)
|
||||
|
||||
case (None, _, _, _) =>
|
||||
log.error("received message from non-subscribed actor")
|
||||
|
||||
}
|
||||
|
||||
case CMT_NOTE =>
|
||||
subs.filter(_.session.player.Name == message.recipient).foreach {
|
||||
case JoinChannel(actor, _, _) =>
|
||||
actor ! MessageResponse(session, message.copy(recipient = session.player.Name), channel)
|
||||
}
|
||||
|
||||
// faction commands
|
||||
case CMT_OPEN | CMT_PLATOON | CMT_COMMAND =>
|
||||
subs.filter(_.session.player.Faction == session.player.Faction).foreach {
|
||||
case JoinChannel(actor, _, _) => actor ! MessageResponse(session, message, channel)
|
||||
}
|
||||
|
||||
case CMT_GMBROADCAST_NC =>
|
||||
subs.filter(_.session.player.Faction == PlanetSideEmpire.NC).foreach {
|
||||
case JoinChannel(actor, _, _) => actor ! MessageResponse(session, message, channel)
|
||||
}
|
||||
|
||||
case CMT_GMBROADCAST_TR =>
|
||||
subs.filter(_.session.player.Faction == PlanetSideEmpire.TR).foreach {
|
||||
case JoinChannel(actor, _, _) => actor ! MessageResponse(session, message, channel)
|
||||
}
|
||||
|
||||
case CMT_GMBROADCAST_VS =>
|
||||
subs.filter(_.session.player.Faction == PlanetSideEmpire.VS).foreach {
|
||||
case JoinChannel(actor, _, _) => actor ! MessageResponse(session, message, channel)
|
||||
}
|
||||
|
||||
// cross faction commands
|
||||
case CMT_BROADCAST | CMT_VOICE | CMT_GMBROADCAST | CMT_GMBROADCASTPOPUP | UNK_227 =>
|
||||
subs.foreach {
|
||||
case JoinChannel(actor, _, _) => actor ! MessageResponse(session, message, channel)
|
||||
}
|
||||
|
||||
case _ =>
|
||||
log.warn(s"unhandled chat message, add a case for $message")
|
||||
}
|
||||
this
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +0,0 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package services.chat
|
||||
|
||||
final case class ChatServiceMessage(forChannel: String, actionMessage: ChatAction.Action)
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package services.chat
|
||||
|
||||
import net.psforever.objects.zones.Zone
|
||||
import net.psforever.packet.game.ChatMsg
|
||||
import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID, Vector3}
|
||||
import services.GenericEventBusMsg
|
||||
|
||||
final case class ChatServiceResponse(
|
||||
toChannel: String,
|
||||
avatar_guid: PlanetSideGUID,
|
||||
avatar_name: String,
|
||||
cont: Zone = Zone.Nowhere,
|
||||
avatar_pos: Vector3 = Vector3(0f, 0f, 0f),
|
||||
avatar_faction: PlanetSideEmpire.Value = PlanetSideEmpire.NEUTRAL,
|
||||
target: Int,
|
||||
replyMessage: ChatMsg
|
||||
) extends GenericEventBusMsg
|
||||
|
|
@ -8,7 +8,7 @@ import net.psforever.packet.game.BuildingInfoUpdateMessage
|
|||
import net.psforever.types.PlanetSideGUID
|
||||
import services.GenericEventBusMsg
|
||||
|
||||
final case class GalaxyServiceResponse(toChannel: String, replyMessage: GalaxyResponse.Response)
|
||||
final case class GalaxyServiceResponse(channel: String, replyMessage: GalaxyResponse.Response)
|
||||
extends GenericEventBusMsg
|
||||
|
||||
object GalaxyResponse {
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID, Vector3}
|
|||
import services.GenericEventBusMsg
|
||||
|
||||
final case class LocalServiceResponse(
|
||||
toChannel: String,
|
||||
channel: String,
|
||||
avatar_guid: PlanetSideGUID,
|
||||
replyMessage: LocalResponse.Response
|
||||
) extends GenericEventBusMsg
|
||||
|
|
|
|||
|
|
@ -2,15 +2,22 @@
|
|||
package services.teamwork
|
||||
|
||||
import akka.actor.{Actor, ActorRef, Terminated}
|
||||
import net.psforever.objects.{Avatar, LivePlayerList, Player}
|
||||
import net.psforever.objects.definition.converter.StatConverter
|
||||
import net.psforever.objects.loadouts.SquadLoadout
|
||||
import net.psforever.objects.teamwork.{Member, Squad, SquadFeatures}
|
||||
import net.psforever.objects.zones.Zone
|
||||
import net.psforever.packet.game._
|
||||
import net.psforever.objects.{Avatar, LivePlayerList, Player}
|
||||
import net.psforever.packet.game.{
|
||||
SquadDetail,
|
||||
SquadInfo,
|
||||
WaypointEventAction,
|
||||
SquadPositionEntry,
|
||||
SquadPositionDetail,
|
||||
WaypointInfo,
|
||||
PlanetSideZoneID
|
||||
}
|
||||
import net.psforever.types._
|
||||
import services.{GenericEventBus, Service}
|
||||
import services.teamwork.SquadAction
|
||||
|
||||
import scala.collection.concurrent.TrieMap
|
||||
import scala.collection.mutable
|
||||
|
|
|
|||
|
|
@ -2,12 +2,11 @@
|
|||
package services.teamwork
|
||||
|
||||
import net.psforever.objects.teamwork.Squad
|
||||
import net.psforever.packet.game._
|
||||
import net.psforever.packet.game.{SquadDetail, SquadInfo, WaypointEventAction, WaypointInfo}
|
||||
import net.psforever.types.{PlanetSideGUID, SquadResponseType, SquadWaypoints}
|
||||
import services.GenericEventBusMsg
|
||||
import services.teamwork.SquadAction
|
||||
|
||||
final case class SquadServiceResponse(toChannel: String, exclude: Iterable[Long], response: SquadResponse.Response)
|
||||
final case class SquadServiceResponse(channel: String, exclude: Iterable[Long], response: SquadResponse.Response)
|
||||
extends GenericEventBusMsg
|
||||
|
||||
object SquadServiceResponse {
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import net.psforever.types.{BailType, DriveState, PlanetSideGUID, Vector3}
|
|||
import services.GenericEventBusMsg
|
||||
|
||||
final case class VehicleServiceResponse(
|
||||
toChannel: String,
|
||||
channel: String,
|
||||
avatar_guid: PlanetSideGUID,
|
||||
replyMessage: VehicleResponse.Response
|
||||
) extends GenericEventBusMsg
|
||||
|
|
|
|||
|
|
@ -2,7 +2,9 @@ package net.psforever.pslogin
|
|||
|
||||
import java.net.InetAddress
|
||||
import java.util.Locale
|
||||
import akka.actor.{ActorSystem, Props}
|
||||
|
||||
import akka.{actor => classic}
|
||||
import akka.actor.typed.ActorSystem
|
||||
import akka.routing.RandomPool
|
||||
import ch.qos.logback.classic.LoggerContext
|
||||
import ch.qos.logback.classic.joran.JoranConfigurator
|
||||
|
|
@ -10,7 +12,6 @@ import net.psforever.crypto.CryptoInterface
|
|||
import net.psforever.objects.Default
|
||||
import net.psforever.objects.zones._
|
||||
import net.psforever.objects.guid.TaskResolver
|
||||
import net.psforever.pslogin.psadmin.PsAdminActor
|
||||
import org.slf4j
|
||||
import org.fusesource.jansi.Ansi._
|
||||
import org.fusesource.jansi.Ansi.Color._
|
||||
|
|
@ -26,6 +27,22 @@ import org.flywaydb.core.Flyway
|
|||
import java.nio.file.Paths
|
||||
import scopt.OParser
|
||||
|
||||
import akka.actor.typed.scaladsl.adapter._
|
||||
import net.psforever.actors.session.SessionActor
|
||||
import net.psforever.login.psadmin.PsAdminActor
|
||||
import net.psforever.login.{
|
||||
CryptoSessionActor,
|
||||
LoginSessionActor,
|
||||
NetworkSimulatorParameters,
|
||||
PacketCodingActor,
|
||||
SessionPipeline,
|
||||
SessionRouter,
|
||||
TcpListener,
|
||||
UdpListener
|
||||
}
|
||||
import net.psforever.util.Config
|
||||
import net.psforever.zones.Zones
|
||||
|
||||
object PsLogin {
|
||||
private val logger = org.log4s.getLogger
|
||||
|
||||
|
|
@ -74,9 +91,11 @@ object PsLogin {
|
|||
}
|
||||
|
||||
/** Start up the main actor system. This "system" is the home for all actors running on this server */
|
||||
implicit val system: ActorSystem = ActorSystem("PsLogin")
|
||||
implicit val system = classic.ActorSystem("PsLogin")
|
||||
Default(system)
|
||||
|
||||
val typedSystem: ActorSystem[Nothing] = system.toTyped
|
||||
|
||||
/** Create pipelines for the login and world servers
|
||||
*
|
||||
* The first node in the pipe is an Actor that handles the crypto for protecting packets.
|
||||
|
|
@ -87,14 +106,14 @@ object PsLogin {
|
|||
* See SessionRouter.scala for a diagram
|
||||
*/
|
||||
val loginTemplate = List(
|
||||
SessionPipeline("crypto-session-", Props[CryptoSessionActor]),
|
||||
SessionPipeline("packet-session-", Props[PacketCodingActor]),
|
||||
SessionPipeline("login-session-", Props[LoginSessionActor])
|
||||
SessionPipeline("crypto-session-", classic.Props[CryptoSessionActor]),
|
||||
SessionPipeline("packet-session-", classic.Props[PacketCodingActor]),
|
||||
SessionPipeline("login-session-", classic.Props[LoginSessionActor])
|
||||
)
|
||||
val worldTemplate = List(
|
||||
SessionPipeline("crypto-session-", Props[CryptoSessionActor]),
|
||||
SessionPipeline("packet-session-", Props[PacketCodingActor]),
|
||||
SessionPipeline("world-session-", Props[WorldSessionActor])
|
||||
SessionPipeline("crypto-session-", classic.Props[CryptoSessionActor]),
|
||||
SessionPipeline("packet-session-", classic.Props[PacketCodingActor]),
|
||||
SessionPipeline("world-session-", classic.Props[SessionActor])
|
||||
)
|
||||
|
||||
val netSim: Option[NetworkSimulatorParameters] = if (Config.app.developer.netSim.enable) {
|
||||
|
|
@ -113,37 +132,38 @@ object PsLogin {
|
|||
|
||||
val continents = Zones.zones.values ++ Seq(Zone.Nowhere)
|
||||
|
||||
val serviceManager = ServiceManager.boot
|
||||
serviceManager ! ServiceManager.Register(Props[AccountIntermediaryService], "accountIntermediary")
|
||||
serviceManager ! ServiceManager.Register(RandomPool(150).props(Props[TaskResolver]), "taskResolver")
|
||||
serviceManager ! ServiceManager.Register(Props[ChatService], "chat")
|
||||
serviceManager ! ServiceManager.Register(Props[GalaxyService], "galaxy")
|
||||
serviceManager ! ServiceManager.Register(Props[SquadService], "squad")
|
||||
serviceManager ! ServiceManager.Register(Props(classOf[InterstellarCluster], continents), "cluster")
|
||||
serviceManager ! ServiceManager.Register(Props[AccountPersistenceService], "accountPersistence")
|
||||
serviceManager ! ServiceManager.Register(Props[PropertyOverrideManager], "propertyOverrideManager")
|
||||
system.spawnAnonymous(ChatService())
|
||||
|
||||
val loginRouter = Props(new SessionRouter("Login", loginTemplate))
|
||||
val worldRouter = Props(new SessionRouter("World", worldTemplate))
|
||||
val serviceManager = ServiceManager.boot
|
||||
serviceManager ! ServiceManager.Register(classic.Props[AccountIntermediaryService], "accountIntermediary")
|
||||
serviceManager ! ServiceManager.Register(RandomPool(150).props(classic.Props[TaskResolver]), "taskResolver")
|
||||
serviceManager ! ServiceManager.Register(classic.Props[GalaxyService], "galaxy")
|
||||
serviceManager ! ServiceManager.Register(classic.Props[SquadService], "squad")
|
||||
serviceManager ! ServiceManager.Register(classic.Props(classOf[InterstellarCluster], continents), "cluster")
|
||||
serviceManager ! ServiceManager.Register(classic.Props[AccountPersistenceService], "accountPersistence")
|
||||
serviceManager ! ServiceManager.Register(classic.Props[PropertyOverrideManager], "propertyOverrideManager")
|
||||
|
||||
val loginRouter = classic.Props(new SessionRouter("Login", loginTemplate))
|
||||
val worldRouter = classic.Props(new SessionRouter("World", worldTemplate))
|
||||
val loginListener = system.actorOf(
|
||||
Props(new UdpListener(loginRouter, "login-session-router", bindAddress, Config.app.login.port, netSim)),
|
||||
classic.Props(new UdpListener(loginRouter, "login-session-router", bindAddress, Config.app.login.port, netSim)),
|
||||
"login-udp-endpoint"
|
||||
)
|
||||
val worldListener = system.actorOf(
|
||||
Props(new UdpListener(worldRouter, "world-session-router", bindAddress, Config.app.world.port, netSim)),
|
||||
classic.Props(new UdpListener(worldRouter, "world-session-router", bindAddress, Config.app.world.port, netSim)),
|
||||
"world-udp-endpoint"
|
||||
)
|
||||
|
||||
val adminListener = system.actorOf(
|
||||
Props(
|
||||
classic.Props(
|
||||
new TcpListener(
|
||||
classOf[PsAdminActor],
|
||||
"psadmin-client-",
|
||||
"net.psforever.login.psadmin-client-",
|
||||
InetAddress.getByName(Config.app.admin.bind),
|
||||
Config.app.admin.port
|
||||
)
|
||||
),
|
||||
"psadmin-tcp-endpoint"
|
||||
"net.psforever.login.psadmin-tcp-endpoint"
|
||||
)
|
||||
|
||||
logger.info(
|
||||
|
|
|
|||
|
|
@ -1,131 +0,0 @@
|
|||
package net.psforever.pslogin.csr
|
||||
|
||||
import net.psforever.packet.PacketCoding
|
||||
import net.psforever.packet.game.ChatMsg
|
||||
import net.psforever.types.{ChatMessageType, Vector3}
|
||||
|
||||
import scala.collection.mutable.ArrayBuffer
|
||||
import scala.util.Try
|
||||
|
||||
/*
|
||||
The following is STILL for development and fun.
|
||||
*/
|
||||
/**
|
||||
* An implementation of the CSR command `/warp`, highly modified to serve the purposes of the testing phases of the server.
|
||||
* See `help()` for details.
|
||||
*/
|
||||
object CSRWarp {
|
||||
|
||||
/**
|
||||
* Accept and confirm that a message sent to a player is a valid `/warp` invocation.
|
||||
* If so, parse the message and send the player to whichever destination in this zone was requested.
|
||||
* @param traveler the player
|
||||
* @param msg the message the player received
|
||||
* @return true, if the player is being transported to another place; false, otherwise
|
||||
*/
|
||||
def read(traveler: Traveler, msg: ChatMsg): (Boolean, Vector3) = {
|
||||
if (!isProperRequest(msg))
|
||||
return (false, Vector3.Zero) //we do not handle this message
|
||||
|
||||
val buffer = decomposeMessage(msg.contents)
|
||||
if (buffer.length == 0 || buffer(0).equals("") || buffer(0).equals("-help")) {
|
||||
CSRWarp.help(traveler) //print usage information to chat
|
||||
return (false, Vector3.Zero)
|
||||
}
|
||||
var destId: String = ""
|
||||
var coords: ArrayBuffer[Int] = ArrayBuffer.empty[Int]
|
||||
var list: Boolean = false
|
||||
var failedCoordInput = false
|
||||
for (o <- buffer) {
|
||||
val toInt = Try(o.toInt)
|
||||
if (toInt.isSuccess) {
|
||||
coords += toInt.get
|
||||
} else if (coords.nonEmpty && coords.size < 3)
|
||||
failedCoordInput = true
|
||||
if (o.equals("-list"))
|
||||
list = true
|
||||
else if (destId.equals(""))
|
||||
destId = o
|
||||
}
|
||||
if (failedCoordInput || (coords.nonEmpty && coords.size < 3)) {
|
||||
CSRWarp.error(traveler, "Needs three integer components (<x> <y> <z>)")
|
||||
return (false, Vector3.Zero)
|
||||
} else {
|
||||
coords
|
||||
.slice(0, 3)
|
||||
.foreach(x => {
|
||||
if (x < 0 || x > 8191) {
|
||||
CSRWarp.error(traveler, "Out of range - 0 < n < 8191, but n = " + x)
|
||||
return (false, Vector3.Zero)
|
||||
}
|
||||
})
|
||||
}
|
||||
val zone = CSRZoneImpl.get(traveler.zone).get //the traveler is already in the appropriate zone
|
||||
if (list && coords.isEmpty && destId.equals("")) {
|
||||
CSRWarp.reply(traveler, CSRZoneImpl.listLocations(zone) + "; " + CSRZoneImpl.listWarpgates(zone))
|
||||
return (false, Vector3.Zero)
|
||||
}
|
||||
val dest: Option[Vector3] =
|
||||
if (coords.nonEmpty) Some(Vector3(coords(0).toFloat, coords(1).toFloat, coords(2).toFloat))
|
||||
else CSRZoneImpl.getWarpLocation(zone, destId) //coords before destId
|
||||
if (dest.isEmpty) {
|
||||
CSRWarp.error(traveler, "Invalid location")
|
||||
return (false, Vector3.Zero)
|
||||
}
|
||||
(true, dest.get)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that the incoming message is an appropriate type for this command.
|
||||
* @param msg the message
|
||||
* @return true, if we will handle it; false, otherwise
|
||||
*/
|
||||
def isProperRequest(msg: ChatMsg): Boolean = {
|
||||
msg.messageType == ChatMessageType.CMT_WARP
|
||||
}
|
||||
|
||||
/**
|
||||
* Break the message in the packet down for parsing.
|
||||
* @param msg the contents portion of the message, a space-separated `String`
|
||||
* @return the contents portion of the message, transformed into an `Array`
|
||||
*/
|
||||
private def decomposeMessage(msg: String): Array[String] = {
|
||||
msg.trim.toLowerCase.split("\\s+")
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a message back to the `Traveler` that will be printed into his chat window.
|
||||
* @param traveler the player
|
||||
* @param msg the message to be sent
|
||||
*/
|
||||
private def reply(traveler: Traveler, msg: String): Unit = {
|
||||
traveler ! PacketCoding.CreateGamePacket(0, ChatMsg(ChatMessageType.CMT_OPEN, true, "", msg, None))
|
||||
}
|
||||
|
||||
/**
|
||||
* Print usage information to the `Traveler`'s chat window.<br>
|
||||
* <br>
|
||||
* The "official" use information for help dictates the command should follow this format:
|
||||
* `/warp <x><y><z> | to <character> | near <object> | above <object> | waypoint`.
|
||||
* In our case, creating fixed coordinate points of interest is not terribly dissimilar from the "near" and "to" aspect.
|
||||
* We can not currently implement most of the options for now, however.<br>
|
||||
* <br>
|
||||
* The destination prioritizes evaluation of the coordinates before the location string.
|
||||
* When the user provides coordinates, he must provide all three components of the coordinate at once, else none will be accepted.
|
||||
* If the coordinates are invalid, the location string will still be checked.
|
||||
* "-list" is accepted while no serious attempt is made to indicate a destination (no location string or not enough coordinates).
|
||||
* @param traveler the player
|
||||
*/
|
||||
private def help(traveler: Traveler): Unit = {
|
||||
CSRWarp.reply(traveler, "usage: /warp <location> | <gatename> | <x> <y> <z> | [-list]")
|
||||
}
|
||||
|
||||
/**
|
||||
* Print error information to the `Traveler`'s chat window.<br>
|
||||
* The most common reason for error is the lack of information, or wrong information.
|
||||
* @param traveler the player
|
||||
*/
|
||||
private def error(traveler: Traveler, msg: String): Unit = {
|
||||
CSRWarp.reply(traveler, "Error! " + msg)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,113 +0,0 @@
|
|||
package net.psforever.pslogin.csr
|
||||
|
||||
import net.psforever.packet.PacketCoding
|
||||
import net.psforever.packet.game.ChatMsg
|
||||
import net.psforever.types.{ChatMessageType, Vector3}
|
||||
|
||||
/*
|
||||
The following is STILL for development and fun.
|
||||
*/
|
||||
/**
|
||||
* An implementation of the CSR command `/zone`, slightly modified to serve the purposes of the testing phases of the server.
|
||||
*/
|
||||
object CSRZone {
|
||||
|
||||
/**
|
||||
* Accept and confirm that a message sent to a player is a valid `/zone` invocation.
|
||||
* If so, parse the message and send the player to whichever zone was requested.
|
||||
* @param traveler the player
|
||||
* @param msg the message the player received
|
||||
* @return true, if the player is being transported to another zone; false, otherwise
|
||||
*/
|
||||
def read(traveler: Traveler, msg: ChatMsg): (Boolean, String, Vector3) = {
|
||||
if (!isProperRequest(msg))
|
||||
return (false, "", Vector3.Zero) //we do not handle this message
|
||||
|
||||
val buffer = decomposeMessage(msg.contents)
|
||||
if (buffer.length == 0 || buffer(0).equals("-help")) {
|
||||
CSRZone.help(traveler) //print usage information to chat
|
||||
return (false, "", Vector3.Zero)
|
||||
}
|
||||
|
||||
var zoneId = ""
|
||||
var gateId = "" //the user can define which warpgate they may visit (actual keyword protocol missing)
|
||||
var list = false //if the user wants a printed list of destination locations
|
||||
for (o <- buffer) {
|
||||
if (o.equals("-list")) {
|
||||
if (zoneId.equals("") || gateId.equals("")) {
|
||||
list = true
|
||||
}
|
||||
} else if (zoneId.equals(""))
|
||||
zoneId = o
|
||||
else if (gateId.equals(""))
|
||||
gateId = o
|
||||
}
|
||||
|
||||
val zoneOpt = CSRZoneImpl.get(zoneId)
|
||||
if (zoneOpt.isEmpty) {
|
||||
if (list)
|
||||
CSRZone.reply(traveler, CSRZoneImpl.list)
|
||||
else
|
||||
CSRZone.error(traveler, "Give a valid zonename (use '/zone -list')")
|
||||
return (false, "", Vector3.Zero)
|
||||
}
|
||||
val zone = zoneOpt.get
|
||||
var destination: Vector3 = CSRZoneImpl.selectRandom(zone) //the destination in the new zone starts as random
|
||||
|
||||
if (!gateId.equals("")) { //if we've defined a warpgate, and can find that warpgate, we re-assign the destination
|
||||
val gateOpt = CSRZoneImpl.getWarpgate(zone, gateId)
|
||||
if (gateOpt.isDefined)
|
||||
destination = gateOpt.get
|
||||
else
|
||||
CSRZone.error(traveler, "Gate id not defined (use '/zone <zone> -list')")
|
||||
} else if (list) {
|
||||
CSRZone.reply(traveler, CSRZoneImpl.listWarpgates(zone))
|
||||
return (false, "", Vector3.Zero)
|
||||
}
|
||||
(true, zone.zonename, destination)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that the incoming message is an appropriate type for this command.
|
||||
* @param msg the message
|
||||
* @return true, if we will handle it; false, otherwise
|
||||
*/
|
||||
def isProperRequest(msg: ChatMsg): Boolean = {
|
||||
msg.messageType == ChatMessageType.CMT_ZONE
|
||||
}
|
||||
|
||||
/**
|
||||
* Break the message in the packet down for parsing.
|
||||
* @param msg the contents portion of the message, a space-separated `String`
|
||||
* @return the contents portion of the message, transformed into an `Array`
|
||||
*/
|
||||
private def decomposeMessage(msg: String): Array[String] = {
|
||||
msg.trim.toLowerCase.split("\\s+")
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a message back to the `Traveler` that will be printed into his chat window.
|
||||
* @param traveler the player
|
||||
* @param msg the message to be sent
|
||||
*/
|
||||
private def reply(traveler: Traveler, msg: String): Unit = {
|
||||
traveler ! PacketCoding.CreateGamePacket(0, ChatMsg(ChatMessageType.CMT_OPEN, true, "", msg, None))
|
||||
}
|
||||
|
||||
/**
|
||||
* Print usage information to the `Traveler`'s chat window.
|
||||
* @param traveler the player
|
||||
*/
|
||||
private def help(traveler: Traveler): Unit = {
|
||||
CSRZone.reply(traveler, "usage: /zone <zone> [gatename] | [-list]")
|
||||
}
|
||||
|
||||
/**
|
||||
* Print error information to the `Traveler`'s chat window.<br>
|
||||
* The most common reason for error is the lack of information, or wrong information.
|
||||
* @param traveler the player
|
||||
*/
|
||||
private def error(traveler: Traveler, msg: String): Unit = {
|
||||
CSRZone.reply(traveler, "Error! " + msg)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
package net.psforever.pslogin.csr
|
||||
|
||||
import akka.actor.ActorRef
|
||||
import net.psforever.packet.PlanetSidePacketContainer
|
||||
|
||||
/*
|
||||
The following is STILL for development and fun.
|
||||
*/
|
||||
/**
|
||||
* The traveler is synonymous with the player.
|
||||
* The primary purpose of the object is to keep track of but not expose the player's session so that packets may be relayed back to him.
|
||||
* csr.Traveler also keeps track of which zone the player currently occupies.
|
||||
* @param session the player's session
|
||||
*/
|
||||
class Traveler(private val session: ActorRef, var zone: String) {
|
||||
|
||||
/**
|
||||
* `sendToSelf` is a call that permits the session to gain access to its internal `rightRef` so that it can dispatch a packet.
|
||||
* @param msg the byte-code translation of a packet
|
||||
*/
|
||||
def sendToSelf(msg: PlanetSidePacketContainer): Unit = {
|
||||
// this.session.sendResponse(msg)
|
||||
}
|
||||
|
||||
def !(msg: Any): Unit = {
|
||||
session ! msg
|
||||
}
|
||||
}
|
||||
|
||||
object Traveler {
|
||||
|
||||
/**
|
||||
* An abbreviated constructor for creating `csr.Traveler`s without invocation of `new`.
|
||||
* @param session the player's session
|
||||
* @return a traveler object for this player
|
||||
*/
|
||||
def apply(session: ActorRef, zoneId: String): Traveler = new Traveler(session, zoneId)
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ package net.psforever.pslogin
|
|||
|
||||
import akka.actor.{ActorRef, MDCContextAware}
|
||||
import akka.testkit.TestProbe
|
||||
import net.psforever.login.HelloFriend
|
||||
import net.psforever.packet.{ControlPacket, GamePacket}
|
||||
|
||||
final case class MDCGamePacket(packet: GamePacket)
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package net.psforever.pslogin
|
|||
import actor.base.ActorTest
|
||||
import akka.actor.{ActorRef, Props}
|
||||
import akka.testkit.TestProbe
|
||||
import net.psforever.login.{HelloFriend, PacketCodingActor, RawPacket}
|
||||
import net.psforever.packet.control.{ControlSync, MultiPacketBundle, SlottedMetaPacket}
|
||||
import net.psforever.packet.{ControlPacket, GamePacket, GamePacketOpcode, PacketCoding}
|
||||
import net.psforever.packet.game._
|
||||
|
|
|
|||
|
|
@ -534,7 +534,7 @@ class AvatarReleaseTest extends ActorTest {
|
|||
val reply1 = receiveOne(200 milliseconds)
|
||||
assert(reply1.isInstanceOf[AvatarServiceResponse])
|
||||
val reply1msg = reply1.asInstanceOf[AvatarServiceResponse]
|
||||
assert(reply1msg.toChannel == "/test/Avatar")
|
||||
assert(reply1msg.channel == "/test/Avatar")
|
||||
assert(reply1msg.avatar_guid == guid)
|
||||
assert(reply1msg.replyMessage.isInstanceOf[AvatarResponse.Release])
|
||||
assert(reply1msg.replyMessage.asInstanceOf[AvatarResponse.Release].player == obj)
|
||||
|
|
@ -542,7 +542,7 @@ class AvatarReleaseTest extends ActorTest {
|
|||
val reply2 = receiveOne(2 seconds)
|
||||
assert(reply2.isInstanceOf[AvatarServiceResponse])
|
||||
val reply2msg = reply2.asInstanceOf[AvatarServiceResponse]
|
||||
assert(reply2msg.toChannel.equals("/test/Avatar"))
|
||||
assert(reply2msg.channel.equals("/test/Avatar"))
|
||||
assert(reply2msg.avatar_guid == Service.defaultPlayerGUID)
|
||||
assert(reply2msg.replyMessage.isInstanceOf[AvatarResponse.ObjectDelete])
|
||||
assert(reply2msg.replyMessage.asInstanceOf[AvatarResponse.ObjectDelete].item_guid == guid)
|
||||
|
|
@ -585,7 +585,7 @@ class AvatarReleaseEarly1Test extends ActorTest {
|
|||
val reply1 = receiveOne(200 milliseconds)
|
||||
assert(reply1.isInstanceOf[AvatarServiceResponse])
|
||||
val reply1msg = reply1.asInstanceOf[AvatarServiceResponse]
|
||||
assert(reply1msg.toChannel == "/test/Avatar")
|
||||
assert(reply1msg.channel == "/test/Avatar")
|
||||
assert(reply1msg.avatar_guid == guid)
|
||||
assert(reply1msg.replyMessage.isInstanceOf[AvatarResponse.Release])
|
||||
assert(reply1msg.replyMessage.asInstanceOf[AvatarResponse.Release].player == obj)
|
||||
|
|
@ -594,7 +594,7 @@ class AvatarReleaseEarly1Test extends ActorTest {
|
|||
val reply2 = receiveOne(200 milliseconds)
|
||||
assert(reply2.isInstanceOf[AvatarServiceResponse])
|
||||
val reply2msg = reply2.asInstanceOf[AvatarServiceResponse]
|
||||
assert(reply2msg.toChannel.equals("/test/Avatar"))
|
||||
assert(reply2msg.channel.equals("/test/Avatar"))
|
||||
assert(reply2msg.avatar_guid == Service.defaultPlayerGUID)
|
||||
assert(reply2msg.replyMessage.isInstanceOf[AvatarResponse.ObjectDelete])
|
||||
assert(reply2msg.replyMessage.asInstanceOf[AvatarResponse.ObjectDelete].item_guid == guid)
|
||||
|
|
@ -641,7 +641,7 @@ class AvatarReleaseEarly2Test extends ActorTest {
|
|||
val reply1 = receiveOne(200 milliseconds)
|
||||
assert(reply1.isInstanceOf[AvatarServiceResponse])
|
||||
val reply1msg = reply1.asInstanceOf[AvatarServiceResponse]
|
||||
assert(reply1msg.toChannel == "/test/Avatar")
|
||||
assert(reply1msg.channel == "/test/Avatar")
|
||||
assert(reply1msg.avatar_guid == guid)
|
||||
assert(reply1msg.replyMessage.isInstanceOf[AvatarResponse.Release])
|
||||
assert(reply1msg.replyMessage.asInstanceOf[AvatarResponse.Release].player == obj)
|
||||
|
|
@ -652,7 +652,7 @@ class AvatarReleaseEarly2Test extends ActorTest {
|
|||
val reply2 = receiveOne(100 milliseconds)
|
||||
assert(reply2.isInstanceOf[AvatarServiceResponse])
|
||||
val reply2msg = reply2.asInstanceOf[AvatarServiceResponse]
|
||||
assert(reply2msg.toChannel.equals("/test/Avatar"))
|
||||
assert(reply2msg.channel.equals("/test/Avatar"))
|
||||
assert(reply2msg.avatar_guid == Service.defaultPlayerGUID)
|
||||
assert(reply2msg.replyMessage.isInstanceOf[AvatarResponse.ObjectDelete])
|
||||
assert(reply2msg.replyMessage.asInstanceOf[AvatarResponse.ObjectDelete].item_guid == guid)
|
||||
|
|
|
|||
Loading…
Reference in a new issue