mirror of
https://github.com/2revoemag/PSF-BotServer.git
synced 2026-04-21 11:55:19 +00:00
Merge pull request #1054 from jgillich/dc50
50 minute disconnect fix/workaround
This commit is contained in:
commit
6c3fd970c4
89 changed files with 467978 additions and 467770 deletions
|
|
@ -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
1
.devcontainer/Dockerfile
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
FROM mcr.microsoft.com/vscode/devcontainers/base:debian
|
||||||
43
.devcontainer/devcontainer.json
Normal file
43
.devcontainer/devcontainer.json
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
|
||||||
|
// README at: https://github.com/devcontainers/templates/tree/main/src/java-postgres
|
||||||
|
{
|
||||||
|
"name": "Java & PostgreSQL",
|
||||||
|
"dockerComposeFile": "docker-compose.yml",
|
||||||
|
"service": "app",
|
||||||
|
"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
|
||||||
|
"features": {
|
||||||
|
"ghcr.io/devcontainers-contrib/features/sbt-sdkman:2": {
|
||||||
|
"jdkVersion": "11"
|
||||||
|
},
|
||||||
|
"ghcr.io/devcontainers-contrib/features/scala-sdkman:2": {
|
||||||
|
"jdkVersion": "11"
|
||||||
|
},
|
||||||
|
"ghcr.io/devcontainers-contrib/features/scalacli-sdkman:2": {
|
||||||
|
"jdkVersion": "11"
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Features to add to the dev container. More info: https://containers.dev/features.
|
||||||
|
// "features": {}
|
||||||
|
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||||
|
// This can be used to network with other containers or with the host.
|
||||||
|
// "forwardPorts": [
|
||||||
|
// 51000,
|
||||||
|
// 51001,
|
||||||
|
// 51002
|
||||||
|
// ],
|
||||||
|
"customizations": {
|
||||||
|
"vscode": {
|
||||||
|
"extensions": [
|
||||||
|
"scalameta.metals",
|
||||||
|
"scala-lang.scala",
|
||||||
|
"EditorConfig.EditorConfig"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Use 'postCreateCommand' to run commands after the container is created.
|
||||||
|
// "postCreateCommand": "java -version",
|
||||||
|
// Configure tool-specific properties.
|
||||||
|
// "customizations": {},
|
||||||
|
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
|
||||||
|
// "remoteUser": "root"
|
||||||
|
}
|
||||||
31
.devcontainer/docker-compose.yml
Normal file
31
.devcontainer/docker-compose.yml
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
version: "3.8"
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
postgres-data:
|
||||||
|
|
||||||
|
services:
|
||||||
|
app:
|
||||||
|
container_name: javadev
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
environment:
|
||||||
|
CONFIG_FORCE_database_host: postgres
|
||||||
|
CONFIG_FORCE_bind: 0.0.0.0
|
||||||
|
volumes:
|
||||||
|
- ../..:/workspaces:cached
|
||||||
|
command: sleep infinity
|
||||||
|
# network_mode: service:postgres
|
||||||
|
ports:
|
||||||
|
- "51000:51000/udp"
|
||||||
|
- "51001:51001/udp"
|
||||||
|
# - "51000:51002"
|
||||||
|
postgres:
|
||||||
|
image: postgres:latest
|
||||||
|
restart: unless-stopped
|
||||||
|
volumes:
|
||||||
|
- postgres-data:/var/lib/postgresql/data
|
||||||
|
environment:
|
||||||
|
POSTGRES_PASSWORD: psforever
|
||||||
|
POSTGRES_USER: psforever
|
||||||
|
POSTGRES_DB: psforever
|
||||||
8
.editorconfig
Normal file
8
.editorconfig
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
2
.github/workflows/publish.yaml
vendored
2
.github/workflows/publish.yaml
vendored
|
|
@ -35,4 +35,4 @@ jobs:
|
||||||
uses: docker/build-push-action@v2
|
uses: docker/build-push-action@v2
|
||||||
with:
|
with:
|
||||||
push: true
|
push: true
|
||||||
tags: ${{ steps.prep.outputs.tags }}
|
tags: ${{ steps.prep.outputs.tags }}
|
||||||
|
|
|
||||||
2
.github/workflows/test.yaml
vendored
2
.github/workflows/test.yaml
vendored
|
|
@ -46,4 +46,4 @@ jobs:
|
||||||
uses: actions/upload-artifact@v2
|
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
|
||||||
|
|
|
||||||
1
.jvmopts
1
.jvmopts
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version = 2.6.4
|
version = 2.6.4
|
||||||
preset = defaultWithAlign
|
preset = defaultWithAlign
|
||||||
maxColumn = 120
|
maxColumn = 120
|
||||||
|
|
|
||||||
|
|
@ -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. <<http://fsf.org/>>_
|
_Copyright © 2007 Free Software Foundation, Inc. <<http://fsf.org/>>_
|
||||||
|
|
||||||
Everyone is permitted to copy and distribute verbatim copies of this license
|
Everyone is permitted to copy and distribute verbatim copies of this license
|
||||||
|
|
|
||||||
12
README.md
12
README.md
|
|
@ -30,7 +30,7 @@ which has the instructions on downloading the game and using the PSForever launc
|
||||||
- Up to date
|
- 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.
|
||||||
|
|
|
||||||
55
build.sbt
55
build.sbt
|
|
@ -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("."))
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
sbt.version = 1.4.5
|
sbt.version = 1.8.2
|
||||||
|
|
|
||||||
|
|
@ -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")
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
stacktrace.app.packages=net.psforever
|
stacktrace.app.packages=net.psforever
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,8 @@ import java.nio.file.Paths
|
||||||
import java.util.Locale
|
import java.util.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)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,4 +10,4 @@ interface PrivacyHelper {
|
||||||
return new ByteString1C(array);
|
return new ByteString1C(array);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -4808,4 +4808,4 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -11689,4 +11689,4 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -7393,4 +7393,4 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -5656,4 +5656,4 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -4340,4 +4340,4 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -5992,4 +5992,4 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,6 @@ class LoginActor(
|
||||||
class LoginActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], connectionId: String, sessionId: Long)
|
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,
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -257,26 +257,26 @@ object AvatarActor {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transform from encoded inventory data as a CLOB - character large object - into individual items.
|
* Transform from encoded inventory data as a CLOB - character large object - into individual items.
|
||||||
* Install those items into positions in a target container
|
* Install those items into positions in a target container
|
||||||
* in the same positions in which they were previously recorded.<br>
|
* in the same positions in which they were previously recorded.<br>
|
||||||
* <br>
|
* <br>
|
||||||
* There is no guarantee that the structure of the retained container data encoded in the CLOB
|
* There is no guarantee that the structure of the retained container data encoded in the CLOB
|
||||||
* will fit the current dimensions of the container.
|
* will fit the current dimensions of the container.
|
||||||
* No tests are performed.
|
* No tests are performed.
|
||||||
* A partial decompression of the CLOB may occur.
|
* A partial decompression of the CLOB may occur.
|
||||||
* @param container the container in which to place the pieces of equipment produced from the CLOB
|
* @param container the container in which to place the pieces of equipment produced from the CLOB
|
||||||
* @param clob the inventory data in string form
|
* @param clob the inventory data in string form
|
||||||
* @param log a reference to a logging context
|
* @param log a reference to a logging context
|
||||||
* @param restoreAmmo by default, when `false`, use the maximum ammunition for all ammunition boixes and for all tools;
|
* @param restoreAmmo by default, when `false`, use the maximum ammunition for all ammunition boixes and for all tools;
|
||||||
* if `true`, load the last saved ammunition count for all ammunition boxes and for all tools
|
* if `true`, load the last saved ammunition count for all ammunition boxes and for all tools
|
||||||
*/
|
*/
|
||||||
def buildContainedEquipmentFromClob(
|
def buildContainedEquipmentFromClob(
|
||||||
container: Container,
|
container: Container,
|
||||||
clob: String,
|
clob: String,
|
||||||
log: org.log4s.Logger,
|
log: org.log4s.Logger,
|
||||||
restoreAmmo: Boolean = false
|
restoreAmmo: Boolean = false
|
||||||
): Unit = {
|
): Unit = {
|
||||||
clob.split("/").filter(_.trim.nonEmpty).foreach { value =>
|
clob.split("/").filter(_.trim.nonEmpty).foreach { value =>
|
||||||
val (objectType, objectIndex, objectId, ammoData) = value.split(",") match {
|
val (objectType, objectIndex, objectId, ammoData) = value.split(",") match {
|
||||||
case Array(a, b: String, c: String) => (a, b.toInt, c.toInt, None)
|
case Array(a, b: String, c: String) => (a, b.toInt, c.toInt, None)
|
||||||
|
|
@ -293,8 +293,8 @@ object AvatarActor {
|
||||||
ammoData foreach { toolAmmo =>
|
ammoData foreach { toolAmmo =>
|
||||||
toolAmmo.split("_").drop(1).foreach { value =>
|
toolAmmo.split("_").drop(1).foreach { value =>
|
||||||
val (ammoSlots, ammoTypeIndex, ammoBoxDefinition, ammoCount) = value.split("-") match {
|
val (ammoSlots, ammoTypeIndex, ammoBoxDefinition, ammoCount) = value.split("-") match {
|
||||||
case Array(a: String, b: String, c: String) => (a.toInt, b.toInt, c.toInt, None)
|
case Array(a: String, b: String, c: String) => (a.toInt, b.toInt, c.toInt, None)
|
||||||
case Array(a: String, b: String, c: String, d:String) => (a.toInt, b.toInt, c.toInt, Some(d.toInt))
|
case Array(a: String, b: String, c: String, d: String) => (a.toInt, b.toInt, c.toInt, Some(d.toInt))
|
||||||
}
|
}
|
||||||
val fireMode = tool.AmmoSlots(ammoSlots)
|
val fireMode = tool.AmmoSlots(ammoSlots)
|
||||||
fireMode.AmmoTypeIndex = ammoTypeIndex
|
fireMode.AmmoTypeIndex = ammoTypeIndex
|
||||||
|
|
@ -340,11 +340,11 @@ object AvatarActor {
|
||||||
* @return the resulting text data that represents object to time mappings
|
* @return the resulting text data that represents object to time mappings
|
||||||
*/
|
*/
|
||||||
def buildCooldownsFromClob(
|
def buildCooldownsFromClob(
|
||||||
clob: String,
|
clob: String,
|
||||||
cooldownDurations: Map[BasicDefinition,FiniteDuration],
|
cooldownDurations: Map[BasicDefinition, FiniteDuration],
|
||||||
log: org.log4s.Logger
|
log: org.log4s.Logger
|
||||||
): Map[String, LocalDateTime] = {
|
): Map[String, LocalDateTime] = {
|
||||||
val now = LocalDateTime.now()
|
val now = LocalDateTime.now()
|
||||||
val cooldowns: mutable.Map[String, LocalDateTime] = mutable.Map()
|
val cooldowns: mutable.Map[String, LocalDateTime] = mutable.Map()
|
||||||
clob.split("/").filter(_.trim.nonEmpty).foreach { value =>
|
clob.split("/").filter(_.trim.nonEmpty).foreach { value =>
|
||||||
value.split(",") match {
|
value.split(",") match {
|
||||||
|
|
@ -385,7 +385,7 @@ object AvatarActor {
|
||||||
val factionName: String = faction.toString.toLowerCase
|
val factionName: String = faction.toString.toLowerCase
|
||||||
val name = item match {
|
val name = item match {
|
||||||
case GlobalDefinitions.trhev_dualcycler | GlobalDefinitions.nchev_scattercannon |
|
case GlobalDefinitions.trhev_dualcycler | GlobalDefinitions.nchev_scattercannon |
|
||||||
GlobalDefinitions.vshev_quasar =>
|
GlobalDefinitions.vshev_quasar =>
|
||||||
s"${factionName}hev_antipersonnel"
|
s"${factionName}hev_antipersonnel"
|
||||||
case GlobalDefinitions.trhev_pounder | GlobalDefinitions.nchev_falcon | GlobalDefinitions.vshev_comet =>
|
case GlobalDefinitions.trhev_pounder | GlobalDefinitions.nchev_falcon | GlobalDefinitions.vshev_comet =>
|
||||||
s"${factionName}hev_antivehicular"
|
s"${factionName}hev_antivehicular"
|
||||||
|
|
@ -402,12 +402,12 @@ object AvatarActor {
|
||||||
if (name.matches("(tr|nc|vs)hev_.+") && Config.app.game.sharedMaxCooldown) {
|
if (name.matches("(tr|nc|vs)hev_.+") && Config.app.game.sharedMaxCooldown) {
|
||||||
val faction = name.take(2)
|
val faction = name.take(2)
|
||||||
(if (faction.equals("nc")) {
|
(if (faction.equals("nc")) {
|
||||||
Seq(GlobalDefinitions.nchev_scattercannon, GlobalDefinitions.nchev_falcon, GlobalDefinitions.nchev_sparrow)
|
Seq(GlobalDefinitions.nchev_scattercannon, GlobalDefinitions.nchev_falcon, GlobalDefinitions.nchev_sparrow)
|
||||||
} else if (faction.equals("vs")) {
|
} else if (faction.equals("vs")) {
|
||||||
Seq(GlobalDefinitions.vshev_quasar, GlobalDefinitions.vshev_comet, GlobalDefinitions.vshev_starfire)
|
Seq(GlobalDefinitions.vshev_quasar, GlobalDefinitions.vshev_comet, GlobalDefinitions.vshev_starfire)
|
||||||
} else {
|
} else {
|
||||||
Seq(GlobalDefinitions.trhev_dualcycler, GlobalDefinitions.trhev_pounder, GlobalDefinitions.trhev_burster)
|
Seq(GlobalDefinitions.trhev_dualcycler, GlobalDefinitions.trhev_pounder, GlobalDefinitions.trhev_burster)
|
||||||
}).zip(
|
}).zip(
|
||||||
Seq(s"${faction}hev_antipersonnel", s"${faction}hev_antivehicular", s"${faction}hev_antiaircraft")
|
Seq(s"${faction}hev_antipersonnel", s"${faction}hev_antivehicular", s"${faction}hev_antiaircraft")
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -461,7 +461,6 @@ object AvatarActor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def displayLookingForSquad(session: Session, state: Int): Unit = {
|
def displayLookingForSquad(session: Session, state: Int): Unit = {
|
||||||
val player = session.player
|
val player = session.player
|
||||||
session.zone.AvatarEvents ! AvatarServiceMessage(
|
session.zone.AvatarEvents ! AvatarServiceMessage(
|
||||||
|
|
@ -477,7 +476,10 @@ object AvatarActor {
|
||||||
* @param func functionality that is called upon discovery of the character
|
* @param func functionality that is called upon discovery of the character
|
||||||
* @return if found, the discovered avatar, the avatar's account id, and the avatar's faction affiliation
|
* @return if found, the discovered avatar, the avatar's account id, and the avatar's faction affiliation
|
||||||
*/
|
*/
|
||||||
def getLiveAvatarForFunc(name: String, func: (Long,String,Int)=>Unit): Option[(Avatar, Long, PlanetSideEmpire.Value)] = {
|
def getLiveAvatarForFunc(
|
||||||
|
name: String,
|
||||||
|
func: (Long, String, Int) => Unit
|
||||||
|
): Option[(Avatar, Long, PlanetSideEmpire.Value)] = {
|
||||||
if (name.nonEmpty) {
|
if (name.nonEmpty) {
|
||||||
LivePlayerList.WorldPopulation({ case (_, a) => a.name.equals(name) }).headOption match {
|
LivePlayerList.WorldPopulation({ case (_, a) => a.name.equals(name) }).headOption match {
|
||||||
case Some(otherAvatar) =>
|
case Some(otherAvatar) =>
|
||||||
|
|
@ -500,7 +502,10 @@ object AvatarActor {
|
||||||
* otherwise, always returns `None` as if no avatar was discovered
|
* otherwise, always returns `None` as if no avatar was discovered
|
||||||
* (the query is probably still in progress)
|
* (the query is probably still in progress)
|
||||||
*/
|
*/
|
||||||
def getAvatarForFunc(name: String, func: (Long,String,Int)=>Unit): Option[(Avatar, Long, PlanetSideEmpire.Value)] = {
|
def getAvatarForFunc(
|
||||||
|
name: String,
|
||||||
|
func: (Long, String, Int) => Unit
|
||||||
|
): Option[(Avatar, Long, PlanetSideEmpire.Value)] = {
|
||||||
getLiveAvatarForFunc(name, func).orElse {
|
getLiveAvatarForFunc(name, func).orElse {
|
||||||
if (name.nonEmpty) {
|
if (name.nonEmpty) {
|
||||||
import ctx._
|
import ctx._
|
||||||
|
|
@ -527,7 +532,7 @@ object AvatarActor {
|
||||||
* @param name unique character name
|
* @param name unique character name
|
||||||
* @param faction the faction affiliation
|
* @param faction the faction affiliation
|
||||||
*/
|
*/
|
||||||
def formatForOtherFunc(func: (Long,String)=>Unit)(charId: Long, name: String, faction: Int): Unit = {
|
def formatForOtherFunc(func: (Long, String) => Unit)(charId: Long, name: String, faction: Int): Unit = {
|
||||||
func(charId, name)
|
func(charId, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -540,9 +545,11 @@ object AvatarActor {
|
||||||
*/
|
*/
|
||||||
def onlineIfNotIgnored(onlinePlayerName: String, observerName: String): Boolean = {
|
def onlineIfNotIgnored(onlinePlayerName: String, observerName: String): Boolean = {
|
||||||
val onlinePlayerNameLower = onlinePlayerName.toLowerCase()
|
val onlinePlayerNameLower = onlinePlayerName.toLowerCase()
|
||||||
LivePlayerList.WorldPopulation({ case (_, a) => a.name.toLowerCase().equals(onlinePlayerNameLower) }).headOption match {
|
LivePlayerList
|
||||||
|
.WorldPopulation({ case (_, a) => a.name.toLowerCase().equals(onlinePlayerNameLower) })
|
||||||
|
.headOption match {
|
||||||
case Some(onlinePlayer) => onlineIfNotIgnored(onlinePlayer, observerName)
|
case Some(onlinePlayer) => onlineIfNotIgnored(onlinePlayer, observerName)
|
||||||
case _ => false
|
case _ => false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -556,9 +563,9 @@ object AvatarActor {
|
||||||
*/
|
*/
|
||||||
def onlineIfNotIgnoredEitherWay(observer: Avatar, onlinePlayerName: String): Boolean = {
|
def onlineIfNotIgnoredEitherWay(observer: Avatar, onlinePlayerName: String): Boolean = {
|
||||||
LivePlayerList.WorldPopulation({ case (_, a) => a.name.equals(onlinePlayerName) }) match {
|
LivePlayerList.WorldPopulation({ case (_, a) => a.name.equals(onlinePlayerName) }) match {
|
||||||
case Nil => false //weird case, but ...
|
case Nil => false //weird case, but ...
|
||||||
case onlinePlayer :: Nil => onlineIfNotIgnoredEitherWay(onlinePlayer, observer)
|
case onlinePlayer :: Nil => onlineIfNotIgnoredEitherWay(onlinePlayer, observer)
|
||||||
case _ => throw new Exception("only trying to find two players, but too many matching search results!")
|
case _ => throw new Exception("only trying to find two players, but too many matching search results!")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -598,24 +605,25 @@ object AvatarActor {
|
||||||
import ctx._
|
import ctx._
|
||||||
import scala.concurrent.ExecutionContext.Implicits.global
|
import scala.concurrent.ExecutionContext.Implicits.global
|
||||||
val out: Promise[persistence.Savedplayer] = Promise()
|
val out: Promise[persistence.Savedplayer] = Promise()
|
||||||
val queryResult = ctx.run(query[persistence.Savedplayer].filter { _.avatarId == lift(avatarId) })
|
val queryResult = ctx.run(query[persistence.Savedplayer].filter { _.avatarId == lift(avatarId) })
|
||||||
queryResult.onComplete {
|
queryResult.onComplete {
|
||||||
case Success(data) if data.nonEmpty =>
|
case Success(data) if data.nonEmpty =>
|
||||||
out.completeWith(Future(data.head))
|
out.completeWith(Future(data.head))
|
||||||
case _ =>
|
case _ =>
|
||||||
ctx.run(query[persistence.Savedplayer]
|
ctx.run(
|
||||||
.insert(
|
query[persistence.Savedplayer]
|
||||||
_.avatarId -> lift(avatarId),
|
.insert(
|
||||||
_.px -> lift(0),
|
_.avatarId -> lift(avatarId),
|
||||||
_.py -> lift(0),
|
_.px -> lift(0),
|
||||||
_.pz -> lift(0),
|
_.py -> lift(0),
|
||||||
_.orientation -> lift(0),
|
_.pz -> lift(0),
|
||||||
_.zoneNum -> lift(0),
|
_.orientation -> lift(0),
|
||||||
_.health -> lift(0),
|
_.zoneNum -> lift(0),
|
||||||
_.armor -> lift(0),
|
_.health -> lift(0),
|
||||||
_.exosuitNum -> lift(0),
|
_.armor -> lift(0),
|
||||||
_.loadout -> lift("")
|
_.exosuitNum -> lift(0),
|
||||||
)
|
_.loadout -> lift("")
|
||||||
|
)
|
||||||
)
|
)
|
||||||
out.completeWith(Future(persistence.Savedplayer(avatarId, 0, 0, 0, 0, 0, 0, 0, 0, "")))
|
out.completeWith(Future(persistence.Savedplayer(avatarId, 0, 0, 0, 0, 0, 0, 0, 0, "")))
|
||||||
}
|
}
|
||||||
|
|
@ -684,24 +692,25 @@ object AvatarActor {
|
||||||
import ctx._
|
import ctx._
|
||||||
import scala.concurrent.ExecutionContext.Implicits.global
|
import scala.concurrent.ExecutionContext.Implicits.global
|
||||||
val out: Promise[Int] = Promise()
|
val out: Promise[Int] = Promise()
|
||||||
val avatarId = player.avatar.id
|
val avatarId = player.avatar.id
|
||||||
val position = player.Position
|
val position = player.Position
|
||||||
val queryResult = ctx.run(query[persistence.Savedplayer].filter { _.avatarId == lift(avatarId) })
|
val queryResult = ctx.run(query[persistence.Savedplayer].filter { _.avatarId == lift(avatarId) })
|
||||||
queryResult.onComplete {
|
queryResult.onComplete {
|
||||||
case Success(results) if results.nonEmpty =>
|
case Success(results) if results.nonEmpty =>
|
||||||
ctx.run(query[persistence.Savedplayer]
|
ctx.run(
|
||||||
.filter { _.avatarId == lift(avatarId) }
|
query[persistence.Savedplayer]
|
||||||
.update(
|
.filter { _.avatarId == lift(avatarId) }
|
||||||
_.px -> lift((position.x * 1000).toInt),
|
.update(
|
||||||
_.py -> lift((position.y * 1000).toInt),
|
_.px -> lift((position.x * 1000).toInt),
|
||||||
_.pz -> lift((position.z * 1000).toInt),
|
_.py -> lift((position.y * 1000).toInt),
|
||||||
_.orientation -> lift((player.Orientation.z * 1000).toInt),
|
_.pz -> lift((position.z * 1000).toInt),
|
||||||
_.zoneNum -> lift(player.Zone.Number),
|
_.orientation -> lift((player.Orientation.z * 1000).toInt),
|
||||||
_.health -> lift(health),
|
_.zoneNum -> lift(player.Zone.Number),
|
||||||
_.armor -> lift(player.Armor),
|
_.health -> lift(health),
|
||||||
_.exosuitNum -> lift(player.ExoSuit.id),
|
_.armor -> lift(player.Armor),
|
||||||
_.loadout -> lift(buildClobFromPlayerLoadout(player))
|
_.exosuitNum -> lift(player.ExoSuit.id),
|
||||||
)
|
_.loadout -> lift(buildClobFromPlayerLoadout(player))
|
||||||
|
)
|
||||||
)
|
)
|
||||||
out.completeWith(Future(1))
|
out.completeWith(Future(1))
|
||||||
case _ =>
|
case _ =>
|
||||||
|
|
@ -722,20 +731,21 @@ object AvatarActor {
|
||||||
import ctx._
|
import ctx._
|
||||||
import scala.concurrent.ExecutionContext.Implicits.global
|
import scala.concurrent.ExecutionContext.Implicits.global
|
||||||
val out: Promise[Int] = Promise()
|
val out: Promise[Int] = Promise()
|
||||||
val avatarId = player.avatar.id
|
val avatarId = player.avatar.id
|
||||||
val position = player.Position
|
val position = player.Position
|
||||||
val queryResult = ctx.run(query[persistence.Savedplayer].filter { _.avatarId == lift(avatarId) })
|
val queryResult = ctx.run(query[persistence.Savedplayer].filter { _.avatarId == lift(avatarId) })
|
||||||
queryResult.onComplete {
|
queryResult.onComplete {
|
||||||
case Success(results) if results.nonEmpty =>
|
case Success(results) if results.nonEmpty =>
|
||||||
ctx.run(query[persistence.Savedplayer]
|
ctx.run(
|
||||||
.filter { _.avatarId == lift(avatarId) }
|
query[persistence.Savedplayer]
|
||||||
.update(
|
.filter { _.avatarId == lift(avatarId) }
|
||||||
_.px -> lift((position.x * 1000).toInt),
|
.update(
|
||||||
_.py -> lift((position.y * 1000).toInt),
|
_.px -> lift((position.x * 1000).toInt),
|
||||||
_.pz -> lift((position.z * 1000).toInt),
|
_.py -> lift((position.y * 1000).toInt),
|
||||||
_.orientation -> lift((player.Orientation.z * 1000).toInt),
|
_.pz -> lift((position.z * 1000).toInt),
|
||||||
_.zoneNum -> lift(player.Zone.Number)
|
_.orientation -> lift((player.Orientation.z * 1000).toInt),
|
||||||
)
|
_.zoneNum -> lift(player.Zone.Number)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
out.completeWith(Future(1))
|
out.completeWith(Future(1))
|
||||||
case _ =>
|
case _ =>
|
||||||
|
|
@ -757,19 +767,20 @@ object AvatarActor {
|
||||||
import ctx._
|
import ctx._
|
||||||
import scala.concurrent.ExecutionContext.Implicits.global
|
import scala.concurrent.ExecutionContext.Implicits.global
|
||||||
val out: Promise[persistence.Savedavatar] = Promise()
|
val out: Promise[persistence.Savedavatar] = Promise()
|
||||||
val queryResult = ctx.run(query[persistence.Savedavatar].filter { _.avatarId == lift(avatarId) })
|
val queryResult = ctx.run(query[persistence.Savedavatar].filter { _.avatarId == lift(avatarId) })
|
||||||
queryResult.onComplete {
|
queryResult.onComplete {
|
||||||
case Success(data) if data.nonEmpty =>
|
case Success(data) if data.nonEmpty =>
|
||||||
out.completeWith(Future(data.head))
|
out.completeWith(Future(data.head))
|
||||||
case _ =>
|
case _ =>
|
||||||
val now = LocalDateTime.now()
|
val now = LocalDateTime.now()
|
||||||
ctx.run(query[persistence.Savedavatar]
|
ctx.run(
|
||||||
.insert(
|
query[persistence.Savedavatar]
|
||||||
_.avatarId -> lift(avatarId),
|
.insert(
|
||||||
_.forgetCooldown -> lift(now),
|
_.avatarId -> lift(avatarId),
|
||||||
_.purchaseCooldowns -> lift(""),
|
_.forgetCooldown -> lift(now),
|
||||||
_.useCooldowns -> lift("")
|
_.purchaseCooldowns -> lift(""),
|
||||||
)
|
_.useCooldowns -> lift("")
|
||||||
|
)
|
||||||
)
|
)
|
||||||
out.completeWith(Future(persistence.Savedavatar(avatarId, now, "", "")))
|
out.completeWith(Future(persistence.Savedavatar(avatarId, now, "", "")))
|
||||||
}
|
}
|
||||||
|
|
@ -788,16 +799,17 @@ object AvatarActor {
|
||||||
import ctx._
|
import ctx._
|
||||||
import scala.concurrent.ExecutionContext.Implicits.global
|
import scala.concurrent.ExecutionContext.Implicits.global
|
||||||
val out: Promise[Int] = Promise()
|
val out: Promise[Int] = Promise()
|
||||||
val avatarId = avatar.id
|
val avatarId = avatar.id
|
||||||
val queryResult = ctx.run(query[persistence.Savedavatar].filter { _.avatarId == lift(avatarId) })
|
val queryResult = ctx.run(query[persistence.Savedavatar].filter { _.avatarId == lift(avatarId) })
|
||||||
queryResult.onComplete {
|
queryResult.onComplete {
|
||||||
case Success(results) if results.nonEmpty =>
|
case Success(results) if results.nonEmpty =>
|
||||||
ctx.run(query[persistence.Savedavatar]
|
ctx.run(
|
||||||
.filter { _.avatarId == lift(avatarId) }
|
query[persistence.Savedavatar]
|
||||||
.update(
|
.filter { _.avatarId == lift(avatarId) }
|
||||||
_.purchaseCooldowns -> lift(buildClobfromCooldowns(avatar.cooldowns.purchase)),
|
.update(
|
||||||
_.useCooldowns -> lift(buildClobfromCooldowns(avatar.cooldowns.use))
|
_.purchaseCooldowns -> lift(buildClobfromCooldowns(avatar.cooldowns.purchase)),
|
||||||
)
|
_.useCooldowns -> lift(buildClobfromCooldowns(avatar.cooldowns.use))
|
||||||
|
)
|
||||||
)
|
)
|
||||||
out.completeWith(Future(1))
|
out.completeWith(Future(1))
|
||||||
case _ =>
|
case _ =>
|
||||||
|
|
@ -959,12 +971,13 @@ class AvatarActor(
|
||||||
deleted.headOption match {
|
deleted.headOption match {
|
||||||
case Some(a) if !a.deleted =>
|
case Some(a) if !a.deleted =>
|
||||||
val flagDeletion = for {
|
val flagDeletion = for {
|
||||||
_ <- ctx.run(query[persistence.Avatar]
|
_ <- ctx.run(
|
||||||
.filter(_.id == lift(id))
|
query[persistence.Avatar]
|
||||||
.update(
|
.filter(_.id == lift(id))
|
||||||
_.deleted -> lift(true),
|
.update(
|
||||||
_.lastModified -> lift(LocalDateTime.now())
|
_.deleted -> lift(true),
|
||||||
)
|
_.lastModified -> lift(LocalDateTime.now())
|
||||||
|
)
|
||||||
)
|
)
|
||||||
} yield ()
|
} yield ()
|
||||||
flagDeletion.onComplete {
|
flagDeletion.onComplete {
|
||||||
|
|
@ -1008,11 +1021,12 @@ class AvatarActor(
|
||||||
case LoginAvatar(replyTo) =>
|
case LoginAvatar(replyTo) =>
|
||||||
import ctx._
|
import ctx._
|
||||||
val avatarId = avatar.id
|
val avatarId = avatar.id
|
||||||
ctx.run(
|
ctx
|
||||||
query[persistence.Avatar]
|
.run(
|
||||||
.filter(_.id == lift(avatarId))
|
query[persistence.Avatar]
|
||||||
.map { c => (c.created, c.lastLogin) }
|
.filter(_.id == lift(avatarId))
|
||||||
)
|
.map { c => (c.created, c.lastLogin) }
|
||||||
|
)
|
||||||
.onComplete {
|
.onComplete {
|
||||||
case Success(value) if value.nonEmpty =>
|
case Success(value) if value.nonEmpty =>
|
||||||
val (created, lastLogin) = value.head
|
val (created, lastLogin) = value.head
|
||||||
|
|
@ -1031,12 +1045,12 @@ class AvatarActor(
|
||||||
persistence.Certification(Certification.ATV.value, avatarId),
|
persistence.Certification(Certification.ATV.value, avatarId),
|
||||||
persistence.Certification(Certification.Harasser.value, avatarId)
|
persistence.Certification(Certification.Harasser.value, avatarId)
|
||||||
)
|
)
|
||||||
).foreach(c => query[persistence.Certification].insert(c))
|
).foreach(c => query[persistence.Certification].insertValue(c))
|
||||||
)
|
)
|
||||||
_ <- ctx.run(
|
_ <- ctx.run(
|
||||||
liftQuery(
|
liftQuery(
|
||||||
List(persistence.Shortcut(avatarId, 0, 0, "medkit"))
|
List(persistence.Shortcut(avatarId, 0, 0, "medkit"))
|
||||||
).foreach(c => query[persistence.Shortcut].insert(c))
|
).foreach(c => query[persistence.Shortcut].insertValue(c))
|
||||||
)
|
)
|
||||||
} yield true
|
} yield true
|
||||||
inits.onComplete {
|
inits.onComplete {
|
||||||
|
|
@ -1109,13 +1123,14 @@ class AvatarActor(
|
||||||
val replace = certification.replaces.intersect(avatar.certifications)
|
val replace = certification.replaces.intersect(avatar.certifications)
|
||||||
Future
|
Future
|
||||||
.sequence(replace.map(cert => {
|
.sequence(replace.map(cert => {
|
||||||
ctx.run(
|
ctx
|
||||||
query[persistence.Certification]
|
.run(
|
||||||
.filter(_.avatarId == lift(avatar.id))
|
query[persistence.Certification]
|
||||||
.filter(_.id == lift(cert.value))
|
.filter(_.avatarId == lift(avatar.id))
|
||||||
.delete
|
.filter(_.id == lift(cert.value))
|
||||||
)
|
.delete
|
||||||
.map(_ => cert)
|
)
|
||||||
|
.map(_ => cert)
|
||||||
}))
|
}))
|
||||||
.onComplete {
|
.onComplete {
|
||||||
case Failure(exception) =>
|
case Failure(exception) =>
|
||||||
|
|
@ -1129,10 +1144,11 @@ class AvatarActor(
|
||||||
PlanetsideAttributeMessage(session.get.player.GUID, 25, cert.value)
|
PlanetsideAttributeMessage(session.get.player.GUID, 25, cert.value)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
ctx.run(
|
ctx
|
||||||
query[persistence.Certification]
|
.run(
|
||||||
.insert(_.id -> lift(certification.value), _.avatarId -> lift(avatar.id))
|
query[persistence.Certification]
|
||||||
)
|
.insert(_.id -> lift(certification.value), _.avatarId -> lift(avatar.id))
|
||||||
|
)
|
||||||
.onComplete {
|
.onComplete {
|
||||||
case Failure(exception) =>
|
case Failure(exception) =>
|
||||||
log.error(exception)("db failure")
|
log.error(exception)("db failure")
|
||||||
|
|
@ -1180,13 +1196,14 @@ class AvatarActor(
|
||||||
avatar.certifications
|
avatar.certifications
|
||||||
.intersect(requiredByCert)
|
.intersect(requiredByCert)
|
||||||
.map(cert => {
|
.map(cert => {
|
||||||
ctx.run(
|
ctx
|
||||||
query[persistence.Certification]
|
.run(
|
||||||
.filter(_.avatarId == lift(avatar.id))
|
query[persistence.Certification]
|
||||||
.filter(_.id == lift(cert.value))
|
.filter(_.avatarId == lift(avatar.id))
|
||||||
.delete
|
.filter(_.id == lift(cert.value))
|
||||||
)
|
.delete
|
||||||
.map(_ => cert)
|
)
|
||||||
|
.map(_ => cert)
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.onComplete {
|
.onComplete {
|
||||||
|
|
@ -1329,25 +1346,26 @@ class AvatarActor(
|
||||||
index match {
|
index match {
|
||||||
case Some(_index) =>
|
case Some(_index) =>
|
||||||
import ctx._
|
import ctx._
|
||||||
ctx.run(
|
ctx
|
||||||
query[persistence.Implant]
|
.run(
|
||||||
.filter(_.name == lift(definition.Name))
|
query[persistence.Implant]
|
||||||
.filter(_.avatarId == lift(avatar.id))
|
.filter(_.name == lift(definition.Name))
|
||||||
.delete
|
.filter(_.avatarId == lift(avatar.id))
|
||||||
)
|
.delete
|
||||||
.onComplete {
|
)
|
||||||
case Success(_) =>
|
.onComplete {
|
||||||
replaceAvatar(avatar.copy(implants = avatar.implants.updated(_index, None)))
|
case Success(_) =>
|
||||||
sessionActor ! SessionActor.SendResponse(
|
replaceAvatar(avatar.copy(implants = avatar.implants.updated(_index, None)))
|
||||||
AvatarImplantMessage(session.get.player.GUID, ImplantAction.Remove, _index, 0)
|
sessionActor ! SessionActor.SendResponse(
|
||||||
)
|
AvatarImplantMessage(session.get.player.GUID, ImplantAction.Remove, _index, 0)
|
||||||
sessionActor ! SessionActor.SendResponse(
|
)
|
||||||
ItemTransactionResultMessage(terminalGuid, TransactionType.Sell, success = true)
|
sessionActor ! SessionActor.SendResponse(
|
||||||
)
|
ItemTransactionResultMessage(terminalGuid, TransactionType.Sell, success = true)
|
||||||
context.self ! ResetImplants()
|
)
|
||||||
sessionActor ! SessionActor.CharSaved
|
context.self ! ResetImplants()
|
||||||
case Failure(exception) => log.error(exception)("db failure")
|
sessionActor ! SessionActor.CharSaved
|
||||||
}
|
case Failure(exception) => log.error(exception)("db failure")
|
||||||
|
}
|
||||||
|
|
||||||
case None =>
|
case None =>
|
||||||
log.warn("attempted to sell implant but could not find slot")
|
log.warn("attempted to sell implant but could not find slot")
|
||||||
|
|
@ -1462,23 +1480,25 @@ class AvatarActor(
|
||||||
|
|
||||||
case UpdatePurchaseTime(definition, time) =>
|
case UpdatePurchaseTime(definition, time) =>
|
||||||
var newTimes = avatar.cooldowns.purchase
|
var newTimes = avatar.cooldowns.purchase
|
||||||
AvatarActor.resolveSharedPurchaseTimeNames(AvatarActor.resolvePurchaseTimeName(avatar.faction, definition)).foreach {
|
AvatarActor
|
||||||
case (item, name) =>
|
.resolveSharedPurchaseTimeNames(AvatarActor.resolvePurchaseTimeName(avatar.faction, definition))
|
||||||
Avatar.purchaseCooldowns.get(item) match {
|
.foreach {
|
||||||
case Some(cooldown) =>
|
case (item, name) =>
|
||||||
//only send for items with cooldowns
|
Avatar.purchaseCooldowns.get(item) match {
|
||||||
newTimes = newTimes.updated(name, time)
|
case Some(cooldown) =>
|
||||||
updatePurchaseTimer(
|
//only send for items with cooldowns
|
||||||
name,
|
newTimes = newTimes.updated(name, time)
|
||||||
cooldown.toSeconds,
|
updatePurchaseTimer(
|
||||||
item match {
|
name,
|
||||||
case _: KitDefinition => false
|
cooldown.toSeconds,
|
||||||
case _ => true
|
item match {
|
||||||
}
|
case _: KitDefinition => false
|
||||||
)
|
case _ => true
|
||||||
case _ => ;
|
}
|
||||||
}
|
)
|
||||||
}
|
case _ => ;
|
||||||
|
}
|
||||||
|
}
|
||||||
avatarCopy(avatar.copy(cooldowns = avatar.cooldowns.copy(purchase = newTimes)))
|
avatarCopy(avatar.copy(cooldowns = avatar.cooldowns.copy(purchase = newTimes)))
|
||||||
Behaviors.same
|
Behaviors.same
|
||||||
|
|
||||||
|
|
@ -1675,14 +1695,16 @@ class AvatarActor(
|
||||||
Behaviors.same
|
Behaviors.same
|
||||||
|
|
||||||
case SetRibbon(ribbon, bar) =>
|
case SetRibbon(ribbon, bar) =>
|
||||||
val decor = avatar.decoration
|
val decor = avatar.decoration
|
||||||
val previousRibbonBars = decor.ribbonBars
|
val previousRibbonBars = decor.ribbonBars
|
||||||
val useRibbonBars = Seq(previousRibbonBars.upper, previousRibbonBars.middle, previousRibbonBars.lower)
|
val useRibbonBars = Seq(previousRibbonBars.upper, previousRibbonBars.middle, previousRibbonBars.lower)
|
||||||
.indexWhere { _ == ribbon } match {
|
.indexWhere { _ == ribbon } match {
|
||||||
case -1 => previousRibbonBars
|
case -1 => previousRibbonBars
|
||||||
case n => AvatarActor.changeRibbons(previousRibbonBars, MeritCommendation.None, RibbonBarSlot(n))
|
case n => AvatarActor.changeRibbons(previousRibbonBars, MeritCommendation.None, RibbonBarSlot(n))
|
||||||
}
|
}
|
||||||
replaceAvatar(avatar.copy(decoration = decor.copy(ribbonBars = AvatarActor.changeRibbons(useRibbonBars, ribbon, bar))))
|
replaceAvatar(
|
||||||
|
avatar.copy(decoration = decor.copy(ribbonBars = AvatarActor.changeRibbons(useRibbonBars, ribbon, bar)))
|
||||||
|
)
|
||||||
val player = session.get.player
|
val player = session.get.player
|
||||||
val zone = player.Zone
|
val zone = player.Zone
|
||||||
zone.AvatarEvents ! AvatarServiceMessage(
|
zone.AvatarEvents ! AvatarServiceMessage(
|
||||||
|
|
@ -1706,9 +1728,11 @@ class AvatarActor(
|
||||||
case _ => false
|
case _ => false
|
||||||
})
|
})
|
||||||
if (isDifferentShortcut) {
|
if (isDifferentShortcut) {
|
||||||
if (!isMacroShortcut && avatar.shortcuts.flatten.exists {
|
if (
|
||||||
a => AvatarShortcut.equals(shortcut, a)
|
!isMacroShortcut && avatar.shortcuts.flatten.exists { a =>
|
||||||
}) {
|
AvatarShortcut.equals(shortcut, a)
|
||||||
|
}
|
||||||
|
) {
|
||||||
//duplicate implant or medkit found
|
//duplicate implant or medkit found
|
||||||
if (shortcut.isInstanceOf[Shortcut.Implant]) {
|
if (shortcut.isInstanceOf[Shortcut.Implant]) {
|
||||||
//duplicate implant
|
//duplicate implant
|
||||||
|
|
@ -1716,11 +1740,17 @@ class AvatarActor(
|
||||||
case Some(existingShortcut: AvatarShortcut) =>
|
case Some(existingShortcut: AvatarShortcut) =>
|
||||||
//redraw redundant shortcut slot with existing shortcut
|
//redraw redundant shortcut slot with existing shortcut
|
||||||
sessionActor ! SessionActor.SendResponse(
|
sessionActor ! SessionActor.SendResponse(
|
||||||
CreateShortcutMessage(session.get.player.GUID, slot + 1, Some(AvatarShortcut.convert(existingShortcut)))
|
CreateShortcutMessage(
|
||||||
|
session.get.player.GUID,
|
||||||
|
slot + 1,
|
||||||
|
Some(AvatarShortcut.convert(existingShortcut))
|
||||||
|
)
|
||||||
)
|
)
|
||||||
case _ =>
|
case _ =>
|
||||||
//blank shortcut slot
|
//blank shortcut slot
|
||||||
sessionActor ! SessionActor.SendResponse(CreateShortcutMessage(session.get.player.GUID, slot + 1, None))
|
sessionActor ! SessionActor.SendResponse(
|
||||||
|
CreateShortcutMessage(session.get.player.GUID, slot + 1, None)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -1743,7 +1773,7 @@ class AvatarActor(
|
||||||
.filter(_.slot == lift(slot))
|
.filter(_.slot == lift(slot))
|
||||||
.update(
|
.update(
|
||||||
_.purpose -> lift(shortcut.code),
|
_.purpose -> lift(shortcut.code),
|
||||||
_.tile -> lift(shortcut.tile),
|
_.tile -> lift(shortcut.tile),
|
||||||
_.effect1 -> Option(lift(optEffect1)),
|
_.effect1 -> Option(lift(optEffect1)),
|
||||||
_.effect2 -> Option(lift(optEffect2))
|
_.effect2 -> Option(lift(optEffect2))
|
||||||
)
|
)
|
||||||
|
|
@ -1752,11 +1782,11 @@ class AvatarActor(
|
||||||
ctx.run(
|
ctx.run(
|
||||||
query[persistence.Shortcut].insert(
|
query[persistence.Shortcut].insert(
|
||||||
_.avatarId -> lift(avatar.id.toLong),
|
_.avatarId -> lift(avatar.id.toLong),
|
||||||
_.slot -> lift(slot),
|
_.slot -> lift(slot),
|
||||||
_.purpose -> lift(shortcut.code),
|
_.purpose -> lift(shortcut.code),
|
||||||
_.tile -> lift(shortcut.tile),
|
_.tile -> lift(shortcut.tile),
|
||||||
_.effect1 -> Option(lift(optEffect1)),
|
_.effect1 -> Option(lift(optEffect1)),
|
||||||
_.effect2 -> Option(lift(optEffect2))
|
_.effect2 -> Option(lift(optEffect2))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -1771,10 +1801,11 @@ class AvatarActor(
|
||||||
avatar.shortcuts.lift(slot).flatten match {
|
avatar.shortcuts.lift(slot).flatten match {
|
||||||
case None => ;
|
case None => ;
|
||||||
case Some(_) =>
|
case Some(_) =>
|
||||||
ctx.run(query[persistence.Shortcut]
|
ctx.run(
|
||||||
.filter(_.avatarId == lift(avatar.id.toLong))
|
query[persistence.Shortcut]
|
||||||
.filter(_.slot == lift(slot))
|
.filter(_.avatarId == lift(avatar.id.toLong))
|
||||||
.delete
|
.filter(_.slot == lift(slot))
|
||||||
|
.delete
|
||||||
)
|
)
|
||||||
avatar.shortcuts.update(slot, None)
|
avatar.shortcuts.update(slot, None)
|
||||||
}
|
}
|
||||||
|
|
@ -1803,12 +1834,16 @@ class AvatarActor(
|
||||||
|
|
||||||
val result = for {
|
val result = for {
|
||||||
//log this login
|
//log this login
|
||||||
_ <- ctx.run(query[persistence.Avatar].filter(_.id == lift(avatarId))
|
_ <- ctx.run(
|
||||||
.update(_.lastLogin -> lift(LocalDateTime.now()))
|
query[persistence.Avatar]
|
||||||
|
.filter(_.id == lift(avatarId))
|
||||||
|
.update(_.lastLogin -> lift(LocalDateTime.now()))
|
||||||
)
|
)
|
||||||
//log this choice of faction (no empire switching)
|
//log this choice of faction (no empire switching)
|
||||||
_ <- ctx.run(query[persistence.Account].filter(_.id == lift(accountId))
|
_ <- ctx.run(
|
||||||
.update(_.lastFactionId -> lift(avatar.faction.id))
|
query[persistence.Account]
|
||||||
|
.filter(_.id == lift(accountId))
|
||||||
|
.update(_.lastFactionId -> lift(avatar.faction.id))
|
||||||
)
|
)
|
||||||
//retrieve avatar data
|
//retrieve avatar data
|
||||||
loadouts <- initializeAllLoadouts()
|
loadouts <- initializeAllLoadouts()
|
||||||
|
|
@ -1887,11 +1922,12 @@ class AvatarActor(
|
||||||
val p = Promise[Unit]()
|
val p = Promise[Unit]()
|
||||||
|
|
||||||
import ctx._
|
import ctx._
|
||||||
ctx.run(
|
ctx
|
||||||
query[persistence.Avatar]
|
.run(
|
||||||
.filter(_.id == lift(avatar.id))
|
query[persistence.Avatar]
|
||||||
.update(_.cosmetics -> lift(Some(Cosmetic.valuesToObjectCreateValue(cosmetics)): Option[Int]))
|
.filter(_.id == lift(avatar.id))
|
||||||
)
|
.update(_.cosmetics -> lift(Some(Cosmetic.valuesToObjectCreateValue(cosmetics)): Option[Int]))
|
||||||
|
)
|
||||||
.onComplete {
|
.onComplete {
|
||||||
case Success(_) =>
|
case Success(_) =>
|
||||||
val zone = session.get.zone
|
val zone = session.get.zone
|
||||||
|
|
@ -2177,6 +2213,7 @@ class AvatarActor(
|
||||||
secondsSinceLastLogin
|
secondsSinceLastLogin
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
/** After the user has selected a character to load from the "character select screen,"
|
/** After the user has selected a character to load from the "character select screen,"
|
||||||
* the temporary global unique identifiers used for that screen are stripped from the underlying `Player` object that was selected.
|
* the temporary global unique identifiers used for that screen are stripped from the underlying `Player` object that was selected.
|
||||||
* Characters that were not selected may be destroyed along with their temporary GUIDs.
|
* Characters that were not selected may be destroyed along with their temporary GUIDs.
|
||||||
|
|
@ -2471,42 +2508,45 @@ class AvatarActor(
|
||||||
val locker = Avatar.makeLocker()
|
val locker = Avatar.makeLocker()
|
||||||
saveLockerFunc = storeLocker
|
saveLockerFunc = storeLocker
|
||||||
val out = Promise[LockerContainer]()
|
val out = Promise[LockerContainer]()
|
||||||
ctx.run(query[persistence.Locker].filter(_.avatarId == lift(charId)))
|
ctx
|
||||||
|
.run(query[persistence.Locker].filter(_.avatarId == lift(charId)))
|
||||||
.onComplete {
|
.onComplete {
|
||||||
case Success(entry) if entry.nonEmpty =>
|
case Success(entry) if entry.nonEmpty =>
|
||||||
AvatarActor.buildContainedEquipmentFromClob(locker, entry.head.items, log, restoreAmmo = true)
|
AvatarActor.buildContainedEquipmentFromClob(locker, entry.head.items, log, restoreAmmo = true)
|
||||||
out.completeWith(Future(locker))
|
out.completeWith(Future(locker))
|
||||||
case Success(_) =>
|
case Success(_) =>
|
||||||
//no locker, or maybe default empty locker?
|
//no locker, or maybe default empty locker?
|
||||||
ctx.run(query[persistence.Locker].insert(_.avatarId -> lift(avatar.id), _.items -> lift("")))
|
ctx
|
||||||
.onComplete {
|
.run(query[persistence.Locker].insert(_.avatarId -> lift(avatar.id), _.items -> lift("")))
|
||||||
_ => out.completeWith(Future(locker))
|
.onComplete { _ =>
|
||||||
}
|
out.completeWith(Future(locker))
|
||||||
case Failure(e) =>
|
}
|
||||||
saveLockerFunc = doNotStoreLocker
|
case Failure(e) =>
|
||||||
log.error(e)("db failure")
|
saveLockerFunc = doNotStoreLocker
|
||||||
out.tryFailure(e)
|
log.error(e)("db failure")
|
||||||
}
|
out.tryFailure(e)
|
||||||
|
}
|
||||||
out.future
|
out.future
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def loadFriendList(avatarId: Long): Future[List[AvatarFriend]] = {
|
def loadFriendList(avatarId: Long): Future[List[AvatarFriend]] = {
|
||||||
import ctx._
|
import ctx._
|
||||||
val out: Promise[List[AvatarFriend]] = Promise()
|
val out: Promise[List[AvatarFriend]] = Promise()
|
||||||
|
|
||||||
val queryResult = ctx.run(
|
val queryResult = ctx.run(
|
||||||
query[persistence.Friend].filter { _.avatarId == lift(avatarId) }
|
query[persistence.Friend]
|
||||||
|
.filter { _.avatarId == lift(avatarId) }
|
||||||
.join(query[persistence.Avatar])
|
.join(query[persistence.Avatar])
|
||||||
.on { case (friend, avatar) => friend.charId == avatar.id }
|
.on { case (friend, avatar) => friend.charId == avatar.id }
|
||||||
.map { case (_, avatar) => (avatar.id, avatar.name, avatar.factionId) }
|
.map { case (_, avatar) => (avatar.id, avatar.name, avatar.factionId) }
|
||||||
)
|
)
|
||||||
queryResult.onComplete {
|
queryResult.onComplete {
|
||||||
case Success(list) =>
|
case Success(list) =>
|
||||||
out.completeWith(Future(
|
out.completeWith(
|
||||||
list.map { case (id, name, faction) => AvatarFriend(id, name, PlanetSideEmpire(faction)) }.toList
|
Future(
|
||||||
))
|
list.map { case (id, name, faction) => AvatarFriend(id, name, PlanetSideEmpire(faction)) }.toList
|
||||||
|
)
|
||||||
|
)
|
||||||
case _ =>
|
case _ =>
|
||||||
out.completeWith(Future(List.empty[AvatarFriend]))
|
out.completeWith(Future(List.empty[AvatarFriend]))
|
||||||
}
|
}
|
||||||
|
|
@ -2518,16 +2558,19 @@ class AvatarActor(
|
||||||
val out: Promise[List[AvatarIgnored]] = Promise()
|
val out: Promise[List[AvatarIgnored]] = Promise()
|
||||||
|
|
||||||
val queryResult = ctx.run(
|
val queryResult = ctx.run(
|
||||||
query[persistence.Ignored].filter { _.avatarId == lift(avatarId) }
|
query[persistence.Ignored]
|
||||||
|
.filter { _.avatarId == lift(avatarId) }
|
||||||
.join(query[persistence.Avatar])
|
.join(query[persistence.Avatar])
|
||||||
.on { case (friend, avatar) => friend.charId == avatar.id }
|
.on { case (friend, avatar) => friend.charId == avatar.id }
|
||||||
.map { case (_, avatar) => (avatar.id, avatar.name) }
|
.map { case (_, avatar) => (avatar.id, avatar.name) }
|
||||||
)
|
)
|
||||||
queryResult.onComplete {
|
queryResult.onComplete {
|
||||||
case Success(list) =>
|
case Success(list) =>
|
||||||
out.completeWith(Future(
|
out.completeWith(
|
||||||
list.map { case (id, name) => AvatarIgnored(id, name) }.toList
|
Future(
|
||||||
))
|
list.map { case (id, name) => AvatarIgnored(id, name) }.toList
|
||||||
|
)
|
||||||
|
)
|
||||||
case _ =>
|
case _ =>
|
||||||
out.completeWith(Future(List.empty[AvatarIgnored]))
|
out.completeWith(Future(List.empty[AvatarIgnored]))
|
||||||
}
|
}
|
||||||
|
|
@ -2539,14 +2582,16 @@ class AvatarActor(
|
||||||
val out: Promise[Array[Option[AvatarShortcut]]] = Promise()
|
val out: Promise[Array[Option[AvatarShortcut]]] = Promise()
|
||||||
|
|
||||||
val queryResult = ctx.run(
|
val queryResult = ctx.run(
|
||||||
query[persistence.Shortcut].filter { _.avatarId == lift(avatarId) }
|
query[persistence.Shortcut]
|
||||||
|
.filter { _.avatarId == lift(avatarId) }
|
||||||
.map { shortcut => (shortcut.slot, shortcut.purpose, shortcut.tile, shortcut.effect1, shortcut.effect2) }
|
.map { shortcut => (shortcut.slot, shortcut.purpose, shortcut.tile, shortcut.effect1, shortcut.effect2) }
|
||||||
)
|
)
|
||||||
val output = Array.fill[Option[AvatarShortcut]](64)(None)
|
val output = Array.fill[Option[AvatarShortcut]](64)(None)
|
||||||
queryResult.onComplete {
|
queryResult.onComplete {
|
||||||
case Success(list) =>
|
case Success(list) =>
|
||||||
list.foreach { case (slot, purpose, tile, effect1, effect2) =>
|
list.foreach {
|
||||||
output.update(slot, Some(AvatarShortcut(purpose, tile, effect1.getOrElse(""), effect2.getOrElse(""))))
|
case (slot, purpose, tile, effect1, effect2) =>
|
||||||
|
output.update(slot, Some(AvatarShortcut(purpose, tile, effect1.getOrElse(""), effect2.getOrElse(""))))
|
||||||
}
|
}
|
||||||
out.completeWith(Future(output))
|
out.completeWith(Future(output))
|
||||||
case Failure(e) =>
|
case Failure(e) =>
|
||||||
|
|
@ -2597,7 +2642,7 @@ class AvatarActor(
|
||||||
cooldown.toSeconds - secondsSincePurchase,
|
cooldown.toSeconds - secondsSincePurchase,
|
||||||
obj match {
|
obj match {
|
||||||
case _: KitDefinition => false
|
case _: KitDefinition => false
|
||||||
case _ => true
|
case _ => true
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -2648,7 +2693,7 @@ class AvatarActor(
|
||||||
case MemberAction.RemoveFriend => getAvatarForFunc(name, formatForOtherFunc(memberActionRemoveFriend))
|
case MemberAction.RemoveFriend => getAvatarForFunc(name, formatForOtherFunc(memberActionRemoveFriend))
|
||||||
case MemberAction.AddIgnoredPlayer => getAvatarForFunc(name, memberActionAddIgnored)
|
case MemberAction.AddIgnoredPlayer => getAvatarForFunc(name, memberActionAddIgnored)
|
||||||
case MemberAction.RemoveIgnoredPlayer => getAvatarForFunc(name, formatForOtherFunc(memberActionRemoveIgnored))
|
case MemberAction.RemoveIgnoredPlayer => getAvatarForFunc(name, formatForOtherFunc(memberActionRemoveIgnored))
|
||||||
case _ => ;
|
case _ => ;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2658,15 +2703,17 @@ class AvatarActor(
|
||||||
* @return a list of `Friends` suitable for putting into a packet
|
* @return a list of `Friends` suitable for putting into a packet
|
||||||
*/
|
*/
|
||||||
def transformFriendsList(): List[GameFriend] = {
|
def transformFriendsList(): List[GameFriend] = {
|
||||||
avatar.people.friend.map { f => GameFriend(f.name, f.online)}
|
avatar.people.friend.map { f => GameFriend(f.name, f.online) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transform the ignored players list in a list of packet entities.
|
* Transform the ignored players list in a list of packet entities.
|
||||||
* @return a list of `Friends` suitable for putting into a packet
|
* @return a list of `Friends` suitable for putting into a packet
|
||||||
*/
|
*/
|
||||||
def transformIgnoredList(): List[GameFriend] = {
|
def transformIgnoredList(): List[GameFriend] = {
|
||||||
avatar.people.ignored.map { f => GameFriend(f.name, f.online)}
|
avatar.people.ignored.map { f => GameFriend(f.name, f.online) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reload the list of friend players or ignored players for the client.
|
* Reload the list of friend players or ignored players for the client.
|
||||||
* This does not update any player's online status, but merely reloads current states.
|
* This does not update any player's online status, but merely reloads current states.
|
||||||
|
|
@ -2674,7 +2721,7 @@ class AvatarActor(
|
||||||
* (either `InitializeFriendList` or `InitializeIgnoreList`, hopefully)
|
* (either `InitializeFriendList` or `InitializeIgnoreList`, hopefully)
|
||||||
* @param listFunc transformation function that produces data suitable for a game paket
|
* @param listFunc transformation function that produces data suitable for a game paket
|
||||||
*/
|
*/
|
||||||
def memberActionListManagement(action: MemberAction.Value, listFunc: ()=>List[GameFriend]): Unit = {
|
def memberActionListManagement(action: MemberAction.Value, listFunc: () => List[GameFriend]): Unit = {
|
||||||
FriendsResponse.packetSequence(action, listFunc()).foreach { msg =>
|
FriendsResponse.packetSequence(action, listFunc()).foreach { msg =>
|
||||||
sessionActor ! SessionActor.SendResponse(msg)
|
sessionActor ! SessionActor.SendResponse(msg)
|
||||||
}
|
}
|
||||||
|
|
@ -2693,16 +2740,20 @@ class AvatarActor(
|
||||||
case Some(_) => ;
|
case Some(_) => ;
|
||||||
case None =>
|
case None =>
|
||||||
import ctx._
|
import ctx._
|
||||||
ctx.run(query[persistence.Friend]
|
ctx.run(
|
||||||
.insert(
|
query[persistence.Friend]
|
||||||
_.avatarId -> lift(avatar.id.toLong),
|
.insert(
|
||||||
_.charId -> lift(charId)
|
_.avatarId -> lift(avatar.id.toLong),
|
||||||
)
|
_.charId -> lift(charId)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
val isOnline = onlineIfNotIgnoredEitherWay(avatar, name)
|
val isOnline = onlineIfNotIgnoredEitherWay(avatar, name)
|
||||||
replaceAvatar(avatar.copy(
|
replaceAvatar(
|
||||||
people = people.copy(friend = people.friend :+ AvatarFriend(charId, name, PlanetSideEmpire(faction), isOnline))
|
avatar.copy(
|
||||||
))
|
people =
|
||||||
|
people.copy(friend = people.friend :+ AvatarFriend(charId, name, PlanetSideEmpire(faction), isOnline))
|
||||||
|
)
|
||||||
|
)
|
||||||
sessionActor ! SessionActor.SendResponse(FriendsResponse(MemberAction.AddFriend, GameFriend(name, isOnline)))
|
sessionActor ! SessionActor.SendResponse(FriendsResponse(MemberAction.AddFriend, GameFriend(name, isOnline)))
|
||||||
sessionActor ! SessionActor.CharSaved
|
sessionActor ! SessionActor.CharSaved
|
||||||
}
|
}
|
||||||
|
|
@ -2724,17 +2775,17 @@ class AvatarActor(
|
||||||
)
|
)
|
||||||
case None => ;
|
case None => ;
|
||||||
}
|
}
|
||||||
ctx.run(query[persistence.Friend]
|
ctx.run(
|
||||||
.filter(_.avatarId == lift(avatar.id))
|
query[persistence.Friend]
|
||||||
.filter(_.charId == lift(charId))
|
.filter(_.avatarId == lift(avatar.id))
|
||||||
.delete
|
.filter(_.charId == lift(charId))
|
||||||
|
.delete
|
||||||
)
|
)
|
||||||
sessionActor ! SessionActor.SendResponse(FriendsResponse(MemberAction.RemoveFriend, GameFriend(name)))
|
sessionActor ! SessionActor.SendResponse(FriendsResponse(MemberAction.RemoveFriend, GameFriend(name)))
|
||||||
sessionActor ! SessionActor.CharSaved
|
sessionActor ! SessionActor.CharSaved
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
* @param name unique character name
|
* @param name unique character name
|
||||||
* @return if the avatar is found, that avatar's unique identifier and the avatar's faction affiliation
|
* @return if the avatar is found, that avatar's unique identifier and the avatar's faction affiliation
|
||||||
*/
|
*/
|
||||||
|
|
@ -2752,11 +2803,13 @@ class AvatarActor(
|
||||||
case None =>
|
case None =>
|
||||||
(None, false)
|
(None, false)
|
||||||
}
|
}
|
||||||
replaceAvatar(avatar.copy(
|
replaceAvatar(
|
||||||
people = people.copy(
|
avatar.copy(
|
||||||
friend = people.friend.filterNot { _.name.equals(name) } :+ otherFriend.copy(online = online)
|
people = people.copy(
|
||||||
|
friend = people.friend.filterNot { _.name.equals(name) } :+ otherFriend.copy(online = online)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
))
|
)
|
||||||
sessionActor ! SessionActor.SendResponse(FriendsResponse(MemberAction.UpdateFriend, GameFriend(name, online)))
|
sessionActor ! SessionActor.SendResponse(FriendsResponse(MemberAction.UpdateFriend, GameFriend(name, online)))
|
||||||
out
|
out
|
||||||
case None =>
|
case None =>
|
||||||
|
|
@ -2782,16 +2835,19 @@ class AvatarActor(
|
||||||
case Some(_) => ;
|
case Some(_) => ;
|
||||||
case None =>
|
case None =>
|
||||||
import ctx._
|
import ctx._
|
||||||
ctx.run(query[persistence.Ignored]
|
ctx.run(
|
||||||
.insert(
|
query[persistence.Ignored]
|
||||||
_.avatarId -> lift(avatar.id.toLong),
|
.insert(
|
||||||
_.charId -> lift(charId)
|
_.avatarId -> lift(avatar.id.toLong),
|
||||||
)
|
_.charId -> lift(charId)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
replaceAvatar(
|
replaceAvatar(
|
||||||
avatar.copy(people = people.copy(ignored = people.ignored :+ AvatarIgnored(charId, name)))
|
avatar.copy(people = people.copy(ignored = people.ignored :+ AvatarIgnored(charId, name)))
|
||||||
)
|
)
|
||||||
sessionActor ! SessionActor.UpdateIgnoredPlayers(FriendsResponse(MemberAction.AddIgnoredPlayer, GameFriend(name)))
|
sessionActor ! SessionActor.UpdateIgnoredPlayers(
|
||||||
|
FriendsResponse(MemberAction.AddIgnoredPlayer, GameFriend(name))
|
||||||
|
)
|
||||||
sessionActor ! SessionActor.CharSaved
|
sessionActor ! SessionActor.CharSaved
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2814,31 +2870,34 @@ class AvatarActor(
|
||||||
)
|
)
|
||||||
case None => ;
|
case None => ;
|
||||||
}
|
}
|
||||||
ctx.run(query[persistence.Ignored]
|
ctx.run(
|
||||||
.filter(_.avatarId == lift(avatar.id.toLong))
|
query[persistence.Ignored]
|
||||||
.filter(_.charId == lift(charId))
|
.filter(_.avatarId == lift(avatar.id.toLong))
|
||||||
.delete
|
.filter(_.charId == lift(charId))
|
||||||
|
.delete
|
||||||
|
)
|
||||||
|
sessionActor ! SessionActor.UpdateIgnoredPlayers(
|
||||||
|
FriendsResponse(MemberAction.RemoveIgnoredPlayer, GameFriend(name))
|
||||||
)
|
)
|
||||||
sessionActor ! SessionActor.UpdateIgnoredPlayers(FriendsResponse(MemberAction.RemoveIgnoredPlayer, GameFriend(name)))
|
|
||||||
sessionActor ! SessionActor.CharSaved
|
sessionActor ! SessionActor.CharSaved
|
||||||
}
|
}
|
||||||
|
|
||||||
def setBep(bep: Long, modifier: ExperienceType): Unit = {
|
def setBep(bep: Long, modifier: ExperienceType): Unit = {
|
||||||
import ctx._
|
import ctx._
|
||||||
val current = BattleRank.withExperience(avatar.bep).value
|
val current = BattleRank.withExperience(avatar.bep).value
|
||||||
val next = BattleRank.withExperience(bep).value
|
val next = BattleRank.withExperience(bep).value
|
||||||
lazy val br24 = BattleRank.BR24.value
|
lazy val br24 = BattleRank.BR24.value
|
||||||
val result = for {
|
val result = for {
|
||||||
r <- ctx.run(query[persistence.Avatar].filter(_.id == lift(avatar.id)).update(_.bep -> lift(bep)))
|
r <- ctx.run(query[persistence.Avatar].filter(_.id == lift(avatar.id)).update(_.bep -> lift(bep)))
|
||||||
} yield r
|
} yield r
|
||||||
result.onComplete {
|
result.onComplete {
|
||||||
case Success(_) =>
|
case Success(_) =>
|
||||||
val sess = session.get
|
val sess = session.get
|
||||||
val zone = sess.zone
|
val zone = sess.zone
|
||||||
val zoneId = zone.id
|
val zoneId = zone.id
|
||||||
val events = zone.AvatarEvents
|
val events = zone.AvatarEvents
|
||||||
val player = sess.player
|
val player = sess.player
|
||||||
val pguid = player.GUID
|
val pguid = player.GUID
|
||||||
val localModifier = modifier
|
val localModifier = modifier
|
||||||
sessionActor ! SessionActor.SendResponse(BattleExperienceMessage(pguid, bep, localModifier))
|
sessionActor ! SessionActor.SendResponse(BattleExperienceMessage(pguid, bep, localModifier))
|
||||||
events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(pguid, 17, bep))
|
events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(pguid, 17, bep))
|
||||||
|
|
@ -2854,15 +2913,18 @@ class AvatarActor(
|
||||||
val implants = avatar.implants.zipWithIndex.map {
|
val implants = avatar.implants.zipWithIndex.map {
|
||||||
case (implant, index) =>
|
case (implant, index) =>
|
||||||
if (index >= BattleRank.withExperience(bep).implantSlots && implant.isDefined) {
|
if (index >= BattleRank.withExperience(bep).implantSlots && implant.isDefined) {
|
||||||
ctx.run(
|
ctx
|
||||||
query[persistence.Implant]
|
.run(
|
||||||
.filter(_.name == lift(implant.get.definition.Name))
|
query[persistence.Implant]
|
||||||
.filter(_.avatarId == lift(avatar.id))
|
.filter(_.name == lift(implant.get.definition.Name))
|
||||||
.delete
|
.filter(_.avatarId == lift(avatar.id))
|
||||||
)
|
.delete
|
||||||
|
)
|
||||||
.onComplete {
|
.onComplete {
|
||||||
case Success(_) =>
|
case Success(_) =>
|
||||||
sessionActor ! SessionActor.SendResponse(AvatarImplantMessage(pguid, ImplantAction.Remove, index, 0))
|
sessionActor ! SessionActor.SendResponse(
|
||||||
|
AvatarImplantMessage(pguid, ImplantAction.Remove, index, 0)
|
||||||
|
)
|
||||||
case Failure(exception) =>
|
case Failure(exception) =>
|
||||||
log.error(exception)("db failure")
|
log.error(exception)("db failure")
|
||||||
}
|
}
|
||||||
|
|
@ -2895,22 +2957,22 @@ class AvatarActor(
|
||||||
|
|
||||||
def updateKillsDeathsAssists(kdaStat: KDAStat): Unit = {
|
def updateKillsDeathsAssists(kdaStat: KDAStat): Unit = {
|
||||||
avatar.scorecard.rate(kdaStat)
|
avatar.scorecard.rate(kdaStat)
|
||||||
val exp = kdaStat.experienceEarned
|
val exp = kdaStat.experienceEarned
|
||||||
val _session = session.get
|
val _session = session.get
|
||||||
val zone = _session.zone
|
val zone = _session.zone
|
||||||
val player = _session.player
|
val player = _session.player
|
||||||
kdaStat match {
|
kdaStat match {
|
||||||
case kill: Kill =>
|
case kill: Kill =>
|
||||||
val _ = PlayerSource(player)
|
val _ = PlayerSource(player)
|
||||||
(kill.info.interaction.cause match {
|
(kill.info.interaction.cause match {
|
||||||
case pr: ProjectileReason => pr.projectile.mounted_in.map { a => zone.GUID(a._1) }
|
case pr: ProjectileReason => pr.projectile.mounted_in.map { a => zone.GUID(a._1) }
|
||||||
case _ => None
|
case _ => None
|
||||||
}) match {
|
}) match {
|
||||||
case Some(Some(_: Vitality)) =>
|
case Some(Some(_: Vitality)) =>
|
||||||
//zone.actor ! ZoneActor.RewardOurSupporters(playerSource, obj.History, kill, exp)
|
//zone.actor ! ZoneActor.RewardOurSupporters(playerSource, obj.History, kill, exp)
|
||||||
case _ => ;
|
case _ => ;
|
||||||
}
|
}
|
||||||
//zone.actor ! ZoneActor.RewardOurSupporters(playerSource, player.History, kill, exp)
|
//zone.actor ! ZoneActor.RewardOurSupporters(playerSource, player.History, kill, exp)
|
||||||
case _: Death =>
|
case _: Death =>
|
||||||
player.Zone.AvatarEvents ! AvatarServiceMessage(
|
player.Zone.AvatarEvents ! AvatarServiceMessage(
|
||||||
player.Name,
|
player.Name,
|
||||||
|
|
|
||||||
|
|
@ -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,9 +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
|
override val supervisorStrategy: SupervisorStrategy = sessionFuncs.sessionSupervisorStrategy
|
||||||
|
|
||||||
|
|
@ -195,7 +197,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 +205,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 +355,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 +369,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 +594,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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -256,5 +256,3 @@ object Tool {
|
||||||
def Definition: FireModeDefinition = fdef
|
def Definition: FireModeDefinition = fdef
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -39,4 +39,4 @@ object NonvitalDefinition {
|
||||||
out
|
out
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -654,4 +654,3 @@ object CarrierBehavior {
|
||||||
msgs
|
msgs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,4 +40,3 @@ trait MountableWeapons
|
||||||
|
|
||||||
def Definition: MountableWeaponsDefinition
|
def Definition: MountableWeaponsDefinition
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -55,4 +55,3 @@ class AmsControl(vehicle: Vehicle)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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] = {
|
||||||
|
|
|
||||||
|
|
@ -59,4 +59,4 @@ object TriggerUsedReason {
|
||||||
ResistUsing = NoResistanceSelection
|
ResistUsing = NoResistanceSelection
|
||||||
Model = SimpleResolutions.calculate
|
Model = SimpleResolutions.calculate
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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()}'"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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](
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -332,4 +332,3 @@ object MountableInventory {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -549,7 +549,7 @@ class CavernRotationService(
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param sendToSession callback reference
|
* @param sendToSession callback reference
|
||||||
*/
|
*/
|
||||||
def sendCavernRotationUpdates(sendToSession: ActorRef): Unit = {
|
def sendCavernRotationUpdates(sendToSession: ActorRef): Unit = {
|
||||||
|
|
|
||||||
|
|
@ -56,4 +56,4 @@ object HartTimerActions {
|
||||||
LocalAction.ShuttleState(shuttle.GUID, shuttle.Position, shuttle.Orientation, state)
|
LocalAction.ShuttleState(shuttle.GUID, shuttle.Position, shuttle.Orientation, state)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,12 +40,12 @@ 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.asInstanceOf[A]
|
||||||
},
|
},
|
||||||
_.toString
|
_.toString
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ import net.psforever.objects.serverobject.terminals.implant.ImplantTerminalMech
|
||||||
import net.psforever.objects.serverobject.tube.SpawnTube
|
import net.psforever.objects.serverobject.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
|
||||||
|
|
|
||||||
|
|
@ -45,4 +45,3 @@ class CaptureFlagUpdateMessageTest extends Specification with Debug {
|
||||||
pkt mustEqual stringOne
|
pkt mustEqual stringOne
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -65,4 +65,3 @@ class ComponentDamageMessageTest extends Specification {
|
||||||
pkt mustEqual string_off
|
pkt mustEqual string_off
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -54,4 +54,3 @@ class FrameVehicleStateMessageTest extends Specification {
|
||||||
pkt mustEqual string
|
pkt mustEqual string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,4 +28,3 @@ class GenericObjectActionAtPositionMessageTest extends Specification {
|
||||||
pkt mustEqual string
|
pkt mustEqual string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -347,4 +347,3 @@ class BattleframeRoboticsTest extends Specification {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,4 +21,4 @@ class LocalTest extends Specification {
|
||||||
obj.Definition.Name mustEqual "locker-equipment"
|
obj.Definition.Name mustEqual "locker-equipment"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -48,30 +48,52 @@ import scala.collection.mutable
|
||||||
import scala.concurrent.duration.{DurationInt, FiniteDuration}
|
import scala.concurrent.duration.{DurationInt, FiniteDuration}
|
||||||
import scala.reflect.ClassTag
|
import scala.reflect.ClassTag
|
||||||
import java.util.concurrent.{Executors, TimeUnit}
|
import java.util.concurrent.{Executors, TimeUnit}
|
||||||
|
import net.psforever.packet.game._
|
||||||
|
import net.psforever.types._
|
||||||
|
import scala.concurrent.Future
|
||||||
|
import scala.concurrent.Await
|
||||||
|
import scala.concurrent.duration.Duration
|
||||||
|
|
||||||
object Client {
|
object Client {
|
||||||
|
implicit val ec: scala.concurrent.ExecutionContext = scala.concurrent.ExecutionContext.global
|
||||||
Security.addProvider(new BouncyCastleProvider)
|
Security.addProvider(new BouncyCastleProvider)
|
||||||
|
|
||||||
private[this] val log = org.log4s.getLogger
|
private[this] val log = org.log4s.getLogger
|
||||||
|
|
||||||
def main(args: Array[String]): Unit = {
|
def main(args: Array[String]): Unit = {
|
||||||
val client = new Client("test", "test")
|
|
||||||
client.login(new InetSocketAddress("localhost", 51000))
|
|
||||||
client.joinWorld(client.state.worlds.head)
|
|
||||||
client.selectCharacter(client.state.characters.head.charId)
|
|
||||||
client.startTasks()
|
|
||||||
|
|
||||||
while (true) {
|
for (i <- 0 until 20) {
|
||||||
client.updateAvatar(client.state.avatar.copy(crouching = !client.state.avatar.crouching))
|
val id = i
|
||||||
Thread.sleep(2000)
|
|
||||||
//Thread.sleep(Int.MaxValue)
|
new Thread {
|
||||||
|
override def run: Unit = {
|
||||||
|
val client = new Client(s"bot${id}", "bot")
|
||||||
|
client.login(new InetSocketAddress("localhost", 51000))
|
||||||
|
|
||||||
|
client.joinWorld(client.state.worlds.head)
|
||||||
|
|
||||||
|
if (client.state.characters.isEmpty) {
|
||||||
|
client.createCharacter(s"bot${id}")
|
||||||
|
}
|
||||||
|
|
||||||
|
client.selectCharacter(client.state.characters.head.charId)
|
||||||
|
client.startTasks()
|
||||||
|
|
||||||
|
client.send(ChatMsg(ChatMessageType.CMT_ZONE, wideContents = false, "", "z1", None))
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
client.updateAvatar(client.state.avatar.copy(crouching = !client.state.avatar.crouching))
|
||||||
|
Thread.sleep(2000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Await.ready(Future.never, Duration.Inf)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Client(username: String, password: String) {
|
class Client(username: String, password: String) {
|
||||||
import Client._
|
|
||||||
|
|
||||||
private var sequence = 0
|
private var sequence = 0
|
||||||
private def nextSequence = {
|
private def nextSequence = {
|
||||||
val r = sequence
|
val r = sequence
|
||||||
|
|
@ -188,21 +210,34 @@ class Client(username: String, password: String) {
|
||||||
}
|
}
|
||||||
setupConnection()
|
setupConnection()
|
||||||
send(ConnectToWorldRequestMessage("", state.token.get, 0, 0, 0, "", 0)).require
|
send(ConnectToWorldRequestMessage("", state.token.get, 0, 0, 0, "", 0)).require
|
||||||
waitFor[CharacterInfoMessage]().require
|
while (true) {
|
||||||
|
val r = waitFor[CharacterInfoMessage]().require
|
||||||
|
if (r.finished) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def selectCharacter(charId: Long): Unit = {
|
def selectCharacter(charId: Long): Unit = {
|
||||||
assert(state.connection == Connection.AvatarSelection)
|
assert(state.connection == Connection.AvatarSelection)
|
||||||
send(CharacterRequestMessage(charId, CharacterRequestAction.Select)).require
|
send(CharacterRequestMessage(charId, CharacterRequestAction.Select)).require
|
||||||
waitFor[LoadMapMessage](timeout = 15.seconds).require
|
waitFor[LoadMapMessage](timeout = 30.seconds).require
|
||||||
}
|
}
|
||||||
|
|
||||||
def createCharacter(): Unit = {
|
def createCharacter(name: String): Unit = {
|
||||||
???
|
assert(state.connection == Connection.AvatarSelection)
|
||||||
|
send(CharacterCreateRequestMessage(name, 0, CharacterVoice.Voice1, CharacterSex.Male, PlanetSideEmpire.TR)).require
|
||||||
|
val r = waitFor[ActionResultMessage](timeout = 15.seconds).require
|
||||||
|
assert(r.errorCode == None)
|
||||||
|
while (true) {
|
||||||
|
val r = waitFor[CharacterInfoMessage]().require
|
||||||
|
if (r.finished) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def deleteCharacter(charId: Long): Unit = {
|
def deleteCharacter(charId: Long): Unit = {
|
||||||
??? // never been tested
|
|
||||||
assert(state.connection == Connection.AvatarSelection)
|
assert(state.connection == Connection.AvatarSelection)
|
||||||
send(CharacterRequestMessage(charId, CharacterRequestAction.Delete)).require
|
send(CharacterRequestMessage(charId, CharacterRequestAction.Delete)).require
|
||||||
}
|
}
|
||||||
|
|
@ -293,13 +328,11 @@ class Client(username: String, password: String) {
|
||||||
private def _process(packet: PlanetSidePacket): Unit = {
|
private def _process(packet: PlanetSidePacket): Unit = {
|
||||||
packet match {
|
packet match {
|
||||||
case _: KeepAliveMessage => ()
|
case _: KeepAliveMessage => ()
|
||||||
case _: LoadMapMessage =>
|
case _: LoadMapMessage =>
|
||||||
log.info(s"process: ${packet}")
|
|
||||||
send(BeginZoningMessage()).require
|
send(BeginZoningMessage()).require
|
||||||
_state = state.update(packet)
|
_state = state.update(packet)
|
||||||
case packet: PlanetSideGamePacket =>
|
case packet: PlanetSideGamePacket =>
|
||||||
_state = state.update(packet)
|
_state = state.update(packet)
|
||||||
log.info(s"process: ${packet}")
|
|
||||||
()
|
()
|
||||||
case _ => ()
|
case _ => ()
|
||||||
}
|
}
|
||||||
|
|
@ -346,10 +379,6 @@ class Client(username: String, password: String) {
|
||||||
sequence: Option[Int],
|
sequence: Option[Int],
|
||||||
crypto: Option[CryptoCoding]
|
crypto: Option[CryptoCoding]
|
||||||
): Attempt[BitVector] = {
|
): Attempt[BitVector] = {
|
||||||
packet match {
|
|
||||||
case _: KeepAliveMessage => ()
|
|
||||||
case _ => log.info(s"send: ${packet}")
|
|
||||||
}
|
|
||||||
PacketCoding.marshalPacket(packet, sequence, crypto) match {
|
PacketCoding.marshalPacket(packet, sequence, crypto) match {
|
||||||
case Successful(payload) =>
|
case Successful(payload) =>
|
||||||
send(payload.toByteArray)
|
send(payload.toByteArray)
|
||||||
|
|
|
||||||
|
|
@ -89,8 +89,14 @@ case class State(
|
||||||
case LoginRespMessage(token, _, _, _, _, _, _) => this.copy(token = Some(token))
|
case LoginRespMessage(token, _, _, _, _, _, _) => this.copy(token = Some(token))
|
||||||
case VNLWorldStatusMessage(_, worlds) => this.copy(worlds = worlds, connection = Connection.WorldSelection)
|
case VNLWorldStatusMessage(_, worlds) => this.copy(worlds = worlds, connection = Connection.WorldSelection)
|
||||||
case ObjectCreateDetailedMessage(_, objectClass, guid, _, _) => this.copy(objects = objects ++ Seq(guid.guid))
|
case ObjectCreateDetailedMessage(_, objectClass, guid, _, _) => this.copy(objects = objects ++ Seq(guid.guid))
|
||||||
case message @ CharacterInfoMessage(_, _, _, _, _, _) =>
|
case message @ CharacterInfoMessage(_, _, _, _, finished, _) =>
|
||||||
this.copy(characters = characters ++ Seq(message), connection = Connection.AvatarSelection)
|
// if finished is true, it is not real character but rather signal that list is complete
|
||||||
|
if (finished) {
|
||||||
|
this.copy(connection = Connection.AvatarSelection)
|
||||||
|
} else {
|
||||||
|
this.copy(characters = characters ++ Seq(message), connection = Connection.AvatarSelection)
|
||||||
|
}
|
||||||
|
|
||||||
case _ => this
|
case _ => this
|
||||||
}).copy(avatar = avatar.update(packet))
|
}).copy(avatar = avatar.update(packet))
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue