mirror of
https://github.com/psforever/PSF-LoginServer.git
synced 2026-04-29 16:25:30 +00:00
Merge pull request #1054 from jgillich/dc50
50 minute disconnect fix/workaround
This commit is contained in:
commit
6c3fd970c4
89 changed files with 467978 additions and 467770 deletions
1
.devcontainer/Dockerfile
Normal file
1
.devcontainer/Dockerfile
Normal file
|
|
@ -0,0 +1 @@
|
|||
FROM mcr.microsoft.com/vscode/devcontainers/base:debian
|
||||
43
.devcontainer/devcontainer.json
Normal file
43
.devcontainer/devcontainer.json
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
|
||||
// README at: https://github.com/devcontainers/templates/tree/main/src/java-postgres
|
||||
{
|
||||
"name": "Java & PostgreSQL",
|
||||
"dockerComposeFile": "docker-compose.yml",
|
||||
"service": "app",
|
||||
"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
|
||||
"features": {
|
||||
"ghcr.io/devcontainers-contrib/features/sbt-sdkman:2": {
|
||||
"jdkVersion": "11"
|
||||
},
|
||||
"ghcr.io/devcontainers-contrib/features/scala-sdkman:2": {
|
||||
"jdkVersion": "11"
|
||||
},
|
||||
"ghcr.io/devcontainers-contrib/features/scalacli-sdkman:2": {
|
||||
"jdkVersion": "11"
|
||||
},
|
||||
},
|
||||
// Features to add to the dev container. More info: https://containers.dev/features.
|
||||
// "features": {}
|
||||
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||
// This can be used to network with other containers or with the host.
|
||||
// "forwardPorts": [
|
||||
// 51000,
|
||||
// 51001,
|
||||
// 51002
|
||||
// ],
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"extensions": [
|
||||
"scalameta.metals",
|
||||
"scala-lang.scala",
|
||||
"EditorConfig.EditorConfig"
|
||||
]
|
||||
}
|
||||
}
|
||||
// Use 'postCreateCommand' to run commands after the container is created.
|
||||
// "postCreateCommand": "java -version",
|
||||
// Configure tool-specific properties.
|
||||
// "customizations": {},
|
||||
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
|
||||
// "remoteUser": "root"
|
||||
}
|
||||
31
.devcontainer/docker-compose.yml
Normal file
31
.devcontainer/docker-compose.yml
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
version: "3.8"
|
||||
|
||||
volumes:
|
||||
postgres-data:
|
||||
|
||||
services:
|
||||
app:
|
||||
container_name: javadev
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
environment:
|
||||
CONFIG_FORCE_database_host: postgres
|
||||
CONFIG_FORCE_bind: 0.0.0.0
|
||||
volumes:
|
||||
- ../..:/workspaces:cached
|
||||
command: sleep infinity
|
||||
# network_mode: service:postgres
|
||||
ports:
|
||||
- "51000:51000/udp"
|
||||
- "51001:51001/udp"
|
||||
# - "51000:51002"
|
||||
postgres:
|
||||
image: postgres:latest
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- postgres-data:/var/lib/postgresql/data
|
||||
environment:
|
||||
POSTGRES_PASSWORD: psforever
|
||||
POSTGRES_USER: psforever
|
||||
POSTGRES_DB: psforever
|
||||
8
.editorconfig
Normal file
8
.editorconfig
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
1
.jvmopts
1
.jvmopts
|
|
@ -2,3 +2,4 @@
|
|||
-Xss6M
|
||||
-Dconfig.override_with_env_vars=true
|
||||
-Dsbt.server.forcestart=true
|
||||
-Dquill.macro.log=false
|
||||
|
|
|
|||
55
build.sbt
55
build.sbt
|
|
@ -3,7 +3,9 @@ import xerial.sbt.pack.PackPlugin._
|
|||
lazy val psforeverSettings = Seq(
|
||||
organization := "net.psforever",
|
||||
version := "1.0.2-SNAPSHOT",
|
||||
scalaVersion := "2.13.3",
|
||||
// TODO 2.13.5+ breaks Md5Mac test
|
||||
// possibly due to ListBuffer changes? https://github.com/scala/scala/pull/9257
|
||||
scalaVersion := "2.13.4",
|
||||
Global / cancelable := false,
|
||||
semanticdbEnabled := true,
|
||||
semanticdbVersion := scalafixSemanticdb.revision,
|
||||
|
|
@ -40,53 +42,50 @@ lazy val psforeverSettings = Seq(
|
|||
classLoaderLayeringStrategy := ClassLoaderLayeringStrategy.Flat,
|
||||
resolvers += "Sonatype OSS Snapshots" at "https://oss.sonatype.org/content/repositories/snapshots",
|
||||
libraryDependencies ++= Seq(
|
||||
"com.typesafe.akka" %% "akka-actor" % "2.6.17",
|
||||
"com.typesafe.akka" %% "akka-slf4j" % "2.6.17",
|
||||
"com.typesafe.akka" %% "akka-protobuf-v3" % "2.6.17",
|
||||
"com.typesafe.akka" %% "akka-stream" % "2.6.17",
|
||||
"com.typesafe.akka" %% "akka-testkit" % "2.6.17" % "test",
|
||||
"com.typesafe.akka" %% "akka-actor-typed" % "2.6.17",
|
||||
"com.typesafe.akka" %% "akka-actor-testkit-typed" % "2.6.17" % "test",
|
||||
"com.typesafe.akka" %% "akka-slf4j" % "2.6.17",
|
||||
"com.typesafe.akka" %% "akka-cluster-typed" % "2.6.17",
|
||||
"com.typesafe.akka" %% "akka-coordination" % "2.6.17",
|
||||
"com.typesafe.akka" %% "akka-cluster-tools" % "2.6.17",
|
||||
"com.typesafe.akka" %% "akka-actor" % "2.6.20",
|
||||
"com.typesafe.akka" %% "akka-slf4j" % "2.6.20",
|
||||
"com.typesafe.akka" %% "akka-protobuf-v3" % "2.6.20",
|
||||
"com.typesafe.akka" %% "akka-stream" % "2.6.20",
|
||||
"com.typesafe.akka" %% "akka-testkit" % "2.6.20" % "test",
|
||||
"com.typesafe.akka" %% "akka-actor-typed" % "2.6.20",
|
||||
"com.typesafe.akka" %% "akka-actor-testkit-typed" % "2.6.20" % "test",
|
||||
"com.typesafe.akka" %% "akka-slf4j" % "2.6.20",
|
||||
"com.typesafe.akka" %% "akka-cluster-typed" % "2.6.20",
|
||||
"com.typesafe.akka" %% "akka-coordination" % "2.6.20",
|
||||
"com.typesafe.akka" %% "akka-cluster-tools" % "2.6.20",
|
||||
"com.typesafe.akka" %% "akka-http" % "10.2.6",
|
||||
"com.typesafe.scala-logging" %% "scala-logging" % "3.9.4",
|
||||
"org.specs2" %% "specs2-core" % "4.13.0" % "test",
|
||||
"org.scalatest" %% "scalatest" % "3.2.10" % "test",
|
||||
"org.specs2" %% "specs2-core" % "4.20.0" % "test",
|
||||
"org.scalatest" %% "scalatest" % "3.2.15" % "test",
|
||||
"org.scodec" %% "scodec-core" % "1.11.9",
|
||||
"ch.qos.logback" % "logback-classic" % "1.2.6",
|
||||
"org.log4s" %% "log4s" % "1.10.0",
|
||||
"org.fusesource.jansi" % "jansi" % "2.4.0",
|
||||
"org.scoverage" %% "scalac-scoverage-plugin" % "1.4.2",
|
||||
"com.github.nscala-time" %% "nscala-time" % "2.30.0",
|
||||
"com.github.t3hnar" %% "scala-bcrypt" % "4.3.0",
|
||||
"org.scala-graph" %% "graph-core" % "1.13.3",
|
||||
"io.kamon" %% "kamon-bundle" % "2.3.1",
|
||||
"io.kamon" %% "kamon-apm-reporter" % "2.3.1",
|
||||
"org.json4s" %% "json4s-native" % "4.0.3",
|
||||
"io.getquill" %% "quill-jasync-postgres" % "3.12.0",
|
||||
"org.flywaydb" % "flyway-core" % "8.0.3",
|
||||
"io.getquill" %% "quill-jasync-postgres" % "3.18.0",
|
||||
"org.flywaydb" % "flyway-core" % "9.0.0",
|
||||
"org.postgresql" % "postgresql" % "42.3.1",
|
||||
"com.typesafe" % "config" % "1.4.1",
|
||||
"com.github.pureconfig" %% "pureconfig" % "0.17.0",
|
||||
"com.beachape" %% "enumeratum" % "1.7.0",
|
||||
"joda-time" % "joda-time" % "2.10.13",
|
||||
"commons-io" % "commons-io" % "2.11.0",
|
||||
"com.github.scopt" %% "scopt" % "4.0.1",
|
||||
"io.sentry" % "sentry-logback" % "5.3.0",
|
||||
"io.circe" %% "circe-core" % "0.14.1",
|
||||
"io.circe" %% "circe-generic" % "0.14.1",
|
||||
"io.circe" %% "circe-parser" % "0.14.1",
|
||||
"com.github.scopt" %% "scopt" % "4.1.0",
|
||||
"io.sentry" % "sentry-logback" % "6.16.0",
|
||||
"io.circe" %% "circe-core" % "0.14.5",
|
||||
"io.circe" %% "circe-generic" % "0.14.5",
|
||||
"io.circe" %% "circe-parser" % "0.14.5",
|
||||
"org.scala-lang.modules" %% "scala-parallel-collections" % "1.0.4",
|
||||
"org.bouncycastle" % "bcprov-jdk15on" % "1.69"
|
||||
),
|
||||
dependencyOverrides ++= Seq(
|
||||
"com.github.jasync-sql" % "jasync-postgresql" % "1.1.7"
|
||||
),
|
||||
"com.github.jasync-sql" % "jasync-postgresql" % "1.1.7",
|
||||
"org.scala-lang.modules" %% "scala-java8-compat" % "1.0.2"
|
||||
)
|
||||
// TODO(chord): remove exclusion when SessionActor is refactored: https://github.com/psforever/PSF-LoginServer/issues/279
|
||||
coverageExcludedPackages := "net\\.psforever\\.actors\\.session\\.SessionActor.*"
|
||||
// coverageExcludedPackages := "net\\.psforever\\.actors\\.session\\.SessionActor.*"
|
||||
)
|
||||
|
||||
lazy val psforever = (project in file("."))
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
sbt.version = 1.4.5
|
||||
sbt.version = 1.8.2
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
logLevel := Level.Warn
|
||||
|
||||
addSbtPlugin("org.xerial.sbt" % "sbt-pack" % "0.14")
|
||||
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.9.1")
|
||||
addSbtPlugin("io.kamon" % "sbt-kanela-runner" % "2.0.12")
|
||||
addSbtPlugin("com.eed3si9n" % "sbt-unidoc" % "0.4.3")
|
||||
addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.3")
|
||||
addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.31")
|
||||
addSbtPlugin("org.xerial.sbt" % "sbt-pack" % "0.17")
|
||||
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.0.7")
|
||||
addSbtPlugin("com.eed3si9n" % "sbt-unidoc" % "0.4.3")
|
||||
addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.0")
|
||||
addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.10.4")
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import java.nio.file.Paths
|
|||
import java.util.Locale
|
||||
import java.util.UUID.randomUUID
|
||||
import java.util.concurrent.atomic.AtomicLong
|
||||
import scala.concurrent.Future
|
||||
import scala.concurrent.Await
|
||||
|
||||
import akka.actor.ActorSystem
|
||||
import akka.actor.typed.ActorRef
|
||||
|
|
@ -13,7 +15,6 @@ import akka.{actor => classic}
|
|||
import ch.qos.logback.classic.LoggerContext
|
||||
import ch.qos.logback.classic.joran.JoranConfigurator
|
||||
import io.sentry.{Sentry, SentryOptions}
|
||||
import kamon.Kamon
|
||||
import net.psforever.actors.net.{LoginActor, MiddlewareActor, SocketActor}
|
||||
import net.psforever.actors.session.SessionActor
|
||||
import net.psforever.login.psadmin.PsAdminActor
|
||||
|
|
@ -37,6 +38,7 @@ import scopt.OParser
|
|||
import akka.actor.typed.scaladsl.adapter._
|
||||
import net.psforever.packet.PlanetSidePacket
|
||||
import net.psforever.services.hart.HartService
|
||||
import scala.concurrent.duration.Duration
|
||||
|
||||
object Server {
|
||||
private val logger = org.log4s.getLogger
|
||||
|
|
@ -80,11 +82,6 @@ object Server {
|
|||
case None => InetAddress.getByName(Config.app.bind) // address from config
|
||||
}
|
||||
|
||||
if (Config.app.kamon.enable) {
|
||||
logger.info("Starting Kamon")
|
||||
Kamon.init()
|
||||
}
|
||||
|
||||
if (Config.app.sentry.enable) {
|
||||
logger.info(s"Enabling Sentry")
|
||||
val options = new SentryOptions()
|
||||
|
|
@ -110,8 +107,9 @@ object Server {
|
|||
}
|
||||
val session = (ref: ActorRef[MiddlewareActor.Command], info: InetSocketAddress, connectionId: String) => {
|
||||
Behaviors.setup[PlanetSidePacket](context => {
|
||||
val uuid = randomUUID().toString
|
||||
val actor = context.actorOf(classic.Props(new SessionActor(ref, connectionId, Session.getNewId())), s"session-$uuid")
|
||||
val uuid = randomUUID().toString
|
||||
val actor =
|
||||
context.actorOf(classic.Props(new SessionActor(ref, connectionId, Session.getNewId())), s"session-$uuid")
|
||||
Behaviors.receiveMessage(message => {
|
||||
actor ! message
|
||||
Behaviors.same
|
||||
|
|
@ -119,7 +117,7 @@ object Server {
|
|||
})
|
||||
}
|
||||
|
||||
val zones = Zones.zones ++ Seq(Zone.Nowhere)
|
||||
val zones = Zones.zones ++ Seq(Zone.Nowhere)
|
||||
val serviceManager = ServiceManager.boot
|
||||
serviceManager ! ServiceManager.Register(classic.Props[AccountIntermediaryService](), "accountIntermediary")
|
||||
serviceManager ! ServiceManager.Register(classic.Props[GalaxyService](), "galaxy")
|
||||
|
|
@ -156,6 +154,8 @@ object Server {
|
|||
// TODO: clean up active sessions and close resources safely
|
||||
logger.info("Login server now shutting down...")
|
||||
}
|
||||
|
||||
Await.ready(Future.never, Duration.Inf)
|
||||
}
|
||||
|
||||
def flyway(args: CliConfig): Flyway = {
|
||||
|
|
@ -228,6 +228,7 @@ object Server {
|
|||
}
|
||||
|
||||
sealed trait AuthoritativeCounter {
|
||||
|
||||
/** the id accumulator */
|
||||
private val masterIdKeyRing: AtomicLong = new AtomicLong(0L)
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ akka {
|
|||
loggers = ["akka.event.slf4j.Slf4jLogger"]
|
||||
loglevel = INFO
|
||||
logging-filter = akka.event.slf4j.Slf4jLoggingFilter
|
||||
log-dead-letters-during-shutdown = off
|
||||
}
|
||||
|
||||
akka.actor.deployment {
|
||||
|
|
|
|||
|
|
@ -45,7 +45,6 @@ class LoginActor(
|
|||
class LoginActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], connectionId: String, sessionId: Long)
|
||||
extends Actor
|
||||
with MDCContextAware {
|
||||
private[this] val log = org.log4s.getLogger
|
||||
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
|
||||
|
|
@ -100,9 +99,9 @@ class LoginActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], conne
|
|||
val clientVersion = s"Client Version: $majorVersion.$minorVersion.$revision, $buildDate"
|
||||
|
||||
if (token.isDefined)
|
||||
log.trace(s"New login UN:$username Token:${token.get}. $clientVersion")
|
||||
log.debug(s"New login UN:$username Token:${token.get}. $clientVersion")
|
||||
else {
|
||||
log.trace(s"New login UN:$username. $clientVersion")
|
||||
log.debug(s"New login UN:$username. $clientVersion")
|
||||
}
|
||||
|
||||
accountLogin(username, password.getOrElse(""))
|
||||
|
|
@ -114,7 +113,7 @@ class LoginActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], conne
|
|||
middlewareActor ! MiddlewareActor.Close()
|
||||
|
||||
case _ =>
|
||||
log.warn(s"Unhandled GamePacket $pkt")
|
||||
log.warning(s"Unhandled GamePacket $pkt")
|
||||
}
|
||||
|
||||
def accountLogin(username: String, password: String): Unit = {
|
||||
|
|
@ -196,7 +195,7 @@ class LoginActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], conne
|
|||
}
|
||||
|
||||
def loginPwdFailureResponse(username: String, newToken: String) = {
|
||||
log.warn(s"Failed login to account $username")
|
||||
log.warning(s"Failed login to account $username")
|
||||
middlewareActor ! MiddlewareActor.Send(
|
||||
LoginRespMessage(
|
||||
newToken,
|
||||
|
|
@ -211,7 +210,7 @@ class LoginActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], conne
|
|||
}
|
||||
|
||||
def loginFailureResponse(username: String, newToken: String) = {
|
||||
log.warn("DB problem")
|
||||
log.warning("DB problem")
|
||||
middlewareActor ! MiddlewareActor.Send(
|
||||
LoginRespMessage(
|
||||
newToken,
|
||||
|
|
@ -226,7 +225,7 @@ class LoginActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], conne
|
|||
}
|
||||
|
||||
def loginAccountFailureResponse(username: String, newToken: String) = {
|
||||
log.warn(s"Account $username inactive")
|
||||
log.warning(s"Account $username inactive")
|
||||
middlewareActor ! MiddlewareActor.Send(
|
||||
LoginRespMessage(
|
||||
newToken,
|
||||
|
|
|
|||
|
|
@ -193,10 +193,8 @@ class MiddlewareActor(
|
|||
/** Queue of outgoing packets ready for sending */
|
||||
val outQueueBundled: mutable.Queue[PlanetSidePacket] = mutable.Queue()
|
||||
|
||||
/** Latest outbound sequence number;
|
||||
* the current sequence is one less than this number
|
||||
*/
|
||||
var outSequence = 0
|
||||
/** Latest outbound sequence number */
|
||||
var outSequence = -1
|
||||
|
||||
/**
|
||||
* Increment the outbound sequence number.
|
||||
|
|
@ -205,13 +203,18 @@ class MiddlewareActor(
|
|||
* @return
|
||||
*/
|
||||
def nextSequence: Int = {
|
||||
val r = outSequence
|
||||
if (outSequence == 0xffff) {
|
||||
outSequence = 0
|
||||
} else {
|
||||
outSequence += 1
|
||||
if (outSequence >= 0xffff) {
|
||||
// TODO resetting the sequence to 0 causes a client crash
|
||||
// but that does not happen when we always send the same number
|
||||
// the solution is most likely to send the proper ResetSequence payload
|
||||
// send(ResetSequence(), None, crypto)
|
||||
|
||||
// outSequence = -1
|
||||
// return nextSequence
|
||||
return outSequence
|
||||
}
|
||||
r
|
||||
outSequence += 1
|
||||
outSequence
|
||||
}
|
||||
|
||||
/** Latest outbound subslot number;
|
||||
|
|
@ -302,14 +305,14 @@ class MiddlewareActor(
|
|||
Unknown30 is used to reuse an existing crypto session when switching from login to world
|
||||
When not handling it, it appears that the client will fall back to using ClientStart
|
||||
Do we need to implement this?
|
||||
*/
|
||||
*/
|
||||
connectionClose()
|
||||
|
||||
case (ConnectionClose(), _) =>
|
||||
/*
|
||||
indicates the user has willingly quit the game world
|
||||
we do not need to implement this
|
||||
*/
|
||||
*/
|
||||
Behaviors.same
|
||||
|
||||
// TODO ResetSequence
|
||||
|
|
@ -454,6 +457,11 @@ class MiddlewareActor(
|
|||
case Successful((packet, Some(sequence))) =>
|
||||
activeSequenceFunc(packet, sequence)
|
||||
case Successful((packet, None)) =>
|
||||
packet match {
|
||||
case _: PlanetSideResetSequencePacket =>
|
||||
log.info(s"ResetSequence: ${msg.toHex}, inSeq: ${inSequence}, outSeq: ${outSequence}")
|
||||
case _ => ()
|
||||
}
|
||||
in(packet)
|
||||
case Failure(e) =>
|
||||
log.error(s"Could not decode $connectionId's packet: $e")
|
||||
|
|
@ -569,9 +577,14 @@ class MiddlewareActor(
|
|||
log.error(s"Unexpected crypto packet '$packet'")
|
||||
Behaviors.same
|
||||
|
||||
case _: PlanetSideResetSequencePacket =>
|
||||
log.debug("Received sequence reset request from client; complying")
|
||||
outSequence = 0
|
||||
case packet: PlanetSideResetSequencePacket =>
|
||||
// TODO This is wrong
|
||||
// I suspect ResetSequence is a notification that the remote sequence has been reset
|
||||
// rather than a request to reset our outgoing sequence number
|
||||
// Resetting it this way causes a client crash, see nextSequence
|
||||
|
||||
// log.debug(s"Received sequence reset request from client: $packet.}")
|
||||
// outSequence = 0
|
||||
Behaviors.same
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -257,26 +257,26 @@ object AvatarActor {
|
|||
}
|
||||
|
||||
/**
|
||||
* Transform from encoded inventory data as a CLOB - character large object - into individual items.
|
||||
* Install those items into positions in a target container
|
||||
* in the same positions in which they were previously recorded.<br>
|
||||
* <br>
|
||||
* There is no guarantee that the structure of the retained container data encoded in the CLOB
|
||||
* will fit the current dimensions of the container.
|
||||
* No tests are performed.
|
||||
* A partial decompression of the CLOB may occur.
|
||||
* @param container the container in which to place the pieces of equipment produced from the CLOB
|
||||
* @param clob the inventory data in string form
|
||||
* @param log a reference to a logging context
|
||||
* @param restoreAmmo by default, when `false`, use the maximum ammunition for all ammunition boixes and for all tools;
|
||||
* if `true`, load the last saved ammunition count for all ammunition boxes and for all tools
|
||||
*/
|
||||
* Transform from encoded inventory data as a CLOB - character large object - into individual items.
|
||||
* Install those items into positions in a target container
|
||||
* in the same positions in which they were previously recorded.<br>
|
||||
* <br>
|
||||
* There is no guarantee that the structure of the retained container data encoded in the CLOB
|
||||
* will fit the current dimensions of the container.
|
||||
* No tests are performed.
|
||||
* A partial decompression of the CLOB may occur.
|
||||
* @param container the container in which to place the pieces of equipment produced from the CLOB
|
||||
* @param clob the inventory data in string form
|
||||
* @param log a reference to a logging context
|
||||
* @param restoreAmmo by default, when `false`, use the maximum ammunition for all ammunition boixes and for all tools;
|
||||
* if `true`, load the last saved ammunition count for all ammunition boxes and for all tools
|
||||
*/
|
||||
def buildContainedEquipmentFromClob(
|
||||
container: Container,
|
||||
clob: String,
|
||||
log: org.log4s.Logger,
|
||||
restoreAmmo: Boolean = false
|
||||
): Unit = {
|
||||
container: Container,
|
||||
clob: String,
|
||||
log: org.log4s.Logger,
|
||||
restoreAmmo: Boolean = false
|
||||
): Unit = {
|
||||
clob.split("/").filter(_.trim.nonEmpty).foreach { value =>
|
||||
val (objectType, objectIndex, objectId, ammoData) = value.split(",") match {
|
||||
case Array(a, b: String, c: String) => (a, b.toInt, c.toInt, None)
|
||||
|
|
@ -293,8 +293,8 @@ object AvatarActor {
|
|||
ammoData foreach { toolAmmo =>
|
||||
toolAmmo.split("_").drop(1).foreach { value =>
|
||||
val (ammoSlots, ammoTypeIndex, ammoBoxDefinition, ammoCount) = value.split("-") match {
|
||||
case Array(a: String, b: String, c: String) => (a.toInt, b.toInt, c.toInt, None)
|
||||
case Array(a: String, b: String, c: String, d:String) => (a.toInt, b.toInt, c.toInt, Some(d.toInt))
|
||||
case Array(a: String, b: String, c: String) => (a.toInt, b.toInt, c.toInt, None)
|
||||
case Array(a: String, b: String, c: String, d: String) => (a.toInt, b.toInt, c.toInt, Some(d.toInt))
|
||||
}
|
||||
val fireMode = tool.AmmoSlots(ammoSlots)
|
||||
fireMode.AmmoTypeIndex = ammoTypeIndex
|
||||
|
|
@ -340,11 +340,11 @@ object AvatarActor {
|
|||
* @return the resulting text data that represents object to time mappings
|
||||
*/
|
||||
def buildCooldownsFromClob(
|
||||
clob: String,
|
||||
cooldownDurations: Map[BasicDefinition,FiniteDuration],
|
||||
log: org.log4s.Logger
|
||||
): Map[String, LocalDateTime] = {
|
||||
val now = LocalDateTime.now()
|
||||
clob: String,
|
||||
cooldownDurations: Map[BasicDefinition, FiniteDuration],
|
||||
log: org.log4s.Logger
|
||||
): Map[String, LocalDateTime] = {
|
||||
val now = LocalDateTime.now()
|
||||
val cooldowns: mutable.Map[String, LocalDateTime] = mutable.Map()
|
||||
clob.split("/").filter(_.trim.nonEmpty).foreach { value =>
|
||||
value.split(",") match {
|
||||
|
|
@ -385,7 +385,7 @@ object AvatarActor {
|
|||
val factionName: String = faction.toString.toLowerCase
|
||||
val name = item match {
|
||||
case GlobalDefinitions.trhev_dualcycler | GlobalDefinitions.nchev_scattercannon |
|
||||
GlobalDefinitions.vshev_quasar =>
|
||||
GlobalDefinitions.vshev_quasar =>
|
||||
s"${factionName}hev_antipersonnel"
|
||||
case GlobalDefinitions.trhev_pounder | GlobalDefinitions.nchev_falcon | GlobalDefinitions.vshev_comet =>
|
||||
s"${factionName}hev_antivehicular"
|
||||
|
|
@ -402,12 +402,12 @@ object AvatarActor {
|
|||
if (name.matches("(tr|nc|vs)hev_.+") && Config.app.game.sharedMaxCooldown) {
|
||||
val faction = name.take(2)
|
||||
(if (faction.equals("nc")) {
|
||||
Seq(GlobalDefinitions.nchev_scattercannon, GlobalDefinitions.nchev_falcon, GlobalDefinitions.nchev_sparrow)
|
||||
} else if (faction.equals("vs")) {
|
||||
Seq(GlobalDefinitions.vshev_quasar, GlobalDefinitions.vshev_comet, GlobalDefinitions.vshev_starfire)
|
||||
} else {
|
||||
Seq(GlobalDefinitions.trhev_dualcycler, GlobalDefinitions.trhev_pounder, GlobalDefinitions.trhev_burster)
|
||||
}).zip(
|
||||
Seq(GlobalDefinitions.nchev_scattercannon, GlobalDefinitions.nchev_falcon, GlobalDefinitions.nchev_sparrow)
|
||||
} else if (faction.equals("vs")) {
|
||||
Seq(GlobalDefinitions.vshev_quasar, GlobalDefinitions.vshev_comet, GlobalDefinitions.vshev_starfire)
|
||||
} else {
|
||||
Seq(GlobalDefinitions.trhev_dualcycler, GlobalDefinitions.trhev_pounder, GlobalDefinitions.trhev_burster)
|
||||
}).zip(
|
||||
Seq(s"${faction}hev_antipersonnel", s"${faction}hev_antivehicular", s"${faction}hev_antiaircraft")
|
||||
)
|
||||
} else {
|
||||
|
|
@ -461,7 +461,6 @@ object AvatarActor {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
def displayLookingForSquad(session: Session, state: Int): Unit = {
|
||||
val player = session.player
|
||||
session.zone.AvatarEvents ! AvatarServiceMessage(
|
||||
|
|
@ -477,7 +476,10 @@ object AvatarActor {
|
|||
* @param func functionality that is called upon discovery of the character
|
||||
* @return if found, the discovered avatar, the avatar's account id, and the avatar's faction affiliation
|
||||
*/
|
||||
def getLiveAvatarForFunc(name: String, func: (Long,String,Int)=>Unit): Option[(Avatar, Long, PlanetSideEmpire.Value)] = {
|
||||
def getLiveAvatarForFunc(
|
||||
name: String,
|
||||
func: (Long, String, Int) => Unit
|
||||
): Option[(Avatar, Long, PlanetSideEmpire.Value)] = {
|
||||
if (name.nonEmpty) {
|
||||
LivePlayerList.WorldPopulation({ case (_, a) => a.name.equals(name) }).headOption match {
|
||||
case Some(otherAvatar) =>
|
||||
|
|
@ -500,7 +502,10 @@ object AvatarActor {
|
|||
* otherwise, always returns `None` as if no avatar was discovered
|
||||
* (the query is probably still in progress)
|
||||
*/
|
||||
def getAvatarForFunc(name: String, func: (Long,String,Int)=>Unit): Option[(Avatar, Long, PlanetSideEmpire.Value)] = {
|
||||
def getAvatarForFunc(
|
||||
name: String,
|
||||
func: (Long, String, Int) => Unit
|
||||
): Option[(Avatar, Long, PlanetSideEmpire.Value)] = {
|
||||
getLiveAvatarForFunc(name, func).orElse {
|
||||
if (name.nonEmpty) {
|
||||
import ctx._
|
||||
|
|
@ -527,7 +532,7 @@ object AvatarActor {
|
|||
* @param name unique character name
|
||||
* @param faction the faction affiliation
|
||||
*/
|
||||
def formatForOtherFunc(func: (Long,String)=>Unit)(charId: Long, name: String, faction: Int): Unit = {
|
||||
def formatForOtherFunc(func: (Long, String) => Unit)(charId: Long, name: String, faction: Int): Unit = {
|
||||
func(charId, name)
|
||||
}
|
||||
|
||||
|
|
@ -540,9 +545,11 @@ object AvatarActor {
|
|||
*/
|
||||
def onlineIfNotIgnored(onlinePlayerName: String, observerName: String): Boolean = {
|
||||
val onlinePlayerNameLower = onlinePlayerName.toLowerCase()
|
||||
LivePlayerList.WorldPopulation({ case (_, a) => a.name.toLowerCase().equals(onlinePlayerNameLower) }).headOption match {
|
||||
LivePlayerList
|
||||
.WorldPopulation({ case (_, a) => a.name.toLowerCase().equals(onlinePlayerNameLower) })
|
||||
.headOption match {
|
||||
case Some(onlinePlayer) => onlineIfNotIgnored(onlinePlayer, observerName)
|
||||
case _ => false
|
||||
case _ => false
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -556,9 +563,9 @@ object AvatarActor {
|
|||
*/
|
||||
def onlineIfNotIgnoredEitherWay(observer: Avatar, onlinePlayerName: String): Boolean = {
|
||||
LivePlayerList.WorldPopulation({ case (_, a) => a.name.equals(onlinePlayerName) }) match {
|
||||
case Nil => false //weird case, but ...
|
||||
case Nil => false //weird case, but ...
|
||||
case onlinePlayer :: Nil => onlineIfNotIgnoredEitherWay(onlinePlayer, observer)
|
||||
case _ => throw new Exception("only trying to find two players, but too many matching search results!")
|
||||
case _ => throw new Exception("only trying to find two players, but too many matching search results!")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -598,24 +605,25 @@ object AvatarActor {
|
|||
import ctx._
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
val out: Promise[persistence.Savedplayer] = Promise()
|
||||
val queryResult = ctx.run(query[persistence.Savedplayer].filter { _.avatarId == lift(avatarId) })
|
||||
val queryResult = ctx.run(query[persistence.Savedplayer].filter { _.avatarId == lift(avatarId) })
|
||||
queryResult.onComplete {
|
||||
case Success(data) if data.nonEmpty =>
|
||||
out.completeWith(Future(data.head))
|
||||
case _ =>
|
||||
ctx.run(query[persistence.Savedplayer]
|
||||
.insert(
|
||||
_.avatarId -> lift(avatarId),
|
||||
_.px -> lift(0),
|
||||
_.py -> lift(0),
|
||||
_.pz -> lift(0),
|
||||
_.orientation -> lift(0),
|
||||
_.zoneNum -> lift(0),
|
||||
_.health -> lift(0),
|
||||
_.armor -> lift(0),
|
||||
_.exosuitNum -> lift(0),
|
||||
_.loadout -> lift("")
|
||||
)
|
||||
ctx.run(
|
||||
query[persistence.Savedplayer]
|
||||
.insert(
|
||||
_.avatarId -> lift(avatarId),
|
||||
_.px -> lift(0),
|
||||
_.py -> lift(0),
|
||||
_.pz -> lift(0),
|
||||
_.orientation -> lift(0),
|
||||
_.zoneNum -> lift(0),
|
||||
_.health -> lift(0),
|
||||
_.armor -> lift(0),
|
||||
_.exosuitNum -> lift(0),
|
||||
_.loadout -> lift("")
|
||||
)
|
||||
)
|
||||
out.completeWith(Future(persistence.Savedplayer(avatarId, 0, 0, 0, 0, 0, 0, 0, 0, "")))
|
||||
}
|
||||
|
|
@ -684,24 +692,25 @@ object AvatarActor {
|
|||
import ctx._
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
val out: Promise[Int] = Promise()
|
||||
val avatarId = player.avatar.id
|
||||
val position = player.Position
|
||||
val queryResult = ctx.run(query[persistence.Savedplayer].filter { _.avatarId == lift(avatarId) })
|
||||
val avatarId = player.avatar.id
|
||||
val position = player.Position
|
||||
val queryResult = ctx.run(query[persistence.Savedplayer].filter { _.avatarId == lift(avatarId) })
|
||||
queryResult.onComplete {
|
||||
case Success(results) if results.nonEmpty =>
|
||||
ctx.run(query[persistence.Savedplayer]
|
||||
.filter { _.avatarId == lift(avatarId) }
|
||||
.update(
|
||||
_.px -> lift((position.x * 1000).toInt),
|
||||
_.py -> lift((position.y * 1000).toInt),
|
||||
_.pz -> lift((position.z * 1000).toInt),
|
||||
_.orientation -> lift((player.Orientation.z * 1000).toInt),
|
||||
_.zoneNum -> lift(player.Zone.Number),
|
||||
_.health -> lift(health),
|
||||
_.armor -> lift(player.Armor),
|
||||
_.exosuitNum -> lift(player.ExoSuit.id),
|
||||
_.loadout -> lift(buildClobFromPlayerLoadout(player))
|
||||
)
|
||||
ctx.run(
|
||||
query[persistence.Savedplayer]
|
||||
.filter { _.avatarId == lift(avatarId) }
|
||||
.update(
|
||||
_.px -> lift((position.x * 1000).toInt),
|
||||
_.py -> lift((position.y * 1000).toInt),
|
||||
_.pz -> lift((position.z * 1000).toInt),
|
||||
_.orientation -> lift((player.Orientation.z * 1000).toInt),
|
||||
_.zoneNum -> lift(player.Zone.Number),
|
||||
_.health -> lift(health),
|
||||
_.armor -> lift(player.Armor),
|
||||
_.exosuitNum -> lift(player.ExoSuit.id),
|
||||
_.loadout -> lift(buildClobFromPlayerLoadout(player))
|
||||
)
|
||||
)
|
||||
out.completeWith(Future(1))
|
||||
case _ =>
|
||||
|
|
@ -722,20 +731,21 @@ object AvatarActor {
|
|||
import ctx._
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
val out: Promise[Int] = Promise()
|
||||
val avatarId = player.avatar.id
|
||||
val position = player.Position
|
||||
val queryResult = ctx.run(query[persistence.Savedplayer].filter { _.avatarId == lift(avatarId) })
|
||||
val avatarId = player.avatar.id
|
||||
val position = player.Position
|
||||
val queryResult = ctx.run(query[persistence.Savedplayer].filter { _.avatarId == lift(avatarId) })
|
||||
queryResult.onComplete {
|
||||
case Success(results) if results.nonEmpty =>
|
||||
ctx.run(query[persistence.Savedplayer]
|
||||
.filter { _.avatarId == lift(avatarId) }
|
||||
.update(
|
||||
_.px -> lift((position.x * 1000).toInt),
|
||||
_.py -> lift((position.y * 1000).toInt),
|
||||
_.pz -> lift((position.z * 1000).toInt),
|
||||
_.orientation -> lift((player.Orientation.z * 1000).toInt),
|
||||
_.zoneNum -> lift(player.Zone.Number)
|
||||
)
|
||||
ctx.run(
|
||||
query[persistence.Savedplayer]
|
||||
.filter { _.avatarId == lift(avatarId) }
|
||||
.update(
|
||||
_.px -> lift((position.x * 1000).toInt),
|
||||
_.py -> lift((position.y * 1000).toInt),
|
||||
_.pz -> lift((position.z * 1000).toInt),
|
||||
_.orientation -> lift((player.Orientation.z * 1000).toInt),
|
||||
_.zoneNum -> lift(player.Zone.Number)
|
||||
)
|
||||
)
|
||||
out.completeWith(Future(1))
|
||||
case _ =>
|
||||
|
|
@ -757,19 +767,20 @@ object AvatarActor {
|
|||
import ctx._
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
val out: Promise[persistence.Savedavatar] = Promise()
|
||||
val queryResult = ctx.run(query[persistence.Savedavatar].filter { _.avatarId == lift(avatarId) })
|
||||
val queryResult = ctx.run(query[persistence.Savedavatar].filter { _.avatarId == lift(avatarId) })
|
||||
queryResult.onComplete {
|
||||
case Success(data) if data.nonEmpty =>
|
||||
out.completeWith(Future(data.head))
|
||||
case _ =>
|
||||
val now = LocalDateTime.now()
|
||||
ctx.run(query[persistence.Savedavatar]
|
||||
.insert(
|
||||
_.avatarId -> lift(avatarId),
|
||||
_.forgetCooldown -> lift(now),
|
||||
_.purchaseCooldowns -> lift(""),
|
||||
_.useCooldowns -> lift("")
|
||||
)
|
||||
ctx.run(
|
||||
query[persistence.Savedavatar]
|
||||
.insert(
|
||||
_.avatarId -> lift(avatarId),
|
||||
_.forgetCooldown -> lift(now),
|
||||
_.purchaseCooldowns -> lift(""),
|
||||
_.useCooldowns -> lift("")
|
||||
)
|
||||
)
|
||||
out.completeWith(Future(persistence.Savedavatar(avatarId, now, "", "")))
|
||||
}
|
||||
|
|
@ -788,16 +799,17 @@ object AvatarActor {
|
|||
import ctx._
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
val out: Promise[Int] = Promise()
|
||||
val avatarId = avatar.id
|
||||
val queryResult = ctx.run(query[persistence.Savedavatar].filter { _.avatarId == lift(avatarId) })
|
||||
val avatarId = avatar.id
|
||||
val queryResult = ctx.run(query[persistence.Savedavatar].filter { _.avatarId == lift(avatarId) })
|
||||
queryResult.onComplete {
|
||||
case Success(results) if results.nonEmpty =>
|
||||
ctx.run(query[persistence.Savedavatar]
|
||||
.filter { _.avatarId == lift(avatarId) }
|
||||
.update(
|
||||
_.purchaseCooldowns -> lift(buildClobfromCooldowns(avatar.cooldowns.purchase)),
|
||||
_.useCooldowns -> lift(buildClobfromCooldowns(avatar.cooldowns.use))
|
||||
)
|
||||
ctx.run(
|
||||
query[persistence.Savedavatar]
|
||||
.filter { _.avatarId == lift(avatarId) }
|
||||
.update(
|
||||
_.purchaseCooldowns -> lift(buildClobfromCooldowns(avatar.cooldowns.purchase)),
|
||||
_.useCooldowns -> lift(buildClobfromCooldowns(avatar.cooldowns.use))
|
||||
)
|
||||
)
|
||||
out.completeWith(Future(1))
|
||||
case _ =>
|
||||
|
|
@ -959,12 +971,13 @@ class AvatarActor(
|
|||
deleted.headOption match {
|
||||
case Some(a) if !a.deleted =>
|
||||
val flagDeletion = for {
|
||||
_ <- ctx.run(query[persistence.Avatar]
|
||||
.filter(_.id == lift(id))
|
||||
.update(
|
||||
_.deleted -> lift(true),
|
||||
_.lastModified -> lift(LocalDateTime.now())
|
||||
)
|
||||
_ <- ctx.run(
|
||||
query[persistence.Avatar]
|
||||
.filter(_.id == lift(id))
|
||||
.update(
|
||||
_.deleted -> lift(true),
|
||||
_.lastModified -> lift(LocalDateTime.now())
|
||||
)
|
||||
)
|
||||
} yield ()
|
||||
flagDeletion.onComplete {
|
||||
|
|
@ -1008,11 +1021,12 @@ class AvatarActor(
|
|||
case LoginAvatar(replyTo) =>
|
||||
import ctx._
|
||||
val avatarId = avatar.id
|
||||
ctx.run(
|
||||
query[persistence.Avatar]
|
||||
.filter(_.id == lift(avatarId))
|
||||
.map { c => (c.created, c.lastLogin) }
|
||||
)
|
||||
ctx
|
||||
.run(
|
||||
query[persistence.Avatar]
|
||||
.filter(_.id == lift(avatarId))
|
||||
.map { c => (c.created, c.lastLogin) }
|
||||
)
|
||||
.onComplete {
|
||||
case Success(value) if value.nonEmpty =>
|
||||
val (created, lastLogin) = value.head
|
||||
|
|
@ -1031,12 +1045,12 @@ class AvatarActor(
|
|||
persistence.Certification(Certification.ATV.value, avatarId),
|
||||
persistence.Certification(Certification.Harasser.value, avatarId)
|
||||
)
|
||||
).foreach(c => query[persistence.Certification].insert(c))
|
||||
).foreach(c => query[persistence.Certification].insertValue(c))
|
||||
)
|
||||
_ <- ctx.run(
|
||||
liftQuery(
|
||||
List(persistence.Shortcut(avatarId, 0, 0, "medkit"))
|
||||
).foreach(c => query[persistence.Shortcut].insert(c))
|
||||
).foreach(c => query[persistence.Shortcut].insertValue(c))
|
||||
)
|
||||
} yield true
|
||||
inits.onComplete {
|
||||
|
|
@ -1109,13 +1123,14 @@ class AvatarActor(
|
|||
val replace = certification.replaces.intersect(avatar.certifications)
|
||||
Future
|
||||
.sequence(replace.map(cert => {
|
||||
ctx.run(
|
||||
query[persistence.Certification]
|
||||
.filter(_.avatarId == lift(avatar.id))
|
||||
.filter(_.id == lift(cert.value))
|
||||
.delete
|
||||
)
|
||||
.map(_ => cert)
|
||||
ctx
|
||||
.run(
|
||||
query[persistence.Certification]
|
||||
.filter(_.avatarId == lift(avatar.id))
|
||||
.filter(_.id == lift(cert.value))
|
||||
.delete
|
||||
)
|
||||
.map(_ => cert)
|
||||
}))
|
||||
.onComplete {
|
||||
case Failure(exception) =>
|
||||
|
|
@ -1129,10 +1144,11 @@ class AvatarActor(
|
|||
PlanetsideAttributeMessage(session.get.player.GUID, 25, cert.value)
|
||||
)
|
||||
}
|
||||
ctx.run(
|
||||
query[persistence.Certification]
|
||||
.insert(_.id -> lift(certification.value), _.avatarId -> lift(avatar.id))
|
||||
)
|
||||
ctx
|
||||
.run(
|
||||
query[persistence.Certification]
|
||||
.insert(_.id -> lift(certification.value), _.avatarId -> lift(avatar.id))
|
||||
)
|
||||
.onComplete {
|
||||
case Failure(exception) =>
|
||||
log.error(exception)("db failure")
|
||||
|
|
@ -1180,13 +1196,14 @@ class AvatarActor(
|
|||
avatar.certifications
|
||||
.intersect(requiredByCert)
|
||||
.map(cert => {
|
||||
ctx.run(
|
||||
query[persistence.Certification]
|
||||
.filter(_.avatarId == lift(avatar.id))
|
||||
.filter(_.id == lift(cert.value))
|
||||
.delete
|
||||
)
|
||||
.map(_ => cert)
|
||||
ctx
|
||||
.run(
|
||||
query[persistence.Certification]
|
||||
.filter(_.avatarId == lift(avatar.id))
|
||||
.filter(_.id == lift(cert.value))
|
||||
.delete
|
||||
)
|
||||
.map(_ => cert)
|
||||
})
|
||||
)
|
||||
.onComplete {
|
||||
|
|
@ -1329,25 +1346,26 @@ class AvatarActor(
|
|||
index match {
|
||||
case Some(_index) =>
|
||||
import ctx._
|
||||
ctx.run(
|
||||
query[persistence.Implant]
|
||||
.filter(_.name == lift(definition.Name))
|
||||
.filter(_.avatarId == lift(avatar.id))
|
||||
.delete
|
||||
)
|
||||
.onComplete {
|
||||
case Success(_) =>
|
||||
replaceAvatar(avatar.copy(implants = avatar.implants.updated(_index, None)))
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
AvatarImplantMessage(session.get.player.GUID, ImplantAction.Remove, _index, 0)
|
||||
)
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
ItemTransactionResultMessage(terminalGuid, TransactionType.Sell, success = true)
|
||||
)
|
||||
context.self ! ResetImplants()
|
||||
sessionActor ! SessionActor.CharSaved
|
||||
case Failure(exception) => log.error(exception)("db failure")
|
||||
}
|
||||
ctx
|
||||
.run(
|
||||
query[persistence.Implant]
|
||||
.filter(_.name == lift(definition.Name))
|
||||
.filter(_.avatarId == lift(avatar.id))
|
||||
.delete
|
||||
)
|
||||
.onComplete {
|
||||
case Success(_) =>
|
||||
replaceAvatar(avatar.copy(implants = avatar.implants.updated(_index, None)))
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
AvatarImplantMessage(session.get.player.GUID, ImplantAction.Remove, _index, 0)
|
||||
)
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
ItemTransactionResultMessage(terminalGuid, TransactionType.Sell, success = true)
|
||||
)
|
||||
context.self ! ResetImplants()
|
||||
sessionActor ! SessionActor.CharSaved
|
||||
case Failure(exception) => log.error(exception)("db failure")
|
||||
}
|
||||
|
||||
case None =>
|
||||
log.warn("attempted to sell implant but could not find slot")
|
||||
|
|
@ -1462,23 +1480,25 @@ class AvatarActor(
|
|||
|
||||
case UpdatePurchaseTime(definition, time) =>
|
||||
var newTimes = avatar.cooldowns.purchase
|
||||
AvatarActor.resolveSharedPurchaseTimeNames(AvatarActor.resolvePurchaseTimeName(avatar.faction, definition)).foreach {
|
||||
case (item, name) =>
|
||||
Avatar.purchaseCooldowns.get(item) match {
|
||||
case Some(cooldown) =>
|
||||
//only send for items with cooldowns
|
||||
newTimes = newTimes.updated(name, time)
|
||||
updatePurchaseTimer(
|
||||
name,
|
||||
cooldown.toSeconds,
|
||||
item match {
|
||||
case _: KitDefinition => false
|
||||
case _ => true
|
||||
}
|
||||
)
|
||||
case _ => ;
|
||||
}
|
||||
}
|
||||
AvatarActor
|
||||
.resolveSharedPurchaseTimeNames(AvatarActor.resolvePurchaseTimeName(avatar.faction, definition))
|
||||
.foreach {
|
||||
case (item, name) =>
|
||||
Avatar.purchaseCooldowns.get(item) match {
|
||||
case Some(cooldown) =>
|
||||
//only send for items with cooldowns
|
||||
newTimes = newTimes.updated(name, time)
|
||||
updatePurchaseTimer(
|
||||
name,
|
||||
cooldown.toSeconds,
|
||||
item match {
|
||||
case _: KitDefinition => false
|
||||
case _ => true
|
||||
}
|
||||
)
|
||||
case _ => ;
|
||||
}
|
||||
}
|
||||
avatarCopy(avatar.copy(cooldowns = avatar.cooldowns.copy(purchase = newTimes)))
|
||||
Behaviors.same
|
||||
|
||||
|
|
@ -1675,14 +1695,16 @@ class AvatarActor(
|
|||
Behaviors.same
|
||||
|
||||
case SetRibbon(ribbon, bar) =>
|
||||
val decor = avatar.decoration
|
||||
val decor = avatar.decoration
|
||||
val previousRibbonBars = decor.ribbonBars
|
||||
val useRibbonBars = Seq(previousRibbonBars.upper, previousRibbonBars.middle, previousRibbonBars.lower)
|
||||
.indexWhere { _ == ribbon } match {
|
||||
case -1 => previousRibbonBars
|
||||
case n => AvatarActor.changeRibbons(previousRibbonBars, MeritCommendation.None, RibbonBarSlot(n))
|
||||
}
|
||||
replaceAvatar(avatar.copy(decoration = decor.copy(ribbonBars = AvatarActor.changeRibbons(useRibbonBars, ribbon, bar))))
|
||||
replaceAvatar(
|
||||
avatar.copy(decoration = decor.copy(ribbonBars = AvatarActor.changeRibbons(useRibbonBars, ribbon, bar)))
|
||||
)
|
||||
val player = session.get.player
|
||||
val zone = player.Zone
|
||||
zone.AvatarEvents ! AvatarServiceMessage(
|
||||
|
|
@ -1706,9 +1728,11 @@ class AvatarActor(
|
|||
case _ => false
|
||||
})
|
||||
if (isDifferentShortcut) {
|
||||
if (!isMacroShortcut && avatar.shortcuts.flatten.exists {
|
||||
a => AvatarShortcut.equals(shortcut, a)
|
||||
}) {
|
||||
if (
|
||||
!isMacroShortcut && avatar.shortcuts.flatten.exists { a =>
|
||||
AvatarShortcut.equals(shortcut, a)
|
||||
}
|
||||
) {
|
||||
//duplicate implant or medkit found
|
||||
if (shortcut.isInstanceOf[Shortcut.Implant]) {
|
||||
//duplicate implant
|
||||
|
|
@ -1716,11 +1740,17 @@ class AvatarActor(
|
|||
case Some(existingShortcut: AvatarShortcut) =>
|
||||
//redraw redundant shortcut slot with existing shortcut
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
CreateShortcutMessage(session.get.player.GUID, slot + 1, Some(AvatarShortcut.convert(existingShortcut)))
|
||||
CreateShortcutMessage(
|
||||
session.get.player.GUID,
|
||||
slot + 1,
|
||||
Some(AvatarShortcut.convert(existingShortcut))
|
||||
)
|
||||
)
|
||||
case _ =>
|
||||
//blank shortcut slot
|
||||
sessionActor ! SessionActor.SendResponse(CreateShortcutMessage(session.get.player.GUID, slot + 1, None))
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
CreateShortcutMessage(session.get.player.GUID, slot + 1, None)
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
@ -1743,7 +1773,7 @@ class AvatarActor(
|
|||
.filter(_.slot == lift(slot))
|
||||
.update(
|
||||
_.purpose -> lift(shortcut.code),
|
||||
_.tile -> lift(shortcut.tile),
|
||||
_.tile -> lift(shortcut.tile),
|
||||
_.effect1 -> Option(lift(optEffect1)),
|
||||
_.effect2 -> Option(lift(optEffect2))
|
||||
)
|
||||
|
|
@ -1752,11 +1782,11 @@ class AvatarActor(
|
|||
ctx.run(
|
||||
query[persistence.Shortcut].insert(
|
||||
_.avatarId -> lift(avatar.id.toLong),
|
||||
_.slot -> lift(slot),
|
||||
_.purpose -> lift(shortcut.code),
|
||||
_.tile -> lift(shortcut.tile),
|
||||
_.effect1 -> Option(lift(optEffect1)),
|
||||
_.effect2 -> Option(lift(optEffect2))
|
||||
_.slot -> lift(slot),
|
||||
_.purpose -> lift(shortcut.code),
|
||||
_.tile -> lift(shortcut.tile),
|
||||
_.effect1 -> Option(lift(optEffect1)),
|
||||
_.effect2 -> Option(lift(optEffect2))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
@ -1771,10 +1801,11 @@ class AvatarActor(
|
|||
avatar.shortcuts.lift(slot).flatten match {
|
||||
case None => ;
|
||||
case Some(_) =>
|
||||
ctx.run(query[persistence.Shortcut]
|
||||
.filter(_.avatarId == lift(avatar.id.toLong))
|
||||
.filter(_.slot == lift(slot))
|
||||
.delete
|
||||
ctx.run(
|
||||
query[persistence.Shortcut]
|
||||
.filter(_.avatarId == lift(avatar.id.toLong))
|
||||
.filter(_.slot == lift(slot))
|
||||
.delete
|
||||
)
|
||||
avatar.shortcuts.update(slot, None)
|
||||
}
|
||||
|
|
@ -1803,12 +1834,16 @@ class AvatarActor(
|
|||
|
||||
val result = for {
|
||||
//log this login
|
||||
_ <- ctx.run(query[persistence.Avatar].filter(_.id == lift(avatarId))
|
||||
.update(_.lastLogin -> lift(LocalDateTime.now()))
|
||||
_ <- ctx.run(
|
||||
query[persistence.Avatar]
|
||||
.filter(_.id == lift(avatarId))
|
||||
.update(_.lastLogin -> lift(LocalDateTime.now()))
|
||||
)
|
||||
//log this choice of faction (no empire switching)
|
||||
_ <- ctx.run(query[persistence.Account].filter(_.id == lift(accountId))
|
||||
.update(_.lastFactionId -> lift(avatar.faction.id))
|
||||
_ <- ctx.run(
|
||||
query[persistence.Account]
|
||||
.filter(_.id == lift(accountId))
|
||||
.update(_.lastFactionId -> lift(avatar.faction.id))
|
||||
)
|
||||
//retrieve avatar data
|
||||
loadouts <- initializeAllLoadouts()
|
||||
|
|
@ -1887,11 +1922,12 @@ class AvatarActor(
|
|||
val p = Promise[Unit]()
|
||||
|
||||
import ctx._
|
||||
ctx.run(
|
||||
query[persistence.Avatar]
|
||||
.filter(_.id == lift(avatar.id))
|
||||
.update(_.cosmetics -> lift(Some(Cosmetic.valuesToObjectCreateValue(cosmetics)): Option[Int]))
|
||||
)
|
||||
ctx
|
||||
.run(
|
||||
query[persistence.Avatar]
|
||||
.filter(_.id == lift(avatar.id))
|
||||
.update(_.cosmetics -> lift(Some(Cosmetic.valuesToObjectCreateValue(cosmetics)): Option[Int]))
|
||||
)
|
||||
.onComplete {
|
||||
case Success(_) =>
|
||||
val zone = session.get.zone
|
||||
|
|
@ -2177,6 +2213,7 @@ class AvatarActor(
|
|||
secondsSinceLastLogin
|
||||
)
|
||||
)
|
||||
|
||||
/** After the user has selected a character to load from the "character select screen,"
|
||||
* the temporary global unique identifiers used for that screen are stripped from the underlying `Player` object that was selected.
|
||||
* Characters that were not selected may be destroyed along with their temporary GUIDs.
|
||||
|
|
@ -2471,42 +2508,45 @@ class AvatarActor(
|
|||
val locker = Avatar.makeLocker()
|
||||
saveLockerFunc = storeLocker
|
||||
val out = Promise[LockerContainer]()
|
||||
ctx.run(query[persistence.Locker].filter(_.avatarId == lift(charId)))
|
||||
ctx
|
||||
.run(query[persistence.Locker].filter(_.avatarId == lift(charId)))
|
||||
.onComplete {
|
||||
case Success(entry) if entry.nonEmpty =>
|
||||
AvatarActor.buildContainedEquipmentFromClob(locker, entry.head.items, log, restoreAmmo = true)
|
||||
out.completeWith(Future(locker))
|
||||
case Success(_) =>
|
||||
//no locker, or maybe default empty locker?
|
||||
ctx.run(query[persistence.Locker].insert(_.avatarId -> lift(avatar.id), _.items -> lift("")))
|
||||
.onComplete {
|
||||
_ => out.completeWith(Future(locker))
|
||||
}
|
||||
case Failure(e) =>
|
||||
saveLockerFunc = doNotStoreLocker
|
||||
log.error(e)("db failure")
|
||||
out.tryFailure(e)
|
||||
}
|
||||
case Success(entry) if entry.nonEmpty =>
|
||||
AvatarActor.buildContainedEquipmentFromClob(locker, entry.head.items, log, restoreAmmo = true)
|
||||
out.completeWith(Future(locker))
|
||||
case Success(_) =>
|
||||
//no locker, or maybe default empty locker?
|
||||
ctx
|
||||
.run(query[persistence.Locker].insert(_.avatarId -> lift(avatar.id), _.items -> lift("")))
|
||||
.onComplete { _ =>
|
||||
out.completeWith(Future(locker))
|
||||
}
|
||||
case Failure(e) =>
|
||||
saveLockerFunc = doNotStoreLocker
|
||||
log.error(e)("db failure")
|
||||
out.tryFailure(e)
|
||||
}
|
||||
out.future
|
||||
}
|
||||
|
||||
|
||||
|
||||
def loadFriendList(avatarId: Long): Future[List[AvatarFriend]] = {
|
||||
import ctx._
|
||||
val out: Promise[List[AvatarFriend]] = Promise()
|
||||
|
||||
val queryResult = ctx.run(
|
||||
query[persistence.Friend].filter { _.avatarId == lift(avatarId) }
|
||||
query[persistence.Friend]
|
||||
.filter { _.avatarId == lift(avatarId) }
|
||||
.join(query[persistence.Avatar])
|
||||
.on { case (friend, avatar) => friend.charId == avatar.id }
|
||||
.map { case (_, avatar) => (avatar.id, avatar.name, avatar.factionId) }
|
||||
)
|
||||
queryResult.onComplete {
|
||||
case Success(list) =>
|
||||
out.completeWith(Future(
|
||||
list.map { case (id, name, faction) => AvatarFriend(id, name, PlanetSideEmpire(faction)) }.toList
|
||||
))
|
||||
out.completeWith(
|
||||
Future(
|
||||
list.map { case (id, name, faction) => AvatarFriend(id, name, PlanetSideEmpire(faction)) }.toList
|
||||
)
|
||||
)
|
||||
case _ =>
|
||||
out.completeWith(Future(List.empty[AvatarFriend]))
|
||||
}
|
||||
|
|
@ -2518,16 +2558,19 @@ class AvatarActor(
|
|||
val out: Promise[List[AvatarIgnored]] = Promise()
|
||||
|
||||
val queryResult = ctx.run(
|
||||
query[persistence.Ignored].filter { _.avatarId == lift(avatarId) }
|
||||
query[persistence.Ignored]
|
||||
.filter { _.avatarId == lift(avatarId) }
|
||||
.join(query[persistence.Avatar])
|
||||
.on { case (friend, avatar) => friend.charId == avatar.id }
|
||||
.map { case (_, avatar) => (avatar.id, avatar.name) }
|
||||
)
|
||||
queryResult.onComplete {
|
||||
case Success(list) =>
|
||||
out.completeWith(Future(
|
||||
list.map { case (id, name) => AvatarIgnored(id, name) }.toList
|
||||
))
|
||||
out.completeWith(
|
||||
Future(
|
||||
list.map { case (id, name) => AvatarIgnored(id, name) }.toList
|
||||
)
|
||||
)
|
||||
case _ =>
|
||||
out.completeWith(Future(List.empty[AvatarIgnored]))
|
||||
}
|
||||
|
|
@ -2539,14 +2582,16 @@ class AvatarActor(
|
|||
val out: Promise[Array[Option[AvatarShortcut]]] = Promise()
|
||||
|
||||
val queryResult = ctx.run(
|
||||
query[persistence.Shortcut].filter { _.avatarId == lift(avatarId) }
|
||||
query[persistence.Shortcut]
|
||||
.filter { _.avatarId == lift(avatarId) }
|
||||
.map { shortcut => (shortcut.slot, shortcut.purpose, shortcut.tile, shortcut.effect1, shortcut.effect2) }
|
||||
)
|
||||
val output = Array.fill[Option[AvatarShortcut]](64)(None)
|
||||
queryResult.onComplete {
|
||||
case Success(list) =>
|
||||
list.foreach { case (slot, purpose, tile, effect1, effect2) =>
|
||||
output.update(slot, Some(AvatarShortcut(purpose, tile, effect1.getOrElse(""), effect2.getOrElse(""))))
|
||||
list.foreach {
|
||||
case (slot, purpose, tile, effect1, effect2) =>
|
||||
output.update(slot, Some(AvatarShortcut(purpose, tile, effect1.getOrElse(""), effect2.getOrElse(""))))
|
||||
}
|
||||
out.completeWith(Future(output))
|
||||
case Failure(e) =>
|
||||
|
|
@ -2597,7 +2642,7 @@ class AvatarActor(
|
|||
cooldown.toSeconds - secondsSincePurchase,
|
||||
obj match {
|
||||
case _: KitDefinition => false
|
||||
case _ => true
|
||||
case _ => true
|
||||
}
|
||||
)
|
||||
|
||||
|
|
@ -2648,7 +2693,7 @@ class AvatarActor(
|
|||
case MemberAction.RemoveFriend => getAvatarForFunc(name, formatForOtherFunc(memberActionRemoveFriend))
|
||||
case MemberAction.AddIgnoredPlayer => getAvatarForFunc(name, memberActionAddIgnored)
|
||||
case MemberAction.RemoveIgnoredPlayer => getAvatarForFunc(name, formatForOtherFunc(memberActionRemoveIgnored))
|
||||
case _ => ;
|
||||
case _ => ;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2658,15 +2703,17 @@ class AvatarActor(
|
|||
* @return a list of `Friends` suitable for putting into a packet
|
||||
*/
|
||||
def transformFriendsList(): List[GameFriend] = {
|
||||
avatar.people.friend.map { f => GameFriend(f.name, f.online)}
|
||||
avatar.people.friend.map { f => GameFriend(f.name, f.online) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform the ignored players list in a list of packet entities.
|
||||
* @return a list of `Friends` suitable for putting into a packet
|
||||
*/
|
||||
def transformIgnoredList(): List[GameFriend] = {
|
||||
avatar.people.ignored.map { f => GameFriend(f.name, f.online)}
|
||||
avatar.people.ignored.map { f => GameFriend(f.name, f.online) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Reload the list of friend players or ignored players for the client.
|
||||
* This does not update any player's online status, but merely reloads current states.
|
||||
|
|
@ -2674,7 +2721,7 @@ class AvatarActor(
|
|||
* (either `InitializeFriendList` or `InitializeIgnoreList`, hopefully)
|
||||
* @param listFunc transformation function that produces data suitable for a game paket
|
||||
*/
|
||||
def memberActionListManagement(action: MemberAction.Value, listFunc: ()=>List[GameFriend]): Unit = {
|
||||
def memberActionListManagement(action: MemberAction.Value, listFunc: () => List[GameFriend]): Unit = {
|
||||
FriendsResponse.packetSequence(action, listFunc()).foreach { msg =>
|
||||
sessionActor ! SessionActor.SendResponse(msg)
|
||||
}
|
||||
|
|
@ -2693,16 +2740,20 @@ class AvatarActor(
|
|||
case Some(_) => ;
|
||||
case None =>
|
||||
import ctx._
|
||||
ctx.run(query[persistence.Friend]
|
||||
.insert(
|
||||
_.avatarId -> lift(avatar.id.toLong),
|
||||
_.charId -> lift(charId)
|
||||
)
|
||||
ctx.run(
|
||||
query[persistence.Friend]
|
||||
.insert(
|
||||
_.avatarId -> lift(avatar.id.toLong),
|
||||
_.charId -> lift(charId)
|
||||
)
|
||||
)
|
||||
val isOnline = onlineIfNotIgnoredEitherWay(avatar, name)
|
||||
replaceAvatar(avatar.copy(
|
||||
people = people.copy(friend = people.friend :+ AvatarFriend(charId, name, PlanetSideEmpire(faction), isOnline))
|
||||
))
|
||||
replaceAvatar(
|
||||
avatar.copy(
|
||||
people =
|
||||
people.copy(friend = people.friend :+ AvatarFriend(charId, name, PlanetSideEmpire(faction), isOnline))
|
||||
)
|
||||
)
|
||||
sessionActor ! SessionActor.SendResponse(FriendsResponse(MemberAction.AddFriend, GameFriend(name, isOnline)))
|
||||
sessionActor ! SessionActor.CharSaved
|
||||
}
|
||||
|
|
@ -2724,17 +2775,17 @@ class AvatarActor(
|
|||
)
|
||||
case None => ;
|
||||
}
|
||||
ctx.run(query[persistence.Friend]
|
||||
.filter(_.avatarId == lift(avatar.id))
|
||||
.filter(_.charId == lift(charId))
|
||||
.delete
|
||||
ctx.run(
|
||||
query[persistence.Friend]
|
||||
.filter(_.avatarId == lift(avatar.id))
|
||||
.filter(_.charId == lift(charId))
|
||||
.delete
|
||||
)
|
||||
sessionActor ! SessionActor.SendResponse(FriendsResponse(MemberAction.RemoveFriend, GameFriend(name)))
|
||||
sessionActor ! SessionActor.CharSaved
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param name unique character name
|
||||
* @return if the avatar is found, that avatar's unique identifier and the avatar's faction affiliation
|
||||
*/
|
||||
|
|
@ -2752,11 +2803,13 @@ class AvatarActor(
|
|||
case None =>
|
||||
(None, false)
|
||||
}
|
||||
replaceAvatar(avatar.copy(
|
||||
people = people.copy(
|
||||
friend = people.friend.filterNot { _.name.equals(name) } :+ otherFriend.copy(online = online)
|
||||
replaceAvatar(
|
||||
avatar.copy(
|
||||
people = people.copy(
|
||||
friend = people.friend.filterNot { _.name.equals(name) } :+ otherFriend.copy(online = online)
|
||||
)
|
||||
)
|
||||
))
|
||||
)
|
||||
sessionActor ! SessionActor.SendResponse(FriendsResponse(MemberAction.UpdateFriend, GameFriend(name, online)))
|
||||
out
|
||||
case None =>
|
||||
|
|
@ -2782,16 +2835,19 @@ class AvatarActor(
|
|||
case Some(_) => ;
|
||||
case None =>
|
||||
import ctx._
|
||||
ctx.run(query[persistence.Ignored]
|
||||
.insert(
|
||||
_.avatarId -> lift(avatar.id.toLong),
|
||||
_.charId -> lift(charId)
|
||||
)
|
||||
ctx.run(
|
||||
query[persistence.Ignored]
|
||||
.insert(
|
||||
_.avatarId -> lift(avatar.id.toLong),
|
||||
_.charId -> lift(charId)
|
||||
)
|
||||
)
|
||||
replaceAvatar(
|
||||
avatar.copy(people = people.copy(ignored = people.ignored :+ AvatarIgnored(charId, name)))
|
||||
)
|
||||
sessionActor ! SessionActor.UpdateIgnoredPlayers(FriendsResponse(MemberAction.AddIgnoredPlayer, GameFriend(name)))
|
||||
sessionActor ! SessionActor.UpdateIgnoredPlayers(
|
||||
FriendsResponse(MemberAction.AddIgnoredPlayer, GameFriend(name))
|
||||
)
|
||||
sessionActor ! SessionActor.CharSaved
|
||||
}
|
||||
}
|
||||
|
|
@ -2814,31 +2870,34 @@ class AvatarActor(
|
|||
)
|
||||
case None => ;
|
||||
}
|
||||
ctx.run(query[persistence.Ignored]
|
||||
.filter(_.avatarId == lift(avatar.id.toLong))
|
||||
.filter(_.charId == lift(charId))
|
||||
.delete
|
||||
ctx.run(
|
||||
query[persistence.Ignored]
|
||||
.filter(_.avatarId == lift(avatar.id.toLong))
|
||||
.filter(_.charId == lift(charId))
|
||||
.delete
|
||||
)
|
||||
sessionActor ! SessionActor.UpdateIgnoredPlayers(
|
||||
FriendsResponse(MemberAction.RemoveIgnoredPlayer, GameFriend(name))
|
||||
)
|
||||
sessionActor ! SessionActor.UpdateIgnoredPlayers(FriendsResponse(MemberAction.RemoveIgnoredPlayer, GameFriend(name)))
|
||||
sessionActor ! SessionActor.CharSaved
|
||||
}
|
||||
|
||||
def setBep(bep: Long, modifier: ExperienceType): Unit = {
|
||||
import ctx._
|
||||
val current = BattleRank.withExperience(avatar.bep).value
|
||||
val next = BattleRank.withExperience(bep).value
|
||||
val current = BattleRank.withExperience(avatar.bep).value
|
||||
val next = BattleRank.withExperience(bep).value
|
||||
lazy val br24 = BattleRank.BR24.value
|
||||
val result = for {
|
||||
r <- ctx.run(query[persistence.Avatar].filter(_.id == lift(avatar.id)).update(_.bep -> lift(bep)))
|
||||
} yield r
|
||||
result.onComplete {
|
||||
case Success(_) =>
|
||||
val sess = session.get
|
||||
val zone = sess.zone
|
||||
val zoneId = zone.id
|
||||
val events = zone.AvatarEvents
|
||||
val player = sess.player
|
||||
val pguid = player.GUID
|
||||
val sess = session.get
|
||||
val zone = sess.zone
|
||||
val zoneId = zone.id
|
||||
val events = zone.AvatarEvents
|
||||
val player = sess.player
|
||||
val pguid = player.GUID
|
||||
val localModifier = modifier
|
||||
sessionActor ! SessionActor.SendResponse(BattleExperienceMessage(pguid, bep, localModifier))
|
||||
events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(pguid, 17, bep))
|
||||
|
|
@ -2854,15 +2913,18 @@ class AvatarActor(
|
|||
val implants = avatar.implants.zipWithIndex.map {
|
||||
case (implant, index) =>
|
||||
if (index >= BattleRank.withExperience(bep).implantSlots && implant.isDefined) {
|
||||
ctx.run(
|
||||
query[persistence.Implant]
|
||||
.filter(_.name == lift(implant.get.definition.Name))
|
||||
.filter(_.avatarId == lift(avatar.id))
|
||||
.delete
|
||||
)
|
||||
ctx
|
||||
.run(
|
||||
query[persistence.Implant]
|
||||
.filter(_.name == lift(implant.get.definition.Name))
|
||||
.filter(_.avatarId == lift(avatar.id))
|
||||
.delete
|
||||
)
|
||||
.onComplete {
|
||||
case Success(_) =>
|
||||
sessionActor ! SessionActor.SendResponse(AvatarImplantMessage(pguid, ImplantAction.Remove, index, 0))
|
||||
sessionActor ! SessionActor.SendResponse(
|
||||
AvatarImplantMessage(pguid, ImplantAction.Remove, index, 0)
|
||||
)
|
||||
case Failure(exception) =>
|
||||
log.error(exception)("db failure")
|
||||
}
|
||||
|
|
@ -2895,22 +2957,22 @@ class AvatarActor(
|
|||
|
||||
def updateKillsDeathsAssists(kdaStat: KDAStat): Unit = {
|
||||
avatar.scorecard.rate(kdaStat)
|
||||
val exp = kdaStat.experienceEarned
|
||||
val exp = kdaStat.experienceEarned
|
||||
val _session = session.get
|
||||
val zone = _session.zone
|
||||
val player = _session.player
|
||||
val zone = _session.zone
|
||||
val player = _session.player
|
||||
kdaStat match {
|
||||
case kill: Kill =>
|
||||
val _ = PlayerSource(player)
|
||||
(kill.info.interaction.cause match {
|
||||
case pr: ProjectileReason => pr.projectile.mounted_in.map { a => zone.GUID(a._1) }
|
||||
case _ => None
|
||||
case _ => None
|
||||
}) match {
|
||||
case Some(Some(_: Vitality)) =>
|
||||
//zone.actor ! ZoneActor.RewardOurSupporters(playerSource, obj.History, kill, exp)
|
||||
//zone.actor ! ZoneActor.RewardOurSupporters(playerSource, obj.History, kill, exp)
|
||||
case _ => ;
|
||||
}
|
||||
//zone.actor ! ZoneActor.RewardOurSupporters(playerSource, player.History, kill, exp)
|
||||
//zone.actor ! ZoneActor.RewardOurSupporters(playerSource, player.History, kill, exp)
|
||||
case _: Death =>
|
||||
player.Zone.AvatarEvents ! AvatarServiceMessage(
|
||||
player.Name,
|
||||
|
|
|
|||
|
|
@ -102,7 +102,10 @@ object SessionActor {
|
|||
tickTime: Long = 250L
|
||||
)
|
||||
|
||||
private[session] final case class AvatarAwardMessageBundle(bundle: Iterable[Iterable[PlanetSideGamePacket]], delay: Long)
|
||||
private[session] final case class AvatarAwardMessageBundle(
|
||||
bundle: Iterable[Iterable[PlanetSideGamePacket]],
|
||||
delay: Long
|
||||
)
|
||||
}
|
||||
|
||||
class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], connectionId: String, sessionId: Long)
|
||||
|
|
@ -110,9 +113,8 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
|||
with MDCContextAware {
|
||||
MDC("connectionId") = connectionId
|
||||
|
||||
private[this] val log = org.log4s.getLogger
|
||||
private[this] val buffer: mutable.ListBuffer[Any] = new mutable.ListBuffer[Any]()
|
||||
private[this] val sessionFuncs = new SessionData(middlewareActor, context)
|
||||
private[this] val sessionFuncs = new SessionData(middlewareActor, context)
|
||||
|
||||
override val supervisorStrategy: SupervisorStrategy = sessionFuncs.sessionSupervisorStrategy
|
||||
|
||||
|
|
@ -195,7 +197,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
|||
sessionFuncs.zoning.spawn.handlePlayerLoaded(tplayer)
|
||||
|
||||
case Zone.Population.PlayerHasLeft(zone, None) =>
|
||||
log.trace(s"PlayerHasLeft: ${sessionFuncs.player.Name} does not have a body on ${zone.id}")
|
||||
log.debug(s"PlayerHasLeft: ${sessionFuncs.player.Name} does not have a body on ${zone.id}")
|
||||
|
||||
case Zone.Population.PlayerHasLeft(zone, Some(tplayer)) =>
|
||||
if (tplayer.isAlive) {
|
||||
|
|
@ -203,16 +205,20 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
|||
}
|
||||
|
||||
case Zone.Population.PlayerCanNotSpawn(zone, tplayer) =>
|
||||
log.warn(s"${tplayer.Name} can not spawn in zone ${zone.id}; why?")
|
||||
log.warning(s"${tplayer.Name} can not spawn in zone ${zone.id}; why?")
|
||||
|
||||
case Zone.Population.PlayerAlreadySpawned(zone, tplayer) =>
|
||||
log.warn(s"${tplayer.Name} is already spawned on zone ${zone.id}; is this a clerical error?")
|
||||
log.warning(s"${tplayer.Name} is already spawned on zone ${zone.id}; is this a clerical error?")
|
||||
|
||||
case Zone.Vehicle.CanNotSpawn(zone, vehicle, reason) =>
|
||||
log.warn(s"${sessionFuncs.player.Name}'s ${vehicle.Definition.Name} can not spawn in ${zone.id} because $reason")
|
||||
log.warning(
|
||||
s"${sessionFuncs.player.Name}'s ${vehicle.Definition.Name} can not spawn in ${zone.id} because $reason"
|
||||
)
|
||||
|
||||
case Zone.Vehicle.CanNotDespawn(zone, vehicle, reason) =>
|
||||
log.warn(s"${sessionFuncs.player.Name}'s ${vehicle.Definition.Name} can not deconstruct in ${zone.id} because $reason")
|
||||
log.warning(
|
||||
s"${sessionFuncs.player.Name}'s ${vehicle.Definition.Name} can not deconstruct in ${zone.id} because $reason"
|
||||
)
|
||||
|
||||
case ICS.ZoneResponse(Some(zone)) =>
|
||||
sessionFuncs.zoning.handleZoneResponse(zone)
|
||||
|
|
@ -349,7 +355,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
|||
log.debug(s"CanNotPutItemInSlot: $msg")
|
||||
|
||||
case default =>
|
||||
log.warn(s"Invalid packet class received: $default from ${sender()}")
|
||||
log.warning(s"Invalid packet class received: $default from ${sender()}")
|
||||
}
|
||||
|
||||
private def handleGamePkt: PlanetSideGamePacket => Unit = {
|
||||
|
|
@ -363,7 +369,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
|||
sessionFuncs.vehicles.handleDismountVehicleCargo(packet)
|
||||
|
||||
case packet: CharacterCreateRequestMessage =>
|
||||
sessionFuncs.handleCharacterCreateRequest(packet)
|
||||
sessionFuncs.handleCharacterCreateRequest(packet)
|
||||
|
||||
case packet: CharacterRequestMessage =>
|
||||
sessionFuncs.handleCharacterRequest(packet)
|
||||
|
|
@ -588,6 +594,6 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
|||
sessionFuncs.handleHitHint(packet)
|
||||
|
||||
case pkt =>
|
||||
log.warn(s"Unhandled GamePacket $pkt")
|
||||
log.warning(s"Unhandled GamePacket $pkt")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -256,5 +256,3 @@ object Tool {
|
|||
def Definition: FireModeDefinition = fdef
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -654,4 +654,3 @@ object CarrierBehavior {
|
|||
msgs
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -40,4 +40,3 @@ trait MountableWeapons
|
|||
|
||||
def Definition: MountableWeaponsDefinition
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -55,4 +55,3 @@ class AmsControl(vehicle: Vehicle)
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -89,20 +89,20 @@ object PacketHelpers {
|
|||
}
|
||||
|
||||
/** Create a Codec for an enumeration type that can correctly represent its value
|
||||
* @param enum the enumeration type to create a codec for
|
||||
* @param e the enumeration type to create a codec for
|
||||
* @param storageCodec the Codec used for actually representing the value
|
||||
* @tparam E The inferred type
|
||||
* @return Generated codec
|
||||
*/
|
||||
def createEnumerationCodec[E <: Enumeration](enum: E, storageCodec: Codec[Int]): Codec[E#Value] = {
|
||||
def createEnumerationCodec[E <: Enumeration](e: E, storageCodec: Codec[Int]): Codec[E#Value] = {
|
||||
type Struct = Int :: HNil
|
||||
val struct: Codec[Struct] = storageCodec.hlist
|
||||
val primitiveLimit = Math.pow(2, storageCodec.sizeBound.exact.get.toDouble)
|
||||
|
||||
// Assure that the enum will always be able to fit in a N-bit int
|
||||
assert(
|
||||
enum.maxId <= primitiveLimit,
|
||||
enum.getClass.getCanonicalName + s": maxId exceeds primitive type (limit of $primitiveLimit, maxId ${enum.maxId})"
|
||||
e.maxId <= primitiveLimit,
|
||||
e.getClass.getCanonicalName + s": maxId exceeds primitive type (limit of $primitiveLimit, maxId ${e.maxId})"
|
||||
)
|
||||
|
||||
def to(pkt: E#Value): Struct = {
|
||||
|
|
@ -113,13 +113,13 @@ object PacketHelpers {
|
|||
struct match {
|
||||
case enumVal :: HNil =>
|
||||
// verify that this int can match the enum
|
||||
val first = enum.values.firstKey.id
|
||||
val last = enum.maxId - 1
|
||||
val first = e.values.firstKey.id
|
||||
val last = e.maxId - 1
|
||||
|
||||
if (enumVal >= first && enumVal <= last)
|
||||
Attempt.successful(enum(enumVal))
|
||||
Attempt.successful(e(enumVal))
|
||||
else
|
||||
Attempt.failure(Err(s"Expected ${enum} with ID between [${first}, ${last}], but got '${enumVal}'"))
|
||||
Attempt.failure(Err(s"Expected ${e} with ID between [${first}, ${last}], but got '${enumVal}'"))
|
||||
}
|
||||
|
||||
struct.narrow[E#Value](from, to)
|
||||
|
|
@ -130,12 +130,12 @@ object PacketHelpers {
|
|||
* NOTE: enumerations in scala can't be represented by more than an Int anyways, so this conversion shouldn't matter.
|
||||
* This is only to overload createEnumerationCodec to work with uint32[L] codecs (which are Long)
|
||||
*/
|
||||
def createLongEnumerationCodec[E <: Enumeration](enum: E, storageCodec: Codec[Long]): Codec[E#Value] = {
|
||||
createEnumerationCodec(enum, storageCodec.xmap[Int](_.toInt, _.toLong))
|
||||
def createLongEnumerationCodec[E <: Enumeration](e: E, storageCodec: Codec[Long]): Codec[E#Value] = {
|
||||
createEnumerationCodec(e, storageCodec.xmap[Int](_.toInt, _.toLong))
|
||||
}
|
||||
|
||||
/** Create a Codec for enumeratum's IntEnum type */
|
||||
def createIntEnumCodec[E <: IntEnumEntry](enum: IntEnum[E], storageCodec: Codec[Int]): Codec[E] = {
|
||||
def createIntEnumCodec[E <: IntEnumEntry](e: IntEnum[E], storageCodec: Codec[Int]): Codec[E] = {
|
||||
type Struct = Int :: HNil
|
||||
val struct: Codec[Struct] = storageCodec.hlist
|
||||
|
||||
|
|
@ -146,36 +146,36 @@ object PacketHelpers {
|
|||
def from(struct: Struct): Attempt[E] =
|
||||
struct match {
|
||||
case enumVal :: HNil =>
|
||||
enum.withValueOpt(enumVal) match {
|
||||
e.withValueOpt(enumVal) match {
|
||||
case Some(v) => Attempt.successful(v)
|
||||
case None =>
|
||||
Attempt.failure(Err(s"Enum value '${enumVal}' not found in values '${enum.values.toString()}'"))
|
||||
Attempt.failure(Err(s"Enum value '${enumVal}' not found in values '${e.values.toString()}'"))
|
||||
}
|
||||
}
|
||||
|
||||
struct.narrow[E](from, to)
|
||||
}
|
||||
|
||||
def createLongIntEnumCodec[E <: IntEnumEntry](enum: IntEnum[E], storageCodec: Codec[Long]): Codec[E] = {
|
||||
createIntEnumCodec(enum, storageCodec.xmap[Int](_.toInt, _.toLong))
|
||||
def createLongIntEnumCodec[E <: IntEnumEntry](e: IntEnum[E], storageCodec: Codec[Long]): Codec[E] = {
|
||||
createIntEnumCodec(e, storageCodec.xmap[Int](_.toInt, _.toLong))
|
||||
}
|
||||
|
||||
/** Create a Codec for enumeratum's Enum type */
|
||||
def createEnumCodec[E <: EnumEntry](enum: Enum[E], storageCodec: Codec[Int]): Codec[E] = {
|
||||
def createEnumCodec[E <: EnumEntry](e: Enum[E], storageCodec: Codec[Int]): Codec[E] = {
|
||||
type Struct = Int :: HNil
|
||||
val struct: Codec[Struct] = storageCodec.hlist
|
||||
|
||||
def to(pkt: E): Struct = {
|
||||
enum.indexOf(pkt) :: HNil
|
||||
e.indexOf(pkt) :: HNil
|
||||
}
|
||||
|
||||
def from(struct: Struct): Attempt[E] =
|
||||
struct match {
|
||||
case enumVal :: HNil =>
|
||||
enum.valuesToIndex.find(_._2 == enumVal) match {
|
||||
e.valuesToIndex.find(_._2 == enumVal) match {
|
||||
case Some((v, _)) => Attempt.successful(v)
|
||||
case None =>
|
||||
Attempt.failure(Err(s"Enum index '${enumVal}' not found in values '${enum.valuesToIndex.toString()}'"))
|
||||
Attempt.failure(Err(s"Enum index '${enumVal}' not found in values '${e.valuesToIndex.toString()}'"))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ object PacketCoding {
|
|||
): Attempt[BitVector] = {
|
||||
val seq = packet match {
|
||||
case _: PlanetSideControlPacket if crypto.isEmpty => BitVector.empty
|
||||
case _: PlanetSideResetSequencePacket => BitVector.empty
|
||||
case _ =>
|
||||
sequence match {
|
||||
case Some(_sequence) =>
|
||||
|
|
@ -93,6 +94,17 @@ object PacketCoding {
|
|||
)
|
||||
case f @ Failure(_) => return f
|
||||
}
|
||||
case packet: PlanetSideResetSequencePacket =>
|
||||
encodePacket(packet) match {
|
||||
case Successful(_payload) =>
|
||||
(
|
||||
PlanetSidePacketFlags.codec
|
||||
.encode(PlanetSidePacketFlags(PacketType.ResetSequence, secured = false))
|
||||
.require,
|
||||
_payload
|
||||
)
|
||||
case f @ Failure(_) => return f
|
||||
}
|
||||
}
|
||||
|
||||
Successful(flags ++ seq ++ payload)
|
||||
|
|
|
|||
|
|
@ -15,4 +15,3 @@ final case class Ignore(data: ByteVector) extends PlanetSideCryptoPacket {
|
|||
object Ignore extends Marshallable[Ignore] {
|
||||
implicit val codec: Codec[Ignore] = ("data" | bytes).as[Ignore]
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,32 +11,32 @@ sealed abstract class GenericAction(val value: Int) extends IntEnumEntry
|
|||
object GenericAction extends IntEnum[GenericAction] {
|
||||
val values: IndexedSeq[GenericAction] = findValues
|
||||
|
||||
final case object ShowMosquitoRadar extends GenericAction(value = 3)
|
||||
final case object HideMosquitoRadar extends GenericAction(value = 4)
|
||||
final case object MissileLock extends GenericAction(value = 7)
|
||||
final case object WaspMissileLock extends GenericAction(value = 8)
|
||||
final case object TRekLock extends GenericAction(value = 9)
|
||||
final case object DropSpecialItem extends GenericAction(value = 11)
|
||||
final case object FacilityCaptureFanfare extends GenericAction(value = 12)
|
||||
final case object NewCharacterBasicTrainingPrompt extends GenericAction(value = 14)
|
||||
final case object MaxAnchorsExtend_RCV extends GenericAction(value = 15)
|
||||
final case object MaxAnchorsRelease_RCV extends GenericAction(value = 16)
|
||||
final case object MaxSpecialEffect_RCV extends GenericAction(value = 20)
|
||||
final case object StopMaxSpecialEffect_RCV extends GenericAction(value = 21)
|
||||
final case object CavernFacilityCapture extends GenericAction(value = 22)
|
||||
final case object CavernFacilityKill extends GenericAction(value = 23)
|
||||
final case object Imprinted extends GenericAction(value = 24)
|
||||
final case object NoLongerImprinted extends GenericAction(value = 25)
|
||||
final case object PurchaseTimersReset extends GenericAction(value = 27)
|
||||
final case object LeaveWarpQueue_RCV extends GenericAction(value = 28)
|
||||
final case object AwayFromKeyboard_RCV extends GenericAction(value = 29)
|
||||
final case object BackInGame_RCV extends GenericAction(value = 30)
|
||||
final case object FirstPersonViewWithEffect extends GenericAction(value = 31)
|
||||
final case object ShowMosquitoRadar extends GenericAction(value = 3)
|
||||
final case object HideMosquitoRadar extends GenericAction(value = 4)
|
||||
final case object MissileLock extends GenericAction(value = 7)
|
||||
final case object WaspMissileLock extends GenericAction(value = 8)
|
||||
final case object TRekLock extends GenericAction(value = 9)
|
||||
final case object DropSpecialItem extends GenericAction(value = 11)
|
||||
final case object FacilityCaptureFanfare extends GenericAction(value = 12)
|
||||
final case object NewCharacterBasicTrainingPrompt extends GenericAction(value = 14)
|
||||
final case object MaxAnchorsExtend_RCV extends GenericAction(value = 15)
|
||||
final case object MaxAnchorsRelease_RCV extends GenericAction(value = 16)
|
||||
final case object MaxSpecialEffect_RCV extends GenericAction(value = 20)
|
||||
final case object StopMaxSpecialEffect_RCV extends GenericAction(value = 21)
|
||||
final case object CavernFacilityCapture extends GenericAction(value = 22)
|
||||
final case object CavernFacilityKill extends GenericAction(value = 23)
|
||||
final case object Imprinted extends GenericAction(value = 24)
|
||||
final case object NoLongerImprinted extends GenericAction(value = 25)
|
||||
final case object PurchaseTimersReset extends GenericAction(value = 27)
|
||||
final case object LeaveWarpQueue_RCV extends GenericAction(value = 28)
|
||||
final case object AwayFromKeyboard_RCV extends GenericAction(value = 29)
|
||||
final case object BackInGame_RCV extends GenericAction(value = 30)
|
||||
final case object FirstPersonViewWithEffect extends GenericAction(value = 31)
|
||||
final case object FirstPersonViewFailToDeconstruct extends GenericAction(value = 32)
|
||||
final case object FailToDeconstruct extends GenericAction(value = 33)
|
||||
final case object LookingForSquad_RCV extends GenericAction(value = 36)
|
||||
final case object NotLookingForSquad_RCV extends GenericAction(value = 37)
|
||||
final case object Unknown45 extends GenericAction(value = 45)
|
||||
final case object FailToDeconstruct extends GenericAction(value = 33)
|
||||
final case object LookingForSquad_RCV extends GenericAction(value = 36)
|
||||
final case object NotLookingForSquad_RCV extends GenericAction(value = 37)
|
||||
final case object Unknown45 extends GenericAction(value = 45)
|
||||
|
||||
final case class Unknown(override val value: Int) extends GenericAction(value)
|
||||
}
|
||||
|
|
@ -55,16 +55,19 @@ object GenericActionMessage extends Marshallable[GenericActionMessage] {
|
|||
def apply(i: Int): GenericActionMessage = {
|
||||
GenericActionMessage(GenericAction.values.find { _.value == i } match {
|
||||
case Some(enum) => enum
|
||||
case None => GenericAction.Unknown(i)
|
||||
case None => GenericAction.Unknown(i)
|
||||
})
|
||||
}
|
||||
|
||||
private val genericActionCodec = uint(bits = 6).xmap[GenericAction]({
|
||||
i => GenericAction.values.find { _.value == i } match {
|
||||
case Some(enum) => enum
|
||||
case None => GenericAction.Unknown(i)
|
||||
}
|
||||
}, enum => enum.value)
|
||||
private val genericActionCodec = uint(bits = 6).xmap[GenericAction](
|
||||
{ i =>
|
||||
GenericAction.values.find { _.value == i } match {
|
||||
case Some(enum) => enum
|
||||
case None => GenericAction.Unknown(i)
|
||||
}
|
||||
},
|
||||
e => e.value
|
||||
)
|
||||
|
||||
implicit val codec: Codec[GenericActionMessage] = ("action" | genericActionCodec).as[GenericActionMessage]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ object TerrainCondition extends Enumeration {
|
|||
type Type = Value
|
||||
val Safe, Unsafe = Value
|
||||
|
||||
implicit val codec = PacketHelpers.createEnumerationCodec(enum = this, uint(bits = 1))
|
||||
implicit val codec = PacketHelpers.createEnumerationCodec(e = this, uint(bits = 1))
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -28,11 +28,11 @@ object TerrainCondition extends Enumeration {
|
|||
* @param pos the vehicle's current position in the game world
|
||||
*/
|
||||
final case class InvalidTerrainMessage(
|
||||
player_guid: PlanetSideGUID,
|
||||
vehicle_guid: PlanetSideGUID,
|
||||
proximity_alert: TerrainCondition.Value,
|
||||
pos: Vector3
|
||||
) extends PlanetSideGamePacket {
|
||||
player_guid: PlanetSideGUID,
|
||||
vehicle_guid: PlanetSideGUID,
|
||||
proximity_alert: TerrainCondition.Value,
|
||||
pos: Vector3
|
||||
) extends PlanetSideGamePacket {
|
||||
type Packet = InvalidTerrainMessage
|
||||
def opcode = GamePacketOpcode.InvalidTerrainMessage
|
||||
def encode = InvalidTerrainMessage.encode(this)
|
||||
|
|
@ -40,8 +40,7 @@ final case class InvalidTerrainMessage(
|
|||
|
||||
object InvalidTerrainMessage extends Marshallable[InvalidTerrainMessage] {
|
||||
|
||||
implicit val codec: Codec[InvalidTerrainMessage] = (
|
||||
("player_guid" | PlanetSideGUID.codec) ::
|
||||
implicit val codec: Codec[InvalidTerrainMessage] = (("player_guid" | PlanetSideGUID.codec) ::
|
||||
("vehicle_guid" | PlanetSideGUID.codec) ::
|
||||
("proximity_alert" | TerrainCondition.codec) ::
|
||||
("pos" | floatL :: floatL :: floatL).narrow[Vector3](
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ object SquadAction {
|
|||
|
||||
val AnyPositions, AvailablePositions, SomeCertifications, AllCertifications = Value
|
||||
|
||||
implicit val codec: Codec[SearchMode.Value] = PacketHelpers.createEnumerationCodec(enum = this, uint(bits = 3))
|
||||
implicit val codec: Codec[SearchMode.Value] = PacketHelpers.createEnumerationCodec(e = this, uint(bits = 3))
|
||||
}
|
||||
|
||||
final case class DisplaySquad() extends SquadAction(code = 0)
|
||||
|
|
@ -280,7 +280,7 @@ object SquadAction {
|
|||
|
||||
val squadListDecoratorCodec = (
|
||||
SquadListDecoration.codec ::
|
||||
ignore(size = 3)
|
||||
ignore(size = 3)
|
||||
).xmap[SquadListDecorator](
|
||||
{
|
||||
case value :: _ :: HNil => SquadListDecorator(value)
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ object MemberEvent extends Enumeration {
|
|||
|
||||
val Add, Remove, Promote, UpdateZone, Outfit = Value
|
||||
|
||||
implicit val codec = PacketHelpers.createEnumerationCodec(enum = this, uint(bits = 3))
|
||||
implicit val codec = PacketHelpers.createEnumerationCodec(e = this, uint(bits = 3))
|
||||
}
|
||||
|
||||
final case class SquadMemberEvent(
|
||||
|
|
@ -58,13 +58,18 @@ object SquadMemberEvent extends Marshallable[SquadMemberEvent] {
|
|||
("unk2" | uint16L) ::
|
||||
("char_id" | uint32L) ::
|
||||
("position" | uint4) ::
|
||||
("player_name" | conditional(action == MemberEvent.Add, PacketHelpers.encodedWideStringAligned(adjustment = 1))) ::
|
||||
("player_name" | conditional(
|
||||
action == MemberEvent.Add,
|
||||
PacketHelpers.encodedWideStringAligned(adjustment = 1)
|
||||
)) ::
|
||||
("zone_number" | conditional(action == MemberEvent.Add || action == MemberEvent.UpdateZone, uint16L)) ::
|
||||
("outfit_id" | conditional(action == MemberEvent.Add || action == MemberEvent.Outfit, uint32L))
|
||||
}).exmap[SquadMemberEvent](
|
||||
{
|
||||
case action :: unk2 :: char_id :: member_position :: player_name :: zone_number :: outfit_id :: HNil =>
|
||||
Attempt.Successful(SquadMemberEvent(action, unk2, char_id, member_position, player_name, zone_number, outfit_id))
|
||||
Attempt.Successful(
|
||||
SquadMemberEvent(action, unk2, char_id, member_position, player_name, zone_number, outfit_id)
|
||||
)
|
||||
},
|
||||
{
|
||||
case SquadMemberEvent(
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ object WaypointEventAction extends Enumeration {
|
|||
val Add, Unknown1, Remove, Unknown3 //unconfirmed
|
||||
= Value
|
||||
|
||||
implicit val codec: Codec[WaypointEventAction.Value] = PacketHelpers.createEnumerationCodec(enum = this, uint2)
|
||||
implicit val codec: Codec[WaypointEventAction.Value] = PacketHelpers.createEnumerationCodec(e = this, uint2)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -332,4 +332,3 @@ object MountableInventory {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,11 +11,11 @@ import scodec.codecs.uint2L
|
|||
* Blame the lack of gender dysphoria on the Terran Republic.
|
||||
*/
|
||||
sealed abstract class CharacterSex(
|
||||
val value: Int,
|
||||
val pronounSubject: String,
|
||||
val pronounObject: String,
|
||||
val possessive: String
|
||||
) extends IntEnumEntry {
|
||||
val value: Int,
|
||||
val pronounSubject: String,
|
||||
val pronounObject: String,
|
||||
val possessive: String
|
||||
) extends IntEnumEntry {
|
||||
def possessiveNoObject: String = possessive
|
||||
}
|
||||
|
||||
|
|
@ -25,21 +25,23 @@ sealed abstract class CharacterSex(
|
|||
object CharacterSex extends IntEnum[CharacterSex] {
|
||||
val values = findValues
|
||||
|
||||
case object Male extends CharacterSex(
|
||||
value = 1,
|
||||
pronounSubject = "he",
|
||||
pronounObject = "him",
|
||||
possessive = "his"
|
||||
)
|
||||
case object Male
|
||||
extends CharacterSex(
|
||||
value = 1,
|
||||
pronounSubject = "he",
|
||||
pronounObject = "him",
|
||||
possessive = "his"
|
||||
)
|
||||
|
||||
case object Female extends CharacterSex(
|
||||
value = 2,
|
||||
pronounSubject = "she",
|
||||
pronounObject = "her",
|
||||
possessive = "her"
|
||||
) {
|
||||
case object Female
|
||||
extends CharacterSex(
|
||||
value = 2,
|
||||
pronounSubject = "she",
|
||||
pronounObject = "her",
|
||||
possessive = "her"
|
||||
) {
|
||||
override def possessiveNoObject: String = "hers"
|
||||
}
|
||||
|
||||
implicit val codec = PacketHelpers.createIntEnumCodec(enum = this, uint2L)
|
||||
implicit val codec = PacketHelpers.createIntEnumCodec(e = this, uint2L)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,9 +11,9 @@ sealed abstract class ExperienceType(val value: Int) extends IntEnumEntry
|
|||
object ExperienceType extends IntEnum[ExperienceType] {
|
||||
val values: IndexedSeq[ExperienceType] = findValues
|
||||
|
||||
case object Normal extends ExperienceType(value = 0)
|
||||
case object Support extends ExperienceType(value = 2)
|
||||
case object Normal extends ExperienceType(value = 0)
|
||||
case object Support extends ExperienceType(value = 2)
|
||||
case object RabbitBall extends ExperienceType(value = 4)
|
||||
|
||||
implicit val codec: Codec[ExperienceType] = PacketHelpers.createIntEnumCodec(enum = this, uint(bits = 3))
|
||||
implicit val codec: Codec[ExperienceType] = PacketHelpers.createIntEnumCodec(e = this, uint(bits = 3))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -165,8 +165,8 @@ object MeritCommendation extends Enumeration {
|
|||
{
|
||||
case MeritCommendation.None =>
|
||||
Attempt.successful(0xffffffffL)
|
||||
case enum =>
|
||||
Attempt.successful(enum.id.toLong)
|
||||
case e =>
|
||||
Attempt.successful(e.id.toLong)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,5 +23,5 @@ object OxygenState extends Enum[OxygenState] {
|
|||
case object Recovery extends OxygenState
|
||||
case object Suffocation extends OxygenState
|
||||
|
||||
implicit val codec: Codec[OxygenState] = PacketHelpers.createEnumCodec(enum = this, uint(bits = 1))
|
||||
implicit val codec: Codec[OxygenState] = PacketHelpers.createEnumCodec(e = this, uint(bits = 1))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
package net.psforever.types
|
||||
|
||||
import enumeratum.values.{IntEnum, IntEnumEntry}
|
||||
import net.psforever.types.StatisticalElement.{AMS, ANT, AgileExoSuit, ApcNc, ApcTr, ApcVs, Aphelion, AphelionFlight, AphelionGunner, Battlewagon, Colossus, ColossusFlight, ColossusGunner, Droppod, Dropship, Flail, Fury, GalaxyGunship, ImplantTerminalMech, InfiltrationExoSuit, Liberator, Lightgunship, Lightning, Lodestar, Magrider, MechanizedAssaultExoSuit, MediumTransport, Mosquito, Peregrine, PeregrineFlight, PeregrineGunner, PhalanxTurret, Phantasm, PortableMannedTurretNc, PortableMannedTurretTr, PortableMannedTurretVs, Prowler, QuadAssault, QuadStealth, Raider, ReinforcedExoSuit, Router, Skyguard, SpitfireAA, SpitfireCloaked, SpitfireTurret, StandardExoSuit, Sunderer, Switchblade, TankTraps, ThreeManHeavyBuggy, Thunderer, TwoManAssaultBuggy, TwoManHeavyBuggy, TwoManHoverBuggy, VanSentryTurret, Vanguard, Vulture, Wasp}
|
||||
import net.psforever.types.StatisticalElement.{AMS, ANT, AgileExoSuit, ApcNc, ApcTr, ApcVs, Aphelion, AphelionFlight, AphelionGunner, Battlewagon, Colossus, ColossusFlight, ColossusGunner, Dropship, Flail, Fury, GalaxyGunship, InfiltrationExoSuit, Liberator, Lightgunship, Lightning, Lodestar, Magrider, MechanizedAssaultExoSuit, MediumTransport, Mosquito, Peregrine, PeregrineFlight, PeregrineGunner, PhalanxTurret, PortableMannedTurretNc, PortableMannedTurretTr, PortableMannedTurretVs, Prowler, QuadAssault, QuadStealth, Raider, ReinforcedExoSuit, Router, Skyguard, StandardExoSuit, Sunderer, Switchblade, ThreeManHeavyBuggy, Thunderer, TwoManAssaultBuggy, TwoManHeavyBuggy, TwoManHoverBuggy, VanSentryTurret, Vanguard, Vulture, Wasp}
|
||||
|
||||
sealed abstract class StatisticalCategory(val value: Int) extends IntEnumEntry
|
||||
|
||||
|
|
|
|||
|
|
@ -25,12 +25,12 @@ object Config {
|
|||
}
|
||||
|
||||
implicit def enumeratumIntConfigConvert[A <: IntEnumEntry](implicit
|
||||
enum: IntEnum[A],
|
||||
e: IntEnum[A],
|
||||
ct: ClassTag[A]
|
||||
): ConfigConvert[A] =
|
||||
viaNonEmptyStringOpt[A](
|
||||
v =>
|
||||
enum.values.toList.collectFirst {
|
||||
e.values.toList.collectFirst {
|
||||
case e: ServerType if e.name == v => e.asInstanceOf[A]
|
||||
case e: BattleRank if e.value.toString == v => e.asInstanceOf[A]
|
||||
case e: CommandRank if e.value.toString == v => e.asInstanceOf[A]
|
||||
|
|
@ -40,12 +40,12 @@ object Config {
|
|||
)
|
||||
|
||||
implicit def enumeratumConfigConvert[A <: EnumEntry](implicit
|
||||
enum: Enum[A],
|
||||
e: Enum[A],
|
||||
ct: ClassTag[A]
|
||||
): ConfigConvert[A] =
|
||||
viaNonEmptyStringOpt[A](
|
||||
v =>
|
||||
enum.values.toList.collectFirst {
|
||||
e.values.toList.collectFirst {
|
||||
case e if e.toString.toLowerCase == v.toLowerCase => e.asInstanceOf[A]
|
||||
},
|
||||
_.toString
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ import net.psforever.objects.serverobject.terminals.implant.ImplantTerminalMech
|
|||
import net.psforever.objects.serverobject.tube.SpawnTube
|
||||
import net.psforever.objects.serverobject.turret.{FacilityTurret, FacilityTurretDefinition}
|
||||
import net.psforever.objects.serverobject.zipline.ZipLinePath
|
||||
import net.psforever.objects.sourcing.{DeployableSource, ObjectSource, PlayerSource, TurretSource, VehicleSource}
|
||||
import net.psforever.objects.sourcing.{DeployableSource, PlayerSource, TurretSource, VehicleSource}
|
||||
import net.psforever.objects.zones.{MapInfo, Zone, ZoneInfo, ZoneMap}
|
||||
import net.psforever.types.{Angular, PlanetSideEmpire, Vector3}
|
||||
import net.psforever.util.DefinitionUtil
|
||||
|
|
|
|||
|
|
@ -45,4 +45,3 @@ class CaptureFlagUpdateMessageTest extends Specification with Debug {
|
|||
pkt mustEqual stringOne
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -65,4 +65,3 @@ class ComponentDamageMessageTest extends Specification {
|
|||
pkt mustEqual string_off
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -54,4 +54,3 @@ class FrameVehicleStateMessageTest extends Specification {
|
|||
pkt mustEqual string
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -28,4 +28,3 @@ class GenericObjectActionAtPositionMessageTest extends Specification {
|
|||
pkt mustEqual string
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -347,4 +347,3 @@ class BattleframeRoboticsTest extends Specification {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -48,30 +48,52 @@ import scala.collection.mutable
|
|||
import scala.concurrent.duration.{DurationInt, FiniteDuration}
|
||||
import scala.reflect.ClassTag
|
||||
import java.util.concurrent.{Executors, TimeUnit}
|
||||
import net.psforever.packet.game._
|
||||
import net.psforever.types._
|
||||
import scala.concurrent.Future
|
||||
import scala.concurrent.Await
|
||||
import scala.concurrent.duration.Duration
|
||||
|
||||
object Client {
|
||||
implicit val ec: scala.concurrent.ExecutionContext = scala.concurrent.ExecutionContext.global
|
||||
Security.addProvider(new BouncyCastleProvider)
|
||||
|
||||
private[this] val log = org.log4s.getLogger
|
||||
|
||||
def main(args: Array[String]): Unit = {
|
||||
val client = new Client("test", "test")
|
||||
client.login(new InetSocketAddress("localhost", 51000))
|
||||
client.joinWorld(client.state.worlds.head)
|
||||
client.selectCharacter(client.state.characters.head.charId)
|
||||
client.startTasks()
|
||||
|
||||
while (true) {
|
||||
client.updateAvatar(client.state.avatar.copy(crouching = !client.state.avatar.crouching))
|
||||
Thread.sleep(2000)
|
||||
//Thread.sleep(Int.MaxValue)
|
||||
for (i <- 0 until 20) {
|
||||
val id = i
|
||||
|
||||
new Thread {
|
||||
override def run: Unit = {
|
||||
val client = new Client(s"bot${id}", "bot")
|
||||
client.login(new InetSocketAddress("localhost", 51000))
|
||||
|
||||
client.joinWorld(client.state.worlds.head)
|
||||
|
||||
if (client.state.characters.isEmpty) {
|
||||
client.createCharacter(s"bot${id}")
|
||||
}
|
||||
|
||||
client.selectCharacter(client.state.characters.head.charId)
|
||||
client.startTasks()
|
||||
|
||||
client.send(ChatMsg(ChatMessageType.CMT_ZONE, wideContents = false, "", "z1", None))
|
||||
|
||||
while (true) {
|
||||
client.updateAvatar(client.state.avatar.copy(crouching = !client.state.avatar.crouching))
|
||||
Thread.sleep(2000)
|
||||
}
|
||||
}
|
||||
}.start()
|
||||
}
|
||||
|
||||
Await.ready(Future.never, Duration.Inf)
|
||||
}
|
||||
}
|
||||
|
||||
class Client(username: String, password: String) {
|
||||
import Client._
|
||||
|
||||
private var sequence = 0
|
||||
private def nextSequence = {
|
||||
val r = sequence
|
||||
|
|
@ -188,21 +210,34 @@ class Client(username: String, password: String) {
|
|||
}
|
||||
setupConnection()
|
||||
send(ConnectToWorldRequestMessage("", state.token.get, 0, 0, 0, "", 0)).require
|
||||
waitFor[CharacterInfoMessage]().require
|
||||
while (true) {
|
||||
val r = waitFor[CharacterInfoMessage]().require
|
||||
if (r.finished) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def selectCharacter(charId: Long): Unit = {
|
||||
assert(state.connection == Connection.AvatarSelection)
|
||||
send(CharacterRequestMessage(charId, CharacterRequestAction.Select)).require
|
||||
waitFor[LoadMapMessage](timeout = 15.seconds).require
|
||||
waitFor[LoadMapMessage](timeout = 30.seconds).require
|
||||
}
|
||||
|
||||
def createCharacter(): Unit = {
|
||||
???
|
||||
def createCharacter(name: String): Unit = {
|
||||
assert(state.connection == Connection.AvatarSelection)
|
||||
send(CharacterCreateRequestMessage(name, 0, CharacterVoice.Voice1, CharacterSex.Male, PlanetSideEmpire.TR)).require
|
||||
val r = waitFor[ActionResultMessage](timeout = 15.seconds).require
|
||||
assert(r.errorCode == None)
|
||||
while (true) {
|
||||
val r = waitFor[CharacterInfoMessage]().require
|
||||
if (r.finished) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def deleteCharacter(charId: Long): Unit = {
|
||||
??? // never been tested
|
||||
assert(state.connection == Connection.AvatarSelection)
|
||||
send(CharacterRequestMessage(charId, CharacterRequestAction.Delete)).require
|
||||
}
|
||||
|
|
@ -293,13 +328,11 @@ class Client(username: String, password: String) {
|
|||
private def _process(packet: PlanetSidePacket): Unit = {
|
||||
packet match {
|
||||
case _: KeepAliveMessage => ()
|
||||
case _: LoadMapMessage =>
|
||||
log.info(s"process: ${packet}")
|
||||
case _: LoadMapMessage =>
|
||||
send(BeginZoningMessage()).require
|
||||
_state = state.update(packet)
|
||||
case packet: PlanetSideGamePacket =>
|
||||
_state = state.update(packet)
|
||||
log.info(s"process: ${packet}")
|
||||
()
|
||||
case _ => ()
|
||||
}
|
||||
|
|
@ -346,10 +379,6 @@ class Client(username: String, password: String) {
|
|||
sequence: Option[Int],
|
||||
crypto: Option[CryptoCoding]
|
||||
): Attempt[BitVector] = {
|
||||
packet match {
|
||||
case _: KeepAliveMessage => ()
|
||||
case _ => log.info(s"send: ${packet}")
|
||||
}
|
||||
PacketCoding.marshalPacket(packet, sequence, crypto) match {
|
||||
case Successful(payload) =>
|
||||
send(payload.toByteArray)
|
||||
|
|
|
|||
|
|
@ -89,8 +89,14 @@ case class State(
|
|||
case LoginRespMessage(token, _, _, _, _, _, _) => this.copy(token = Some(token))
|
||||
case VNLWorldStatusMessage(_, worlds) => this.copy(worlds = worlds, connection = Connection.WorldSelection)
|
||||
case ObjectCreateDetailedMessage(_, objectClass, guid, _, _) => this.copy(objects = objects ++ Seq(guid.guid))
|
||||
case message @ CharacterInfoMessage(_, _, _, _, _, _) =>
|
||||
this.copy(characters = characters ++ Seq(message), connection = Connection.AvatarSelection)
|
||||
case message @ CharacterInfoMessage(_, _, _, _, finished, _) =>
|
||||
// if finished is true, it is not real character but rather signal that list is complete
|
||||
if (finished) {
|
||||
this.copy(connection = Connection.AvatarSelection)
|
||||
} else {
|
||||
this.copy(characters = characters ++ Seq(message), connection = Connection.AvatarSelection)
|
||||
}
|
||||
|
||||
case _ => this
|
||||
}).copy(avatar = avatar.update(packet))
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue