mirror of
https://github.com/2revoemag/PSF-BotServer.git
synced 2026-07-14 07:55:07 +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
66 changed files with 1560 additions and 1838 deletions
|
|
@ -4,6 +4,7 @@ lazy val commonSettings = Seq(
|
||||||
organization := "net.psforever",
|
organization := "net.psforever",
|
||||||
version := "1.0.2-SNAPSHOT",
|
version := "1.0.2-SNAPSHOT",
|
||||||
scalaVersion := "2.13.2",
|
scalaVersion := "2.13.2",
|
||||||
|
Global / cancelable := false,
|
||||||
semanticdbEnabled := true,
|
semanticdbEnabled := true,
|
||||||
semanticdbVersion := scalafixSemanticdb.revision,
|
semanticdbVersion := scalafixSemanticdb.revision,
|
||||||
scalacOptions := Seq(
|
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
|
// ActorTests have specific timing requirements and will be flaky if run in parallel
|
||||||
parallelExecution in Test := false,
|
parallelExecution in Test := false,
|
||||||
// TODO(chord): remove exclusion when WorldSessionActor is refactored: https://github.com/psforever/PSF-LoginServer/issues/279
|
// 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)
|
// Copy all tests from Test -> QuietTest (we're only changing the run options)
|
||||||
inConfig(QuietTest)(Defaults.testTasks)
|
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 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.control._
|
||||||
import net.psforever.packet.crypto._
|
import net.psforever.packet.crypto._
|
||||||
import net.psforever.packet.game.PingMsg
|
import net.psforever.packet.game.PingMsg
|
||||||
import org.log4s.MDC
|
import org.log4s.MDC
|
||||||
import MDCContextAware.Implicits._
|
import scodec.Attempt.{Failure, Successful}
|
||||||
|
import scodec.bits._
|
||||||
|
|
||||||
sealed trait CryptoSessionAPI
|
sealed trait CryptoSessionAPI
|
||||||
final case class DropCryptoSession() extends 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 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.control._
|
||||||
|
import net.psforever.packet.game.LoginRespMessage.{LoginError, StationError, StationSubscriptionStatus}
|
||||||
import net.psforever.packet.game._
|
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 org.log4s.MDC
|
||||||
import scodec.bits._
|
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
|
||||||
import services.ServiceManager.Lookup
|
import services.ServiceManager.Lookup
|
||||||
import services.account.{ReceiveIPAddress, RetrieveIPAddress, StoreAccountData}
|
import services.account.{ReceiveIPAddress, RetrieveIPAddress, StoreAccountData}
|
||||||
import com.github.t3hnar.bcrypt._
|
|
||||||
import net.psforever.packet.game.LoginRespMessage.{LoginError, StationError, StationSubscriptionStatus}
|
import scala.concurrent.Future
|
||||||
import net.psforever.persistence
|
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
import scala.util.{Failure, Success}
|
import scala.util.{Failure, Success}
|
||||||
import scala.concurrent.Future
|
|
||||||
import Database._
|
|
||||||
import java.net.InetAddress
|
|
||||||
|
|
||||||
class LoginSessionActor extends Actor with MDCContextAware {
|
class LoginSessionActor extends Actor with MDCContextAware {
|
||||||
private[this] val log = org.log4s.getLogger
|
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 akka.actor.{Actor, ActorRef, Cancellable, MDCContextAware}
|
||||||
|
import net.psforever.objects.Default
|
||||||
import net.psforever.packet._
|
import net.psforever.packet._
|
||||||
|
import net.psforever.packet.control.{HandleGamePacket, _}
|
||||||
|
import org.log4s.MDC
|
||||||
import scodec.Attempt.{Failure, Successful}
|
import scodec.Attempt.{Failure, Successful}
|
||||||
import scodec.bits._
|
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.annotation.tailrec
|
||||||
import scala.collection.mutable
|
import scala.collection.mutable
|
||||||
|
|
@ -1,13 +1,11 @@
|
||||||
package net.psforever.pslogin
|
package net.psforever.login
|
||||||
|
|
||||||
import java.net.InetSocketAddress
|
import java.net.InetSocketAddress
|
||||||
|
|
||||||
import akka.actor._
|
import akka.actor.MDCContextAware.Implicits._
|
||||||
import scodec.bits._
|
import akka.actor.{ActorContext, ActorRef, PoisonPill, _}
|
||||||
|
|
||||||
import akka.actor.{ActorContext, ActorRef, PoisonPill}
|
|
||||||
import com.github.nscala_time.time.Imports._
|
import com.github.nscala_time.time.Imports._
|
||||||
import MDCContextAware.Implicits._
|
import scodec.bits._
|
||||||
|
|
||||||
sealed trait SessionState
|
sealed trait SessionState
|
||||||
final case class New() extends SessionState
|
final case class New() extends SessionState
|
||||||
|
|
@ -1,19 +1,18 @@
|
||||||
package net.psforever.pslogin
|
package net.psforever.login
|
||||||
|
|
||||||
import java.net.InetSocketAddress
|
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.SupervisorStrategy.Stop
|
||||||
|
import akka.actor._
|
||||||
import net.psforever.packet.PacketCoding
|
import net.psforever.packet.PacketCoding
|
||||||
import net.psforever.packet.control.ConnectionClose
|
import net.psforever.packet.control.ConnectionClose
|
||||||
|
import net.psforever.util.Config
|
||||||
|
import org.log4s.MDC
|
||||||
|
import scodec.bits._
|
||||||
import services.ServiceManager
|
import services.ServiceManager
|
||||||
import services.ServiceManager.Lookup
|
import services.ServiceManager.Lookup
|
||||||
import services.account.{IPAddress, StoreIPAddress}
|
import services.account.{IPAddress, StoreIPAddress}
|
||||||
|
import scala.collection.mutable
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
|
|
||||||
sealed trait SessionRouterAPI
|
sealed trait SessionRouterAPI
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package net.psforever.pslogin
|
package net.psforever.login
|
||||||
|
|
||||||
import java.net.{InetAddress, InetSocketAddress}
|
import java.net.{InetAddress, InetSocketAddress}
|
||||||
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package net.psforever.pslogin
|
package net.psforever.login
|
||||||
|
|
||||||
import java.net.{InetAddress, InetSocketAddress}
|
import java.net.{InetAddress, InetSocketAddress}
|
||||||
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
package net.psforever.pslogin
|
package net.psforever.login
|
||||||
|
|
||||||
import akka.actor.{Actor, ActorRef}
|
import akka.actor.{Actor, ActorRef}
|
||||||
import akka.io._
|
import akka.io._
|
||||||
|
|
||||||
import scala.util.Random
|
|
||||||
import scala.collection.mutable
|
import scala.collection.mutable
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
|
import scala.util.Random
|
||||||
|
|
||||||
/** Parameters for the Network simulator
|
/** Parameters for the Network simulator
|
||||||
*
|
*
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
package net.psforever.pslogin
|
package net.psforever.login
|
||||||
|
|
||||||
import akka.actor.ActorRef
|
import akka.actor.ActorRef
|
||||||
import akka.pattern.{AskTimeoutException, ask}
|
import akka.pattern.{AskTimeoutException, ask}
|
||||||
import akka.util.Timeout
|
import akka.util.Timeout
|
||||||
import net.psforever.objects.{AmmoBox, GlobalDefinitions, Player, Tool}
|
|
||||||
import net.psforever.objects.equipment.{Ammo, Equipment}
|
import net.psforever.objects.equipment.{Ammo, Equipment}
|
||||||
import net.psforever.objects.guid.{GUIDTask, Task, TaskResolver}
|
import net.psforever.objects.guid.{GUIDTask, Task, TaskResolver}
|
||||||
import net.psforever.objects.inventory.{Container, InventoryItem}
|
import net.psforever.objects.inventory.{Container, InventoryItem}
|
||||||
import net.psforever.objects.serverobject.PlanetSideServerObject
|
import net.psforever.objects.serverobject.PlanetSideServerObject
|
||||||
import net.psforever.objects.serverobject.containable.Containable
|
import net.psforever.objects.serverobject.containable.Containable
|
||||||
import net.psforever.objects.zones.Zone
|
import net.psforever.objects.zones.Zone
|
||||||
|
import net.psforever.objects.{AmmoBox, GlobalDefinitions, Player, Tool}
|
||||||
import net.psforever.packet.game.ObjectHeldMessage
|
import net.psforever.packet.game.ObjectHeldMessage
|
||||||
import net.psforever.types.{PlanetSideGUID, TransactionType, Vector3}
|
import net.psforever.types.{PlanetSideGUID, TransactionType, Vector3}
|
||||||
import services.Service
|
import services.Service
|
||||||
|
|
@ -19,7 +19,7 @@ import scala.concurrent.ExecutionContext.Implicits.global
|
||||||
import scala.concurrent.Future
|
import scala.concurrent.Future
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
import scala.language.implicitConversions
|
import scala.language.implicitConversions
|
||||||
import scala.util.{Success, Failure}
|
import scala.util.{Failure, Success}
|
||||||
|
|
||||||
object WorldSession {
|
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 scala.collection.mutable
|
||||||
import net.psforever.pslogin.Config
|
|
||||||
|
|
||||||
import scala.jdk.CollectionConverters._
|
import scala.jdk.CollectionConverters._
|
||||||
|
|
||||||
object CmdInternal {
|
object CmdInternal {
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
package net.psforever.pslogin.psadmin
|
package net.psforever.login.psadmin
|
||||||
|
|
||||||
import akka.actor.ActorRef
|
import akka.actor.{Actor, ActorRef}
|
||||||
import akka.actor.Actor
|
|
||||||
import scala.collection.mutable.Map
|
|
||||||
import net.psforever.objects.zones.InterstellarCluster
|
import net.psforever.objects.zones.InterstellarCluster
|
||||||
|
|
||||||
|
import scala.collection.mutable.Map
|
||||||
|
|
||||||
class CmdListPlayers(args: Array[String], services: Map[String, ActorRef]) extends Actor {
|
class CmdListPlayers(args: Array[String], services: Map[String, ActorRef]) extends Actor {
|
||||||
private[this] val log = org.log4s.getLogger(self.path.name)
|
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 akka.actor.{Actor, ActorRef}
|
||||||
|
|
||||||
import scala.collection.mutable.Map
|
import scala.collection.mutable.Map
|
||||||
|
|
||||||
class CmdShutdown(args: Array[String], services: Map[String, ActorRef]) extends Actor {
|
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 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._
|
||||||
import org.json4s.native.Serialization.write
|
import org.json4s.native.Serialization.write
|
||||||
|
import scodec.bits._
|
||||||
|
import scodec.interop.akka._
|
||||||
import services.ServiceManager.Lookup
|
import services.ServiceManager.Lookup
|
||||||
import services._
|
import services._
|
||||||
|
|
||||||
|
import scala.collection.mutable.Map
|
||||||
|
|
||||||
object PsAdminActor {
|
object PsAdminActor {
|
||||||
val whiteSpaceRegex = """\s+""".r
|
val whiteSpaceRegex = """\s+""".r
|
||||||
}
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package net.psforever.pslogin.psadmin
|
package net.psforever.login.psadmin
|
||||||
|
|
||||||
import scala.collection.mutable
|
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_GMTELL, // /gmtell (actually causes normal /tell 0x20 when not a gm???)
|
||||||
CMT_NOTE, // /note
|
CMT_NOTE, // /note
|
||||||
CMT_GMBROADCASTPOPUP, // /gmpopup
|
CMT_GMBROADCASTPOPUP, // /gmpopup
|
||||||
U_CMT_GMTELLFROM, // ??? Recipient of /gmtell?
|
U_CMT_GMTELLFROM, // Acknowledgement of /gmtell for sender
|
||||||
U_CMT_TELLFROM, // ??? Recipient of /t?
|
U_CMT_TELLFROM, // Acknowledgement of /tell for sender
|
||||||
UNK_45, // ??? empty
|
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_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
|
CMT_INSTANTACTION, // /instantaction OR /ia
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,24 @@
|
||||||
package net.psforever.pslogin
|
package net.psforever.util
|
||||||
|
|
||||||
import java.nio.file.Paths
|
import java.nio.file.Paths
|
||||||
|
|
||||||
import com.typesafe.config.{Config => TypesafeConfig}
|
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 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 scala.reflect.ClassTag
|
||||||
|
import pureconfig.generic.auto._ // intellij: this is not unused
|
||||||
|
|
||||||
object Config {
|
object Config {
|
||||||
// prog.home is defined when we are running from SBT pack
|
// prog.home is defined when we are running from SBT pack
|
||||||
val directory: String = System.getProperty("prog.home") match {
|
val directory: String = System.getProperty("prog.home") match {
|
||||||
case null =>
|
case null =>
|
||||||
Paths.get("config").toAbsolutePath().toString()
|
Paths.get("config").toAbsolutePath.toString
|
||||||
case home =>
|
case home =>
|
||||||
Paths.get(home, "config").toAbsolutePath().toString()
|
Paths.get(home, "config").toAbsolutePath.toString
|
||||||
}
|
}
|
||||||
|
|
||||||
implicit def enumeratumIntConfigConvert[A <: IntEnumEntry](implicit
|
implicit def enumeratumIntConfigConvert[A <: IntEnumEntry](implicit
|
||||||
|
|
@ -42,7 +41,7 @@ object Config {
|
||||||
ConfigSource.defaultApplication
|
ConfigSource.defaultApplication
|
||||||
}
|
}
|
||||||
|
|
||||||
val result = source.load[AppConfig]
|
val result: Result[AppConfig] = source.load[AppConfig]
|
||||||
|
|
||||||
// Raw config object - prefer app when possible
|
// Raw config object - prefer app when possible
|
||||||
lazy val config: TypesafeConfig = source.config().toOption.get
|
lazy val config: TypesafeConfig = source.config().toOption.get
|
||||||
|
|
@ -88,7 +87,7 @@ case class DatabaseConfig(
|
||||||
database: String,
|
database: String,
|
||||||
sslmode: String
|
sslmode: String
|
||||||
) {
|
) {
|
||||||
def toJdbc = s"jdbc:postgresql://${host}:${port}/${database}"
|
def toJdbc = s"jdbc:postgresql://$host:$port/$database"
|
||||||
}
|
}
|
||||||
|
|
||||||
case class AntiCheatConfig(
|
case class AntiCheatConfig(
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package net.psforever.pslogin
|
package net.psforever.util
|
||||||
|
|
||||||
import io.getquill.PostgresJAsyncContext
|
import io.getquill.{PostgresJAsyncContext, SnakeCase}
|
||||||
import io.getquill.SnakeCase
|
|
||||||
import net.psforever.persistence
|
import net.psforever.persistence
|
||||||
|
|
||||||
object Database {
|
object Database {
|
||||||
|
|
@ -1,13 +1,10 @@
|
||||||
package net.psforever.pslogin.csr
|
package net.psforever.util
|
||||||
|
|
||||||
import net.psforever.types.Vector3
|
import net.psforever.types.Vector3
|
||||||
|
|
||||||
import scala.collection.mutable
|
import scala.collection.mutable
|
||||||
import scala.util.Random
|
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").
|
* 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`.
|
* 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 map the map name of the zone (this map is loaded)
|
||||||
* @param zonename the zone's internal name
|
* @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.
|
* 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()
|
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.
|
* 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.
|
* The keys in this map should be directly usable by the `/zone` command.
|
||||||
*/
|
*/
|
||||||
private val zones = Map[String, CSRZoneImpl](
|
private val zones = Map[String, PointOfInterest](
|
||||||
"z1" -> CSRZoneImpl("Solsar", "map01", "z1"),
|
"z1" -> PointOfInterest("Solsar", "map01", "z1"),
|
||||||
"z2" -> CSRZoneImpl("Hossin", "map02", "z2"),
|
"z2" -> PointOfInterest("Hossin", "map02", "z2"),
|
||||||
"z3" -> CSRZoneImpl("Cyssor", "map03", "z3"),
|
"z3" -> PointOfInterest("Cyssor", "map03", "z3"),
|
||||||
"z4" -> CSRZoneImpl("Ishundar", "map04", "z4"),
|
"z4" -> PointOfInterest("Ishundar", "map04", "z4"),
|
||||||
"z5" -> CSRZoneImpl("Forseral", "map05", "z5"),
|
"z5" -> PointOfInterest("Forseral", "map05", "z5"),
|
||||||
"z6" -> CSRZoneImpl("Ceryshen", "map06", "z6"),
|
"z6" -> PointOfInterest("Ceryshen", "map06", "z6"),
|
||||||
"z7" -> CSRZoneImpl("Esamir", "map07", "z7"),
|
"z7" -> PointOfInterest("Esamir", "map07", "z7"),
|
||||||
"z8" -> CSRZoneImpl("Oshur", "map08", "z8"),
|
"z8" -> PointOfInterest("Oshur", "map08", "z8"),
|
||||||
"z9" -> CSRZoneImpl("Searhus", "map09", "z9"),
|
"z9" -> PointOfInterest("Searhus", "map09", "z9"),
|
||||||
"z10" -> CSRZoneImpl("Amerish", "map10", "z10"),
|
"z10" -> PointOfInterest("Amerish", "map10", "z10"),
|
||||||
"home1" -> CSRZoneImpl("NC Sanctuary", "map11", "home1"),
|
"home1" -> PointOfInterest("NC Sanctuary", "map11", "home1"),
|
||||||
"home2" -> CSRZoneImpl("TR Sanctuary", "map12", "home2"),
|
"home2" -> PointOfInterest("TR Sanctuary", "map12", "home2"),
|
||||||
"home3" -> CSRZoneImpl("VS Sanctuary", "map13", "home3"),
|
"home3" -> PointOfInterest("VS Sanctuary", "map13", "home3"),
|
||||||
"tzshtr" -> CSRZoneImpl("VR Shooting Range TR", "map14", "tzshtr"),
|
"tzshtr" -> PointOfInterest("VR Shooting Range TR", "map14", "tzshtr"),
|
||||||
"tzdrtr" -> CSRZoneImpl("VR Driving Range TR", "map15", "tzdrtr"),
|
"tzdrtr" -> PointOfInterest("VR Driving Range TR", "map15", "tzdrtr"),
|
||||||
"tzcotr" -> CSRZoneImpl("VR Combat Zone TR", "map16", "tzcotr"),
|
"tzcotr" -> PointOfInterest("VR Combat Zone TR", "map16", "tzcotr"),
|
||||||
"tzshvs" -> CSRZoneImpl("VR Shooting Range VS", "map14", "tzshvs"),
|
"tzshvs" -> PointOfInterest("VR Shooting Range VS", "map14", "tzshvs"),
|
||||||
"tzdrvs" -> CSRZoneImpl("VR Driving Range VS", "map15", "tzdrvs"),
|
"tzdrvs" -> PointOfInterest("VR Driving Range VS", "map15", "tzdrvs"),
|
||||||
"tzcovs" -> CSRZoneImpl("VR Combat Zone VS", "map16", "tzcovs"),
|
"tzcovs" -> PointOfInterest("VR Combat Zone VS", "map16", "tzcovs"),
|
||||||
"tzshnc" -> CSRZoneImpl("VR Shooting Range NC", "map14", "tzshnc"),
|
"tzshnc" -> PointOfInterest("VR Shooting Range NC", "map14", "tzshnc"),
|
||||||
"tzdrnc" -> CSRZoneImpl("VR Driving Range NC", "map15", "tzdrnc"),
|
"tzdrnc" -> PointOfInterest("VR Driving Range NC", "map15", "tzdrnc"),
|
||||||
"tzconc" -> CSRZoneImpl("VR Combat Zone NC", "map16", "tzconc"),
|
"tzconc" -> PointOfInterest("VR Combat Zone NC", "map16", "tzconc"),
|
||||||
"c1" -> CSRZoneImpl("Supai", "ugd01", "c1"),
|
"c1" -> PointOfInterest("Supai", "ugd01", "c1"),
|
||||||
"c2" -> CSRZoneImpl("Hunhau", "ugd02", "c2"),
|
"c2" -> PointOfInterest("Hunhau", "ugd02", "c2"),
|
||||||
"c3" -> CSRZoneImpl("Adlivun", "ugd03", "c3"),
|
"c3" -> PointOfInterest("Adlivun", "ugd03", "c3"),
|
||||||
"c4" -> CSRZoneImpl("Byblos", "ugd04", "c4"),
|
"c4" -> PointOfInterest("Byblos", "ugd04", "c4"),
|
||||||
"c5" -> CSRZoneImpl("Annwn", "ugd05", "c5"),
|
"c5" -> PointOfInterest("Annwn", "ugd05", "c5"),
|
||||||
"c6" -> CSRZoneImpl("Drugaskan", "ugd06", "c6"),
|
"c6" -> PointOfInterest("Drugaskan", "ugd06", "c6"),
|
||||||
"i4" -> CSRZoneImpl("Nexus", "map96", "i4"),
|
"i4" -> PointOfInterest("Nexus", "map96", "i4"),
|
||||||
"i3" -> CSRZoneImpl("Desolation", "map97", "i3"),
|
"i3" -> PointOfInterest("Desolation", "map97", "i3"),
|
||||||
"i2" -> CSRZoneImpl("Ascension", "map98", "i2"),
|
"i2" -> PointOfInterest("Ascension", "map98", "i2"),
|
||||||
"i1" -> CSRZoneImpl("Extinction", "map99", "i1"),
|
"i1" -> PointOfInterest("Extinction", "map99", "i1"),
|
||||||
"homebo" -> CSRZoneImpl("Black_ops_hq", "Black_ops_hq", "homebo"),
|
"homebo" -> PointOfInterest("Black_ops_hq", "Black_ops_hq", "homebo"),
|
||||||
"station1" -> CSRZoneImpl("TR Station", "Station1", "station1"),
|
"station1" -> PointOfInterest("TR Station", "Station1", "station1"),
|
||||||
"station2" -> CSRZoneImpl("NC Station", "Station2", "station2"),
|
"station2" -> PointOfInterest("NC Station", "Station2", "station2"),
|
||||||
"station3" -> CSRZoneImpl("VS Station", "Station3", "station3")
|
"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 map the map name of the zone (this map is loaded)
|
||||||
* @param zonename the zone's internal name
|
* @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.
|
* Get a valid `CSRZone`'s information.
|
||||||
* @param zoneId a name that describes the zone and should be searchable
|
* @param zoneId a name that describes the zone and should be searchable
|
||||||
* @return the `CSRZone`, or `None`
|
* @return the `CSRZone`, or `None`
|
||||||
*/
|
*/
|
||||||
def get(zoneId: String): Option[CSRZoneImpl] = {
|
def get(zoneId: String): Option[PointOfInterest] = {
|
||||||
var zId = zoneId.toLowerCase
|
var zId = zoneId.toLowerCase
|
||||||
if (alias.get(zId).isDefined)
|
if (alias.get(zId).isDefined)
|
||||||
zId = alias(zId)
|
zId = alias(zId)
|
||||||
|
|
@ -151,16 +148,19 @@ object CSRZoneImpl {
|
||||||
/**
|
/**
|
||||||
* Get a location within the `CSRZone`.
|
* Get a location within the `CSRZone`.
|
||||||
* The location should be a facility or a warpgate or interesting.
|
* The location should be a facility or a warpgate or interesting.
|
||||||
* @param zone the `CSRZone`
|
* @param zoneId the `CSRZone`
|
||||||
* @param locId a name that describes a known location in the provided `CSRZone` and is searchable
|
* @param locationId a name that describes a known location in the provided `CSRZone` and is searchable
|
||||||
* @return the coordinates of that location, or None
|
* @return the coordinates of that location, or None
|
||||||
*/
|
*/
|
||||||
def getWarpLocation(zone: CSRZoneImpl, locId: String): Option[Vector3] = {
|
def getWarpLocation(zoneId: String, locationId: String): Option[Vector3] = {
|
||||||
val low_locId = locId.toLowerCase
|
get(zoneId) match {
|
||||||
var location = zone.locations.get(low_locId)
|
case Some(poi) =>
|
||||||
if (location.isEmpty)
|
poi.locations.get(locationId) match {
|
||||||
location = zone.gates.get(low_locId)
|
case Some(position) => Some(position)
|
||||||
location
|
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
|
* @param gateId a name that describes a known warpgate in the provided `CSRZone` and is searchable
|
||||||
* @return the coordinates of that warpgate, or None
|
* @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)
|
zone.gates.get(gateId.toLowerCase)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -178,7 +178,7 @@ object CSRZoneImpl {
|
||||||
* @return all of the zonenames
|
* @return all of the zonenames
|
||||||
*/
|
*/
|
||||||
def list: String = {
|
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`
|
* @param zone the `CSRZone`
|
||||||
* @return all of the location keys
|
* @return all of the location keys
|
||||||
*/
|
*/
|
||||||
def listLocations(zone: CSRZoneImpl): String = {
|
def listLocations(zone: PointOfInterest): String = {
|
||||||
var out: String = "warps: "
|
var out: String = "warps: "
|
||||||
if (zone.locations.nonEmpty) {
|
if (zone.locations.nonEmpty) {
|
||||||
out += zone.locations.keys.toArray.sorted.mkString(", ")
|
out += zone.locations.keys.toArray.sorted.mkString(", ")
|
||||||
|
|
@ -200,7 +200,7 @@ object CSRZoneImpl {
|
||||||
* @param zone the `CSRZone`
|
* @param zone the `CSRZone`
|
||||||
* @return all of the warpgate keys
|
* @return all of the warpgate keys
|
||||||
*/
|
*/
|
||||||
def listWarpgates(zone: CSRZoneImpl): String = {
|
def listWarpgates(zone: PointOfInterest): String = {
|
||||||
var out: String = "gatenames: "
|
var out: String = "gatenames: "
|
||||||
if (zone.gates.isEmpty)
|
if (zone.gates.isEmpty)
|
||||||
out += "none"
|
out += "none"
|
||||||
|
|
@ -214,7 +214,7 @@ object CSRZoneImpl {
|
||||||
* @param zone the `CSRZone`
|
* @param zone the `CSRZone`
|
||||||
* @return the coordinates of the spawn point
|
* @return the coordinates of the spawn point
|
||||||
*/
|
*/
|
||||||
def selectRandom(zone: CSRZoneImpl): Vector3 = {
|
def selectRandom(zone: PointOfInterest): Vector3 = {
|
||||||
var outlets = zone.locations //random location?
|
var outlets = zone.locations //random location?
|
||||||
if (outlets.nonEmpty) {
|
if (outlets.nonEmpty) {
|
||||||
return outlets.values.toArray.apply(rand.nextInt(outlets.size))
|
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.LocalProjectile
|
||||||
import net.psforever.objects.ballistics.Projectile
|
import net.psforever.objects.ballistics.Projectile
|
||||||
import net.psforever.objects.zones.ZoneMap
|
import net.psforever.objects.zones.ZoneMap
|
||||||
import zonemaps._
|
import net.psforever.zones.zonemaps._
|
||||||
|
|
||||||
import scala.concurrent.Future
|
import scala.concurrent.Future
|
||||||
import scala.util.{Failure, Success}
|
import scala.util.{Failure, Success}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package net.psforever.pslogin
|
package net.psforever.zones
|
||||||
|
|
||||||
import akka.actor.ActorContext
|
import akka.actor.ActorContext
|
||||||
import net.psforever.objects.GlobalDefinitions
|
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.serverobject.structures.WarpGate
|
||||||
import net.psforever.objects.zones.Zone
|
import net.psforever.objects.zones.Zone
|
||||||
import net.psforever.types.PlanetSideEmpire
|
import net.psforever.types.PlanetSideEmpire
|
||||||
|
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
import scala.concurrent.Await
|
|
||||||
import scala.collection.immutable.HashMap
|
import scala.collection.immutable.HashMap
|
||||||
|
import scala.concurrent.Await
|
||||||
|
|
||||||
object Zones {
|
object Zones {
|
||||||
val zones: HashMap[String, Zone] = HashMap(
|
val zones: HashMap[String, Zone] = HashMap(
|
||||||
|
|
@ -547,8 +546,8 @@ object Zones {
|
||||||
* @return the duration
|
* @return the duration
|
||||||
*/
|
*/
|
||||||
def StandardTimeRules(defender: SourceEntry, attacker: SourceEntry): FiniteDuration = {
|
def StandardTimeRules(defender: SourceEntry, attacker: SourceEntry): FiniteDuration = {
|
||||||
import net.psforever.objects.ballistics._
|
|
||||||
import net.psforever.objects.GlobalDefinitions
|
import net.psforever.objects.GlobalDefinitions
|
||||||
|
import net.psforever.objects.ballistics._
|
||||||
if (attacker.Faction == defender.Faction) {
|
if (attacker.Faction == defender.Faction) {
|
||||||
0 seconds
|
0 seconds
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package net.psforever.pslogin.zonemaps
|
package net.psforever.zones.zonemaps
|
||||||
|
|
||||||
import net.psforever.objects.GlobalDefinitions._
|
import net.psforever.objects.GlobalDefinitions._
|
||||||
import net.psforever.objects.serverobject.doors.Door
|
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.GlobalDefinitions._
|
||||||
import net.psforever.objects.serverobject.doors.Door
|
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.GlobalDefinitions._
|
||||||
import net.psforever.objects.serverobject.doors.Door
|
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.GlobalDefinitions._
|
||||||
import net.psforever.objects.serverobject.doors.Door
|
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.GlobalDefinitions._
|
||||||
import net.psforever.objects.serverobject.doors.Door
|
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.GlobalDefinitions._
|
||||||
import net.psforever.objects.serverobject.doors.Door
|
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.GlobalDefinitions._
|
||||||
import net.psforever.objects.serverobject.doors.Door
|
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.GlobalDefinitions._
|
||||||
import net.psforever.objects.serverobject.doors.Door
|
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.GlobalDefinitions._
|
||||||
import net.psforever.objects.serverobject.doors.Door
|
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.GlobalDefinitions._
|
||||||
import net.psforever.objects.serverobject.doors.Door
|
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.GlobalDefinitions._
|
||||||
import net.psforever.objects.serverobject.doors.Door
|
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.GlobalDefinitions._
|
||||||
import net.psforever.objects.serverobject.doors.Door
|
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.GlobalDefinitions._
|
||||||
import net.psforever.objects.serverobject.doors.Door
|
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.GlobalDefinitions._
|
||||||
import net.psforever.objects.serverobject.doors.Door
|
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.GlobalDefinitions._
|
||||||
import net.psforever.objects.serverobject.doors.Door
|
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.GlobalDefinitions._
|
||||||
import net.psforever.objects.serverobject.doors.Door
|
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.GlobalDefinitions._
|
||||||
import net.psforever.objects.serverobject.doors.Door
|
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.GlobalDefinitions._
|
||||||
import net.psforever.objects.serverobject.doors.Door
|
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.GlobalDefinitions._
|
||||||
import net.psforever.objects.serverobject.doors.Door
|
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.GlobalDefinitions._
|
||||||
import net.psforever.objects.serverobject.doors.Door
|
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.GlobalDefinitions._
|
||||||
import net.psforever.objects.serverobject.doors.Door
|
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.GlobalDefinitions._
|
||||||
import net.psforever.objects.serverobject.doors.Door
|
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.GlobalDefinitions._
|
||||||
import net.psforever.objects.serverobject.doors.Door
|
import net.psforever.objects.serverobject.doors.Door
|
||||||
|
|
@ -15,14 +15,14 @@ object Service {
|
||||||
}
|
}
|
||||||
|
|
||||||
trait GenericEventBusMsg {
|
trait GenericEventBusMsg {
|
||||||
def toChannel: String
|
def channel: String
|
||||||
}
|
}
|
||||||
|
|
||||||
class GenericEventBus[A <: GenericEventBusMsg] extends ActorEventBus with SubchannelClassification {
|
class GenericEventBus[A <: GenericEventBusMsg] extends ActorEventBus with SubchannelClassification {
|
||||||
type Event = A
|
type Event = A
|
||||||
type Classifier = String
|
type Classifier = String
|
||||||
|
|
||||||
protected def classify(event: Event): Classifier = event.toChannel
|
protected def classify(event: Event): Classifier = event.channel
|
||||||
|
|
||||||
protected def subclassification =
|
protected def subclassification =
|
||||||
new Subclassification[Classifier] {
|
new Subclassification[Classifier] {
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ import net.psforever.types.{ExoSuitType, PlanetSideEmpire, PlanetSideGUID, Trans
|
||||||
import services.GenericEventBusMsg
|
import services.GenericEventBusMsg
|
||||||
|
|
||||||
final case class AvatarServiceResponse(
|
final case class AvatarServiceResponse(
|
||||||
toChannel: String,
|
channel: String,
|
||||||
avatar_guid: PlanetSideGUID,
|
avatar_guid: PlanetSideGUID,
|
||||||
replyMessage: AvatarResponse.Response
|
replyMessage: AvatarResponse.Response
|
||||||
) extends GenericEventBusMsg
|
) 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
|
// Copyright (c) 2017 PSForever
|
||||||
package services.chat
|
package services.chat
|
||||||
|
|
||||||
import akka.actor.Actor
|
import akka.actor.typed.receptionist.{Receptionist, ServiceKey}
|
||||||
import net.psforever.objects.LivePlayerList
|
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.packet.game.ChatMsg
|
||||||
import net.psforever.types.ChatMessageType
|
import net.psforever.types.{ChatMessageType, PlanetSideEmpire, PlanetSideGUID}
|
||||||
import services.{GenericEventBus, Service}
|
|
||||||
|
|
||||||
class ChatService extends Actor {
|
object ChatService {
|
||||||
private[this] val log = org.log4s.getLogger
|
val ChatServiceKey: ServiceKey[Command] = ServiceKey[ChatService.Command]("chatService")
|
||||||
|
|
||||||
override def preStart = {
|
def apply(): Behavior[Command] =
|
||||||
log.info("Starting....")
|
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 = {
|
class ChatService(context: ActorContext[ChatService.Command]) extends AbstractBehavior[ChatService.Command](context) {
|
||||||
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())
|
|
||||||
|
|
||||||
case ChatServiceMessage(forChannel, action) =>
|
import ChatService._
|
||||||
action match {
|
import ChatMessageType._
|
||||||
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)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
case ChatAction.Note(player_guid, player_name, msg) => // note
|
private[this] val log = org.log4s.getLogger
|
||||||
ChatEvents.publish(
|
var subscriptions: List[JoinChannel] = List[JoinChannel]()
|
||||||
ChatServiceResponse(
|
|
||||||
s"/Chat/$forChannel",
|
override def onMessage(msg: Command): Behavior[Command] = {
|
||||||
player_guid,
|
msg match {
|
||||||
player_name,
|
case subscription: JoinChannel =>
|
||||||
target = 1,
|
subscriptions ++= List(subscription)
|
||||||
replyMessage = ChatMsg(ChatMessageType.U_CMT_GMTELLFROM, true, msg.recipient, msg.contents, None)
|
this
|
||||||
)
|
|
||||||
)
|
case LeaveChannel(actor, channel) =>
|
||||||
ChatEvents.publish(
|
subscriptions = subscriptions.filter {
|
||||||
ChatServiceResponse(
|
case JoinChannel(a, _, c) => actor != a && channel != c
|
||||||
s"/Chat/$forChannel",
|
}
|
||||||
player_guid,
|
this
|
||||||
player_name,
|
|
||||||
target = 1,
|
case LeaveAllChannels(actor) =>
|
||||||
replyMessage = ChatMsg(
|
subscriptions = subscriptions.filter {
|
||||||
ChatMessageType.CMT_GMTELL,
|
case JoinChannel(a, _, _) => actor != a
|
||||||
true,
|
}
|
||||||
"Server",
|
this
|
||||||
"Why do you try to /note ? That's a GM command ! ... Or not, nobody can /note",
|
|
||||||
None
|
case Message(session, message, channel) =>
|
||||||
)
|
(channel, message.messageType) match {
|
||||||
)
|
case (ChatChannel.Squad(_), CMT_SQUAD) =>
|
||||||
)
|
case (ChatChannel.Default(), messageType) if messageType != CMT_SQUAD =>
|
||||||
case ChatAction.Squad(player_guid, player_name, cont, player_pos, player_faction, msg) => // squad
|
case _ =>
|
||||||
ChatEvents.publish(
|
log.error(s"invalid chat channel $channel for messageType ${message.messageType}")
|
||||||
ChatServiceResponse(
|
return this
|
||||||
s"/Chat/$forChannel",
|
}
|
||||||
player_guid,
|
val subs = subscriptions.filter(_.channel == channel)
|
||||||
player_name,
|
message.messageType match {
|
||||||
cont,
|
case CMT_TELL | CMT_GMTELL =>
|
||||||
player_pos,
|
subs.find(_.session.player.Name == session.player.Name).foreach {
|
||||||
player_faction,
|
case JoinChannel(sender, _, _) =>
|
||||||
2,
|
sender ! MessageResponse(
|
||||||
ChatMsg(ChatMessageType.CMT_SQUAD, msg.wideContents, player_name, msg.contents, None)
|
session,
|
||||||
)
|
message.copy(messageType = if (message.messageType == CMT_TELL) U_CMT_TELLFROM else U_CMT_GMTELLFROM),
|
||||||
)
|
channel
|
||||||
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)
|
|
||||||
)
|
)
|
||||||
)
|
subs.find(_.session.player.Name == message.recipient) match {
|
||||||
// if(player_guid != PlanetSideGUID(0)) {
|
case Some(JoinChannel(receiver, _, _)) =>
|
||||||
//
|
receiver ! MessageResponse(session, message, channel)
|
||||||
// val args = msg.contents.split(" ")
|
case None =>
|
||||||
// var silence_name : String = ""
|
sender ! MessageResponse(
|
||||||
// var silence_time : Int = 5
|
session,
|
||||||
// if (args.length == 1) {
|
ChatMsg(ChatMessageType.UNK_45, false, "", "@NoTell_Target", None),
|
||||||
// silence_name = args(0)
|
channel
|
||||||
// }
|
)
|
||||||
// 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 _ => ;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 net.psforever.types.PlanetSideGUID
|
||||||
import services.GenericEventBusMsg
|
import services.GenericEventBusMsg
|
||||||
|
|
||||||
final case class GalaxyServiceResponse(toChannel: String, replyMessage: GalaxyResponse.Response)
|
final case class GalaxyServiceResponse(channel: String, replyMessage: GalaxyResponse.Response)
|
||||||
extends GenericEventBusMsg
|
extends GenericEventBusMsg
|
||||||
|
|
||||||
object GalaxyResponse {
|
object GalaxyResponse {
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID, Vector3}
|
||||||
import services.GenericEventBusMsg
|
import services.GenericEventBusMsg
|
||||||
|
|
||||||
final case class LocalServiceResponse(
|
final case class LocalServiceResponse(
|
||||||
toChannel: String,
|
channel: String,
|
||||||
avatar_guid: PlanetSideGUID,
|
avatar_guid: PlanetSideGUID,
|
||||||
replyMessage: LocalResponse.Response
|
replyMessage: LocalResponse.Response
|
||||||
) extends GenericEventBusMsg
|
) extends GenericEventBusMsg
|
||||||
|
|
|
||||||
|
|
@ -2,15 +2,22 @@
|
||||||
package services.teamwork
|
package services.teamwork
|
||||||
|
|
||||||
import akka.actor.{Actor, ActorRef, Terminated}
|
import akka.actor.{Actor, ActorRef, Terminated}
|
||||||
import net.psforever.objects.{Avatar, LivePlayerList, Player}
|
|
||||||
import net.psforever.objects.definition.converter.StatConverter
|
import net.psforever.objects.definition.converter.StatConverter
|
||||||
import net.psforever.objects.loadouts.SquadLoadout
|
import net.psforever.objects.loadouts.SquadLoadout
|
||||||
import net.psforever.objects.teamwork.{Member, Squad, SquadFeatures}
|
import net.psforever.objects.teamwork.{Member, Squad, SquadFeatures}
|
||||||
import net.psforever.objects.zones.Zone
|
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 net.psforever.types._
|
||||||
import services.{GenericEventBus, Service}
|
import services.{GenericEventBus, Service}
|
||||||
import services.teamwork.SquadAction
|
|
||||||
|
|
||||||
import scala.collection.concurrent.TrieMap
|
import scala.collection.concurrent.TrieMap
|
||||||
import scala.collection.mutable
|
import scala.collection.mutable
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,11 @@
|
||||||
package services.teamwork
|
package services.teamwork
|
||||||
|
|
||||||
import net.psforever.objects.teamwork.Squad
|
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 net.psforever.types.{PlanetSideGUID, SquadResponseType, SquadWaypoints}
|
||||||
import services.GenericEventBusMsg
|
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
|
extends GenericEventBusMsg
|
||||||
|
|
||||||
object SquadServiceResponse {
|
object SquadServiceResponse {
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ import net.psforever.types.{BailType, DriveState, PlanetSideGUID, Vector3}
|
||||||
import services.GenericEventBusMsg
|
import services.GenericEventBusMsg
|
||||||
|
|
||||||
final case class VehicleServiceResponse(
|
final case class VehicleServiceResponse(
|
||||||
toChannel: String,
|
channel: String,
|
||||||
avatar_guid: PlanetSideGUID,
|
avatar_guid: PlanetSideGUID,
|
||||||
replyMessage: VehicleResponse.Response
|
replyMessage: VehicleResponse.Response
|
||||||
) extends GenericEventBusMsg
|
) extends GenericEventBusMsg
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,9 @@ package net.psforever.pslogin
|
||||||
|
|
||||||
import java.net.InetAddress
|
import java.net.InetAddress
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import akka.actor.{ActorSystem, Props}
|
|
||||||
|
import akka.{actor => classic}
|
||||||
|
import akka.actor.typed.ActorSystem
|
||||||
import akka.routing.RandomPool
|
import akka.routing.RandomPool
|
||||||
import ch.qos.logback.classic.LoggerContext
|
import ch.qos.logback.classic.LoggerContext
|
||||||
import ch.qos.logback.classic.joran.JoranConfigurator
|
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.Default
|
||||||
import net.psforever.objects.zones._
|
import net.psforever.objects.zones._
|
||||||
import net.psforever.objects.guid.TaskResolver
|
import net.psforever.objects.guid.TaskResolver
|
||||||
import net.psforever.pslogin.psadmin.PsAdminActor
|
|
||||||
import org.slf4j
|
import org.slf4j
|
||||||
import org.fusesource.jansi.Ansi._
|
import org.fusesource.jansi.Ansi._
|
||||||
import org.fusesource.jansi.Ansi.Color._
|
import org.fusesource.jansi.Ansi.Color._
|
||||||
|
|
@ -26,6 +27,22 @@ import org.flywaydb.core.Flyway
|
||||||
import java.nio.file.Paths
|
import java.nio.file.Paths
|
||||||
import scopt.OParser
|
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 {
|
object PsLogin {
|
||||||
private val logger = org.log4s.getLogger
|
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 */
|
/** 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)
|
Default(system)
|
||||||
|
|
||||||
|
val typedSystem: ActorSystem[Nothing] = system.toTyped
|
||||||
|
|
||||||
/** Create pipelines for the login and world servers
|
/** Create pipelines for the login and world servers
|
||||||
*
|
*
|
||||||
* The first node in the pipe is an Actor that handles the crypto for protecting packets.
|
* 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
|
* See SessionRouter.scala for a diagram
|
||||||
*/
|
*/
|
||||||
val loginTemplate = List(
|
val loginTemplate = List(
|
||||||
SessionPipeline("crypto-session-", Props[CryptoSessionActor]),
|
SessionPipeline("crypto-session-", classic.Props[CryptoSessionActor]),
|
||||||
SessionPipeline("packet-session-", Props[PacketCodingActor]),
|
SessionPipeline("packet-session-", classic.Props[PacketCodingActor]),
|
||||||
SessionPipeline("login-session-", Props[LoginSessionActor])
|
SessionPipeline("login-session-", classic.Props[LoginSessionActor])
|
||||||
)
|
)
|
||||||
val worldTemplate = List(
|
val worldTemplate = List(
|
||||||
SessionPipeline("crypto-session-", Props[CryptoSessionActor]),
|
SessionPipeline("crypto-session-", classic.Props[CryptoSessionActor]),
|
||||||
SessionPipeline("packet-session-", Props[PacketCodingActor]),
|
SessionPipeline("packet-session-", classic.Props[PacketCodingActor]),
|
||||||
SessionPipeline("world-session-", Props[WorldSessionActor])
|
SessionPipeline("world-session-", classic.Props[SessionActor])
|
||||||
)
|
)
|
||||||
|
|
||||||
val netSim: Option[NetworkSimulatorParameters] = if (Config.app.developer.netSim.enable) {
|
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 continents = Zones.zones.values ++ Seq(Zone.Nowhere)
|
||||||
|
|
||||||
val serviceManager = ServiceManager.boot
|
system.spawnAnonymous(ChatService())
|
||||||
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")
|
|
||||||
|
|
||||||
val loginRouter = Props(new SessionRouter("Login", loginTemplate))
|
val serviceManager = ServiceManager.boot
|
||||||
val worldRouter = Props(new SessionRouter("World", worldTemplate))
|
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(
|
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"
|
"login-udp-endpoint"
|
||||||
)
|
)
|
||||||
val worldListener = system.actorOf(
|
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"
|
"world-udp-endpoint"
|
||||||
)
|
)
|
||||||
|
|
||||||
val adminListener = system.actorOf(
|
val adminListener = system.actorOf(
|
||||||
Props(
|
classic.Props(
|
||||||
new TcpListener(
|
new TcpListener(
|
||||||
classOf[PsAdminActor],
|
classOf[PsAdminActor],
|
||||||
"psadmin-client-",
|
"net.psforever.login.psadmin-client-",
|
||||||
InetAddress.getByName(Config.app.admin.bind),
|
InetAddress.getByName(Config.app.admin.bind),
|
||||||
Config.app.admin.port
|
Config.app.admin.port
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
"psadmin-tcp-endpoint"
|
"net.psforever.login.psadmin-tcp-endpoint"
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.info(
|
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.actor.{ActorRef, MDCContextAware}
|
||||||
import akka.testkit.TestProbe
|
import akka.testkit.TestProbe
|
||||||
|
import net.psforever.login.HelloFriend
|
||||||
import net.psforever.packet.{ControlPacket, GamePacket}
|
import net.psforever.packet.{ControlPacket, GamePacket}
|
||||||
|
|
||||||
final case class MDCGamePacket(packet: GamePacket)
|
final case class MDCGamePacket(packet: GamePacket)
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package net.psforever.pslogin
|
||||||
import actor.base.ActorTest
|
import actor.base.ActorTest
|
||||||
import akka.actor.{ActorRef, Props}
|
import akka.actor.{ActorRef, Props}
|
||||||
import akka.testkit.TestProbe
|
import akka.testkit.TestProbe
|
||||||
|
import net.psforever.login.{HelloFriend, PacketCodingActor, RawPacket}
|
||||||
import net.psforever.packet.control.{ControlSync, MultiPacketBundle, SlottedMetaPacket}
|
import net.psforever.packet.control.{ControlSync, MultiPacketBundle, SlottedMetaPacket}
|
||||||
import net.psforever.packet.{ControlPacket, GamePacket, GamePacketOpcode, PacketCoding}
|
import net.psforever.packet.{ControlPacket, GamePacket, GamePacketOpcode, PacketCoding}
|
||||||
import net.psforever.packet.game._
|
import net.psforever.packet.game._
|
||||||
|
|
|
||||||
|
|
@ -534,7 +534,7 @@ class AvatarReleaseTest extends ActorTest {
|
||||||
val reply1 = receiveOne(200 milliseconds)
|
val reply1 = receiveOne(200 milliseconds)
|
||||||
assert(reply1.isInstanceOf[AvatarServiceResponse])
|
assert(reply1.isInstanceOf[AvatarServiceResponse])
|
||||||
val reply1msg = reply1.asInstanceOf[AvatarServiceResponse]
|
val reply1msg = reply1.asInstanceOf[AvatarServiceResponse]
|
||||||
assert(reply1msg.toChannel == "/test/Avatar")
|
assert(reply1msg.channel == "/test/Avatar")
|
||||||
assert(reply1msg.avatar_guid == guid)
|
assert(reply1msg.avatar_guid == guid)
|
||||||
assert(reply1msg.replyMessage.isInstanceOf[AvatarResponse.Release])
|
assert(reply1msg.replyMessage.isInstanceOf[AvatarResponse.Release])
|
||||||
assert(reply1msg.replyMessage.asInstanceOf[AvatarResponse.Release].player == obj)
|
assert(reply1msg.replyMessage.asInstanceOf[AvatarResponse.Release].player == obj)
|
||||||
|
|
@ -542,7 +542,7 @@ class AvatarReleaseTest extends ActorTest {
|
||||||
val reply2 = receiveOne(2 seconds)
|
val reply2 = receiveOne(2 seconds)
|
||||||
assert(reply2.isInstanceOf[AvatarServiceResponse])
|
assert(reply2.isInstanceOf[AvatarServiceResponse])
|
||||||
val reply2msg = reply2.asInstanceOf[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.avatar_guid == Service.defaultPlayerGUID)
|
||||||
assert(reply2msg.replyMessage.isInstanceOf[AvatarResponse.ObjectDelete])
|
assert(reply2msg.replyMessage.isInstanceOf[AvatarResponse.ObjectDelete])
|
||||||
assert(reply2msg.replyMessage.asInstanceOf[AvatarResponse.ObjectDelete].item_guid == guid)
|
assert(reply2msg.replyMessage.asInstanceOf[AvatarResponse.ObjectDelete].item_guid == guid)
|
||||||
|
|
@ -585,7 +585,7 @@ class AvatarReleaseEarly1Test extends ActorTest {
|
||||||
val reply1 = receiveOne(200 milliseconds)
|
val reply1 = receiveOne(200 milliseconds)
|
||||||
assert(reply1.isInstanceOf[AvatarServiceResponse])
|
assert(reply1.isInstanceOf[AvatarServiceResponse])
|
||||||
val reply1msg = reply1.asInstanceOf[AvatarServiceResponse]
|
val reply1msg = reply1.asInstanceOf[AvatarServiceResponse]
|
||||||
assert(reply1msg.toChannel == "/test/Avatar")
|
assert(reply1msg.channel == "/test/Avatar")
|
||||||
assert(reply1msg.avatar_guid == guid)
|
assert(reply1msg.avatar_guid == guid)
|
||||||
assert(reply1msg.replyMessage.isInstanceOf[AvatarResponse.Release])
|
assert(reply1msg.replyMessage.isInstanceOf[AvatarResponse.Release])
|
||||||
assert(reply1msg.replyMessage.asInstanceOf[AvatarResponse.Release].player == obj)
|
assert(reply1msg.replyMessage.asInstanceOf[AvatarResponse.Release].player == obj)
|
||||||
|
|
@ -594,7 +594,7 @@ class AvatarReleaseEarly1Test extends ActorTest {
|
||||||
val reply2 = receiveOne(200 milliseconds)
|
val reply2 = receiveOne(200 milliseconds)
|
||||||
assert(reply2.isInstanceOf[AvatarServiceResponse])
|
assert(reply2.isInstanceOf[AvatarServiceResponse])
|
||||||
val reply2msg = reply2.asInstanceOf[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.avatar_guid == Service.defaultPlayerGUID)
|
||||||
assert(reply2msg.replyMessage.isInstanceOf[AvatarResponse.ObjectDelete])
|
assert(reply2msg.replyMessage.isInstanceOf[AvatarResponse.ObjectDelete])
|
||||||
assert(reply2msg.replyMessage.asInstanceOf[AvatarResponse.ObjectDelete].item_guid == guid)
|
assert(reply2msg.replyMessage.asInstanceOf[AvatarResponse.ObjectDelete].item_guid == guid)
|
||||||
|
|
@ -641,7 +641,7 @@ class AvatarReleaseEarly2Test extends ActorTest {
|
||||||
val reply1 = receiveOne(200 milliseconds)
|
val reply1 = receiveOne(200 milliseconds)
|
||||||
assert(reply1.isInstanceOf[AvatarServiceResponse])
|
assert(reply1.isInstanceOf[AvatarServiceResponse])
|
||||||
val reply1msg = reply1.asInstanceOf[AvatarServiceResponse]
|
val reply1msg = reply1.asInstanceOf[AvatarServiceResponse]
|
||||||
assert(reply1msg.toChannel == "/test/Avatar")
|
assert(reply1msg.channel == "/test/Avatar")
|
||||||
assert(reply1msg.avatar_guid == guid)
|
assert(reply1msg.avatar_guid == guid)
|
||||||
assert(reply1msg.replyMessage.isInstanceOf[AvatarResponse.Release])
|
assert(reply1msg.replyMessage.isInstanceOf[AvatarResponse.Release])
|
||||||
assert(reply1msg.replyMessage.asInstanceOf[AvatarResponse.Release].player == obj)
|
assert(reply1msg.replyMessage.asInstanceOf[AvatarResponse.Release].player == obj)
|
||||||
|
|
@ -652,7 +652,7 @@ class AvatarReleaseEarly2Test extends ActorTest {
|
||||||
val reply2 = receiveOne(100 milliseconds)
|
val reply2 = receiveOne(100 milliseconds)
|
||||||
assert(reply2.isInstanceOf[AvatarServiceResponse])
|
assert(reply2.isInstanceOf[AvatarServiceResponse])
|
||||||
val reply2msg = reply2.asInstanceOf[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.avatar_guid == Service.defaultPlayerGUID)
|
||||||
assert(reply2msg.replyMessage.isInstanceOf[AvatarResponse.ObjectDelete])
|
assert(reply2msg.replyMessage.isInstanceOf[AvatarResponse.ObjectDelete])
|
||||||
assert(reply2msg.replyMessage.asInstanceOf[AvatarResponse.ObjectDelete].item_guid == guid)
|
assert(reply2msg.replyMessage.asInstanceOf[AvatarResponse.ObjectDelete].item_guid == guid)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue