mirror of
https://github.com/psforever/PSF-LoginServer.git
synced 2026-07-16 08:55:18 +00:00
Merge branch 'master' into reduce-psm-load
This commit is contained in:
commit
61a75f13cd
106 changed files with 468298 additions and 468246 deletions
|
|
@ -76,4 +76,4 @@ ignore:
|
|||
- "src/main/scala/net/psforever/services/local/LocalAction.scala"
|
||||
- "src/main/scala/net/psforever/services/local/LocalResponse.scala"
|
||||
- "src/main/scala/net/psforever/services/vehicle/VehicleAction.scala"
|
||||
- "src/main/scala/net/psforever/services/vehicle/VehicleResponse.scala"
|
||||
- "src/main/scala/net/psforever/services/vehicle/VehicleResponse.scala"
|
||||
|
|
|
|||
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
|
||||
2
.github/workflows/publish.yaml
vendored
2
.github/workflows/publish.yaml
vendored
|
|
@ -35,4 +35,4 @@ jobs:
|
|||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
push: true
|
||||
tags: ${{ steps.prep.outputs.tags }}
|
||||
tags: ${{ steps.prep.outputs.tags }}
|
||||
|
|
|
|||
2
.github/workflows/test.yaml
vendored
2
.github/workflows/test.yaml
vendored
|
|
@ -46,4 +46,4 @@ jobs:
|
|||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: server.zip
|
||||
path: server/target/psforever-server-*.zip
|
||||
path: server/target/psforever-server-*.zip
|
||||
|
|
|
|||
1
.jvmopts
1
.jvmopts
|
|
@ -2,3 +2,4 @@
|
|||
-Xss6M
|
||||
-Dconfig.override_with_env_vars=true
|
||||
-Dsbt.server.forcestart=true
|
||||
-Dquill.macro.log=false
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version = 2.6.4
|
||||
preset = defaultWithAlign
|
||||
maxColumn = 120
|
||||
maxColumn = 120
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
FROM mozilla/sbt as builder
|
||||
FROM sbtscala/scala-sbt:eclipse-temurin-focal-11.0.17_8_1.8.2_2.13.10 as builder
|
||||
|
||||
COPY . /PSF-LoginServer
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
GNU General Public License
|
||||
==========================
|
||||
|
||||
_Version 3, 29 June 2007_
|
||||
_Version 3, 29 June 2007_
|
||||
_Copyright © 2007 Free Software Foundation, Inc. <<http://fsf.org/>>_
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim copies of this license
|
||||
|
|
|
|||
12
README.md
12
README.md
|
|
@ -30,7 +30,7 @@ which has the instructions on downloading the game and using the PSForever launc
|
|||
- Up to date
|
||||
- [PostgreSQL](https://www.postgresql.org/)
|
||||
- 10+
|
||||
- Development (+Running)
|
||||
- Development (+Running)
|
||||
- [Git](https://en.wikipedia.org/wiki/Git)
|
||||
- IDE or Text Editor
|
||||
|
||||
|
|
@ -87,7 +87,7 @@ arguments - is recommended in order to avoid this startup time.
|
|||
### PostgreSQL Database
|
||||
A database is required for persistence of game state and player characters. The login server and game server (which are
|
||||
considered the same things, more or else) are set up to accept queries to a PostgreSQL server. It doesn't matter if you
|
||||
don't understand what PostgreSQL actually is compared to MySQL. I don't get it either - just install it:
|
||||
don't understand what PostgreSQL actually is compared to MySQL. I don't get it either - just install it:
|
||||
for [Windows](https://www.postgresql.org/download/windows/);
|
||||
for Linux [Debian](https://www.postgresql.org/download/linux/debian/),
|
||||
for Linux [Ubuntu](https://www.postgresql.org/download/linux/ubuntu/);
|
||||
|
|
@ -102,7 +102,7 @@ To use pgAdmin, run the appropriate binary to start the pgAdmin server. Dependi
|
|||
browser will open, or maybe a dedicated application window will open. Either way, create necessary passwords during
|
||||
the first login, then enter the connection details that were used during the PostgreSQL installation. When connected,
|
||||
expand the tree and right click on "Databases", menu -> Create... -> Database. Enter name as "psforever", then Save.
|
||||
Right click on the psforever database, menu -> Query Tool... Copy and paste the commands below, then hit the
|
||||
Right click on the psforever database, menu -> Query Tool... Copy and paste the commands below, then hit the
|
||||
"Play/Run" button. The user should be created and made owner of the database. (Prior to that, it should be "postgresql".)
|
||||
(Check menu -> Properties to confirm. May need to refresh first to see these changes.)
|
||||
```sql
|
||||
|
|
@ -117,11 +117,11 @@ If this happens, drop all objects and try again or apply permissions to everythi
|
|||
Scala code can be fairly complex, and a good IDE helps you understand the code and what methods are available for certain
|
||||
types, especially as you are learning the language. IntelliJ IDEA has some of the most mature support for Scala of any
|
||||
IDE today. It has advanced type introspection (examine the properties of an object at runtime) and excellent code
|
||||
completion (examine the code as you are writing it).
|
||||
completion (examine the code as you are writing it).
|
||||
Download the [community edition of IDEA](https://www.jetbrains.com/idea/download/) directly from IntelliJ's website
|
||||
then get the [required Scala plugin for IDEA](https://www.jetbrains.com/help/idea/managing-plugins.html).
|
||||
|
||||
You will need to import the project into the IDE. Older versions of IDEA (2016.3.4, etc.) have an
|
||||
You will need to import the project into the IDE. Older versions of IDEA (2016.3.4, etc.) have an
|
||||
[import procedure](https://www.lagomframework.com/documentation/1.6.x/scala/IntellijSbt.html)
|
||||
where it is necessary to instruct the IDE what kind of project is being imported. Modern IDEA (2022.1.3) still
|
||||
utilizes this procedure but can also open the repo as a project and contextually determine what
|
||||
|
|
@ -206,7 +206,7 @@ some helper scripts. Run the correct file for your platform (.BAT for Windows an
|
|||
1. If dependency resolution results in certificate issues or generates a `/null/` directory into which some library
|
||||
files are placed, the Java versioning is incorrectly applied. Your system's Java, via `JAVA_HOME` environment variable,
|
||||
must be advanced enough to operate the toolset and only the project itself requires JDK 8. Check that project settings
|
||||
import and utilize Java 1.8_251. Perform normal generated file cleanup, e.g., sbt's `clean`.
|
||||
import and utilize Java 1.8_251. Perform normal generated file cleanup, e.g., sbt's `clean`.
|
||||
Any extraneous folders may also be deleted without issue.
|
||||
2. If the server repeatedly complains that "authentication method 10 not supported" during startup, your PostgreSQL
|
||||
database does not support [scram-sha-256](https://www.postgresql.org/docs/current/auth-password.html) authentication.
|
||||
|
|
|
|||
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("."))
|
||||
|
|
|
|||
|
|
@ -3,19 +3,19 @@
|
|||
\usepackage{lmodern}
|
||||
|
||||
\usepackage{graphicx}
|
||||
\usepackage[margin=1in]{geometry}
|
||||
\usepackage[margin=1in]{geometry}
|
||||
\usepackage{float}
|
||||
\usepackage{xcolor}
|
||||
\usepackage{hyperref}
|
||||
\usepackage{float}
|
||||
\usepackage{amsmath}
|
||||
|
||||
|
||||
\begin{document}
|
||||
|
||||
\title{PSForever Server Notes}
|
||||
|
||||
|
||||
\author{Chord $<$chord@tuta.io$>$}
|
||||
|
||||
|
||||
\maketitle
|
||||
|
||||
%\section*{Security Model}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ for f in $FILES; do
|
|||
else
|
||||
SED_CMD='s#'"$LINESPEC_EXISTING_COPY"'#'"$COPYRIGHT"'#'
|
||||
echo "Replacing '$LINESPEC_EXISTING_COPY' --> '$COPYRIGHT'"
|
||||
sed -i -b "$SED_CMD" "$f"
|
||||
sed -i -b "$SED_CMD" "$f"
|
||||
fi
|
||||
else
|
||||
echo "$f: Not found"
|
||||
|
|
@ -63,7 +63,7 @@ for f in $FILES; do
|
|||
if [ $CHOICE = "n" ]; then
|
||||
:
|
||||
else
|
||||
sed -i -b '1i '"$COPYRIGHT"'' "$f"
|
||||
sed -i -b '1i '"$COPYRIGHT"'' "$f"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -3,4 +3,4 @@ CREATE TABLE IF NOT EXISTS "buildings" (
|
|||
zone_id INT NOT NULL,
|
||||
faction_id INT NOT NULL,
|
||||
PRIMARY KEY (local_id, zone_id)
|
||||
);
|
||||
);
|
||||
|
|
|
|||
|
|
@ -26,4 +26,4 @@ CREATE TABLE implant (
|
|||
name TEXT NOT NULL,
|
||||
avatar_id INT NOT NULL REFERENCES avatar (id),
|
||||
PRIMARY KEY (name, avatar_id)
|
||||
);
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
stacktrace.app.packages=net.psforever
|
||||
stacktrace.app.packages=net.psforever
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -10,4 +10,4 @@ interface PrivacyHelper {
|
|||
return new ByteString1C(array);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -67,8 +67,13 @@ database {
|
|||
|
||||
# Enable non-standard game properties
|
||||
game {
|
||||
# Allow instant action to AMS
|
||||
instant-action-ams = no
|
||||
instant-action = {
|
||||
# Allow instant action to direct a player to a hotspot-local friendly AMS
|
||||
spawn-on-ams = no
|
||||
|
||||
# If no ally hotspots can be found during instant action, find a friendly spawn closest to an enemy hot spot
|
||||
third-party = no
|
||||
}
|
||||
|
||||
# Battle experience rate
|
||||
bep-rate = 1.0
|
||||
|
|
@ -212,6 +217,8 @@ game {
|
|||
# while additional entries with insufficient time spacing may result in no change in behavior.
|
||||
delays = [350, 600, 800]
|
||||
}
|
||||
|
||||
doors-can-be-opened-by-med-app-from-this-distance = 5.05
|
||||
}
|
||||
|
||||
anti-cheat {
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -4808,4 +4808,4 @@
|
|||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
]
|
||||
|
|
|
|||
|
|
@ -11689,4 +11689,4 @@
|
|||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
]
|
||||
|
|
|
|||
|
|
@ -7393,4 +7393,4 @@
|
|||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
]
|
||||
|
|
|
|||
|
|
@ -5656,4 +5656,4 @@
|
|||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
]
|
||||
|
|
|
|||
|
|
@ -4340,4 +4340,4 @@
|
|||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
]
|
||||
|
|
|
|||
|
|
@ -5992,4 +5992,4 @@
|
|||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -3,7 +3,7 @@ package net.psforever.actors.session
|
|||
|
||||
import akka.actor.typed.receptionist.Receptionist
|
||||
import akka.actor.typed.scaladsl.adapter._
|
||||
import akka.actor.{Actor, MDCContextAware, SupervisorStrategy, typed}
|
||||
import akka.actor.{Actor, MDCContextAware, typed}
|
||||
import org.joda.time.LocalDateTime
|
||||
import org.log4s.MDC
|
||||
import scala.collection.mutable
|
||||
|
|
@ -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,11 +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)
|
||||
|
||||
override val supervisorStrategy: SupervisorStrategy = sessionFuncs.sessionSupervisorStrategy
|
||||
private[this] val sessionFuncs = new SessionData(middlewareActor, context)
|
||||
|
||||
ServiceManager.serviceManager ! Lookup("accountIntermediary")
|
||||
ServiceManager.serviceManager ! Lookup("accountPersistence")
|
||||
|
|
@ -195,7 +195,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 +203,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 +353,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 +367,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 +592,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")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
package net.psforever.actors.session.support
|
||||
|
||||
import akka.actor.typed.scaladsl.adapter._
|
||||
import akka.actor.{ActorContext, ActorRef, Cancellable, OneForOneStrategy, SupervisorStrategy, typed}
|
||||
import akka.actor.{ActorContext, ActorRef, Cancellable, typed}
|
||||
import net.psforever.objects.sourcing.{PlayerSource, SourceEntry}
|
||||
|
||||
import scala.collection.mutable
|
||||
|
|
@ -19,11 +19,10 @@ import net.psforever.objects.avatar._
|
|||
import net.psforever.objects.ballistics._
|
||||
import net.psforever.objects.ce._
|
||||
import net.psforever.objects.definition._
|
||||
import net.psforever.objects.entity.{NoGUIDException,WorldEntity}
|
||||
import net.psforever.objects.entity.WorldEntity
|
||||
import net.psforever.objects.equipment._
|
||||
import net.psforever.objects.guid._
|
||||
import net.psforever.objects.inventory.{Container, GridInventory, InventoryItem}
|
||||
import net.psforever.objects.loadouts.InfantryLoadout
|
||||
import net.psforever.objects.inventory.{Container, InventoryItem}
|
||||
import net.psforever.objects.locker.LockerContainer
|
||||
import net.psforever.objects.serverobject.affinity.FactionAffinity
|
||||
import net.psforever.objects.serverobject.deploy.Deployment
|
||||
|
|
@ -65,6 +64,7 @@ import net.psforever.types._
|
|||
import net.psforever.util.Config
|
||||
|
||||
object SessionData {
|
||||
//noinspection ScalaUnusedSymbol
|
||||
private def NoTurnCounterYet(guid: PlanetSideGUID): Unit = { }
|
||||
}
|
||||
|
||||
|
|
@ -540,7 +540,7 @@ class SessionData(
|
|||
}
|
||||
validObject(pkt.object_guid, decorator = "UseItem") match {
|
||||
case Some(door: Door) =>
|
||||
handleUseDoor(door)
|
||||
handleUseDoor(door, equipment)
|
||||
case Some(resourceSilo: ResourceSilo) =>
|
||||
handleUseResourceSilo(resourceSilo, equipment)
|
||||
case Some(panel: IFFLock) =>
|
||||
|
|
@ -1122,212 +1122,6 @@ class SessionData(
|
|||
|
||||
/* supporting functions */
|
||||
|
||||
val sessionSupervisorStrategy: SupervisorStrategy = {
|
||||
import net.psforever.objects.inventory.InventoryDisarrayException
|
||||
OneForOneStrategy(maxNrOfRetries = -1, withinTimeRange = 1 minute) {
|
||||
case nge: NoGUIDException =>
|
||||
nge.getEntity match {
|
||||
case p: Player =>
|
||||
continent.GUID(p.VehicleSeated) match {
|
||||
case Some(v: Vehicle) =>
|
||||
attemptRecoveryFromNoGuidExceptionAsVehicle(v, nge)
|
||||
case _ =>
|
||||
attemptRecoveryFromNoGuidExceptionAsPlayer(p, nge)
|
||||
}
|
||||
|
||||
case v: Vehicle =>
|
||||
attemptRecoveryFromNoGuidExceptionAsVehicle(v, nge)
|
||||
|
||||
case e: Equipment =>
|
||||
(
|
||||
player.Holsters().zipWithIndex.flatMap { case (o, i) =>
|
||||
o.Equipment.flatMap { item => Some((player, InventoryItem(item, i))) }
|
||||
}.toList ++
|
||||
player.Inventory.Items.map { o => (player, o) } ++
|
||||
player.FreeHand.Equipment.flatMap { item => Some((player, InventoryItem(item, Player.FreeHandSlot))) }.toList ++
|
||||
(validObject(player.VehicleSeated) match {
|
||||
case Some(v: Vehicle) => v.Trunk.Items.map { o => (v, o) }
|
||||
case _ => Nil
|
||||
})
|
||||
)
|
||||
.find { case (_, InventoryItem(o, _)) => o eq e } match {
|
||||
case Some((p: Player, InventoryItem(obj, index))) if !obj.HasGUID =>
|
||||
p.Slot(index).Equipment = None
|
||||
attemptRecoveryFromNoGuidExceptionAsPlayer(p, nge)
|
||||
case Some((p: Player, _)) =>
|
||||
attemptRecoveryFromNoGuidExceptionAsPlayer(p, nge)
|
||||
|
||||
case Some((v: Vehicle, InventoryItem(obj, index))) if !obj.HasGUID && v.PassengerInSeat(player).contains(0) =>
|
||||
v.Slot(index).Equipment = None
|
||||
attemptRecoveryFromNoGuidExceptionAsPlayer(player, nge)
|
||||
SupervisorStrategy.resume
|
||||
case Some((v: Vehicle, InventoryItem(obj, index))) if !obj.HasGUID =>
|
||||
v.Slot(index).Equipment = None
|
||||
SupervisorStrategy.resume
|
||||
case Some((v: Vehicle, _)) if v.PassengerInSeat(player).contains(0) =>
|
||||
attemptRecoveryFromNoGuidExceptionAsPlayer(player, nge)
|
||||
SupervisorStrategy.resume
|
||||
case Some((_: Vehicle, _)) =>
|
||||
SupervisorStrategy.resume
|
||||
|
||||
case _ =>
|
||||
//did not discover or resolve the situation
|
||||
writeLogExceptionAndStop(nge)
|
||||
}
|
||||
case _ =>
|
||||
SupervisorStrategy.resume
|
||||
}
|
||||
|
||||
case ide: InventoryDisarrayException =>
|
||||
attemptRecoveryFromInventoryDisarrayException(ide.inventory)
|
||||
//re-evaluate results
|
||||
if (ide.inventory.ElementsOnGridMatchList() > 0) {
|
||||
writeLogExceptionAndStop(ide)
|
||||
} else {
|
||||
SupervisorStrategy.resume
|
||||
}
|
||||
|
||||
case e =>
|
||||
writeLogExceptionAndStop(e)
|
||||
}
|
||||
}
|
||||
|
||||
def attemptRecoveryFromNoGuidExceptionAsVehicle(v: Vehicle, e: Throwable): SupervisorStrategy.Directive = {
|
||||
val entry = v.Seats.find { case (_, s) => s.occupants.contains(player) }
|
||||
entry match {
|
||||
case Some(_) =>
|
||||
player.VehicleSeated = None
|
||||
v.Seats(0).unmount(player)
|
||||
player.Position = v.Position
|
||||
player.Orientation = v.Orientation
|
||||
zoning.interstellarFerry = None
|
||||
zoning.interstellarFerryTopLevelGUID = None
|
||||
attemptRecoveryFromNoGuidExceptionAsPlayer(player, e)
|
||||
case None =>
|
||||
writeLogException(e)
|
||||
}
|
||||
}
|
||||
|
||||
def attemptRecoveryFromNoGuidExceptionAsPlayer(p: Player, e: Throwable): SupervisorStrategy.Directive = {
|
||||
if (p eq player) {
|
||||
val hasGUID = p.HasGUID
|
||||
zoning.zoneLoaded match {
|
||||
case Some(true) if hasGUID =>
|
||||
zoning.spawn.AvatarCreate() //this will probably work?
|
||||
SupervisorStrategy.resume
|
||||
case Some(false) =>
|
||||
zoning.RequestSanctuaryZoneSpawn(p, continent.Number)
|
||||
SupervisorStrategy.resume
|
||||
case None if player.Zone eq Zone.Nowhere =>
|
||||
zoning.RequestSanctuaryZoneSpawn(p, continent.Number)
|
||||
SupervisorStrategy.resume
|
||||
case None =>
|
||||
zoning.zoneReload = true
|
||||
zoning.LoadZoneAsPlayer(player, player.Zone.id)
|
||||
SupervisorStrategy.resume
|
||||
case _ =>
|
||||
writeLogExceptionAndStop(e)
|
||||
}
|
||||
} else {
|
||||
SupervisorStrategy.resume
|
||||
}
|
||||
}
|
||||
|
||||
def attemptRecoveryFromInventoryDisarrayException(inv: GridInventory): Unit = {
|
||||
inv.ElementsInListCollideInGrid() match {
|
||||
case Nil => ()
|
||||
case list if list.isEmpty => ()
|
||||
case overlaps =>
|
||||
val previousItems = inv.Clear()
|
||||
val allOverlaps = overlaps.flatten.sortBy { entry =>
|
||||
val tile = entry.obj.Definition.Tile
|
||||
tile.Width * tile.Height
|
||||
}.toSet
|
||||
val notCollidingRemainder = previousItems.filterNot(allOverlaps.contains)
|
||||
notCollidingRemainder.foreach { entry =>
|
||||
inv.InsertQuickly(entry.start, entry.obj)
|
||||
}
|
||||
var didNotFit: List[Equipment] = Nil
|
||||
allOverlaps.foreach { entry =>
|
||||
inv.Fit(entry.obj.Definition.Tile) match {
|
||||
case Some(newStart) =>
|
||||
inv.InsertQuickly(newStart, entry.obj)
|
||||
case None =>
|
||||
didNotFit = didNotFit :+ entry.obj
|
||||
}
|
||||
}
|
||||
//completely clear the inventory
|
||||
val pguid = player.GUID
|
||||
val equipmentInHand = player.Slot(player.DrawnSlot).Equipment
|
||||
//redraw suit
|
||||
sendResponse(ArmorChangedMessage(
|
||||
pguid,
|
||||
player.ExoSuit,
|
||||
InfantryLoadout.DetermineSubtypeA(player.ExoSuit, equipmentInHand)
|
||||
))
|
||||
//redraw item in free hand (if)
|
||||
player.FreeHand.Equipment.foreach { item =>
|
||||
sendResponse(ObjectCreateDetailedMessage(
|
||||
item.Definition.ObjectId,
|
||||
item.GUID,
|
||||
ObjectCreateMessageParent(pguid, Player.FreeHandSlot),
|
||||
item.Definition.Packet.DetailedConstructorData(item).get
|
||||
))
|
||||
}
|
||||
//redraw items in holsters
|
||||
player.Holsters().zipWithIndex.foreach { case (slot, _) =>
|
||||
slot.Equipment.foreach { item =>
|
||||
sendResponse(ObjectCreateDetailedMessage(
|
||||
item.Definition.ObjectId,
|
||||
item.GUID,
|
||||
item.Definition.Packet.DetailedConstructorData(item).get
|
||||
))
|
||||
}
|
||||
}
|
||||
//redraw raised hand (if)
|
||||
equipmentInHand.foreach { _ =>
|
||||
sendResponse(ObjectHeldMessage(pguid, player.DrawnSlot, unk1 = true))
|
||||
}
|
||||
//redraw inventory items
|
||||
val recoveredItems = inv.Items
|
||||
recoveredItems.foreach { entry =>
|
||||
val item = entry.obj
|
||||
sendResponse(ObjectCreateDetailedMessage(
|
||||
item.Definition.ObjectId,
|
||||
item.GUID,
|
||||
ObjectCreateMessageParent(pguid, entry.start),
|
||||
item.Definition.Packet.DetailedConstructorData(item).get
|
||||
))
|
||||
}
|
||||
//drop items that did not fit
|
||||
val placementData = PlacementData(player.Position, Vector3.z(player.Orientation.z))
|
||||
didNotFit.foreach { item =>
|
||||
sendResponse(ObjectCreateMessage(
|
||||
item.Definition.ObjectId,
|
||||
item.GUID,
|
||||
DroppedItemData(
|
||||
placementData,
|
||||
item.Definition.Packet.ConstructorData(item).get
|
||||
)
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def writeLogException(e: Throwable): SupervisorStrategy.Directive = {
|
||||
import java.io.{PrintWriter, StringWriter}
|
||||
val sw = new StringWriter
|
||||
e.printStackTrace(new PrintWriter(sw))
|
||||
log.error(sw.toString)
|
||||
SupervisorStrategy.Resume
|
||||
}
|
||||
|
||||
def writeLogExceptionAndStop(e: Throwable): SupervisorStrategy.Directive = {
|
||||
writeLogException(e)
|
||||
immediateDisconnect()
|
||||
SupervisorStrategy.stop
|
||||
}
|
||||
|
||||
def buildDependentOperationsForGalaxy(galaxyActor: ActorRef): Unit = {
|
||||
if (vehicleResponseOpt.isEmpty && galaxyActor != Default.Actor) {
|
||||
galaxyResponseOpt = Some(new SessionGalaxyHandlers(sessionData=this, avatarActor, galaxyActor, context))
|
||||
|
|
@ -1451,8 +1245,17 @@ class SessionData(
|
|||
}
|
||||
}
|
||||
|
||||
private def handleUseDoor(door: Door): Unit = {
|
||||
door.Actor ! CommonMessages.Use(player)
|
||||
private def handleUseDoor(door: Door, equipment: Option[Equipment]): Unit = {
|
||||
equipment match {
|
||||
case Some(tool: Tool) if tool.Definition == GlobalDefinitions.medicalapplicator =>
|
||||
val distance: Float = math.max(
|
||||
Config.app.game.doorsCanBeOpenedByMedAppFromThisDistance,
|
||||
door.Definition.initialOpeningDistance
|
||||
)
|
||||
door.Actor ! CommonMessages.Use(player, Some(distance))
|
||||
case _ =>
|
||||
door.Actor ! CommonMessages.Use(player)
|
||||
}
|
||||
}
|
||||
|
||||
private def handleUseResourceSilo(resourceSilo: ResourceSilo, equipment: Option[Equipment]): Unit = {
|
||||
|
|
|
|||
|
|
@ -45,52 +45,7 @@ class SessionGalaxyHandlers(
|
|||
sendResponse(msg)
|
||||
|
||||
case GalaxyResponse.TransferPassenger(temp_channel, vehicle, _, manifest) =>
|
||||
val playerName = player.Name
|
||||
log.debug(s"TransferPassenger: $playerName received the summons to transfer to ${vehicle.Zone.id} ...")
|
||||
(manifest.passengers.find { _.name.equals(playerName) } match {
|
||||
case Some(entry) if vehicle.Seats(entry.mount).occupant.isEmpty =>
|
||||
player.VehicleSeated = None
|
||||
vehicle.Seats(entry.mount).mount(player)
|
||||
player.VehicleSeated = vehicle.GUID
|
||||
Some(vehicle)
|
||||
case Some(entry) if vehicle.Seats(entry.mount).occupant.contains(player) =>
|
||||
Some(vehicle)
|
||||
case Some(entry) =>
|
||||
log.warn(
|
||||
s"TransferPassenger: $playerName tried to mount seat ${entry.mount} during summoning, but it was already occupied, and ${player.Sex.pronounSubject} was rebuked"
|
||||
)
|
||||
None
|
||||
case None =>
|
||||
//log.warn(s"TransferPassenger: $playerName is missing from the manifest of a summoning ${vehicle.Definition.Name} from ${vehicle.Zone.id}")
|
||||
None
|
||||
}).orElse {
|
||||
manifest.cargo.find { _.name.equals(playerName) } match {
|
||||
case Some(entry) =>
|
||||
vehicle.CargoHolds(entry.mount).occupant match {
|
||||
case out @ Some(cargo) if cargo.Seats(0).occupants.exists(_.Name.equals(playerName)) =>
|
||||
out
|
||||
case _ =>
|
||||
None
|
||||
}
|
||||
case None =>
|
||||
None
|
||||
}
|
||||
} match {
|
||||
case Some(v: Vehicle) =>
|
||||
galaxyService ! Service.Leave(Some(temp_channel)) //temporary vehicle-specific channel (see above)
|
||||
sessionData.zoning.spawn.deadState = DeadState.Release
|
||||
sendResponse(AvatarDeadStateMessage(DeadState.Release, 0, 0, player.Position, player.Faction, unk5=true))
|
||||
sessionData.zoning.interstellarFerry = Some(v) //on the other continent and registered to that continent's GUID system
|
||||
sessionData.zoning.spawn.LoadZonePhysicalSpawnPoint(v.Continent, v.Position, v.Orientation, 1 seconds, None)
|
||||
case _ =>
|
||||
sessionData.zoning.interstellarFerry match {
|
||||
case None =>
|
||||
galaxyService ! Service.Leave(Some(temp_channel)) //no longer being transferred between zones
|
||||
sessionData.zoning.interstellarFerryTopLevelGUID = None
|
||||
case Some(_) => ;
|
||||
//wait patiently
|
||||
}
|
||||
}
|
||||
sessionData.zoning.handleTransferPassenger(temp_channel, vehicle, manifest)
|
||||
|
||||
case GalaxyResponse.LockedZoneUpdate(zone, time) =>
|
||||
sendResponse(ZoneInfoMessage(zone.Number, empire_status=false, lock_time=time))
|
||||
|
|
@ -103,13 +58,13 @@ class SessionGalaxyHandlers(
|
|||
val popVS = zone.Players.count(_.faction == PlanetSideEmpire.VS)
|
||||
sendResponse(ZonePopulationUpdateMessage(zone.Number, 414, 138, popTR, 138, popNC, 138, popVS, 138, popBO))
|
||||
|
||||
case GalaxyResponse.LogStatusChange(name) =>
|
||||
if (avatar.people.friend.exists { _.name.equals(name) }) {
|
||||
avatarActor ! AvatarActor.MemberListRequest(MemberAction.UpdateFriend, name)
|
||||
}
|
||||
case GalaxyResponse.LogStatusChange(name) if (avatar.people.friend.exists { _.name.equals(name) }) =>
|
||||
avatarActor ! AvatarActor.MemberListRequest(MemberAction.UpdateFriend, name)
|
||||
|
||||
case GalaxyResponse.SendResponse(msg) =>
|
||||
sendResponse(msg)
|
||||
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -309,11 +309,8 @@ class VehicleOperations(
|
|||
obj.PassengerInSeat(player) match {
|
||||
case Some(seat_num) =>
|
||||
obj.Actor ! Mountable.TryDismount(player, seat_num, bailType)
|
||||
if (sessionData.zoning.interstellarFerry.isDefined) {
|
||||
//short-circuit the temporary channel for transferring between zones, the player is no longer doing that
|
||||
//see above in VehicleResponse.TransferPassenger case
|
||||
sessionData.zoning.interstellarFerry = None
|
||||
}
|
||||
//short-circuit the temporary channel for transferring between zones, the player is no longer doing that
|
||||
sessionData.zoning.interstellarFerry = None
|
||||
// Deconstruct the vehicle if the driver has bailed out and the vehicle is capable of flight
|
||||
//todo: implement auto landing procedure if the pilot bails but passengers are still present instead of deconstructing the vehicle
|
||||
//todo: continue flight path until aircraft crashes if no passengers present (or no passenger seats), then deconstruct.
|
||||
|
|
@ -626,6 +623,7 @@ class VehicleOperations(
|
|||
}
|
||||
}
|
||||
|
||||
//noinspection ScalaUnusedSymbol
|
||||
def TotalDriverVehicleControl(vehicle: Vehicle): Unit = {
|
||||
serverVehicleControlVelocity = None
|
||||
sendResponse(ServerVehicleOverrideMsg(lock_accelerator=false, lock_wheel=false, reverse=false, unk4=false, 0, 0, 0, None))
|
||||
|
|
|
|||
|
|
@ -762,19 +762,19 @@ class ZoningOperations(
|
|||
None
|
||||
}
|
||||
} match {
|
||||
case Some(v: Vehicle) =>
|
||||
case Some(cargo: Vehicle) =>
|
||||
galaxyService ! Service.Leave(Some(temp_channel)) //temporary vehicle-specific channel (see above)
|
||||
spawn.deadState = DeadState.Release
|
||||
sendResponse(AvatarDeadStateMessage(DeadState.Release, 0, 0, player.Position, player.Faction, unk5=true))
|
||||
interstellarFerry = Some(v) //on the other continent and registered to that continent's GUID system
|
||||
spawn.LoadZonePhysicalSpawnPoint(v.Continent, v.Position, v.Orientation, 1 seconds, None)
|
||||
interstellarFerry = Some(cargo) //on the other continent and registered to that continent's GUID system
|
||||
cargo.MountedIn = vehicle.GUID
|
||||
spawn.LoadZonePhysicalSpawnPoint(cargo.Continent, cargo.Position, cargo.Orientation, respawnTime = 1 seconds, None)
|
||||
case _ =>
|
||||
interstellarFerry match {
|
||||
case None =>
|
||||
galaxyService ! Service.Leave(Some(temp_channel)) //no longer being transferred between zones
|
||||
interstellarFerryTopLevelGUID = None
|
||||
case Some(_) => ;
|
||||
//wait patiently
|
||||
case Some(_) => () //wait patiently
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
81
src/main/scala/net/psforever/objects/Doors.scala
Normal file
81
src/main/scala/net/psforever/objects/Doors.scala
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
// Copyright (c) 2023 PSForever
|
||||
package net.psforever.objects
|
||||
|
||||
import net.psforever.objects.serverobject.doors.Door
|
||||
import net.psforever.types.Vector3
|
||||
|
||||
object Doors {
|
||||
/**
|
||||
* If a player was considered as holding the door open,
|
||||
* check that if that player is still in the same zone as a door
|
||||
* and is still standing within a certain distance of that door.
|
||||
* If the target player no longer qualifies to hold the door open,
|
||||
* search for a new player to hold the door open.
|
||||
* If the target player did not initially exist (the door was closed),
|
||||
* do not search any further.
|
||||
* @param door door that is installed somewhere
|
||||
* @param maximumDistance permissible square distance between the player and the door
|
||||
* @return optional player;
|
||||
* `None` if the no player can trigger the door
|
||||
*/
|
||||
def testForTargetHoldingDoorOpen(
|
||||
door: Door,
|
||||
maximumDistance: Float
|
||||
): Option[Player] = {
|
||||
door.Open
|
||||
.flatMap {
|
||||
testForSpecificTargetHoldingDoorOpen(_, door, maximumDistance * maximumDistance)
|
||||
.orElse { testForAnyTargetHoldingDoorOpen(door, maximumDistance) }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that a player is in the same zone as a door
|
||||
* and is standing within a certain distance of that door.
|
||||
* @see `Vector3.MagnitudeSquared`
|
||||
* @param player player who is standing somewhere
|
||||
* @param door door that is installed somewhere
|
||||
* @param maximumDistance permissible square distance between the player and the door
|
||||
* before one can not influence the other
|
||||
* @return optional player (same as parameter);
|
||||
* `None` if the parameter player can not trigger the door
|
||||
*/
|
||||
def testForSpecificTargetHoldingDoorOpen(
|
||||
player: Player,
|
||||
door: Door,
|
||||
maximumDistance: Float
|
||||
): Option[Player] = {
|
||||
if (player.Zone == door.Zone && Vector3.MagnitudeSquared(door.Position - player.Position) <= maximumDistance) {
|
||||
Some(player)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a player, any player, that can hold the door open.
|
||||
* Prop it open with a dead body if no one is available.
|
||||
* @param door door that is installed somewhere
|
||||
* @param maximumDistance permissible square distance between a player and the door
|
||||
* before one can not influence the other
|
||||
* @return optional player;
|
||||
* `None` if no player can trigger the door
|
||||
*/
|
||||
private def testForAnyTargetHoldingDoorOpen(
|
||||
door: Door,
|
||||
maximumDistance: Float
|
||||
): Option[Player] = {
|
||||
//search for nearby players and nearby former players who would block open the door
|
||||
val zone = door.Zone
|
||||
val maximumDistanceSq = maximumDistance * maximumDistance
|
||||
val bmap = zone.blockMap.sector(door.Position, maximumDistance)
|
||||
(bmap.livePlayerList ++ bmap.corpseList)
|
||||
.find { testForSpecificTargetHoldingDoorOpen(_, door, maximumDistanceSq).nonEmpty } match {
|
||||
case out @ Some(newOpener) =>
|
||||
door.Open = newOpener //another player is near the door, keep it open
|
||||
out
|
||||
case _ =>
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -256,5 +256,3 @@ object Tool {
|
|||
def Definition: FireModeDefinition = fdef
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -113,13 +113,6 @@ class Vehicle(private val vehicleDef: VehicleDefinition)
|
|||
private var utilities: Map[Int, Utility] = Map.empty
|
||||
private var subsystems: List[VehicleSubsystem] = Nil
|
||||
private val trunk: GridInventory = GridInventory()
|
||||
|
||||
/*
|
||||
* Records the GUID of the cargo vehicle (galaxy/lodestar) this vehicle is stored in for DismountVehicleCargoMsg use
|
||||
* DismountVehicleCargoMsg only passes the player_guid and this vehicle's guid
|
||||
*/
|
||||
//private var mountedIn: Option[PlanetSideGUID] = None
|
||||
|
||||
private var vehicleGatingManifest: Option[VehicleManifest] = None
|
||||
private var previousVehicleGatingManifest: Option[VehicleManifest] = None
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package net.psforever.objects.serverobject.doors
|
||||
|
||||
import net.psforever.objects.Player
|
||||
import akka.actor.ActorRef
|
||||
import net.psforever.objects.{Default, Doors, Player}
|
||||
import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject}
|
||||
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
|
||||
import net.psforever.objects.serverobject.locks.IFFLock
|
||||
|
|
@ -17,8 +18,9 @@ class DoorControl(door: Door)
|
|||
extends PoweredAmenityControl
|
||||
with FactionAffinityBehavior.Check {
|
||||
def FactionObject: FactionAffinity = door
|
||||
var isLocked: Boolean = false
|
||||
var lockingMechanism: Door.LockingMechanismLogic = DoorControl.alwaysOpen
|
||||
|
||||
private var isLocked: Boolean = false
|
||||
private var lockingMechanism: Door.LockingMechanismLogic = DoorControl.alwaysOpen
|
||||
|
||||
def commonBehavior: Receive = checkBehavior
|
||||
.orElse {
|
||||
|
|
@ -40,15 +42,16 @@ class DoorControl(door: Door)
|
|||
def poweredStateLogic: Receive =
|
||||
commonBehavior
|
||||
.orElse {
|
||||
case CommonMessages.Use(player, Some(customDistance: Float)) =>
|
||||
testToOpenDoor(player, door, customDistance, sender())
|
||||
|
||||
case CommonMessages.Use(player, _) =>
|
||||
if (lockingMechanism(player, door) && !isLocked) {
|
||||
openDoor(player)
|
||||
}
|
||||
testToOpenDoor(player, door, door.Definition.initialOpeningDistance, sender())
|
||||
|
||||
case IFFLock.DoorOpenResponse(target: Player) if !isLocked =>
|
||||
openDoor(target)
|
||||
DoorControl.openDoor(target, door)
|
||||
|
||||
case _ => ;
|
||||
case _ => ()
|
||||
}
|
||||
|
||||
def unpoweredStateLogic: Receive = {
|
||||
|
|
@ -56,30 +59,33 @@ class DoorControl(door: Door)
|
|||
.orElse {
|
||||
case CommonMessages.Use(player, _) if !isLocked =>
|
||||
//without power, the door opens freely
|
||||
openDoor(player)
|
||||
DoorControl.openDoor(player, door)
|
||||
|
||||
case _ => ;
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
|
||||
def openDoor(player: Player): Unit = {
|
||||
val zone = door.Zone
|
||||
val doorGUID = door.GUID
|
||||
if (!door.isOpen) {
|
||||
//global open
|
||||
door.Open = player
|
||||
zone.LocalEvents ! LocalServiceMessage(
|
||||
zone.id,
|
||||
LocalAction.DoorOpens(Service.defaultPlayerGUID, zone, door)
|
||||
)
|
||||
}
|
||||
else {
|
||||
//the door should already open, but the requesting player does not see it as open
|
||||
sender() ! LocalServiceResponse(
|
||||
player.Name,
|
||||
Service.defaultPlayerGUID,
|
||||
LocalResponse.DoorOpens(doorGUID)
|
||||
)
|
||||
/**
|
||||
* If the player is close enough to the door,
|
||||
* the locking mechanism allows for the door to open,
|
||||
* and the door is not bolted shut (locked),
|
||||
* then tell the door that it should open.
|
||||
* @param player player who is standing somewhere
|
||||
* @param door door that is installed somewhere
|
||||
* @param maximumDistance permissible square distance between the player and the door
|
||||
* @param replyTo the player's session message reference
|
||||
*/
|
||||
private def testToOpenDoor(
|
||||
player: Player,
|
||||
door: Door,
|
||||
maximumDistance: Float,
|
||||
replyTo: ActorRef
|
||||
): Unit = {
|
||||
if (
|
||||
Doors.testForSpecificTargetHoldingDoorOpen(player, door, maximumDistance * maximumDistance).contains(player) &&
|
||||
lockingMechanism(player, door) && !isLocked
|
||||
) {
|
||||
DoorControl.openDoor(player, door, replyTo)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -89,5 +95,33 @@ class DoorControl(door: Door)
|
|||
}
|
||||
|
||||
object DoorControl {
|
||||
//noinspection ScalaUnusedSymbol
|
||||
def alwaysOpen(obj: PlanetSideServerObject, door: Door): Boolean = true
|
||||
|
||||
/**
|
||||
* If the door is not open, open this door, propped open by the given player.
|
||||
* If the door is considered open, ensure the door is proper visible as open to the player.
|
||||
* @param player the player
|
||||
* @param door the door
|
||||
* @param replyTo the player's session message reference
|
||||
*/
|
||||
private def openDoor(player: Player, door: Door, replyTo: ActorRef = Default.Actor): Unit = {
|
||||
val zone = door.Zone
|
||||
val doorGUID = door.GUID
|
||||
if (!door.isOpen) {
|
||||
//global open
|
||||
door.Open = player
|
||||
zone.LocalEvents ! LocalServiceMessage(
|
||||
zone.id,
|
||||
LocalAction.DoorOpens(Service.defaultPlayerGUID, zone, door)
|
||||
)
|
||||
} else {
|
||||
//the door should already open, but the requesting player does not see it as open
|
||||
replyTo ! LocalServiceResponse(
|
||||
player.Name,
|
||||
Service.defaultPlayerGUID,
|
||||
LocalResponse.DoorOpens(doorGUID)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,4 +9,9 @@ import net.psforever.objects.serverobject.structures.AmenityDefinition
|
|||
class DoorDefinition(objectId: Int)
|
||||
extends AmenityDefinition(objectId) {
|
||||
Name = "door"
|
||||
/** range wherein the door may first be opened
|
||||
* (note: intentionally inflated as the initial check on the client occurs further than expected) */
|
||||
var initialOpeningDistance: Float = 7.5f
|
||||
/** range within which the door must detect a target player to remain open */
|
||||
var continuousOpenDistance: Float = 5.05f
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,4 +39,4 @@ object NonvitalDefinition {
|
|||
out
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ trait CargoBehavior {
|
|||
case _ => ;
|
||||
}
|
||||
isDismounting = None
|
||||
startCargoDismounting(bailed = false)
|
||||
startCargoDismountingNoCleanup(bailed = false)
|
||||
}
|
||||
|
||||
val cargoBehavior: Receive = {
|
||||
|
|
@ -64,24 +64,26 @@ trait CargoBehavior {
|
|||
}
|
||||
|
||||
def startCargoDismounting(bailed: Boolean): Unit = {
|
||||
val obj = CargoObject
|
||||
obj.Zone.GUID(obj.MountedIn) match {
|
||||
case Some(carrier: Vehicle) =>
|
||||
carrier.CargoHolds.find { case (_, hold) => hold.occupant.contains(obj) } match {
|
||||
case Some((mountPoint, _))
|
||||
if isDismounting.isEmpty && isMounting.isEmpty =>
|
||||
isDismounting = obj.MountedIn
|
||||
carrier.Actor ! CarrierBehavior.CheckCargoDismount(obj.GUID, mountPoint, 0, bailed)
|
||||
|
||||
case _ =>
|
||||
obj.MountedIn = None
|
||||
isDismounting = None
|
||||
}
|
||||
case _ =>
|
||||
obj.MountedIn = None
|
||||
isDismounting = None
|
||||
if (!startCargoDismountingNoCleanup(bailed)) {
|
||||
isDismounting = None
|
||||
CargoObject.MountedIn = None
|
||||
}
|
||||
}
|
||||
|
||||
def startCargoDismountingNoCleanup(bailed: Boolean): Boolean = {
|
||||
val obj = CargoObject
|
||||
obj.Zone.GUID(obj.MountedIn)
|
||||
.collect { case carrier: Vehicle =>
|
||||
(carrier, carrier.CargoHolds.find { case (_, hold) => hold.occupant.contains(obj) })
|
||||
}
|
||||
.collect { case (carrier, Some((mountPoint, _)))
|
||||
if isDismounting.isEmpty && isMounting.isEmpty =>
|
||||
isDismounting = obj.MountedIn
|
||||
carrier.Actor ! CarrierBehavior.CheckCargoDismount(obj.GUID, mountPoint, 0, bailed)
|
||||
true
|
||||
}
|
||||
.nonEmpty
|
||||
}
|
||||
}
|
||||
|
||||
object CargoBehavior {
|
||||
|
|
|
|||
|
|
@ -654,4 +654,3 @@ object CarrierBehavior {
|
|||
msgs
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -40,4 +40,3 @@ trait MountableWeapons
|
|||
|
||||
def Definition: MountableWeaponsDefinition
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -55,4 +55,3 @@ class AmsControl(vehicle: Vehicle)
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -613,7 +613,7 @@ object BfrControl {
|
|||
final val Enabled = 38
|
||||
final val Disabled = 39
|
||||
}
|
||||
|
||||
|
||||
private case object VehicleExplosion
|
||||
|
||||
val dimorphics: List[EquipmentHandiness] = {
|
||||
|
|
|
|||
|
|
@ -25,15 +25,16 @@ class CargoCarrierControl(vehicle: Vehicle)
|
|||
|
||||
/**
|
||||
* If the vehicle becomes disabled, the safety and autonomy of the cargo should be prioritized.
|
||||
* @param kickPassengers passengers need to be ejected "by force"
|
||||
* @param kickPassengers passengers need to be ejected "by force";
|
||||
* actually controls bail protection and flavors a log message (further down the line)
|
||||
*/
|
||||
override def PrepareForDisabled(kickPassengers: Boolean) : Unit = {
|
||||
super.PrepareForDisabled(kickPassengers)
|
||||
//abandon all cargo
|
||||
vehicle.CargoHolds.collect {
|
||||
case (index, hold : Cargo) if hold.isOccupied =>
|
||||
val cargo = hold.occupant.get
|
||||
checkCargoDismount(cargo.GUID, index, iteration = 0, bailed = false)
|
||||
super.PrepareForDisabled(kickPassengers)
|
||||
checkCargoDismount(cargo.GUID, index, iteration = 0, bailed = kickPassengers)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -366,7 +366,7 @@ class VehicleControl(vehicle: Vehicle)
|
|||
//escape being someone else's cargo
|
||||
vehicle.MountedIn match {
|
||||
case Some(_) =>
|
||||
startCargoDismounting(bailed = false)
|
||||
startCargoDismounting(bailed = true)
|
||||
case _ => ;
|
||||
}
|
||||
if (!vehicle.isFlying || kickPassengers) {
|
||||
|
|
|
|||
|
|
@ -59,4 +59,4 @@ object TriggerUsedReason {
|
|||
ResistUsing = NoResistanceSelection
|
||||
Model = SimpleResolutions.calculate
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -549,7 +549,7 @@ class CavernRotationService(
|
|||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param sendToSession callback reference
|
||||
*/
|
||||
def sendCavernRotationUpdates(sendToSession: ActorRef): Unit = {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
// Copyright (c) 2020 PSForever
|
||||
package net.psforever.services
|
||||
|
||||
import akka.actor.typed.receptionist.{Receptionist, ServiceKey}
|
||||
|
|
@ -7,7 +8,7 @@ import net.psforever.actors.zone.ZoneActor
|
|||
import net.psforever.objects.avatar.Avatar
|
||||
import net.psforever.objects.{Player, SpawnPoint, Vehicle}
|
||||
import net.psforever.objects.serverobject.structures.{Building, WarpGate}
|
||||
import net.psforever.objects.zones.Zone
|
||||
import net.psforever.objects.zones.{HotSpotInfo, Zone}
|
||||
import net.psforever.packet.game.DroppodError
|
||||
import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID, SpawnGroup, Vector3}
|
||||
import net.psforever.util.Config
|
||||
|
|
@ -101,8 +102,8 @@ class InterstellarClusterService(context: ActorContext[InterstellarClusterServic
|
|||
import InterstellarClusterService._
|
||||
|
||||
private[this] val log = org.log4s.getLogger
|
||||
var intercontinentalSetup: Boolean = false
|
||||
var cavernRotation: Option[ActorRef[CavernRotationService.Command]] = None
|
||||
private var intercontinentalSetup: Boolean = false
|
||||
private var cavernRotation: Option[ActorRef[CavernRotationService.Command]] = None
|
||||
|
||||
val zoneActors: mutable.Map[String, (ActorRef[ZoneActor.Command], Zone)] = {
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
|
|
@ -164,47 +165,44 @@ class InterstellarClusterService(context: ActorContext[InterstellarClusterServic
|
|||
replyTo ! ZonesResponse(zones.filter(predicate))
|
||||
|
||||
case GetInstantActionSpawnPoint(faction, replyTo) =>
|
||||
val res = zones
|
||||
.filter(_.Players.nonEmpty)
|
||||
.flatMap { zone =>
|
||||
zone.HotSpotData.collect {
|
||||
case spot => (zone, spot)
|
||||
val spawnTarget: Seq[SpawnGroup] = if (Config.app.game.instantAction.spawnOnAms) {
|
||||
Seq(SpawnGroup.Tower, SpawnGroup.Facility, SpawnGroup.AMS)
|
||||
} else {
|
||||
Seq(SpawnGroup.Tower, SpawnGroup.Facility)
|
||||
}
|
||||
val hotspotsAndSpawnsInZones: Iterable[(Zone, HotSpotInfo, List[SpawnPoint])] = zones
|
||||
.collect {
|
||||
case zone if !zone.map.cavern && zone.Players.nonEmpty =>
|
||||
zone.HotSpotData
|
||||
.map {
|
||||
info => (zone, info, zone.findNearestSpawnPoints(faction, info.DisplayLocation, spawnTarget))
|
||||
}
|
||||
}
|
||||
.flatten
|
||||
.collect {
|
||||
case (zone, info, Some(spawns)) if spawns.nonEmpty => (zone, info, spawns)
|
||||
}
|
||||
val spawnResults: Option[(Zone, SpawnPoint)] = hotspotsAndSpawnsInZones
|
||||
.maxByOption {
|
||||
case (_, info, _) => info.ActivityFor(faction).map(_.Heat).getOrElse(0) //heat by target faction
|
||||
}
|
||||
.orElse {
|
||||
if (Config.app.game.instantAction.thirdParty) {
|
||||
hotspotsAndSpawnsInZones
|
||||
.maxByOption {
|
||||
case (_, info, _) => info.Activity.values.foldLeft(0)(_ + _.Heat) //sum of heat for all factions
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
.map {
|
||||
case (zone, spot) =>
|
||||
(
|
||||
zone,
|
||||
spot,
|
||||
zone.findNearestSpawnPoints(
|
||||
faction,
|
||||
spot.DisplayLocation,
|
||||
if (Config.app.game.instantActionAms) Seq(SpawnGroup.Tower, SpawnGroup.Facility, SpawnGroup.AMS)
|
||||
else Seq(SpawnGroup.Tower, SpawnGroup.Facility)
|
||||
)
|
||||
)
|
||||
}
|
||||
.collect {
|
||||
case (zone, info, Some(spawns)) => (zone, info, spawns)
|
||||
}
|
||||
.toList
|
||||
.sortBy { case (_, spot, _) => spot.Activity.values.foldLeft(0)(_ + _.Heat) }(
|
||||
Ordering[Int].reverse
|
||||
) // greatest > least
|
||||
.sortWith {
|
||||
case ((_, spot1, _), (_, _, _)) =>
|
||||
spot1.ActivityBy().contains(faction) // prefer own faction activity
|
||||
}
|
||||
.headOption
|
||||
.flatMap {
|
||||
case (zone, info, spawns) =>
|
||||
val pos = info.DisplayLocation
|
||||
val pos = info.DisplayLocation
|
||||
val spawnPoint = spawns.minBy(point => Vector3.DistanceSquared(point.Position, pos))
|
||||
//Some(zone, pos, spawnPoint)
|
||||
Some(zone, spawnPoint)
|
||||
case _ => None
|
||||
(zone, spawnPoint)
|
||||
}
|
||||
replyTo ! SpawnPointResponse(res)
|
||||
replyTo ! SpawnPointResponse(spawnResults)
|
||||
|
||||
case GetRandomSpawnPoint(zoneNumber, faction, spawnGroups, replyTo) =>
|
||||
val response = zones.find(_.Number == zoneNumber) match {
|
||||
|
|
|
|||
|
|
@ -56,4 +56,4 @@ object HartTimerActions {
|
|||
LocalAction.ShuttleState(shuttle.GUID, shuttle.Position, shuttle.Orientation, state)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,11 +2,10 @@
|
|||
package net.psforever.services.local.support
|
||||
|
||||
import akka.actor.{Actor, Cancellable}
|
||||
import net.psforever.objects.{Default, Player}
|
||||
import net.psforever.objects.{Default, Doors}
|
||||
import net.psforever.objects.serverobject.doors.Door
|
||||
import net.psforever.objects.serverobject.structures.Building
|
||||
import net.psforever.objects.zones.Zone
|
||||
import net.psforever.types.{PlanetSideGUID, Vector3}
|
||||
import net.psforever.types.PlanetSideGUID
|
||||
|
||||
import scala.annotation.tailrec
|
||||
import scala.concurrent.duration._
|
||||
|
|
@ -17,7 +16,6 @@ import scala.concurrent.duration._
|
|||
* @see `LocalService`
|
||||
*/
|
||||
class DoorCloseActor() extends Actor {
|
||||
|
||||
/** The periodic `Executor` that checks for doors to be closed */
|
||||
private var doorCloserTrigger: Cancellable = Default.Cancellable
|
||||
|
||||
|
|
@ -36,43 +34,11 @@ class DoorCloseActor() extends Actor {
|
|||
|
||||
case DoorCloseActor.TryCloseDoors() =>
|
||||
doorCloserTrigger.cancel()
|
||||
val now: Long = System.nanoTime
|
||||
val now: Long = System.currentTimeMillis()
|
||||
val (doorsToClose1, doorsLeftOpen1) = PartitionEntries(openDoors, now)
|
||||
val (doorsToClose2, doorsLeftOpen2) = doorsToClose1.partition(entry => {
|
||||
entry.door.Open match {
|
||||
case Some(player) =>
|
||||
// If the player that opened the door is far enough away, or they're dead,
|
||||
var openerIsGone = Vector3.MagnitudeSquared(entry.door.Position - player.Position) > 25.5 || !player.isAlive
|
||||
|
||||
if (openerIsGone) {
|
||||
// Check nobody else is nearby to hold the door opens
|
||||
val playersToCheck: List[Player] =
|
||||
if (
|
||||
entry.door.Owner
|
||||
.isInstanceOf[Building] && entry.door.Owner.asInstanceOf[Building].Definition.SOIRadius > 0
|
||||
) {
|
||||
entry.door.Owner.asInstanceOf[Building].PlayersInSOI
|
||||
} else {
|
||||
entry.zone.LivePlayers
|
||||
}
|
||||
|
||||
playersToCheck
|
||||
.filter(x => x.isAlive && Vector3.MagnitudeSquared(entry.door.Position - x.Position) < 25.5)
|
||||
.headOption match {
|
||||
case Some(newOpener) =>
|
||||
// Another player is near the door, keep it open
|
||||
entry.door.Open = newOpener
|
||||
openerIsGone = false
|
||||
case _ => ;
|
||||
}
|
||||
}
|
||||
|
||||
openerIsGone
|
||||
case None =>
|
||||
// Door should not be open. Mark it to be closed.
|
||||
true
|
||||
}
|
||||
})
|
||||
val (doorsToClose2, doorsLeftOpen2) = doorsToClose1.partition { entry =>
|
||||
Doors.testForTargetHoldingDoorOpen(entry.door, entry.door.Definition.continuousOpenDistance).isEmpty
|
||||
}
|
||||
openDoors = (
|
||||
doorsLeftOpen1 ++
|
||||
doorsLeftOpen2.map(entry => DoorCloseActor.DoorEntry(entry.door, entry.zone, now))
|
||||
|
|
@ -89,7 +55,7 @@ class DoorCloseActor() extends Actor {
|
|||
doorCloserTrigger = context.system.scheduler.scheduleOnce(short_timeout, self, DoorCloseActor.TryCloseDoors())
|
||||
}
|
||||
|
||||
case _ => ;
|
||||
case _ => ()
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -144,9 +110,9 @@ class DoorCloseActor() extends Actor {
|
|||
object DoorCloseActor {
|
||||
|
||||
/** The wait before an open door closes; as a Long for calculation simplicity */
|
||||
private final val timeout_time: Long = 5000000000L //nanoseconds (5s)
|
||||
private final val timeout_time: Long = 5000L //milliseconds (5s)
|
||||
/** The wait before an open door closes; as a `FiniteDuration` for `Executor` simplicity */
|
||||
private final val timeout: FiniteDuration = timeout_time nanoseconds
|
||||
private final val timeout: FiniteDuration = timeout_time milliseconds
|
||||
|
||||
/**
|
||||
* Message that carries information about a door that has been opened.
|
||||
|
|
@ -155,7 +121,7 @@ object DoorCloseActor {
|
|||
* @param time when the door was opened
|
||||
* @see `DoorEntry`
|
||||
*/
|
||||
final case class DoorIsOpen(door: Door, zone: Zone, time: Long = System.nanoTime())
|
||||
final case class DoorIsOpen(door: Door, zone: Zone, time: Long = System.currentTimeMillis())
|
||||
|
||||
/**
|
||||
* Message that carries information about a door that needs to close.
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,4 +12,4 @@ object MemberAction extends Enumeration {
|
|||
RemoveIgnoredPlayer = Value
|
||||
|
||||
implicit val codec: Codec[MemberAction.Value] = PacketHelpers.createEnumerationCodec(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,13 +40,13 @@ 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 {
|
||||
case e if e.toString.toLowerCase == v.toLowerCase => e.asInstanceOf[A]
|
||||
e.values.toList.collectFirst {
|
||||
case e if e.toString.toLowerCase == v.toLowerCase => e
|
||||
},
|
||||
_.toString
|
||||
)
|
||||
|
|
@ -62,25 +62,23 @@ object Config {
|
|||
// Raw config object - prefer app when possible
|
||||
lazy val config: TypesafeConfig = source.config() match {
|
||||
case Right(config) => config
|
||||
case Left(failures) => {
|
||||
case Left(failures) =>
|
||||
logger.error("Loading config failed")
|
||||
failures.toList.foreach { failure =>
|
||||
logger.error(failure.toString)
|
||||
}
|
||||
sys.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// Typed config object
|
||||
lazy val app: AppConfig = source.load[AppConfig] match {
|
||||
case Right(config) => config
|
||||
case Left(failures) => {
|
||||
case Left(failures) =>
|
||||
logger.error("Loading config failed")
|
||||
failures.toList.foreach { failure =>
|
||||
logger.error(failure.toString)
|
||||
}
|
||||
sys.exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -148,7 +146,7 @@ case class SessionConfig(
|
|||
)
|
||||
|
||||
case class GameConfig(
|
||||
instantActionAms: Boolean,
|
||||
instantAction: InstantActionConfig,
|
||||
amenityAutorepairRate: Float,
|
||||
amenityAutorepairDrainRate: Float,
|
||||
bepRate: Double,
|
||||
|
|
@ -161,6 +159,12 @@ case class GameConfig(
|
|||
cavernRotation: CavernRotationConfig,
|
||||
savedMsg: SavedMessageEvents,
|
||||
playerDraw: PlayerStateDrawSettings
|
||||
doorsCanBeOpenedByMedAppFromThisDistance: Float
|
||||
)
|
||||
|
||||
case class InstantActionConfig(
|
||||
spawnOnAms: Boolean,
|
||||
thirdParty: Boolean
|
||||
)
|
||||
|
||||
case class NewAvatar(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue