Merge branch 'master' into reduce-psm-load

This commit is contained in:
Fate-JH 2023-04-21 23:31:42 -04:00 committed by GitHub
commit 61a75f13cd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
106 changed files with 468298 additions and 468246 deletions

View file

@ -76,4 +76,4 @@ ignore:
- "src/main/scala/net/psforever/services/local/LocalAction.scala" - "src/main/scala/net/psforever/services/local/LocalAction.scala"
- "src/main/scala/net/psforever/services/local/LocalResponse.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/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
View file

@ -0,0 +1 @@
FROM mcr.microsoft.com/vscode/devcontainers/base:debian

View 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"
}

View 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
View file

@ -0,0 +1,8 @@
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

View file

@ -35,4 +35,4 @@ jobs:
uses: docker/build-push-action@v2 uses: docker/build-push-action@v2
with: with:
push: true push: true
tags: ${{ steps.prep.outputs.tags }} tags: ${{ steps.prep.outputs.tags }}

View file

@ -46,4 +46,4 @@ jobs:
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v2
with: with:
name: server.zip name: server.zip
path: server/target/psforever-server-*.zip path: server/target/psforever-server-*.zip

View file

@ -2,3 +2,4 @@
-Xss6M -Xss6M
-Dconfig.override_with_env_vars=true -Dconfig.override_with_env_vars=true
-Dsbt.server.forcestart=true -Dsbt.server.forcestart=true
-Dquill.macro.log=false

View file

@ -1,3 +1,3 @@
version = 2.6.4 version = 2.6.4
preset = defaultWithAlign preset = defaultWithAlign
maxColumn = 120 maxColumn = 120

View file

@ -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 COPY . /PSF-LoginServer

View file

@ -1,7 +1,7 @@
GNU General Public License GNU General Public License
========================== ==========================
_Version 3, 29 June 2007_ _Version 3, 29 June 2007_
_Copyright © 2007 Free Software Foundation, Inc. &lt;<http://fsf.org/>&gt;_ _Copyright © 2007 Free Software Foundation, Inc. &lt;<http://fsf.org/>&gt;_
Everyone is permitted to copy and distribute verbatim copies of this license Everyone is permitted to copy and distribute verbatim copies of this license

View file

@ -30,7 +30,7 @@ which has the instructions on downloading the game and using the PSForever launc
- Up to date - Up to date
- [PostgreSQL](https://www.postgresql.org/) - [PostgreSQL](https://www.postgresql.org/)
- 10+ - 10+
- Development (+Running) - Development (+Running)
- [Git](https://en.wikipedia.org/wiki/Git) - [Git](https://en.wikipedia.org/wiki/Git)
- IDE or Text Editor - IDE or Text Editor
@ -87,7 +87,7 @@ arguments - is recommended in order to avoid this startup time.
### PostgreSQL Database ### PostgreSQL Database
A database is required for persistence of game state and player characters. The login server and game server (which are 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 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 [Windows](https://www.postgresql.org/download/windows/);
for Linux [Debian](https://www.postgresql.org/download/linux/debian/), for Linux [Debian](https://www.postgresql.org/download/linux/debian/),
for Linux [Ubuntu](https://www.postgresql.org/download/linux/ubuntu/); 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 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, 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. 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".) "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.) (Check menu -> Properties to confirm. May need to refresh first to see these changes.)
```sql ```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 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 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 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 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). 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) [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 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 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 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, 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 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. 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 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. database does not support [scram-sha-256](https://www.postgresql.org/docs/current/auth-password.html) authentication.

View file

@ -3,7 +3,9 @@ import xerial.sbt.pack.PackPlugin._
lazy val psforeverSettings = Seq( lazy val psforeverSettings = Seq(
organization := "net.psforever", organization := "net.psforever",
version := "1.0.2-SNAPSHOT", 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, Global / cancelable := false,
semanticdbEnabled := true, semanticdbEnabled := true,
semanticdbVersion := scalafixSemanticdb.revision, semanticdbVersion := scalafixSemanticdb.revision,
@ -40,53 +42,50 @@ lazy val psforeverSettings = Seq(
classLoaderLayeringStrategy := ClassLoaderLayeringStrategy.Flat, classLoaderLayeringStrategy := ClassLoaderLayeringStrategy.Flat,
resolvers += "Sonatype OSS Snapshots" at "https://oss.sonatype.org/content/repositories/snapshots", resolvers += "Sonatype OSS Snapshots" at "https://oss.sonatype.org/content/repositories/snapshots",
libraryDependencies ++= Seq( libraryDependencies ++= Seq(
"com.typesafe.akka" %% "akka-actor" % "2.6.17", "com.typesafe.akka" %% "akka-actor" % "2.6.20",
"com.typesafe.akka" %% "akka-slf4j" % "2.6.17", "com.typesafe.akka" %% "akka-slf4j" % "2.6.20",
"com.typesafe.akka" %% "akka-protobuf-v3" % "2.6.17", "com.typesafe.akka" %% "akka-protobuf-v3" % "2.6.20",
"com.typesafe.akka" %% "akka-stream" % "2.6.17", "com.typesafe.akka" %% "akka-stream" % "2.6.20",
"com.typesafe.akka" %% "akka-testkit" % "2.6.17" % "test", "com.typesafe.akka" %% "akka-testkit" % "2.6.20" % "test",
"com.typesafe.akka" %% "akka-actor-typed" % "2.6.17", "com.typesafe.akka" %% "akka-actor-typed" % "2.6.20",
"com.typesafe.akka" %% "akka-actor-testkit-typed" % "2.6.17" % "test", "com.typesafe.akka" %% "akka-actor-testkit-typed" % "2.6.20" % "test",
"com.typesafe.akka" %% "akka-slf4j" % "2.6.17", "com.typesafe.akka" %% "akka-slf4j" % "2.6.20",
"com.typesafe.akka" %% "akka-cluster-typed" % "2.6.17", "com.typesafe.akka" %% "akka-cluster-typed" % "2.6.20",
"com.typesafe.akka" %% "akka-coordination" % "2.6.17", "com.typesafe.akka" %% "akka-coordination" % "2.6.20",
"com.typesafe.akka" %% "akka-cluster-tools" % "2.6.17", "com.typesafe.akka" %% "akka-cluster-tools" % "2.6.20",
"com.typesafe.akka" %% "akka-http" % "10.2.6", "com.typesafe.akka" %% "akka-http" % "10.2.6",
"com.typesafe.scala-logging" %% "scala-logging" % "3.9.4", "com.typesafe.scala-logging" %% "scala-logging" % "3.9.4",
"org.specs2" %% "specs2-core" % "4.13.0" % "test", "org.specs2" %% "specs2-core" % "4.20.0" % "test",
"org.scalatest" %% "scalatest" % "3.2.10" % "test", "org.scalatest" %% "scalatest" % "3.2.15" % "test",
"org.scodec" %% "scodec-core" % "1.11.9", "org.scodec" %% "scodec-core" % "1.11.9",
"ch.qos.logback" % "logback-classic" % "1.2.6", "ch.qos.logback" % "logback-classic" % "1.2.6",
"org.log4s" %% "log4s" % "1.10.0", "org.log4s" %% "log4s" % "1.10.0",
"org.fusesource.jansi" % "jansi" % "2.4.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.nscala-time" %% "nscala-time" % "2.30.0",
"com.github.t3hnar" %% "scala-bcrypt" % "4.3.0", "com.github.t3hnar" %% "scala-bcrypt" % "4.3.0",
"org.scala-graph" %% "graph-core" % "1.13.3", "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", "org.json4s" %% "json4s-native" % "4.0.3",
"io.getquill" %% "quill-jasync-postgres" % "3.12.0", "io.getquill" %% "quill-jasync-postgres" % "3.18.0",
"org.flywaydb" % "flyway-core" % "8.0.3", "org.flywaydb" % "flyway-core" % "9.0.0",
"org.postgresql" % "postgresql" % "42.3.1", "org.postgresql" % "postgresql" % "42.3.1",
"com.typesafe" % "config" % "1.4.1", "com.typesafe" % "config" % "1.4.1",
"com.github.pureconfig" %% "pureconfig" % "0.17.0", "com.github.pureconfig" %% "pureconfig" % "0.17.0",
"com.beachape" %% "enumeratum" % "1.7.0", "com.beachape" %% "enumeratum" % "1.7.0",
"joda-time" % "joda-time" % "2.10.13",
"commons-io" % "commons-io" % "2.11.0", "commons-io" % "commons-io" % "2.11.0",
"com.github.scopt" %% "scopt" % "4.0.1", "com.github.scopt" %% "scopt" % "4.1.0",
"io.sentry" % "sentry-logback" % "5.3.0", "io.sentry" % "sentry-logback" % "6.16.0",
"io.circe" %% "circe-core" % "0.14.1", "io.circe" %% "circe-core" % "0.14.5",
"io.circe" %% "circe-generic" % "0.14.1", "io.circe" %% "circe-generic" % "0.14.5",
"io.circe" %% "circe-parser" % "0.14.1", "io.circe" %% "circe-parser" % "0.14.5",
"org.scala-lang.modules" %% "scala-parallel-collections" % "1.0.4", "org.scala-lang.modules" %% "scala-parallel-collections" % "1.0.4",
"org.bouncycastle" % "bcprov-jdk15on" % "1.69" "org.bouncycastle" % "bcprov-jdk15on" % "1.69"
), ),
dependencyOverrides ++= Seq( 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 // 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(".")) lazy val psforever = (project in file("."))

View file

@ -3,19 +3,19 @@
\usepackage{lmodern} \usepackage{lmodern}
\usepackage{graphicx} \usepackage{graphicx}
\usepackage[margin=1in]{geometry} \usepackage[margin=1in]{geometry}
\usepackage{float} \usepackage{float}
\usepackage{xcolor} \usepackage{xcolor}
\usepackage{hyperref} \usepackage{hyperref}
\usepackage{float} \usepackage{float}
\usepackage{amsmath} \usepackage{amsmath}
\begin{document} \begin{document}
\title{PSForever Server Notes} \title{PSForever Server Notes}
\author{Chord $<$chord@tuta.io$>$} \author{Chord $<$chord@tuta.io$>$}
\maketitle \maketitle
%\section*{Security Model} %\section*{Security Model}

View file

@ -1 +1 @@
sbt.version = 1.4.5 sbt.version = 1.8.2

View file

@ -1,8 +1,7 @@
logLevel := Level.Warn logLevel := Level.Warn
addSbtPlugin("org.xerial.sbt" % "sbt-pack" % "0.14") addSbtPlugin("org.xerial.sbt" % "sbt-pack" % "0.17")
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.9.1") addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.0.7")
addSbtPlugin("io.kamon" % "sbt-kanela-runner" % "2.0.12") addSbtPlugin("com.eed3si9n" % "sbt-unidoc" % "0.4.3")
addSbtPlugin("com.eed3si9n" % "sbt-unidoc" % "0.4.3") addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.0")
addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.3") addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.10.4")
addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.31")

View file

@ -53,7 +53,7 @@ for f in $FILES; do
else else
SED_CMD='s#'"$LINESPEC_EXISTING_COPY"'#'"$COPYRIGHT"'#' SED_CMD='s#'"$LINESPEC_EXISTING_COPY"'#'"$COPYRIGHT"'#'
echo "Replacing '$LINESPEC_EXISTING_COPY' --> '$COPYRIGHT'" echo "Replacing '$LINESPEC_EXISTING_COPY' --> '$COPYRIGHT'"
sed -i -b "$SED_CMD" "$f" sed -i -b "$SED_CMD" "$f"
fi fi
else else
echo "$f: Not found" echo "$f: Not found"
@ -63,7 +63,7 @@ for f in $FILES; do
if [ $CHOICE = "n" ]; then if [ $CHOICE = "n" ]; then
: :
else else
sed -i -b '1i '"$COPYRIGHT"'' "$f" sed -i -b '1i '"$COPYRIGHT"'' "$f"
fi fi
fi fi
fi fi

View file

@ -3,4 +3,4 @@ CREATE TABLE IF NOT EXISTS "buildings" (
zone_id INT NOT NULL, zone_id INT NOT NULL,
faction_id INT NOT NULL, faction_id INT NOT NULL,
PRIMARY KEY (local_id, zone_id) PRIMARY KEY (local_id, zone_id)
); );

View file

@ -26,4 +26,4 @@ CREATE TABLE implant (
name TEXT NOT NULL, name TEXT NOT NULL,
avatar_id INT NOT NULL REFERENCES avatar (id), avatar_id INT NOT NULL REFERENCES avatar (id),
PRIMARY KEY (name, avatar_id) PRIMARY KEY (name, avatar_id)
); );

View file

@ -1 +1 @@
stacktrace.app.packages=net.psforever stacktrace.app.packages=net.psforever

View file

@ -5,6 +5,8 @@ import java.nio.file.Paths
import java.util.Locale import java.util.Locale
import java.util.UUID.randomUUID import java.util.UUID.randomUUID
import java.util.concurrent.atomic.AtomicLong import java.util.concurrent.atomic.AtomicLong
import scala.concurrent.Future
import scala.concurrent.Await
import akka.actor.ActorSystem import akka.actor.ActorSystem
import akka.actor.typed.ActorRef import akka.actor.typed.ActorRef
@ -13,7 +15,6 @@ import akka.{actor => classic}
import ch.qos.logback.classic.LoggerContext import ch.qos.logback.classic.LoggerContext
import ch.qos.logback.classic.joran.JoranConfigurator import ch.qos.logback.classic.joran.JoranConfigurator
import io.sentry.{Sentry, SentryOptions} import io.sentry.{Sentry, SentryOptions}
import kamon.Kamon
import net.psforever.actors.net.{LoginActor, MiddlewareActor, SocketActor} import net.psforever.actors.net.{LoginActor, MiddlewareActor, SocketActor}
import net.psforever.actors.session.SessionActor import net.psforever.actors.session.SessionActor
import net.psforever.login.psadmin.PsAdminActor import net.psforever.login.psadmin.PsAdminActor
@ -37,6 +38,7 @@ import scopt.OParser
import akka.actor.typed.scaladsl.adapter._ import akka.actor.typed.scaladsl.adapter._
import net.psforever.packet.PlanetSidePacket import net.psforever.packet.PlanetSidePacket
import net.psforever.services.hart.HartService import net.psforever.services.hart.HartService
import scala.concurrent.duration.Duration
object Server { object Server {
private val logger = org.log4s.getLogger private val logger = org.log4s.getLogger
@ -80,11 +82,6 @@ object Server {
case None => InetAddress.getByName(Config.app.bind) // address from config 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) { if (Config.app.sentry.enable) {
logger.info(s"Enabling Sentry") logger.info(s"Enabling Sentry")
val options = new SentryOptions() val options = new SentryOptions()
@ -110,8 +107,9 @@ object Server {
} }
val session = (ref: ActorRef[MiddlewareActor.Command], info: InetSocketAddress, connectionId: String) => { val session = (ref: ActorRef[MiddlewareActor.Command], info: InetSocketAddress, connectionId: String) => {
Behaviors.setup[PlanetSidePacket](context => { Behaviors.setup[PlanetSidePacket](context => {
val uuid = randomUUID().toString val uuid = randomUUID().toString
val actor = context.actorOf(classic.Props(new SessionActor(ref, connectionId, Session.getNewId())), s"session-$uuid") val actor =
context.actorOf(classic.Props(new SessionActor(ref, connectionId, Session.getNewId())), s"session-$uuid")
Behaviors.receiveMessage(message => { Behaviors.receiveMessage(message => {
actor ! message actor ! message
Behaviors.same 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 val serviceManager = ServiceManager.boot
serviceManager ! ServiceManager.Register(classic.Props[AccountIntermediaryService](), "accountIntermediary") serviceManager ! ServiceManager.Register(classic.Props[AccountIntermediaryService](), "accountIntermediary")
serviceManager ! ServiceManager.Register(classic.Props[GalaxyService](), "galaxy") serviceManager ! ServiceManager.Register(classic.Props[GalaxyService](), "galaxy")
@ -156,6 +154,8 @@ object Server {
// TODO: clean up active sessions and close resources safely // TODO: clean up active sessions and close resources safely
logger.info("Login server now shutting down...") logger.info("Login server now shutting down...")
} }
Await.ready(Future.never, Duration.Inf)
} }
def flyway(args: CliConfig): Flyway = { def flyway(args: CliConfig): Flyway = {
@ -228,6 +228,7 @@ object Server {
} }
sealed trait AuthoritativeCounter { sealed trait AuthoritativeCounter {
/** the id accumulator */ /** the id accumulator */
private val masterIdKeyRing: AtomicLong = new AtomicLong(0L) private val masterIdKeyRing: AtomicLong = new AtomicLong(0L)

View file

@ -10,4 +10,4 @@ interface PrivacyHelper {
return new ByteString1C(array); return new ByteString1C(array);
} }
} }

View file

@ -2,6 +2,7 @@ akka {
loggers = ["akka.event.slf4j.Slf4jLogger"] loggers = ["akka.event.slf4j.Slf4jLogger"]
loglevel = INFO loglevel = INFO
logging-filter = akka.event.slf4j.Slf4jLoggingFilter logging-filter = akka.event.slf4j.Slf4jLoggingFilter
log-dead-letters-during-shutdown = off
} }
akka.actor.deployment { akka.actor.deployment {

View file

@ -67,8 +67,13 @@ database {
# Enable non-standard game properties # Enable non-standard game properties
game { game {
# Allow instant action to AMS instant-action = {
instant-action-ams = no # 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 # Battle experience rate
bep-rate = 1.0 bep-rate = 1.0
@ -212,6 +217,8 @@ game {
# while additional entries with insufficient time spacing may result in no change in behavior. # while additional entries with insufficient time spacing may result in no change in behavior.
delays = [350, 600, 800] delays = [350, 600, 800]
} }
doors-can-be-opened-by-med-app-from-this-distance = 5.05
} }
anti-cheat { 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

View file

@ -4808,4 +4808,4 @@
} }
] ]
} }
] ]

View file

@ -11689,4 +11689,4 @@
} }
] ]
} }
] ]

View file

@ -7393,4 +7393,4 @@
} }
] ]
} }
] ]

View file

@ -5656,4 +5656,4 @@
} }
] ]
} }
] ]

View file

@ -4340,4 +4340,4 @@
} }
] ]
} }
] ]

View file

@ -5992,4 +5992,4 @@
} }
] ]
} }
] ]

View file

@ -45,7 +45,6 @@ class LoginActor(
class LoginActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], connectionId: String, sessionId: Long) class LoginActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], connectionId: String, sessionId: Long)
extends Actor extends Actor
with MDCContextAware { with MDCContextAware {
private[this] val log = org.log4s.getLogger
import scala.concurrent.ExecutionContext.Implicits.global 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" val clientVersion = s"Client Version: $majorVersion.$minorVersion.$revision, $buildDate"
if (token.isDefined) 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 { else {
log.trace(s"New login UN:$username. $clientVersion") log.debug(s"New login UN:$username. $clientVersion")
} }
accountLogin(username, password.getOrElse("")) accountLogin(username, password.getOrElse(""))
@ -114,7 +113,7 @@ class LoginActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], conne
middlewareActor ! MiddlewareActor.Close() middlewareActor ! MiddlewareActor.Close()
case _ => case _ =>
log.warn(s"Unhandled GamePacket $pkt") log.warning(s"Unhandled GamePacket $pkt")
} }
def accountLogin(username: String, password: String): Unit = { 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) = { 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( middlewareActor ! MiddlewareActor.Send(
LoginRespMessage( LoginRespMessage(
newToken, newToken,
@ -211,7 +210,7 @@ class LoginActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], conne
} }
def loginFailureResponse(username: String, newToken: String) = { def loginFailureResponse(username: String, newToken: String) = {
log.warn("DB problem") log.warning("DB problem")
middlewareActor ! MiddlewareActor.Send( middlewareActor ! MiddlewareActor.Send(
LoginRespMessage( LoginRespMessage(
newToken, newToken,
@ -226,7 +225,7 @@ class LoginActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], conne
} }
def loginAccountFailureResponse(username: String, newToken: String) = { def loginAccountFailureResponse(username: String, newToken: String) = {
log.warn(s"Account $username inactive") log.warning(s"Account $username inactive")
middlewareActor ! MiddlewareActor.Send( middlewareActor ! MiddlewareActor.Send(
LoginRespMessage( LoginRespMessage(
newToken, newToken,

View file

@ -193,10 +193,8 @@ class MiddlewareActor(
/** Queue of outgoing packets ready for sending */ /** Queue of outgoing packets ready for sending */
val outQueueBundled: mutable.Queue[PlanetSidePacket] = mutable.Queue() val outQueueBundled: mutable.Queue[PlanetSidePacket] = mutable.Queue()
/** Latest outbound sequence number; /** Latest outbound sequence number */
* the current sequence is one less than this number var outSequence = -1
*/
var outSequence = 0
/** /**
* Increment the outbound sequence number. * Increment the outbound sequence number.
@ -205,13 +203,18 @@ class MiddlewareActor(
* @return * @return
*/ */
def nextSequence: Int = { def nextSequence: Int = {
val r = outSequence if (outSequence >= 0xffff) {
if (outSequence == 0xffff) { // TODO resetting the sequence to 0 causes a client crash
outSequence = 0 // but that does not happen when we always send the same number
} else { // the solution is most likely to send the proper ResetSequence payload
outSequence += 1 // send(ResetSequence(), None, crypto)
// outSequence = -1
// return nextSequence
return outSequence
} }
r outSequence += 1
outSequence
} }
/** Latest outbound subslot number; /** 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 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 When not handling it, it appears that the client will fall back to using ClientStart
Do we need to implement this? Do we need to implement this?
*/ */
connectionClose() connectionClose()
case (ConnectionClose(), _) => case (ConnectionClose(), _) =>
/* /*
indicates the user has willingly quit the game world indicates the user has willingly quit the game world
we do not need to implement this we do not need to implement this
*/ */
Behaviors.same Behaviors.same
// TODO ResetSequence // TODO ResetSequence
@ -454,6 +457,11 @@ class MiddlewareActor(
case Successful((packet, Some(sequence))) => case Successful((packet, Some(sequence))) =>
activeSequenceFunc(packet, sequence) activeSequenceFunc(packet, sequence)
case Successful((packet, None)) => case Successful((packet, None)) =>
packet match {
case _: PlanetSideResetSequencePacket =>
log.info(s"ResetSequence: ${msg.toHex}, inSeq: ${inSequence}, outSeq: ${outSequence}")
case _ => ()
}
in(packet) in(packet)
case Failure(e) => case Failure(e) =>
log.error(s"Could not decode $connectionId's packet: $e") log.error(s"Could not decode $connectionId's packet: $e")
@ -569,9 +577,14 @@ class MiddlewareActor(
log.error(s"Unexpected crypto packet '$packet'") log.error(s"Unexpected crypto packet '$packet'")
Behaviors.same Behaviors.same
case _: PlanetSideResetSequencePacket => case packet: PlanetSideResetSequencePacket =>
log.debug("Received sequence reset request from client; complying") // TODO This is wrong
outSequence = 0 // 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 Behaviors.same
} }
} }

File diff suppressed because it is too large Load diff

View file

@ -3,7 +3,7 @@ package net.psforever.actors.session
import akka.actor.typed.receptionist.Receptionist import akka.actor.typed.receptionist.Receptionist
import akka.actor.typed.scaladsl.adapter._ 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.joda.time.LocalDateTime
import org.log4s.MDC import org.log4s.MDC
import scala.collection.mutable import scala.collection.mutable
@ -102,7 +102,10 @@ object SessionActor {
tickTime: Long = 250L 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) 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 { with MDCContextAware {
MDC("connectionId") = connectionId MDC("connectionId") = connectionId
private[this] val log = org.log4s.getLogger
private[this] val buffer: mutable.ListBuffer[Any] = new mutable.ListBuffer[Any]() private[this] val buffer: mutable.ListBuffer[Any] = new mutable.ListBuffer[Any]()
private[this] val sessionFuncs = new SessionData(middlewareActor, context) private[this] val sessionFuncs = new SessionData(middlewareActor, context)
override val supervisorStrategy: SupervisorStrategy = sessionFuncs.sessionSupervisorStrategy
ServiceManager.serviceManager ! Lookup("accountIntermediary") ServiceManager.serviceManager ! Lookup("accountIntermediary")
ServiceManager.serviceManager ! Lookup("accountPersistence") ServiceManager.serviceManager ! Lookup("accountPersistence")
@ -195,7 +195,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
sessionFuncs.zoning.spawn.handlePlayerLoaded(tplayer) sessionFuncs.zoning.spawn.handlePlayerLoaded(tplayer)
case Zone.Population.PlayerHasLeft(zone, None) => 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)) => case Zone.Population.PlayerHasLeft(zone, Some(tplayer)) =>
if (tplayer.isAlive) { if (tplayer.isAlive) {
@ -203,16 +203,20 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
} }
case Zone.Population.PlayerCanNotSpawn(zone, tplayer) => 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) => 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) => 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) => 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)) => case ICS.ZoneResponse(Some(zone)) =>
sessionFuncs.zoning.handleZoneResponse(zone) sessionFuncs.zoning.handleZoneResponse(zone)
@ -349,7 +353,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
log.debug(s"CanNotPutItemInSlot: $msg") log.debug(s"CanNotPutItemInSlot: $msg")
case default => 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 = { private def handleGamePkt: PlanetSideGamePacket => Unit = {
@ -363,7 +367,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
sessionFuncs.vehicles.handleDismountVehicleCargo(packet) sessionFuncs.vehicles.handleDismountVehicleCargo(packet)
case packet: CharacterCreateRequestMessage => case packet: CharacterCreateRequestMessage =>
sessionFuncs.handleCharacterCreateRequest(packet) sessionFuncs.handleCharacterCreateRequest(packet)
case packet: CharacterRequestMessage => case packet: CharacterRequestMessage =>
sessionFuncs.handleCharacterRequest(packet) sessionFuncs.handleCharacterRequest(packet)
@ -588,6 +592,6 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
sessionFuncs.handleHitHint(packet) sessionFuncs.handleHitHint(packet)
case pkt => case pkt =>
log.warn(s"Unhandled GamePacket $pkt") log.warning(s"Unhandled GamePacket $pkt")
} }
} }

View file

@ -2,7 +2,7 @@
package net.psforever.actors.session.support package net.psforever.actors.session.support
import akka.actor.typed.scaladsl.adapter._ 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 net.psforever.objects.sourcing.{PlayerSource, SourceEntry}
import scala.collection.mutable import scala.collection.mutable
@ -19,11 +19,10 @@ import net.psforever.objects.avatar._
import net.psforever.objects.ballistics._ import net.psforever.objects.ballistics._
import net.psforever.objects.ce._ import net.psforever.objects.ce._
import net.psforever.objects.definition._ 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.equipment._
import net.psforever.objects.guid._ import net.psforever.objects.guid._
import net.psforever.objects.inventory.{Container, GridInventory, InventoryItem} import net.psforever.objects.inventory.{Container, InventoryItem}
import net.psforever.objects.loadouts.InfantryLoadout
import net.psforever.objects.locker.LockerContainer import net.psforever.objects.locker.LockerContainer
import net.psforever.objects.serverobject.affinity.FactionAffinity import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.serverobject.deploy.Deployment import net.psforever.objects.serverobject.deploy.Deployment
@ -65,6 +64,7 @@ import net.psforever.types._
import net.psforever.util.Config import net.psforever.util.Config
object SessionData { object SessionData {
//noinspection ScalaUnusedSymbol
private def NoTurnCounterYet(guid: PlanetSideGUID): Unit = { } private def NoTurnCounterYet(guid: PlanetSideGUID): Unit = { }
} }
@ -540,7 +540,7 @@ class SessionData(
} }
validObject(pkt.object_guid, decorator = "UseItem") match { validObject(pkt.object_guid, decorator = "UseItem") match {
case Some(door: Door) => case Some(door: Door) =>
handleUseDoor(door) handleUseDoor(door, equipment)
case Some(resourceSilo: ResourceSilo) => case Some(resourceSilo: ResourceSilo) =>
handleUseResourceSilo(resourceSilo, equipment) handleUseResourceSilo(resourceSilo, equipment)
case Some(panel: IFFLock) => case Some(panel: IFFLock) =>
@ -1122,212 +1122,6 @@ class SessionData(
/* supporting functions */ /* 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 = { def buildDependentOperationsForGalaxy(galaxyActor: ActorRef): Unit = {
if (vehicleResponseOpt.isEmpty && galaxyActor != Default.Actor) { if (vehicleResponseOpt.isEmpty && galaxyActor != Default.Actor) {
galaxyResponseOpt = Some(new SessionGalaxyHandlers(sessionData=this, avatarActor, galaxyActor, context)) galaxyResponseOpt = Some(new SessionGalaxyHandlers(sessionData=this, avatarActor, galaxyActor, context))
@ -1451,8 +1245,17 @@ class SessionData(
} }
} }
private def handleUseDoor(door: Door): Unit = { private def handleUseDoor(door: Door, equipment: Option[Equipment]): Unit = {
door.Actor ! CommonMessages.Use(player) 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 = { private def handleUseResourceSilo(resourceSilo: ResourceSilo, equipment: Option[Equipment]): Unit = {

View file

@ -45,52 +45,7 @@ class SessionGalaxyHandlers(
sendResponse(msg) sendResponse(msg)
case GalaxyResponse.TransferPassenger(temp_channel, vehicle, _, manifest) => case GalaxyResponse.TransferPassenger(temp_channel, vehicle, _, manifest) =>
val playerName = player.Name sessionData.zoning.handleTransferPassenger(temp_channel, vehicle, manifest)
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
}
}
case GalaxyResponse.LockedZoneUpdate(zone, time) => case GalaxyResponse.LockedZoneUpdate(zone, time) =>
sendResponse(ZoneInfoMessage(zone.Number, empire_status=false, lock_time=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) val popVS = zone.Players.count(_.faction == PlanetSideEmpire.VS)
sendResponse(ZonePopulationUpdateMessage(zone.Number, 414, 138, popTR, 138, popNC, 138, popVS, 138, popBO)) sendResponse(ZonePopulationUpdateMessage(zone.Number, 414, 138, popTR, 138, popNC, 138, popVS, 138, popBO))
case GalaxyResponse.LogStatusChange(name) => case GalaxyResponse.LogStatusChange(name) if (avatar.people.friend.exists { _.name.equals(name) }) =>
if (avatar.people.friend.exists { _.name.equals(name) }) { avatarActor ! AvatarActor.MemberListRequest(MemberAction.UpdateFriend, name)
avatarActor ! AvatarActor.MemberListRequest(MemberAction.UpdateFriend, name)
}
case GalaxyResponse.SendResponse(msg) => case GalaxyResponse.SendResponse(msg) =>
sendResponse(msg) sendResponse(msg)
case _ => ()
} }
} }
} }

View file

@ -309,11 +309,8 @@ class VehicleOperations(
obj.PassengerInSeat(player) match { obj.PassengerInSeat(player) match {
case Some(seat_num) => case Some(seat_num) =>
obj.Actor ! Mountable.TryDismount(player, seat_num, bailType) 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
//short-circuit the temporary channel for transferring between zones, the player is no longer doing that sessionData.zoning.interstellarFerry = None
//see above in VehicleResponse.TransferPassenger case
sessionData.zoning.interstellarFerry = None
}
// Deconstruct the vehicle if the driver has bailed out and the vehicle is capable of flight // 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: 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. //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 = { def TotalDriverVehicleControl(vehicle: Vehicle): Unit = {
serverVehicleControlVelocity = None serverVehicleControlVelocity = None
sendResponse(ServerVehicleOverrideMsg(lock_accelerator=false, lock_wheel=false, reverse=false, unk4=false, 0, 0, 0, None)) sendResponse(ServerVehicleOverrideMsg(lock_accelerator=false, lock_wheel=false, reverse=false, unk4=false, 0, 0, 0, None))

View file

@ -762,19 +762,19 @@ class ZoningOperations(
None None
} }
} match { } match {
case Some(v: Vehicle) => case Some(cargo: Vehicle) =>
galaxyService ! Service.Leave(Some(temp_channel)) //temporary vehicle-specific channel (see above) galaxyService ! Service.Leave(Some(temp_channel)) //temporary vehicle-specific channel (see above)
spawn.deadState = DeadState.Release spawn.deadState = DeadState.Release
sendResponse(AvatarDeadStateMessage(DeadState.Release, 0, 0, player.Position, player.Faction, unk5=true)) 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 interstellarFerry = Some(cargo) //on the other continent and registered to that continent's GUID system
spawn.LoadZonePhysicalSpawnPoint(v.Continent, v.Position, v.Orientation, 1 seconds, None) cargo.MountedIn = vehicle.GUID
spawn.LoadZonePhysicalSpawnPoint(cargo.Continent, cargo.Position, cargo.Orientation, respawnTime = 1 seconds, None)
case _ => case _ =>
interstellarFerry match { interstellarFerry match {
case None => case None =>
galaxyService ! Service.Leave(Some(temp_channel)) //no longer being transferred between zones galaxyService ! Service.Leave(Some(temp_channel)) //no longer being transferred between zones
interstellarFerryTopLevelGUID = None interstellarFerryTopLevelGUID = None
case Some(_) => ; case Some(_) => () //wait patiently
//wait patiently
} }
} }
} }

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

View file

@ -256,5 +256,3 @@ object Tool {
def Definition: FireModeDefinition = fdef def Definition: FireModeDefinition = fdef
} }
} }

View file

@ -113,13 +113,6 @@ class Vehicle(private val vehicleDef: VehicleDefinition)
private var utilities: Map[Int, Utility] = Map.empty private var utilities: Map[Int, Utility] = Map.empty
private var subsystems: List[VehicleSubsystem] = Nil private var subsystems: List[VehicleSubsystem] = Nil
private val trunk: GridInventory = GridInventory() 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 vehicleGatingManifest: Option[VehicleManifest] = None
private var previousVehicleGatingManifest: Option[VehicleManifest] = None private var previousVehicleGatingManifest: Option[VehicleManifest] = None

View file

@ -1,7 +1,8 @@
// Copyright (c) 2017 PSForever // Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.doors 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.{CommonMessages, PlanetSideServerObject}
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior} import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
import net.psforever.objects.serverobject.locks.IFFLock import net.psforever.objects.serverobject.locks.IFFLock
@ -17,8 +18,9 @@ class DoorControl(door: Door)
extends PoweredAmenityControl extends PoweredAmenityControl
with FactionAffinityBehavior.Check { with FactionAffinityBehavior.Check {
def FactionObject: FactionAffinity = door 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 def commonBehavior: Receive = checkBehavior
.orElse { .orElse {
@ -40,15 +42,16 @@ class DoorControl(door: Door)
def poweredStateLogic: Receive = def poweredStateLogic: Receive =
commonBehavior commonBehavior
.orElse { .orElse {
case CommonMessages.Use(player, Some(customDistance: Float)) =>
testToOpenDoor(player, door, customDistance, sender())
case CommonMessages.Use(player, _) => case CommonMessages.Use(player, _) =>
if (lockingMechanism(player, door) && !isLocked) { testToOpenDoor(player, door, door.Definition.initialOpeningDistance, sender())
openDoor(player)
}
case IFFLock.DoorOpenResponse(target: Player) if !isLocked => case IFFLock.DoorOpenResponse(target: Player) if !isLocked =>
openDoor(target) DoorControl.openDoor(target, door)
case _ => ; case _ => ()
} }
def unpoweredStateLogic: Receive = { def unpoweredStateLogic: Receive = {
@ -56,30 +59,33 @@ class DoorControl(door: Door)
.orElse { .orElse {
case CommonMessages.Use(player, _) if !isLocked => case CommonMessages.Use(player, _) if !isLocked =>
//without power, the door opens freely //without power, the door opens freely
openDoor(player) DoorControl.openDoor(player, door)
case _ => ; case _ => ()
} }
} }
def openDoor(player: Player): Unit = { /**
val zone = door.Zone * If the player is close enough to the door,
val doorGUID = door.GUID * the locking mechanism allows for the door to open,
if (!door.isOpen) { * and the door is not bolted shut (locked),
//global open * then tell the door that it should open.
door.Open = player * @param player player who is standing somewhere
zone.LocalEvents ! LocalServiceMessage( * @param door door that is installed somewhere
zone.id, * @param maximumDistance permissible square distance between the player and the door
LocalAction.DoorOpens(Service.defaultPlayerGUID, zone, door) * @param replyTo the player's session message reference
) */
} private def testToOpenDoor(
else { player: Player,
//the door should already open, but the requesting player does not see it as open door: Door,
sender() ! LocalServiceResponse( maximumDistance: Float,
player.Name, replyTo: ActorRef
Service.defaultPlayerGUID, ): Unit = {
LocalResponse.DoorOpens(doorGUID) 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 { object DoorControl {
//noinspection ScalaUnusedSymbol
def alwaysOpen(obj: PlanetSideServerObject, door: Door): Boolean = true 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)
)
}
}
} }

View file

@ -9,4 +9,9 @@ import net.psforever.objects.serverobject.structures.AmenityDefinition
class DoorDefinition(objectId: Int) class DoorDefinition(objectId: Int)
extends AmenityDefinition(objectId) { extends AmenityDefinition(objectId) {
Name = "door" 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
} }

View file

@ -39,4 +39,4 @@ object NonvitalDefinition {
out out
} }
} }
} }

View file

@ -27,7 +27,7 @@ trait CargoBehavior {
case _ => ; case _ => ;
} }
isDismounting = None isDismounting = None
startCargoDismounting(bailed = false) startCargoDismountingNoCleanup(bailed = false)
} }
val cargoBehavior: Receive = { val cargoBehavior: Receive = {
@ -64,24 +64,26 @@ trait CargoBehavior {
} }
def startCargoDismounting(bailed: Boolean): Unit = { def startCargoDismounting(bailed: Boolean): Unit = {
val obj = CargoObject if (!startCargoDismountingNoCleanup(bailed)) {
obj.Zone.GUID(obj.MountedIn) match { isDismounting = None
case Some(carrier: Vehicle) => CargoObject.MountedIn = None
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
} }
} }
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 { object CargoBehavior {

View file

@ -654,4 +654,3 @@ object CarrierBehavior {
msgs msgs
} }
} }

View file

@ -40,4 +40,3 @@ trait MountableWeapons
def Definition: MountableWeaponsDefinition def Definition: MountableWeaponsDefinition
} }

View file

@ -55,4 +55,3 @@ class AmsControl(vehicle: Vehicle)
} }
} }
} }

View file

@ -613,7 +613,7 @@ object BfrControl {
final val Enabled = 38 final val Enabled = 38
final val Disabled = 39 final val Disabled = 39
} }
private case object VehicleExplosion private case object VehicleExplosion
val dimorphics: List[EquipmentHandiness] = { val dimorphics: List[EquipmentHandiness] = {

View file

@ -25,15 +25,16 @@ class CargoCarrierControl(vehicle: Vehicle)
/** /**
* If the vehicle becomes disabled, the safety and autonomy of the cargo should be prioritized. * 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 = { override def PrepareForDisabled(kickPassengers: Boolean) : Unit = {
super.PrepareForDisabled(kickPassengers)
//abandon all cargo //abandon all cargo
vehicle.CargoHolds.collect { vehicle.CargoHolds.collect {
case (index, hold : Cargo) if hold.isOccupied => case (index, hold : Cargo) if hold.isOccupied =>
val cargo = hold.occupant.get val cargo = hold.occupant.get
checkCargoDismount(cargo.GUID, index, iteration = 0, bailed = false) checkCargoDismount(cargo.GUID, index, iteration = 0, bailed = kickPassengers)
super.PrepareForDisabled(kickPassengers)
} }
} }

View file

@ -366,7 +366,7 @@ class VehicleControl(vehicle: Vehicle)
//escape being someone else's cargo //escape being someone else's cargo
vehicle.MountedIn match { vehicle.MountedIn match {
case Some(_) => case Some(_) =>
startCargoDismounting(bailed = false) startCargoDismounting(bailed = true)
case _ => ; case _ => ;
} }
if (!vehicle.isFlying || kickPassengers) { if (!vehicle.isFlying || kickPassengers) {

View file

@ -59,4 +59,4 @@ object TriggerUsedReason {
ResistUsing = NoResistanceSelection ResistUsing = NoResistanceSelection
Model = SimpleResolutions.calculate Model = SimpleResolutions.calculate
} }
} }

View file

@ -89,20 +89,20 @@ object PacketHelpers {
} }
/** Create a Codec for an enumeration type that can correctly represent its value /** 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 * @param storageCodec the Codec used for actually representing the value
* @tparam E The inferred type * @tparam E The inferred type
* @return Generated codec * @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 type Struct = Int :: HNil
val struct: Codec[Struct] = storageCodec.hlist val struct: Codec[Struct] = storageCodec.hlist
val primitiveLimit = Math.pow(2, storageCodec.sizeBound.exact.get.toDouble) 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 // Assure that the enum will always be able to fit in a N-bit int
assert( assert(
enum.maxId <= primitiveLimit, e.maxId <= primitiveLimit,
enum.getClass.getCanonicalName + s": maxId exceeds primitive type (limit of $primitiveLimit, maxId ${enum.maxId})" e.getClass.getCanonicalName + s": maxId exceeds primitive type (limit of $primitiveLimit, maxId ${e.maxId})"
) )
def to(pkt: E#Value): Struct = { def to(pkt: E#Value): Struct = {
@ -113,13 +113,13 @@ object PacketHelpers {
struct match { struct match {
case enumVal :: HNil => case enumVal :: HNil =>
// verify that this int can match the enum // verify that this int can match the enum
val first = enum.values.firstKey.id val first = e.values.firstKey.id
val last = enum.maxId - 1 val last = e.maxId - 1
if (enumVal >= first && enumVal <= last) if (enumVal >= first && enumVal <= last)
Attempt.successful(enum(enumVal)) Attempt.successful(e(enumVal))
else 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) 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. * 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) * 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] = { def createLongEnumerationCodec[E <: Enumeration](e: E, storageCodec: Codec[Long]): Codec[E#Value] = {
createEnumerationCodec(enum, storageCodec.xmap[Int](_.toInt, _.toLong)) createEnumerationCodec(e, storageCodec.xmap[Int](_.toInt, _.toLong))
} }
/** Create a Codec for enumeratum's IntEnum type */ /** 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 type Struct = Int :: HNil
val struct: Codec[Struct] = storageCodec.hlist val struct: Codec[Struct] = storageCodec.hlist
@ -146,36 +146,36 @@ object PacketHelpers {
def from(struct: Struct): Attempt[E] = def from(struct: Struct): Attempt[E] =
struct match { struct match {
case enumVal :: HNil => case enumVal :: HNil =>
enum.withValueOpt(enumVal) match { e.withValueOpt(enumVal) match {
case Some(v) => Attempt.successful(v) case Some(v) => Attempt.successful(v)
case None => 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) struct.narrow[E](from, to)
} }
def createLongIntEnumCodec[E <: IntEnumEntry](enum: IntEnum[E], storageCodec: Codec[Long]): Codec[E] = { def createLongIntEnumCodec[E <: IntEnumEntry](e: IntEnum[E], storageCodec: Codec[Long]): Codec[E] = {
createIntEnumCodec(enum, storageCodec.xmap[Int](_.toInt, _.toLong)) createIntEnumCodec(e, storageCodec.xmap[Int](_.toInt, _.toLong))
} }
/** Create a Codec for enumeratum's Enum type */ /** 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 type Struct = Int :: HNil
val struct: Codec[Struct] = storageCodec.hlist val struct: Codec[Struct] = storageCodec.hlist
def to(pkt: E): Struct = { def to(pkt: E): Struct = {
enum.indexOf(pkt) :: HNil e.indexOf(pkt) :: HNil
} }
def from(struct: Struct): Attempt[E] = def from(struct: Struct): Attempt[E] =
struct match { struct match {
case enumVal :: HNil => case enumVal :: HNil =>
enum.valuesToIndex.find(_._2 == enumVal) match { e.valuesToIndex.find(_._2 == enumVal) match {
case Some((v, _)) => Attempt.successful(v) case Some((v, _)) => Attempt.successful(v)
case None => 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()}'"))
} }
} }

View file

@ -37,6 +37,7 @@ object PacketCoding {
): Attempt[BitVector] = { ): Attempt[BitVector] = {
val seq = packet match { val seq = packet match {
case _: PlanetSideControlPacket if crypto.isEmpty => BitVector.empty case _: PlanetSideControlPacket if crypto.isEmpty => BitVector.empty
case _: PlanetSideResetSequencePacket => BitVector.empty
case _ => case _ =>
sequence match { sequence match {
case Some(_sequence) => case Some(_sequence) =>
@ -93,6 +94,17 @@ object PacketCoding {
) )
case f @ Failure(_) => return f 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) Successful(flags ++ seq ++ payload)

View file

@ -15,4 +15,3 @@ final case class Ignore(data: ByteVector) extends PlanetSideCryptoPacket {
object Ignore extends Marshallable[Ignore] { object Ignore extends Marshallable[Ignore] {
implicit val codec: Codec[Ignore] = ("data" | bytes).as[Ignore] implicit val codec: Codec[Ignore] = ("data" | bytes).as[Ignore]
} }

View file

@ -11,32 +11,32 @@ sealed abstract class GenericAction(val value: Int) extends IntEnumEntry
object GenericAction extends IntEnum[GenericAction] { object GenericAction extends IntEnum[GenericAction] {
val values: IndexedSeq[GenericAction] = findValues val values: IndexedSeq[GenericAction] = findValues
final case object ShowMosquitoRadar extends GenericAction(value = 3) final case object ShowMosquitoRadar extends GenericAction(value = 3)
final case object HideMosquitoRadar extends GenericAction(value = 4) final case object HideMosquitoRadar extends GenericAction(value = 4)
final case object MissileLock extends GenericAction(value = 7) final case object MissileLock extends GenericAction(value = 7)
final case object WaspMissileLock extends GenericAction(value = 8) final case object WaspMissileLock extends GenericAction(value = 8)
final case object TRekLock extends GenericAction(value = 9) final case object TRekLock extends GenericAction(value = 9)
final case object DropSpecialItem extends GenericAction(value = 11) final case object DropSpecialItem extends GenericAction(value = 11)
final case object FacilityCaptureFanfare extends GenericAction(value = 12) final case object FacilityCaptureFanfare extends GenericAction(value = 12)
final case object NewCharacterBasicTrainingPrompt extends GenericAction(value = 14) final case object NewCharacterBasicTrainingPrompt extends GenericAction(value = 14)
final case object MaxAnchorsExtend_RCV extends GenericAction(value = 15) final case object MaxAnchorsExtend_RCV extends GenericAction(value = 15)
final case object MaxAnchorsRelease_RCV extends GenericAction(value = 16) final case object MaxAnchorsRelease_RCV extends GenericAction(value = 16)
final case object MaxSpecialEffect_RCV extends GenericAction(value = 20) final case object MaxSpecialEffect_RCV extends GenericAction(value = 20)
final case object StopMaxSpecialEffect_RCV extends GenericAction(value = 21) final case object StopMaxSpecialEffect_RCV extends GenericAction(value = 21)
final case object CavernFacilityCapture extends GenericAction(value = 22) final case object CavernFacilityCapture extends GenericAction(value = 22)
final case object CavernFacilityKill extends GenericAction(value = 23) final case object CavernFacilityKill extends GenericAction(value = 23)
final case object Imprinted extends GenericAction(value = 24) final case object Imprinted extends GenericAction(value = 24)
final case object NoLongerImprinted extends GenericAction(value = 25) final case object NoLongerImprinted extends GenericAction(value = 25)
final case object PurchaseTimersReset extends GenericAction(value = 27) final case object PurchaseTimersReset extends GenericAction(value = 27)
final case object LeaveWarpQueue_RCV extends GenericAction(value = 28) final case object LeaveWarpQueue_RCV extends GenericAction(value = 28)
final case object AwayFromKeyboard_RCV extends GenericAction(value = 29) final case object AwayFromKeyboard_RCV extends GenericAction(value = 29)
final case object BackInGame_RCV extends GenericAction(value = 30) final case object BackInGame_RCV extends GenericAction(value = 30)
final case object FirstPersonViewWithEffect extends GenericAction(value = 31) final case object FirstPersonViewWithEffect extends GenericAction(value = 31)
final case object FirstPersonViewFailToDeconstruct extends GenericAction(value = 32) final case object FirstPersonViewFailToDeconstruct extends GenericAction(value = 32)
final case object FailToDeconstruct extends GenericAction(value = 33) final case object FailToDeconstruct extends GenericAction(value = 33)
final case object LookingForSquad_RCV extends GenericAction(value = 36) final case object LookingForSquad_RCV extends GenericAction(value = 36)
final case object NotLookingForSquad_RCV extends GenericAction(value = 37) final case object NotLookingForSquad_RCV extends GenericAction(value = 37)
final case object Unknown45 extends GenericAction(value = 45) final case object Unknown45 extends GenericAction(value = 45)
final case class Unknown(override val value: Int) extends GenericAction(value) 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 = { def apply(i: Int): GenericActionMessage = {
GenericActionMessage(GenericAction.values.find { _.value == i } match { GenericActionMessage(GenericAction.values.find { _.value == i } match {
case Some(enum) => enum case Some(enum) => enum
case None => GenericAction.Unknown(i) case None => GenericAction.Unknown(i)
}) })
} }
private val genericActionCodec = uint(bits = 6).xmap[GenericAction]({ private val genericActionCodec = uint(bits = 6).xmap[GenericAction](
i => GenericAction.values.find { _.value == i } match { { i =>
case Some(enum) => enum GenericAction.values.find { _.value == i } match {
case None => GenericAction.Unknown(i) case Some(enum) => enum
} case None => GenericAction.Unknown(i)
}, enum => enum.value) }
},
e => e.value
)
implicit val codec: Codec[GenericActionMessage] = ("action" | genericActionCodec).as[GenericActionMessage] implicit val codec: Codec[GenericActionMessage] = ("action" | genericActionCodec).as[GenericActionMessage]
} }

View file

@ -15,7 +15,7 @@ object TerrainCondition extends Enumeration {
type Type = Value type Type = Value
val Safe, Unsafe = 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 * @param pos the vehicle's current position in the game world
*/ */
final case class InvalidTerrainMessage( final case class InvalidTerrainMessage(
player_guid: PlanetSideGUID, player_guid: PlanetSideGUID,
vehicle_guid: PlanetSideGUID, vehicle_guid: PlanetSideGUID,
proximity_alert: TerrainCondition.Value, proximity_alert: TerrainCondition.Value,
pos: Vector3 pos: Vector3
) extends PlanetSideGamePacket { ) extends PlanetSideGamePacket {
type Packet = InvalidTerrainMessage type Packet = InvalidTerrainMessage
def opcode = GamePacketOpcode.InvalidTerrainMessage def opcode = GamePacketOpcode.InvalidTerrainMessage
def encode = InvalidTerrainMessage.encode(this) def encode = InvalidTerrainMessage.encode(this)
@ -40,8 +40,7 @@ final case class InvalidTerrainMessage(
object InvalidTerrainMessage extends Marshallable[InvalidTerrainMessage] { object InvalidTerrainMessage extends Marshallable[InvalidTerrainMessage] {
implicit val codec: Codec[InvalidTerrainMessage] = ( implicit val codec: Codec[InvalidTerrainMessage] = (("player_guid" | PlanetSideGUID.codec) ::
("player_guid" | PlanetSideGUID.codec) ::
("vehicle_guid" | PlanetSideGUID.codec) :: ("vehicle_guid" | PlanetSideGUID.codec) ::
("proximity_alert" | TerrainCondition.codec) :: ("proximity_alert" | TerrainCondition.codec) ::
("pos" | floatL :: floatL :: floatL).narrow[Vector3]( ("pos" | floatL :: floatL :: floatL).narrow[Vector3](

View file

@ -22,7 +22,7 @@ object SquadAction {
val AnyPositions, AvailablePositions, SomeCertifications, AllCertifications = Value 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) final case class DisplaySquad() extends SquadAction(code = 0)
@ -280,7 +280,7 @@ object SquadAction {
val squadListDecoratorCodec = ( val squadListDecoratorCodec = (
SquadListDecoration.codec :: SquadListDecoration.codec ::
ignore(size = 3) ignore(size = 3)
).xmap[SquadListDecorator]( ).xmap[SquadListDecorator](
{ {
case value :: _ :: HNil => SquadListDecorator(value) case value :: _ :: HNil => SquadListDecorator(value)

View file

@ -11,7 +11,7 @@ object MemberEvent extends Enumeration {
val Add, Remove, Promote, UpdateZone, Outfit = Value 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( final case class SquadMemberEvent(
@ -58,13 +58,18 @@ object SquadMemberEvent extends Marshallable[SquadMemberEvent] {
("unk2" | uint16L) :: ("unk2" | uint16L) ::
("char_id" | uint32L) :: ("char_id" | uint32L) ::
("position" | uint4) :: ("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)) :: ("zone_number" | conditional(action == MemberEvent.Add || action == MemberEvent.UpdateZone, uint16L)) ::
("outfit_id" | conditional(action == MemberEvent.Add || action == MemberEvent.Outfit, uint32L)) ("outfit_id" | conditional(action == MemberEvent.Add || action == MemberEvent.Outfit, uint32L))
}).exmap[SquadMemberEvent]( }).exmap[SquadMemberEvent](
{ {
case action :: unk2 :: char_id :: member_position :: player_name :: zone_number :: outfit_id :: HNil => 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( case SquadMemberEvent(

View file

@ -16,7 +16,7 @@ object WaypointEventAction extends Enumeration {
val Add, Unknown1, Remove, Unknown3 //unconfirmed val Add, Unknown1, Remove, Unknown3 //unconfirmed
= Value = Value
implicit val codec: Codec[WaypointEventAction.Value] = PacketHelpers.createEnumerationCodec(enum = this, uint2) implicit val codec: Codec[WaypointEventAction.Value] = PacketHelpers.createEnumerationCodec(e = this, uint2)
} }
/** /**

View file

@ -332,4 +332,3 @@ object MountableInventory {
} }
} }
} }

View file

@ -549,7 +549,7 @@ class CavernRotationService(
} }
/** /**
* *
* @param sendToSession callback reference * @param sendToSession callback reference
*/ */
def sendCavernRotationUpdates(sendToSession: ActorRef): Unit = { def sendCavernRotationUpdates(sendToSession: ActorRef): Unit = {

View file

@ -1,3 +1,4 @@
// Copyright (c) 2020 PSForever
package net.psforever.services package net.psforever.services
import akka.actor.typed.receptionist.{Receptionist, ServiceKey} 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.avatar.Avatar
import net.psforever.objects.{Player, SpawnPoint, Vehicle} import net.psforever.objects.{Player, SpawnPoint, Vehicle}
import net.psforever.objects.serverobject.structures.{Building, WarpGate} 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.packet.game.DroppodError
import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID, SpawnGroup, Vector3} import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID, SpawnGroup, Vector3}
import net.psforever.util.Config import net.psforever.util.Config
@ -101,8 +102,8 @@ class InterstellarClusterService(context: ActorContext[InterstellarClusterServic
import InterstellarClusterService._ import InterstellarClusterService._
private[this] val log = org.log4s.getLogger private[this] val log = org.log4s.getLogger
var intercontinentalSetup: Boolean = false private var intercontinentalSetup: Boolean = false
var cavernRotation: Option[ActorRef[CavernRotationService.Command]] = None private var cavernRotation: Option[ActorRef[CavernRotationService.Command]] = None
val zoneActors: mutable.Map[String, (ActorRef[ZoneActor.Command], Zone)] = { val zoneActors: mutable.Map[String, (ActorRef[ZoneActor.Command], Zone)] = {
import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.ExecutionContext.Implicits.global
@ -164,47 +165,44 @@ class InterstellarClusterService(context: ActorContext[InterstellarClusterServic
replyTo ! ZonesResponse(zones.filter(predicate)) replyTo ! ZonesResponse(zones.filter(predicate))
case GetInstantActionSpawnPoint(faction, replyTo) => case GetInstantActionSpawnPoint(faction, replyTo) =>
val res = zones val spawnTarget: Seq[SpawnGroup] = if (Config.app.game.instantAction.spawnOnAms) {
.filter(_.Players.nonEmpty) Seq(SpawnGroup.Tower, SpawnGroup.Facility, SpawnGroup.AMS)
.flatMap { zone => } else {
zone.HotSpotData.collect { Seq(SpawnGroup.Tower, SpawnGroup.Facility)
case spot => (zone, spot) }
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 { .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) => case (zone, info, spawns) =>
val pos = info.DisplayLocation val pos = info.DisplayLocation
val spawnPoint = spawns.minBy(point => Vector3.DistanceSquared(point.Position, pos)) val spawnPoint = spawns.minBy(point => Vector3.DistanceSquared(point.Position, pos))
//Some(zone, pos, spawnPoint) (zone, spawnPoint)
Some(zone, spawnPoint)
case _ => None
} }
replyTo ! SpawnPointResponse(res) replyTo ! SpawnPointResponse(spawnResults)
case GetRandomSpawnPoint(zoneNumber, faction, spawnGroups, replyTo) => case GetRandomSpawnPoint(zoneNumber, faction, spawnGroups, replyTo) =>
val response = zones.find(_.Number == zoneNumber) match { val response = zones.find(_.Number == zoneNumber) match {

View file

@ -56,4 +56,4 @@ object HartTimerActions {
LocalAction.ShuttleState(shuttle.GUID, shuttle.Position, shuttle.Orientation, state) LocalAction.ShuttleState(shuttle.GUID, shuttle.Position, shuttle.Orientation, state)
) )
} }
} }

View file

@ -2,11 +2,10 @@
package net.psforever.services.local.support package net.psforever.services.local.support
import akka.actor.{Actor, Cancellable} 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.doors.Door
import net.psforever.objects.serverobject.structures.Building
import net.psforever.objects.zones.Zone import net.psforever.objects.zones.Zone
import net.psforever.types.{PlanetSideGUID, Vector3} import net.psforever.types.PlanetSideGUID
import scala.annotation.tailrec import scala.annotation.tailrec
import scala.concurrent.duration._ import scala.concurrent.duration._
@ -17,7 +16,6 @@ import scala.concurrent.duration._
* @see `LocalService` * @see `LocalService`
*/ */
class DoorCloseActor() extends Actor { class DoorCloseActor() extends Actor {
/** The periodic `Executor` that checks for doors to be closed */ /** The periodic `Executor` that checks for doors to be closed */
private var doorCloserTrigger: Cancellable = Default.Cancellable private var doorCloserTrigger: Cancellable = Default.Cancellable
@ -36,43 +34,11 @@ class DoorCloseActor() extends Actor {
case DoorCloseActor.TryCloseDoors() => case DoorCloseActor.TryCloseDoors() =>
doorCloserTrigger.cancel() doorCloserTrigger.cancel()
val now: Long = System.nanoTime val now: Long = System.currentTimeMillis()
val (doorsToClose1, doorsLeftOpen1) = PartitionEntries(openDoors, now) val (doorsToClose1, doorsLeftOpen1) = PartitionEntries(openDoors, now)
val (doorsToClose2, doorsLeftOpen2) = doorsToClose1.partition(entry => { val (doorsToClose2, doorsLeftOpen2) = doorsToClose1.partition { entry =>
entry.door.Open match { Doors.testForTargetHoldingDoorOpen(entry.door, entry.door.Definition.continuousOpenDistance).isEmpty
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
}
})
openDoors = ( openDoors = (
doorsLeftOpen1 ++ doorsLeftOpen1 ++
doorsLeftOpen2.map(entry => DoorCloseActor.DoorEntry(entry.door, entry.zone, now)) 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()) doorCloserTrigger = context.system.scheduler.scheduleOnce(short_timeout, self, DoorCloseActor.TryCloseDoors())
} }
case _ => ; case _ => ()
} }
/** /**
@ -144,9 +110,9 @@ class DoorCloseActor() extends Actor {
object DoorCloseActor { object DoorCloseActor {
/** The wait before an open door closes; as a Long for calculation simplicity */ /** 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 */ /** 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. * Message that carries information about a door that has been opened.
@ -155,7 +121,7 @@ object DoorCloseActor {
* @param time when the door was opened * @param time when the door was opened
* @see `DoorEntry` * @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. * Message that carries information about a door that needs to close.

View file

@ -11,11 +11,11 @@ import scodec.codecs.uint2L
* Blame the lack of gender dysphoria on the Terran Republic. * Blame the lack of gender dysphoria on the Terran Republic.
*/ */
sealed abstract class CharacterSex( sealed abstract class CharacterSex(
val value: Int, val value: Int,
val pronounSubject: String, val pronounSubject: String,
val pronounObject: String, val pronounObject: String,
val possessive: String val possessive: String
) extends IntEnumEntry { ) extends IntEnumEntry {
def possessiveNoObject: String = possessive def possessiveNoObject: String = possessive
} }
@ -25,21 +25,23 @@ sealed abstract class CharacterSex(
object CharacterSex extends IntEnum[CharacterSex] { object CharacterSex extends IntEnum[CharacterSex] {
val values = findValues val values = findValues
case object Male extends CharacterSex( case object Male
value = 1, extends CharacterSex(
pronounSubject = "he", value = 1,
pronounObject = "him", pronounSubject = "he",
possessive = "his" pronounObject = "him",
) possessive = "his"
)
case object Female extends CharacterSex( case object Female
value = 2, extends CharacterSex(
pronounSubject = "she", value = 2,
pronounObject = "her", pronounSubject = "she",
possessive = "her" pronounObject = "her",
) { possessive = "her"
) {
override def possessiveNoObject: String = "hers" override def possessiveNoObject: String = "hers"
} }
implicit val codec = PacketHelpers.createIntEnumCodec(enum = this, uint2L) implicit val codec = PacketHelpers.createIntEnumCodec(e = this, uint2L)
} }

View file

@ -11,9 +11,9 @@ sealed abstract class ExperienceType(val value: Int) extends IntEnumEntry
object ExperienceType extends IntEnum[ExperienceType] { object ExperienceType extends IntEnum[ExperienceType] {
val values: IndexedSeq[ExperienceType] = findValues val values: IndexedSeq[ExperienceType] = findValues
case object Normal extends ExperienceType(value = 0) case object Normal extends ExperienceType(value = 0)
case object Support extends ExperienceType(value = 2) case object Support extends ExperienceType(value = 2)
case object RabbitBall extends ExperienceType(value = 4) 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))
} }

View file

@ -12,4 +12,4 @@ object MemberAction extends Enumeration {
RemoveIgnoredPlayer = Value RemoveIgnoredPlayer = Value
implicit val codec: Codec[MemberAction.Value] = PacketHelpers.createEnumerationCodec(this, uint(bits = 3)) implicit val codec: Codec[MemberAction.Value] = PacketHelpers.createEnumerationCodec(this, uint(bits = 3))
} }

View file

@ -165,8 +165,8 @@ object MeritCommendation extends Enumeration {
{ {
case MeritCommendation.None => case MeritCommendation.None =>
Attempt.successful(0xffffffffL) Attempt.successful(0xffffffffL)
case enum => case e =>
Attempt.successful(enum.id.toLong) Attempt.successful(e.id.toLong)
} }
) )
} }

View file

@ -23,5 +23,5 @@ object OxygenState extends Enum[OxygenState] {
case object Recovery extends OxygenState case object Recovery extends OxygenState
case object Suffocation 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))
} }

View file

@ -2,7 +2,7 @@
package net.psforever.types package net.psforever.types
import enumeratum.values.{IntEnum, IntEnumEntry} 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 sealed abstract class StatisticalCategory(val value: Int) extends IntEnumEntry

View file

@ -25,12 +25,12 @@ object Config {
} }
implicit def enumeratumIntConfigConvert[A <: IntEnumEntry](implicit implicit def enumeratumIntConfigConvert[A <: IntEnumEntry](implicit
enum: IntEnum[A], e: IntEnum[A],
ct: ClassTag[A] ct: ClassTag[A]
): ConfigConvert[A] = ): ConfigConvert[A] =
viaNonEmptyStringOpt[A]( viaNonEmptyStringOpt[A](
v => v =>
enum.values.toList.collectFirst { e.values.toList.collectFirst {
case e: ServerType if e.name == v => e.asInstanceOf[A] case e: ServerType if e.name == v => e.asInstanceOf[A]
case e: BattleRank if e.value.toString == 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] case e: CommandRank if e.value.toString == v => e.asInstanceOf[A]
@ -40,13 +40,13 @@ object Config {
) )
implicit def enumeratumConfigConvert[A <: EnumEntry](implicit implicit def enumeratumConfigConvert[A <: EnumEntry](implicit
enum: Enum[A], e: Enum[A],
ct: ClassTag[A] ct: ClassTag[A]
): ConfigConvert[A] = ): ConfigConvert[A] =
viaNonEmptyStringOpt[A]( viaNonEmptyStringOpt[A](
v => v =>
enum.values.toList.collectFirst { e.values.toList.collectFirst {
case e if e.toString.toLowerCase == v.toLowerCase => e.asInstanceOf[A] case e if e.toString.toLowerCase == v.toLowerCase => e
}, },
_.toString _.toString
) )
@ -62,25 +62,23 @@ object Config {
// Raw config object - prefer app when possible // Raw config object - prefer app when possible
lazy val config: TypesafeConfig = source.config() match { lazy val config: TypesafeConfig = source.config() match {
case Right(config) => config case Right(config) => config
case Left(failures) => { case Left(failures) =>
logger.error("Loading config failed") logger.error("Loading config failed")
failures.toList.foreach { failure => failures.toList.foreach { failure =>
logger.error(failure.toString) logger.error(failure.toString)
} }
sys.exit(1) sys.exit(1)
}
} }
// Typed config object // Typed config object
lazy val app: AppConfig = source.load[AppConfig] match { lazy val app: AppConfig = source.load[AppConfig] match {
case Right(config) => config case Right(config) => config
case Left(failures) => { case Left(failures) =>
logger.error("Loading config failed") logger.error("Loading config failed")
failures.toList.foreach { failure => failures.toList.foreach { failure =>
logger.error(failure.toString) logger.error(failure.toString)
} }
sys.exit(1) sys.exit(1)
}
} }
} }
@ -148,7 +146,7 @@ case class SessionConfig(
) )
case class GameConfig( case class GameConfig(
instantActionAms: Boolean, instantAction: InstantActionConfig,
amenityAutorepairRate: Float, amenityAutorepairRate: Float,
amenityAutorepairDrainRate: Float, amenityAutorepairDrainRate: Float,
bepRate: Double, bepRate: Double,
@ -161,6 +159,12 @@ case class GameConfig(
cavernRotation: CavernRotationConfig, cavernRotation: CavernRotationConfig,
savedMsg: SavedMessageEvents, savedMsg: SavedMessageEvents,
playerDraw: PlayerStateDrawSettings playerDraw: PlayerStateDrawSettings
doorsCanBeOpenedByMedAppFromThisDistance: Float
)
case class InstantActionConfig(
spawnOnAms: Boolean,
thirdParty: Boolean
) )
case class NewAvatar( case class NewAvatar(

View file

@ -25,7 +25,7 @@ import net.psforever.objects.serverobject.terminals.implant.ImplantTerminalMech
import net.psforever.objects.serverobject.tube.SpawnTube import net.psforever.objects.serverobject.tube.SpawnTube
import net.psforever.objects.serverobject.turret.{FacilityTurret, FacilityTurretDefinition} import net.psforever.objects.serverobject.turret.{FacilityTurret, FacilityTurretDefinition}
import net.psforever.objects.serverobject.zipline.ZipLinePath 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.objects.zones.{MapInfo, Zone, ZoneInfo, ZoneMap}
import net.psforever.types.{Angular, PlanetSideEmpire, Vector3} import net.psforever.types.{Angular, PlanetSideEmpire, Vector3}
import net.psforever.util.DefinitionUtil import net.psforever.util.DefinitionUtil

View file

@ -45,4 +45,3 @@ class CaptureFlagUpdateMessageTest extends Specification with Debug {
pkt mustEqual stringOne pkt mustEqual stringOne
} }
} }

View file

@ -65,4 +65,3 @@ class ComponentDamageMessageTest extends Specification {
pkt mustEqual string_off pkt mustEqual string_off
} }
} }

View file

@ -54,4 +54,3 @@ class FrameVehicleStateMessageTest extends Specification {
pkt mustEqual string pkt mustEqual string
} }
} }

View file

@ -28,4 +28,3 @@ class GenericObjectActionAtPositionMessageTest extends Specification {
pkt mustEqual string pkt mustEqual string
} }
} }

Some files were not shown because too many files have changed in this diff Show more