mirror of
https://github.com/psforever/PSF-LoginServer.git
synced 2026-01-19 18:44:45 +00:00
Avatar Persistence
* Add AvatarActor: Responsible for managing the session's avatar object * Convert Avatar object to case class * Add persistence for BEP, CEP, implants, certs and cosmetics * Add cosmetic chat commands and handle UI packet * Add /setbr, /setcr, /certadd, /addbep, /addcep GM commands * Convert zone maps to JSON * Update to Scala 2.13.3 and fix warnings * Fix MAX cooldowns not being applied when purchased manually * Normalize database table names to singular * Add docker image build
This commit is contained in:
parent
1efbedcf8e
commit
3bdc681c9d
23
.github/workflows/ci.yaml
vendored
23
.github/workflows/ci.yaml
vendored
|
|
@ -1,7 +1,7 @@
|
|||
name: CI
|
||||
on: [push, pull_request]
|
||||
jobs:
|
||||
build:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
services:
|
||||
postgres:
|
||||
|
|
@ -43,4 +43,23 @@ jobs:
|
|||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: pslogin.zip
|
||||
path: target/pslogin*.zip
|
||||
path: target/pslogin*.zip
|
||||
docker:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
- name: Inject slug/short variables
|
||||
uses: rlespinasse/github-slug-action@v2.x
|
||||
- name: Set variables
|
||||
run: |
|
||||
echo "::set-env name=REPOSITORY::$(echo $GITHUB_REPOSITORY | tr '[A-Z]' '[a-z]')"
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v1.1.0
|
||||
with:
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ github.token }}
|
||||
registry: docker.pkg.github.com
|
||||
repository: ${{ env.REPOSITORY }}/server
|
||||
tag_with_sha: true
|
||||
tag_with_ref: true
|
||||
|
|
@ -1,3 +1,3 @@
|
|||
version = 2.6.1
|
||||
version = 2.6.4
|
||||
preset = defaultWithAlign
|
||||
maxColumn = 120
|
||||
31
.travis.yml
31
.travis.yml
|
|
@ -1,31 +0,0 @@
|
|||
language: scala
|
||||
jdk: oraclejdk8
|
||||
dist: trusty
|
||||
|
||||
scala:
|
||||
- 2.13.2
|
||||
env:
|
||||
- SBT_COMMAND="test:compile quiet:test packArchiveZip"
|
||||
- SBT_COMMAND="coverage test:compile quiet:test coverageReport"
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.ivy2/cache
|
||||
- $HOME/.sbt
|
||||
before_cache:
|
||||
- rm -fv $HOME/.ivy2/.sbt.ivy.lock
|
||||
- find $HOME/.ivy2/cache -name "ivydata-*.properties" -print -delete
|
||||
- find $HOME/.sbt -name "*.lock" -print -delete
|
||||
|
||||
before_script:
|
||||
- wget https://github.com/psforever/PSCrypto/releases/download/v1.1/pscrypto-lib-1.1.zip
|
||||
- unzip pscrypto-lib-1.1.zip
|
||||
script:
|
||||
- sbt ++$TRAVIS_SCALA_VERSION $SBT_COMMAND
|
||||
after_success:
|
||||
- bash <(curl -s https://codecov.io/bash)
|
||||
|
||||
addons:
|
||||
artifacts:
|
||||
paths:
|
||||
- $(ls target/pslogin*.zip | tr "\n" ":")
|
||||
73
build.sbt
73
build.sbt
|
|
@ -3,7 +3,7 @@ import xerial.sbt.pack.PackPlugin._
|
|||
lazy val commonSettings = Seq(
|
||||
organization := "net.psforever",
|
||||
version := "1.0.2-SNAPSHOT",
|
||||
scalaVersion := "2.13.2",
|
||||
scalaVersion := "2.13.3",
|
||||
Global / cancelable := false,
|
||||
semanticdbEnabled := true,
|
||||
semanticdbVersion := scalafixSemanticdb.revision,
|
||||
|
|
@ -43,40 +43,43 @@ lazy val commonSettings = Seq(
|
|||
classLoaderLayeringStrategy := ClassLoaderLayeringStrategy.Flat,
|
||||
resolvers += "Sonatype OSS Snapshots" at "https://oss.sonatype.org/content/repositories/snapshots",
|
||||
libraryDependencies ++= Seq(
|
||||
"com.typesafe.akka" %% "akka-actor" % "2.6.6",
|
||||
"com.typesafe.akka" %% "akka-slf4j" % "2.6.6",
|
||||
"com.typesafe.akka" %% "akka-protobuf-v3" % "2.6.6",
|
||||
"com.typesafe.akka" %% "akka-stream" % "2.6.6",
|
||||
"com.typesafe.akka" %% "akka-testkit" % "2.6.6" % "test",
|
||||
"com.typesafe.akka" %% "akka-actor-typed" % "2.6.6",
|
||||
"com.typesafe.akka" %% "akka-cluster-typed" % "2.6.6",
|
||||
"com.typesafe.scala-logging" %% "scala-logging" % "3.9.2",
|
||||
"org.specs2" %% "specs2-core" % "4.9.4" % "test",
|
||||
"org.scalatest" %% "scalatest" % "3.1.2" % "test",
|
||||
"org.scodec" %% "scodec-core" % "1.11.7",
|
||||
"net.java.dev.jna" % "jna" % "5.5.0",
|
||||
"com.typesafe.akka" %% "akka-slf4j" % "2.6.5",
|
||||
"ch.qos.logback" % "logback-classic" % "1.2.3",
|
||||
"org.log4s" %% "log4s" % "1.8.2",
|
||||
"org.fusesource.jansi" % "jansi" % "1.12",
|
||||
"org.scoverage" %% "scalac-scoverage-plugin" % "1.4.1",
|
||||
"com.github.nscala-time" %% "nscala-time" % "2.24.0",
|
||||
"com.github.t3hnar" %% "scala-bcrypt" % "4.1",
|
||||
"org.scala-graph" %% "graph-core" % "1.13.1",
|
||||
"io.kamon" %% "kamon-bundle" % "2.1.0",
|
||||
"io.kamon" %% "kamon-apm-reporter" % "2.1.0",
|
||||
"org.json4s" %% "json4s-native" % "3.6.8",
|
||||
"com.typesafe.akka" %% "akka-stream" % "2.6.5",
|
||||
"io.getquill" %% "quill-jasync-postgres" % "3.5.2",
|
||||
"org.flywaydb" % "flyway-core" % "6.5.0",
|
||||
"org.postgresql" % "postgresql" % "42.2.14",
|
||||
"com.typesafe" % "config" % "1.4.0",
|
||||
"com.github.pureconfig" %% "pureconfig" % "0.13.0",
|
||||
"com.beachape" %% "enumeratum" % "1.6.1",
|
||||
"joda-time" % "joda-time" % "2.10.6",
|
||||
"commons-io" % "commons-io" % "2.6",
|
||||
"com.github.scopt" %% "scopt" % "4.0.0-RC2",
|
||||
"io.sentry" % "sentry-logback" % "1.7.30"
|
||||
"com.typesafe.akka" %% "akka-actor" % "2.6.6",
|
||||
"com.typesafe.akka" %% "akka-slf4j" % "2.6.6",
|
||||
"com.typesafe.akka" %% "akka-protobuf-v3" % "2.6.6",
|
||||
"com.typesafe.akka" %% "akka-stream" % "2.6.6",
|
||||
"com.typesafe.akka" %% "akka-testkit" % "2.6.6" % "test",
|
||||
"com.typesafe.akka" %% "akka-actor-typed" % "2.6.6",
|
||||
"com.typesafe.akka" %% "akka-cluster-typed" % "2.6.6",
|
||||
"com.typesafe.scala-logging" %% "scala-logging" % "3.9.2",
|
||||
"org.specs2" %% "specs2-core" % "4.9.4" % "test",
|
||||
"org.scalatest" %% "scalatest" % "3.1.2" % "test",
|
||||
"org.scodec" %% "scodec-core" % "1.11.7",
|
||||
"net.java.dev.jna" % "jna" % "5.5.0",
|
||||
"com.typesafe.akka" %% "akka-slf4j" % "2.6.5",
|
||||
"ch.qos.logback" % "logback-classic" % "1.2.3",
|
||||
"org.log4s" %% "log4s" % "1.8.2",
|
||||
"org.fusesource.jansi" % "jansi" % "1.12",
|
||||
"org.scoverage" %% "scalac-scoverage-plugin" % "1.4.1",
|
||||
"com.github.nscala-time" %% "nscala-time" % "2.24.0",
|
||||
"com.github.t3hnar" %% "scala-bcrypt" % "4.1",
|
||||
"org.scala-graph" %% "graph-core" % "1.13.1",
|
||||
"io.kamon" %% "kamon-bundle" % "2.1.0",
|
||||
"io.kamon" %% "kamon-apm-reporter" % "2.1.0",
|
||||
"org.json4s" %% "json4s-native" % "3.6.8",
|
||||
"io.getquill" %% "quill-jasync-postgres" % "3.5.2",
|
||||
"org.flywaydb" % "flyway-core" % "6.5.0",
|
||||
"org.postgresql" % "postgresql" % "42.2.14",
|
||||
"com.typesafe" % "config" % "1.4.0",
|
||||
"com.github.pureconfig" %% "pureconfig" % "0.13.0",
|
||||
"com.beachape" %% "enumeratum" % "1.6.1",
|
||||
"joda-time" % "joda-time" % "2.10.6",
|
||||
"commons-io" % "commons-io" % "2.6",
|
||||
"com.github.scopt" %% "scopt" % "4.0.0-RC2",
|
||||
"io.sentry" % "sentry-logback" % "1.7.30",
|
||||
"io.circe" %% "circe-core" % "0.12.3",
|
||||
"io.circe" %% "circe-generic" % "0.12.3",
|
||||
"io.circe" %% "circe-parser" % "0.12.3",
|
||||
"org.scala-lang.modules" %% "scala-parallel-collections" % "0.2.0"
|
||||
),
|
||||
// TODO(chord): remove exclusion when SessionActor is refactored: https://github.com/psforever/PSF-LoginServer/issues/279
|
||||
coverageExcludedPackages := "net\\.psforever\\.actors\\.session\\.SessionActor.*;net\\.psforever\\.zones\\.zonemaps.*"
|
||||
|
|
|
|||
1334
common/src/main/resources/zonemaps/lattice.json
Normal file
1334
common/src/main/resources/zonemaps/lattice.json
Normal file
File diff suppressed because it is too large
Load diff
24936
common/src/main/resources/zonemaps/map01.json
Normal file
24936
common/src/main/resources/zonemaps/map01.json
Normal file
File diff suppressed because it is too large
Load diff
30019
common/src/main/resources/zonemaps/map02.json
Normal file
30019
common/src/main/resources/zonemaps/map02.json
Normal file
File diff suppressed because it is too large
Load diff
44488
common/src/main/resources/zonemaps/map03.json
Normal file
44488
common/src/main/resources/zonemaps/map03.json
Normal file
File diff suppressed because it is too large
Load diff
36818
common/src/main/resources/zonemaps/map04.json
Normal file
36818
common/src/main/resources/zonemaps/map04.json
Normal file
File diff suppressed because it is too large
Load diff
25222
common/src/main/resources/zonemaps/map05.json
Normal file
25222
common/src/main/resources/zonemaps/map05.json
Normal file
File diff suppressed because it is too large
Load diff
28966
common/src/main/resources/zonemaps/map06.json
Normal file
28966
common/src/main/resources/zonemaps/map06.json
Normal file
File diff suppressed because it is too large
Load diff
33594
common/src/main/resources/zonemaps/map07.json
Normal file
33594
common/src/main/resources/zonemaps/map07.json
Normal file
File diff suppressed because it is too large
Load diff
25495
common/src/main/resources/zonemaps/map08.json
Normal file
25495
common/src/main/resources/zonemaps/map08.json
Normal file
File diff suppressed because it is too large
Load diff
34777
common/src/main/resources/zonemaps/map09.json
Normal file
34777
common/src/main/resources/zonemaps/map09.json
Normal file
File diff suppressed because it is too large
Load diff
31579
common/src/main/resources/zonemaps/map10.json
Normal file
31579
common/src/main/resources/zonemaps/map10.json
Normal file
File diff suppressed because it is too large
Load diff
14653
common/src/main/resources/zonemaps/map11.json
Normal file
14653
common/src/main/resources/zonemaps/map11.json
Normal file
File diff suppressed because it is too large
Load diff
13574
common/src/main/resources/zonemaps/map12.json
Normal file
13574
common/src/main/resources/zonemaps/map12.json
Normal file
File diff suppressed because it is too large
Load diff
13522
common/src/main/resources/zonemaps/map13.json
Normal file
13522
common/src/main/resources/zonemaps/map13.json
Normal file
File diff suppressed because it is too large
Load diff
9414
common/src/main/resources/zonemaps/map96.json
Normal file
9414
common/src/main/resources/zonemaps/map96.json
Normal file
File diff suppressed because it is too large
Load diff
11078
common/src/main/resources/zonemaps/map97.json
Normal file
11078
common/src/main/resources/zonemaps/map97.json
Normal file
File diff suppressed because it is too large
Load diff
8153
common/src/main/resources/zonemaps/map98.json
Normal file
8153
common/src/main/resources/zonemaps/map98.json
Normal file
File diff suppressed because it is too large
Load diff
3928
common/src/main/resources/zonemaps/map99.json
Normal file
3928
common/src/main/resources/zonemaps/map99.json
Normal file
File diff suppressed because it is too large
Load diff
11910
common/src/main/resources/zonemaps/ugd01.json
Normal file
11910
common/src/main/resources/zonemaps/ugd01.json
Normal file
File diff suppressed because it is too large
Load diff
20295
common/src/main/resources/zonemaps/ugd02.json
Normal file
20295
common/src/main/resources/zonemaps/ugd02.json
Normal file
File diff suppressed because it is too large
Load diff
14549
common/src/main/resources/zonemaps/ugd03.json
Normal file
14549
common/src/main/resources/zonemaps/ugd03.json
Normal file
File diff suppressed because it is too large
Load diff
11572
common/src/main/resources/zonemaps/ugd04.json
Normal file
11572
common/src/main/resources/zonemaps/ugd04.json
Normal file
File diff suppressed because it is too large
Load diff
8218
common/src/main/resources/zonemaps/ugd05.json
Normal file
8218
common/src/main/resources/zonemaps/ugd05.json
Normal file
File diff suppressed because it is too large
Load diff
11676
common/src/main/resources/zonemaps/ugd06.json
Normal file
11676
common/src/main/resources/zonemaps/ugd06.json
Normal file
File diff suppressed because it is too large
Load diff
1329
common/src/main/scala/net/psforever/actors/session/AvatarActor.scala
Normal file
1329
common/src/main/scala/net/psforever/actors/session/AvatarActor.scala
Normal file
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
|
|
@ -140,7 +140,7 @@ class BuildingActor(
|
|||
}
|
||||
building.Faction = faction
|
||||
galaxyService ! GalaxyServiceMessage(GalaxyAction.MapUpdate(building.infoUpdateMessage()))
|
||||
zone.LocalEvents ! LocalServiceMessage(zone.Id, LocalAction.SetEmpire(building.GUID, faction))
|
||||
zone.LocalEvents ! LocalServiceMessage(zone.id, LocalAction.SetEmpire(building.GUID, faction))
|
||||
Behaviors.same
|
||||
|
||||
case MapUpdate() =>
|
||||
|
|
|
|||
|
|
@ -9,10 +9,12 @@ import net.psforever.objects.serverobject.structures.StructureType
|
|||
import net.psforever.objects.zones.Zone
|
||||
import net.psforever.objects.{ConstructionItem, PlanetSideGameObject, Player, Vehicle}
|
||||
import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID, Vector3}
|
||||
|
||||
import scala.collection.mutable.ListBuffer
|
||||
import akka.actor.typed.scaladsl.adapter._
|
||||
import net.psforever.util.Database._
|
||||
import net.psforever.persistence
|
||||
|
||||
import scala.util.{Failure, Success}
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
|
||||
|
|
@ -29,7 +31,11 @@ object ZoneActor {
|
|||
final case class GetZone(replyTo: ActorRef[ZoneResponse]) extends Command
|
||||
|
||||
final case class ZoneResponse(zone: Zone)
|
||||
/*
|
||||
final case class AddAvatar(avatar: Avatar) extends Command
|
||||
|
||||
final case class RemoveAvatar(avatar: Avatar) extends Command
|
||||
*/
|
||||
final case class AddPlayer(player: Player) extends Command
|
||||
|
||||
final case class RemovePlayer(player: Player) extends Command
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ class CryptoSessionActor extends Actor with MDCContextAware {
|
|||
this.sessionId = sharedSessionId
|
||||
leftRef = sender()
|
||||
if (pipe.hasNext) {
|
||||
rightRef = pipe.next // who ever we send to has to send something back to us
|
||||
rightRef = pipe.next() // who ever we send to has to send something back to us
|
||||
rightRef !> HelloFriend(sessionId, pipe)
|
||||
} else {
|
||||
rightRef = sender()
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ class LoginSessionActor extends Actor with MDCContextAware {
|
|||
this.sessionId = aSessionId
|
||||
leftRef = sender()
|
||||
if (pipe.hasNext) {
|
||||
rightRef = pipe.next
|
||||
rightRef = pipe.next()
|
||||
rightRef !> HelloFriend(aSessionId, pipe)
|
||||
} else {
|
||||
rightRef = sender()
|
||||
|
|
@ -174,7 +174,7 @@ class LoginSessionActor extends Actor with MDCContextAware {
|
|||
log.info(s"$account")
|
||||
(account.inactive, password.isBcrypted(account.passhash)) match {
|
||||
case (false, true) =>
|
||||
accountIntermediary ! StoreAccountData(newToken, new Account(account.id, account.username, account.gm))
|
||||
accountIntermediary ! StoreAccountData(newToken, Account(account.id, account.username, account.gm))
|
||||
val future = ctx.run(
|
||||
query[persistence.Login].insert(
|
||||
_.accountId -> lift(account.id),
|
||||
|
|
@ -280,7 +280,7 @@ class LoginSessionActor extends Actor with MDCContextAware {
|
|||
val r = new scala.util.Random
|
||||
val sb = new StringBuilder
|
||||
for (_ <- 1 to 31) {
|
||||
sb.append(r.nextPrintableChar)
|
||||
sb.append(r.nextPrintableChar())
|
||||
}
|
||||
sb.toString
|
||||
}
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ class PacketCodingActor extends Actor with MDCContextAware {
|
|||
this.sessionId = sharedSessionId
|
||||
leftRef = sender()
|
||||
if (pipe.hasNext) {
|
||||
rightRef = pipe.next
|
||||
rightRef = pipe.next()
|
||||
rightRef !> HelloFriend(sessionId, pipe)
|
||||
} else {
|
||||
rightRef = sender()
|
||||
|
|
@ -127,14 +127,14 @@ class PacketCodingActor extends Actor with MDCContextAware {
|
|||
relatedALog.clear()
|
||||
}
|
||||
case RawPacket(msg) =>
|
||||
if (sender == rightRef) { //from LSA, WSA, etc., to network - encode
|
||||
if (sender() == rightRef) { //from LSA, WSA, etc., to network - encode
|
||||
mtuLimit(msg)
|
||||
} else { //from network, to LSA, WSA, etc. - decode
|
||||
UnmarshalInnerPacket(msg, "a packet")
|
||||
}
|
||||
//known elevated packet type
|
||||
case ctrl @ ControlPacket(_, packet) =>
|
||||
if (sender == rightRef) { //from LSA, WSA, to network - encode
|
||||
if (sender() == rightRef) { //from LSA, WSA, to network - encode
|
||||
PacketCoding.EncodePacket(packet) match {
|
||||
case Successful(data) =>
|
||||
mtuLimit(data.toByteVector)
|
||||
|
|
@ -148,7 +148,7 @@ class PacketCodingActor extends Actor with MDCContextAware {
|
|||
}
|
||||
//known elevated packet type
|
||||
case game @ GamePacket(_, _, packet) =>
|
||||
if (sender == rightRef) { //from LSA, WSA, etc., to network - encode
|
||||
if (sender() == rightRef) { //from LSA, WSA, etc., to network - encode
|
||||
PacketCoding.EncodePacket(packet) match {
|
||||
case Successful(data) =>
|
||||
mtuLimit(data.toByteVector)
|
||||
|
|
@ -166,7 +166,7 @@ class PacketCodingActor extends Actor with MDCContextAware {
|
|||
handleBundlePacket(list)
|
||||
//etc
|
||||
case msg =>
|
||||
if (sender == rightRef) {
|
||||
if (sender() == rightRef) {
|
||||
log.trace(s"BASE CASE PACKET SEND, LEFT: $msg")
|
||||
MDC("sessionId") = sessionId.toString
|
||||
leftRef !> msg
|
||||
|
|
@ -421,7 +421,7 @@ class PacketCodingActor extends Actor with MDCContextAware {
|
|||
out
|
||||
} else {
|
||||
import net.psforever.packet.{PlanetSideControlPacket, PlanetSideGamePacket}
|
||||
iter.next match {
|
||||
iter.next() match {
|
||||
case msg: PlanetSideGamePacket =>
|
||||
PacketCoding.EncodePacket(msg) match {
|
||||
case Successful(bytecode) =>
|
||||
|
|
@ -462,7 +462,7 @@ class PacketCodingActor extends Actor with MDCContextAware {
|
|||
if (!iter.hasNext) {
|
||||
out
|
||||
} else {
|
||||
val data = iter.next
|
||||
val data = iter.next()
|
||||
var len = data.length.toInt
|
||||
len = len + (if (len < 256) { 1 }
|
||||
else if (len < 65536) { 2 }
|
||||
|
|
|
|||
|
|
@ -26,8 +26,8 @@ class Session(
|
|||
) {
|
||||
|
||||
var state: SessionState = New()
|
||||
val sessionCreatedTime: DateTime = DateTime.now
|
||||
var sessionEndedTime: DateTime = DateTime.now
|
||||
val sessionCreatedTime: DateTime = DateTime.now()
|
||||
var sessionEndedTime: DateTime = DateTime.now()
|
||||
|
||||
val pipeline = sessionPipeline.map { actor =>
|
||||
val a = context.actorOf(actor.props, actor.nameTemplate + sessionId.toString)
|
||||
|
|
@ -37,7 +37,7 @@ class Session(
|
|||
|
||||
val pipelineIter = pipeline.iterator
|
||||
if (pipelineIter.hasNext) {
|
||||
pipelineIter.next ! HelloFriend(sessionId, pipelineIter)
|
||||
pipelineIter.next() ! HelloFriend(sessionId, pipelineIter)
|
||||
}
|
||||
|
||||
// statistics
|
||||
|
|
@ -74,7 +74,7 @@ class Session(
|
|||
pipeline.foreach(context.unwatch)
|
||||
pipeline.foreach(_ ! PoisonPill)
|
||||
|
||||
sessionEndedTime = DateTime.now
|
||||
sessionEndedTime = DateTime.now()
|
||||
setState(Closed())
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ class SessionRouter(role: String, pipeline: List[SessionPipeline]) extends Actor
|
|||
|
||||
override def supervisorStrategy = OneForOneStrategy() { case _ => Stop }
|
||||
|
||||
override def preStart = {
|
||||
override def preStart() = {
|
||||
log.info(s"SessionRouter (for ${role}s) initializing ...")
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ class UdpNetworkSimulator(server: ActorRef, params: NetworkSimulatorParameters)
|
|||
|
||||
// this packet needs to be sent within 20 milliseconds or more
|
||||
if (lastTime >= 20000000) {
|
||||
server.tell(inPacketQueue.dequeue._1, interface)
|
||||
server.tell(inPacketQueue.dequeue()._1, interface)
|
||||
} else {
|
||||
schedule(lastTime.nanoseconds, outbound = false)
|
||||
exit = true
|
||||
|
|
@ -79,7 +79,7 @@ class UdpNetworkSimulator(server: ActorRef, params: NetworkSimulatorParameters)
|
|||
|
||||
// this packet needs to be sent within 20 milliseconds or more
|
||||
if (lastTime >= 20000000) {
|
||||
interface.tell(outPacketQueue.dequeue._1, server)
|
||||
interface.tell(outPacketQueue.dequeue()._1, server)
|
||||
} else {
|
||||
schedule(lastTime.nanoseconds, outbound = true)
|
||||
exit = true
|
||||
|
|
|
|||
|
|
@ -328,13 +328,13 @@ object WorldSession {
|
|||
)
|
||||
)
|
||||
localZone.AvatarEvents ! AvatarServiceMessage(
|
||||
localZone.Id,
|
||||
localZone.id,
|
||||
AvatarAction.ObjectHeld(localGUID, localPlayer.LastDrawnSlot)
|
||||
)
|
||||
}
|
||||
localPlayer.DrawnSlot = localSlot
|
||||
localZone.AvatarEvents ! AvatarServiceMessage(
|
||||
localZone.Id,
|
||||
localZone.id,
|
||||
AvatarAction.SendResponse(Service.defaultPlayerGUID, ObjectHeldMessage(localGUID, localSlot, false))
|
||||
)
|
||||
}
|
||||
|
|
@ -380,7 +380,7 @@ object WorldSession {
|
|||
case Some(_) => ;
|
||||
case None => //acting on old data?
|
||||
localZone.AvatarEvents ! AvatarServiceMessage(
|
||||
localZone.Id,
|
||||
localZone.id,
|
||||
AvatarAction.ObjectDelete(Service.defaultPlayerGUID, item_guid)
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import akka.actor.typed.scaladsl.adapter._
|
|||
class CmdListPlayers(args: Array[String], services: Map[String, ActorRef]) extends Actor {
|
||||
private[this] val log = org.log4s.getLogger(self.path.name)
|
||||
|
||||
override def preStart = {
|
||||
override def preStart() = {
|
||||
ServiceManager.receptionist ! Receptionist.Find(
|
||||
InterstellarClusterService.InterstellarClusterServiceKey,
|
||||
context.self
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import akka.actor.{Actor, ActorRef}
|
|||
import scala.collection.mutable.Map
|
||||
|
||||
class CmdShutdown(args: Array[String], services: Map[String, ActorRef]) extends Actor {
|
||||
override def preStart = {
|
||||
override def preStart() = {
|
||||
var data = Map[String, Any]()
|
||||
context.parent ! CommandGoodResponse("Shutting down", data)
|
||||
context.system.terminate()
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package net.psforever.objects
|
||||
|
||||
class Account(private val accountId: Int, private val username: String, private val gm: Boolean = false) {
|
||||
def AccountId: Int = accountId
|
||||
def Username: String = username
|
||||
def GM: Boolean = gm
|
||||
}
|
||||
case class Account(
|
||||
id: Int,
|
||||
name: String,
|
||||
gm: Boolean = false
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,384 +0,0 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package net.psforever.objects
|
||||
|
||||
import net.psforever.objects.avatar.{DeployableToolbox, LoadoutManager}
|
||||
import net.psforever.objects.definition.{AvatarDefinition, ImplantDefinition}
|
||||
import net.psforever.objects.equipment.{EquipmentSize, EquipmentSlot}
|
||||
import net.psforever.packet.game.objectcreate.Cosmetics
|
||||
import net.psforever.types._
|
||||
|
||||
import scala.annotation.tailrec
|
||||
import scala.collection.mutable
|
||||
|
||||
class Avatar(
|
||||
private val char_id: Long,
|
||||
val name: String,
|
||||
val faction: PlanetSideEmpire.Value,
|
||||
val sex: CharacterGender.Value,
|
||||
val head: Int,
|
||||
val voice: CharacterVoice.Value
|
||||
) {
|
||||
|
||||
/** char_id, Character ID; a unique identifier corresponding to a database table row index */
|
||||
/** Battle Experience Points */
|
||||
private var bep: Long = 0
|
||||
|
||||
/** Command Experience Points */
|
||||
private var cep: Long = 0
|
||||
|
||||
/** Cosmetics * */
|
||||
private var pStyle: Option[Cosmetics] = None
|
||||
|
||||
/** Certifications */
|
||||
private val certs: mutable.Set[CertificationType.Value] = mutable.Set[CertificationType.Value]()
|
||||
|
||||
/** Implants<br>
|
||||
* Unlike other objects, all `ImplantSlot` objects are already built into the `Avatar`.
|
||||
* Additionally, implants do not have tightly-coupled "`Definition` objects" that explain a formal implant object.
|
||||
* The `ImplantDefinition` objects themselves are moved around as if they were the implants.
|
||||
* The terms externally used for the states of process is "installed" and "uninstalled."
|
||||
* @see `ImplantSlot`
|
||||
* @see `DetailedCharacterData.implants`
|
||||
*/
|
||||
private val implants: Array[ImplantSlot] = Array.fill[ImplantSlot](3)(new ImplantSlot)
|
||||
|
||||
/** Equipment Loadouts<br>
|
||||
* 0-9 are Infantry loadouts<br>
|
||||
* 10-14 are Vehicle loadouts
|
||||
*/
|
||||
private val equipmentLoadouts: LoadoutManager = new LoadoutManager(15)
|
||||
|
||||
/**
|
||||
* Squad Loadouts
|
||||
*/
|
||||
private val squadLoadouts: LoadoutManager = new LoadoutManager(10)
|
||||
|
||||
/** Locker */
|
||||
private val locker: LockerContainer = new LockerContainer() {
|
||||
override def toString: String = {
|
||||
s"$name's ${Definition.Name}"
|
||||
}
|
||||
}
|
||||
|
||||
private val deployables: DeployableToolbox = new DeployableToolbox
|
||||
|
||||
private var firstTimeEvents: List[String] = List[String]()
|
||||
|
||||
/**
|
||||
* Looking For Squad:<br>
|
||||
* Indicates both a player state and the text on the marquee under the player nameplate.
|
||||
* Should only be valid when the player is not in a squad.
|
||||
*/
|
||||
private var lfs: Boolean = false
|
||||
|
||||
private var vehicleOwned: Option[PlanetSideGUID] = None
|
||||
|
||||
/** key - object id<br>
|
||||
* value - time last used (ms)
|
||||
*/
|
||||
private var lastUsedEquipmentTimes: mutable.LongMap[Long] = mutable.LongMap[Long]()
|
||||
|
||||
/** exo-suit times are sorted by `Enumeration` order, which was determined by packet process<br>
|
||||
* key - exo-suit id<br>
|
||||
* value - time last used (ms)
|
||||
*/
|
||||
private val lastUsedExoSuitTimes: Array[Long] = Array.fill[Long](ExoSuitType.values.size)(0L)
|
||||
|
||||
/** mechanized exo-suit times are sorted by subtype distinction, which was determined by packet process<br>
|
||||
* key - subtype id<br>
|
||||
* value - time last used (ms)
|
||||
*/
|
||||
private val lastUsedMaxExoSuitTimes: Array[Long] = Array.fill[Long](4)(0L) //invalid, ai, av, aa
|
||||
/** key - object id<br>
|
||||
* value - time last acquired (from a terminal) (ms)
|
||||
*/
|
||||
private var lastPurchaseTimes: mutable.LongMap[Long] = mutable.LongMap[Long]()
|
||||
|
||||
/**
|
||||
* To reload purchase and use timers, a string representing the item must be produced.
|
||||
* Point directly from the object id to the object definition and get the `Name` from that definition.
|
||||
* Allocate only when an item is purchased or used.
|
||||
* The keys match the keys for both `lastUsedEquipmentTimes` and `lastPurchaseTimes`.<br>
|
||||
* key - object id<br>
|
||||
* value - most basic object definition information
|
||||
*/
|
||||
private val objectTypeNameReference: mutable.LongMap[String] = new mutable.LongMap[String]()
|
||||
|
||||
def CharId: Long = char_id
|
||||
|
||||
def BEP: Long = bep
|
||||
|
||||
def BEP_=(battleExperiencePoints: Long): Long = {
|
||||
bep = math.max(0L, math.min(battleExperiencePoints, 4294967295L))
|
||||
BEP
|
||||
}
|
||||
|
||||
def Certifications: mutable.Set[CertificationType.Value] = certs
|
||||
|
||||
def CEP: Long = cep
|
||||
|
||||
def CEP_=(commandExperiencePoints: Long): Long = {
|
||||
cep = math.max(0L, math.min(commandExperiencePoints, 4294967295L))
|
||||
CEP
|
||||
}
|
||||
|
||||
def PersonalStyleFeatures: Option[Cosmetics] = pStyle
|
||||
|
||||
def PersonalStyleFeatures_=(app: Cosmetics): Option[Cosmetics] = {
|
||||
pStyle = Some(app)
|
||||
pStyle
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the three implant slots for this player.
|
||||
* @return an `Array` of `ImplantSlot` objects
|
||||
*/
|
||||
def Implants: Array[ImplantSlot] = implants
|
||||
|
||||
/**
|
||||
* What kind of implant is installed into the given slot number?
|
||||
* @see `ImplantType`
|
||||
* @param slot the slot number
|
||||
* @return the tye of implant
|
||||
*/
|
||||
def Implant(slot: Int): ImplantType.Value = {
|
||||
if (-1 < slot && slot < implants.length) { implants(slot).Implant }
|
||||
else { ImplantType.None }
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a new implant, assign it into a vacant implant slot on this player.<br>
|
||||
* <br>
|
||||
* The implant must be unique in terms of which implants have already been assigned to this player.
|
||||
* Multiple of a type of implant being assigned at once is not supported.
|
||||
* Additionally, the implant is inserted into the earliest yet-unknown but vacant slot.
|
||||
* Implant slots are vacant by just being unlocked or by having their previous implant uninstalled.
|
||||
* @param implant the implant being installed
|
||||
* @return the index of the `ImplantSlot` where the implant was installed
|
||||
*/
|
||||
def InstallImplant(implant: ImplantDefinition): Option[Int] = {
|
||||
implants
|
||||
.find({ p => p.Installed.contains(implant) || p.Implant == implant.Type }) match { //try to find the installed implant
|
||||
case None =>
|
||||
recursiveFindImplantInSlot(implants.iterator, ImplantType.None) match { //install in a free slot
|
||||
case Some(slot) =>
|
||||
implants(slot).Implant = implant
|
||||
Some(slot)
|
||||
case None =>
|
||||
None
|
||||
}
|
||||
case Some(_) =>
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a specific implant from a player's allocated installed implants.<br>
|
||||
* <br>
|
||||
* Due to the exclusiveness of installed implants,
|
||||
* any implant slot with a matching `Definition` can be uninstalled safely.
|
||||
* (There will never be any doubles.)
|
||||
* This operation can lead to an irregular pattern of installed and uninstalled `ImplantSlot` objects.
|
||||
* Despite that breach of pattern, the logic here is consistent as demonstrated by the client and by packets.
|
||||
* The client also assigns and removes implants based on slot numbers that only express availability of a "slot."
|
||||
* @see `AvatarImplantMessage.implantSlot`
|
||||
* @param implantType the type of implant being uninstalled
|
||||
* @return the index of the `ImplantSlot` where the implant was found and uninstalled
|
||||
*/
|
||||
def UninstallImplant(implantType: ImplantType.Value): Option[Int] = {
|
||||
recursiveFindImplantInSlot(implants.iterator, implantType) match {
|
||||
case Some(slot) =>
|
||||
implants(slot).Implant = None
|
||||
Some(slot)
|
||||
case None =>
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Locate the index of the encountered implant type.
|
||||
* Functional implants may be exclusive in as far as the input `Iterator`'s source is concerned,
|
||||
* but any number of `ImplantType.None` values are alway allowed in the source in any order.
|
||||
* @param iter an `Iterator` of `ImplantSlot` objects
|
||||
* @param implantType the target implant being sought
|
||||
* @param index a defaulted index value representing the structure underlying the `Iterator` param
|
||||
* @return the index where the target implant is installed
|
||||
*/
|
||||
@tailrec private def recursiveFindImplantInSlot(
|
||||
iter: Iterator[ImplantSlot],
|
||||
implantType: ImplantType.Value,
|
||||
index: Int = 0
|
||||
): Option[Int] = {
|
||||
if (!iter.hasNext) {
|
||||
None
|
||||
} else {
|
||||
val slot = iter.next
|
||||
if (slot.Unlocked && slot.Implant == implantType) {
|
||||
Some(index)
|
||||
} else {
|
||||
recursiveFindImplantInSlot(iter, implantType, index + 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def ResetAllImplants(): Unit = {
|
||||
implants.foreach(slot => {
|
||||
slot.Installed match {
|
||||
case Some(_) =>
|
||||
slot.Active = false
|
||||
slot.Initialized = false
|
||||
slot.InitializeTime = 0L
|
||||
case None => ;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
def EquipmentLoadouts: LoadoutManager = equipmentLoadouts
|
||||
|
||||
def SquadLoadouts: LoadoutManager = squadLoadouts
|
||||
|
||||
def Locker: LockerContainer = locker
|
||||
|
||||
def FifthSlot: EquipmentSlot = {
|
||||
new OffhandEquipmentSlot(EquipmentSize.Inventory) {
|
||||
val obj = new LockerEquipment(locker)
|
||||
Equipment = obj
|
||||
}
|
||||
}
|
||||
|
||||
def Deployables: DeployableToolbox = deployables
|
||||
|
||||
def FirstTimeEvents: List[String] = firstTimeEvents
|
||||
|
||||
def FirstTimeEvents_=(event: String): List[String] = FirstTimeEvents_=(List(event))
|
||||
|
||||
def FirstTimeEvents_=(events: List[String]): List[String] = {
|
||||
firstTimeEvents ++= events
|
||||
FirstTimeEvents
|
||||
}
|
||||
|
||||
def LFS: Boolean = lfs
|
||||
|
||||
def LFS_=(looking: Boolean): Boolean = {
|
||||
lfs = looking
|
||||
LFS
|
||||
}
|
||||
|
||||
def VehicleOwned: Option[PlanetSideGUID] = vehicleOwned
|
||||
|
||||
def VehicleOwned_=(guid: PlanetSideGUID): Option[PlanetSideGUID] = VehicleOwned_=(Some(guid))
|
||||
|
||||
def VehicleOwned_=(guid: Option[PlanetSideGUID]): Option[PlanetSideGUID] = {
|
||||
vehicleOwned = guid
|
||||
VehicleOwned
|
||||
}
|
||||
|
||||
def GetLastUsedTime(code: Int): Long = {
|
||||
lastUsedEquipmentTimes.get(code) match {
|
||||
case Some(time) => time
|
||||
case None => 0
|
||||
}
|
||||
}
|
||||
|
||||
def GetLastUsedTime(code: ExoSuitType.Value): Long = {
|
||||
lastUsedExoSuitTimes(code.id)
|
||||
}
|
||||
|
||||
def GetLastUsedTime(code: ExoSuitType.Value, subtype: Int): Long = {
|
||||
if (code == ExoSuitType.MAX) {
|
||||
lastUsedMaxExoSuitTimes(subtype)
|
||||
} else {
|
||||
GetLastUsedTime(code)
|
||||
}
|
||||
}
|
||||
|
||||
def GetAllLastUsedTimes: Map[Long, Long] = lastUsedEquipmentTimes.toMap
|
||||
|
||||
def SetLastUsedTime(code: Int, time: Long): Unit = {
|
||||
lastUsedEquipmentTimes += code.toLong -> time
|
||||
}
|
||||
|
||||
def SetLastUsedTime(code: ExoSuitType.Value): Unit = SetLastUsedTime(code, System.currentTimeMillis())
|
||||
|
||||
def SetLastUsedTime(code: ExoSuitType.Value, time: Long): Unit = {
|
||||
lastUsedExoSuitTimes(code.id) = time
|
||||
}
|
||||
|
||||
def SetLastUsedTime(code: ExoSuitType.Value, subtype: Int, time: Long): Unit = {
|
||||
if (code == ExoSuitType.MAX) {
|
||||
lastUsedMaxExoSuitTimes(subtype) = time
|
||||
}
|
||||
SetLastUsedTime(code, time)
|
||||
}
|
||||
|
||||
def GetLastPurchaseTime(code: Int): Long = {
|
||||
lastPurchaseTimes.get(code) match {
|
||||
case Some(time) => time
|
||||
case None => 0
|
||||
}
|
||||
}
|
||||
|
||||
def GetAllLastPurchaseTimes: Map[Long, Long] = lastPurchaseTimes.toMap
|
||||
|
||||
def SetLastPurchaseTime(code: Int, time: Long): Unit = {
|
||||
lastPurchaseTimes += code.toLong -> time
|
||||
}
|
||||
|
||||
def ObjectTypeNameReference(id: Long): String = {
|
||||
objectTypeNameReference.get(id) match {
|
||||
case Some(objectName) => objectName
|
||||
case None => ""
|
||||
}
|
||||
}
|
||||
|
||||
def ObjectTypeNameReference(id: Long, name: String): String = {
|
||||
objectTypeNameReference(id) = name
|
||||
name
|
||||
}
|
||||
|
||||
def Definition: AvatarDefinition = GlobalDefinitions.avatar
|
||||
|
||||
/*
|
||||
Merit Commendations and Ribbons
|
||||
*/
|
||||
// private var tosRibbon : MeritCommendation.Value = MeritCommendation.None
|
||||
// private var upperRibbon : MeritCommendation.Value = MeritCommendation.None
|
||||
// private var middleRibbon : MeritCommendation.Value = MeritCommendation.None
|
||||
// private var lowerRibbon : MeritCommendation.Value = MeritCommendation.None
|
||||
|
||||
def canEqual(other: Any): Boolean = other.isInstanceOf[Avatar]
|
||||
|
||||
override def equals(other: Any): Boolean =
|
||||
other match {
|
||||
case that: Avatar =>
|
||||
(that canEqual this) &&
|
||||
name == that.name &&
|
||||
faction == that.faction &&
|
||||
sex == that.sex &&
|
||||
head == that.head &&
|
||||
voice == that.voice
|
||||
case _ =>
|
||||
false
|
||||
}
|
||||
|
||||
override def hashCode(): Int = {
|
||||
val state = Seq(name, faction, sex, head, voice)
|
||||
state.map(_.hashCode()).foldLeft(0)((a, b) => 31 * a + b)
|
||||
}
|
||||
|
||||
override def toString: String = Avatar.toString(this)
|
||||
}
|
||||
|
||||
object Avatar {
|
||||
def apply(
|
||||
name: String,
|
||||
faction: PlanetSideEmpire.Value,
|
||||
sex: CharacterGender.Value,
|
||||
head: Int,
|
||||
voice: CharacterVoice.Value
|
||||
): Avatar = {
|
||||
new Avatar(0L, name, faction, sex, head, voice)
|
||||
}
|
||||
|
||||
def toString(avatar: Avatar): String = s"${avatar.faction} ${avatar.name}"
|
||||
}
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package net.psforever.objects
|
||||
|
||||
import net.psforever.objects.avatar.Certification
|
||||
import net.psforever.objects.ce.DeployedItem
|
||||
import net.psforever.objects.definition.{ConstructionFireMode, ConstructionItemDefinition}
|
||||
import net.psforever.objects.equipment.{Equipment, FireModeSwitch}
|
||||
import net.psforever.types.CertificationType
|
||||
|
||||
/**
|
||||
* A type of `Equipment` that can be wielded and applied to the game world to produce other game objects.<br>
|
||||
|
|
@ -55,7 +55,7 @@ class ConstructionItem(private val cItemDef: ConstructionItemDefinition)
|
|||
FireMode.Deployables(ammoTypeIndex)
|
||||
}
|
||||
|
||||
def ModePermissions: Set[CertificationType.Value] = FireMode.Permissions(ammoTypeIndex)
|
||||
def ModePermissions: Set[Certification] = FireMode.Permissions(ammoTypeIndex)
|
||||
|
||||
def Definition: ConstructionItemDefinition = cItemDef
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@ object Default {
|
|||
//cancellable
|
||||
import akka.actor.Cancellable
|
||||
protected class InternalCancellable extends Cancellable {
|
||||
override def cancel: Boolean = true
|
||||
override def cancel(): Boolean = true
|
||||
|
||||
override def isCancelled: Boolean = true
|
||||
}
|
||||
private val cancellable: Cancellable = new InternalCancellable
|
||||
|
|
@ -28,7 +29,7 @@ object Default {
|
|||
*/
|
||||
private class DefaultActor extends AkkaActor {
|
||||
def receive: Receive = {
|
||||
case msg => context.system.deadLetters ! DeadLetter(msg, sender, self)
|
||||
case msg => context.system.deadLetters ! DeadLetter(msg, sender(), self)
|
||||
}
|
||||
}
|
||||
private var defaultRef: ActorRef = ActorRef.noSender
|
||||
|
|
@ -40,7 +41,7 @@ object Default {
|
|||
*/
|
||||
def apply(sys: ActorSystem): ActorRef = {
|
||||
if (defaultRef == ActorRef.noSender) {
|
||||
defaultRef = sys.actorOf(Props[DefaultActor], name = s"system-default-actor")
|
||||
defaultRef = sys.actorOf(Props[DefaultActor](), name = s"system-default-actor")
|
||||
}
|
||||
defaultRef
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,12 +2,13 @@
|
|||
package net.psforever.objects
|
||||
|
||||
import akka.actor.ActorRef
|
||||
import net.psforever.objects.avatar.{Avatar, Certification}
|
||||
|
||||
import scala.concurrent.duration._
|
||||
import net.psforever.objects.ce.{Deployable, DeployedItem}
|
||||
import net.psforever.objects.zones.Zone
|
||||
import net.psforever.packet.game.{DeployableInfo, DeploymentAction}
|
||||
import net.psforever.types.{CertificationType, PlanetSideGUID}
|
||||
import net.psforever.types.PlanetSideGUID
|
||||
import services.RemoverActor
|
||||
import services.local.{LocalAction, LocalServiceMessage}
|
||||
|
||||
|
|
@ -100,7 +101,7 @@ object Deployables {
|
|||
*/
|
||||
def Disown(zone: Zone, avatar: Avatar, replyTo: ActorRef): List[PlanetSideGameObject with Deployable] = {
|
||||
val (boomers, deployables) =
|
||||
avatar.Deployables
|
||||
avatar.deployables
|
||||
.Clear()
|
||||
.map(zone.GUID)
|
||||
.collect { case Some(obj) => obj.asInstanceOf[PlanetSideGameObject with Deployable] }
|
||||
|
|
@ -128,7 +129,7 @@ object Deployables {
|
|||
*/
|
||||
def InitializeDeployableQuantities(avatar: Avatar): Boolean = {
|
||||
log.info("Setting up combat engineering ...")
|
||||
avatar.Deployables.Initialize(avatar.Certifications.toSet)
|
||||
avatar.deployables.Initialize(avatar.certifications)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -137,7 +138,7 @@ object Deployables {
|
|||
*/
|
||||
def InitializeDeployableUIElements(avatar: Avatar): List[(Int, Int, Int, Int)] = {
|
||||
log.info("Setting up combat engineering UI ...")
|
||||
avatar.Deployables.UpdateUI()
|
||||
avatar.deployables.UpdateUI()
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -149,11 +150,11 @@ object Deployables {
|
|||
*/
|
||||
def AddToDeployableQuantities(
|
||||
avatar: Avatar,
|
||||
certification: CertificationType.Value,
|
||||
certificationSet: Set[CertificationType.Value]
|
||||
certification: Certification,
|
||||
certificationSet: Set[Certification]
|
||||
): List[(Int, Int, Int, Int)] = {
|
||||
avatar.Deployables.AddToDeployableQuantities(certification, certificationSet)
|
||||
avatar.Deployables.UpdateUI(certification)
|
||||
avatar.deployables.AddToDeployableQuantities(certification, certificationSet)
|
||||
avatar.deployables.UpdateUI(certification)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -165,10 +166,10 @@ object Deployables {
|
|||
*/
|
||||
def RemoveFromDeployableQuantities(
|
||||
avatar: Avatar,
|
||||
certification: CertificationType.Value,
|
||||
certificationSet: Set[CertificationType.Value]
|
||||
certification: Certification,
|
||||
certificationSet: Set[Certification]
|
||||
): List[(Int, Int, Int, Int)] = {
|
||||
avatar.Deployables.RemoveFromDeployableQuantities(certification, certificationSet)
|
||||
avatar.Deployables.UpdateUI(certification)
|
||||
avatar.deployables.RemoveFromDeployableQuantities(certification, certificationSet)
|
||||
avatar.deployables.UpdateUI(certification)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ object ExplosiveDeployableControl {
|
|||
if (target.Definition.DetonateOnJamming) {
|
||||
val zone = target.Zone
|
||||
zone.Activity ! Zone.HotSpot.Activity(cause.target, cause.projectile.owner, cause.hit_pos)
|
||||
zone.LocalEvents ! LocalServiceMessage(zone.Id, LocalAction.Detonate(target.GUID, target))
|
||||
zone.LocalEvents ! LocalServiceMessage(zone.id, LocalAction.Detonate(target.GUID, target))
|
||||
}
|
||||
DestructionAwareness(target, cause)
|
||||
}
|
||||
|
|
@ -114,12 +114,12 @@ object ExplosiveDeployableControl {
|
|||
target.Destroyed = true
|
||||
Deployables.AnnounceDestroyDeployable(target, Some(if (target.Jammed) 0 seconds else 500 milliseconds))
|
||||
zone.AvatarEvents ! AvatarServiceMessage(
|
||||
zone.Id,
|
||||
zone.id,
|
||||
AvatarAction.Destroy(target.GUID, attribution, Service.defaultPlayerGUID, target.Position)
|
||||
)
|
||||
if (target.Health == 0) {
|
||||
zone.LocalEvents ! LocalServiceMessage(
|
||||
zone.Id,
|
||||
zone.id,
|
||||
LocalAction.TriggerEffect(Service.defaultPlayerGUID, "detonate_damaged_mine", target.GUID)
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package net.psforever.objects
|
||||
|
||||
import net.psforever.objects.avatar.Certification
|
||||
import net.psforever.objects.ballistics.Projectiles
|
||||
import net.psforever.objects.ce.{DeployableCategory, DeployedItem}
|
||||
import net.psforever.objects.definition._
|
||||
|
|
@ -22,8 +23,7 @@ import net.psforever.objects.serverobject.turret.{FacilityTurretDefinition, Turr
|
|||
import net.psforever.objects.vehicles.{DestroyedVehicle, InternalTelepadDefinition, SeatArmorRestriction, UtilityType}
|
||||
import net.psforever.objects.vital.damage.{DamageCalculations, DamageModifiers}
|
||||
import net.psforever.objects.vital.{DamageType, StandardResolutions}
|
||||
import net.psforever.types.{CertificationType, ExoSuitType, PlanetSideEmpire, Vector3}
|
||||
|
||||
import net.psforever.types.{ExoSuitType, ImplantType, PlanetSideEmpire, Vector3}
|
||||
import scala.collection.mutable
|
||||
import scala.concurrent.duration._
|
||||
|
||||
|
|
@ -53,47 +53,67 @@ object GlobalDefinitions {
|
|||
/*
|
||||
Implants
|
||||
*/
|
||||
val advanced_regen = ImplantDefinition(0)
|
||||
val advanced_regen = new ImplantDefinition(ImplantType.AdvancedRegen) {
|
||||
Name = "advanced_regen"
|
||||
}
|
||||
advanced_regen.InitializationDuration = 120
|
||||
advanced_regen.StaminaCost = 2
|
||||
advanced_regen.CostIntervalDefault = 500
|
||||
|
||||
val targeting = ImplantDefinition(1)
|
||||
val targeting = new ImplantDefinition(ImplantType.Targeting) {
|
||||
Name = "targeting"
|
||||
}
|
||||
targeting.InitializationDuration = 60
|
||||
|
||||
val audio_amplifier = ImplantDefinition(2)
|
||||
val audio_amplifier = new ImplantDefinition(ImplantType.AudioAmplifier) {
|
||||
Name = "audio_amplifier"
|
||||
}
|
||||
audio_amplifier.InitializationDuration = 60
|
||||
audio_amplifier.StaminaCost = 1
|
||||
audio_amplifier.CostIntervalDefault = 1000
|
||||
|
||||
val darklight_vision = ImplantDefinition(3)
|
||||
val darklight_vision = new ImplantDefinition(ImplantType.DarklightVision) {
|
||||
Name = "darklight_vision"
|
||||
}
|
||||
darklight_vision.InitializationDuration = 60
|
||||
darklight_vision.ActivationStaminaCost = 3
|
||||
darklight_vision.StaminaCost = 1
|
||||
darklight_vision.CostIntervalDefault = 500
|
||||
|
||||
val melee_booster = ImplantDefinition(4)
|
||||
val melee_booster = new ImplantDefinition(ImplantType.MeleeBooster) {
|
||||
Name = "melee_booster"
|
||||
}
|
||||
melee_booster.InitializationDuration = 120
|
||||
melee_booster.StaminaCost = 10
|
||||
|
||||
val personal_shield = ImplantDefinition(5)
|
||||
val personal_shield = new ImplantDefinition(ImplantType.PersonalShield) {
|
||||
Name = "personal_shield"
|
||||
}
|
||||
personal_shield.InitializationDuration = 120
|
||||
personal_shield.StaminaCost = 1
|
||||
personal_shield.CostIntervalDefault = 600
|
||||
|
||||
val range_magnifier = ImplantDefinition(6)
|
||||
val range_magnifier = new ImplantDefinition(ImplantType.RangeMagnifier) {
|
||||
Name = "range_magnifier"
|
||||
}
|
||||
range_magnifier.InitializationDuration = 60
|
||||
|
||||
val second_wind = ImplantDefinition(7)
|
||||
val second_wind = new ImplantDefinition(ImplantType.SecondWind) {
|
||||
Name = "second_wind"
|
||||
}
|
||||
second_wind.InitializationDuration = 180
|
||||
|
||||
val silent_run = ImplantDefinition(8)
|
||||
val silent_run = new ImplantDefinition(ImplantType.SilentRun) {
|
||||
Name = "silent_run"
|
||||
}
|
||||
silent_run.InitializationDuration = 90
|
||||
silent_run.StaminaCost = 1
|
||||
silent_run.CostIntervalDefault = 333
|
||||
silent_run.CostIntervalByExoSuitHashMap(ExoSuitType.Agile) = 1000
|
||||
|
||||
val surge = ImplantDefinition(9)
|
||||
val surge = new ImplantDefinition(ImplantType.Surge) {
|
||||
Name = "surge"
|
||||
}
|
||||
surge.InitializationDuration = 90
|
||||
surge.StaminaCost = 1
|
||||
surge.CostIntervalDefault = 1000
|
||||
|
|
@ -1633,7 +1653,7 @@ object GlobalDefinitions {
|
|||
|
||||
Reinforced.Name = "med_armor"
|
||||
Reinforced.Descriptor = "reinforced"
|
||||
Reinforced.Permissions = List(CertificationType.ReinforcedExoSuit)
|
||||
Reinforced.Permissions = List(Certification.ReinforcedExoSuit)
|
||||
Reinforced.MaxArmor = 200
|
||||
Reinforced.InventoryScale = InventoryTile.Tile1209
|
||||
Reinforced.InventoryOffset = 6
|
||||
|
|
@ -1647,7 +1667,7 @@ object GlobalDefinitions {
|
|||
Reinforced.ResistanceAggravated = 12
|
||||
|
||||
Infiltration.Name = "infiltration_suit"
|
||||
Infiltration.Permissions = List(CertificationType.InfiltrationSuit)
|
||||
Infiltration.Permissions = List(Certification.InfiltrationSuit)
|
||||
Infiltration.MaxArmor = 0
|
||||
Infiltration.InventoryScale = InventoryTile.Tile66
|
||||
Infiltration.InventoryOffset = 6
|
||||
|
|
@ -1655,8 +1675,7 @@ object GlobalDefinitions {
|
|||
Infiltration.Holster(4, EquipmentSize.Melee)
|
||||
|
||||
def CommonMaxConfig(max: SpecialExoSuitDefinition): Unit = {
|
||||
max.Permissions =
|
||||
List(CertificationType.AIMAX, CertificationType.AVMAX, CertificationType.AAMAX, CertificationType.UniMAX)
|
||||
max.Permissions = List(Certification.AIMAX, Certification.AVMAX, Certification.AAMAX, Certification.UniMAX)
|
||||
max.MaxArmor = 650
|
||||
max.InventoryScale = InventoryTile.Tile1612
|
||||
max.InventoryOffset = 6
|
||||
|
|
@ -4720,35 +4739,33 @@ object GlobalDefinitions {
|
|||
ace.Name = "ace"
|
||||
ace.Size = EquipmentSize.Pistol
|
||||
ace.Modes += new ConstructionFireMode
|
||||
ace.Modes.head.Item(DeployedItem.boomer -> Set(CertificationType.CombatEngineering))
|
||||
ace.Modes.head.Item(DeployedItem.boomer, Set(Certification.CombatEngineering))
|
||||
ace.Modes += new ConstructionFireMode
|
||||
ace.Modes(1).Item(DeployedItem.he_mine -> Set(CertificationType.CombatEngineering))
|
||||
ace.Modes(1).Item(DeployedItem.jammer_mine -> Set(CertificationType.AssaultEngineering))
|
||||
ace.Modes(1).Item(DeployedItem.he_mine, Set(Certification.CombatEngineering))
|
||||
ace.Modes(1).Item(DeployedItem.jammer_mine, Set(Certification.AssaultEngineering))
|
||||
ace.Modes += new ConstructionFireMode
|
||||
ace.Modes(2).Item(DeployedItem.spitfire_turret -> Set(CertificationType.CombatEngineering))
|
||||
ace.Modes(2).Item(DeployedItem.spitfire_cloaked -> Set(CertificationType.FortificationEngineering))
|
||||
ace.Modes(2).Item(DeployedItem.spitfire_aa -> Set(CertificationType.FortificationEngineering))
|
||||
ace.Modes(2).Item(DeployedItem.spitfire_turret, Set(Certification.CombatEngineering))
|
||||
ace.Modes(2).Item(DeployedItem.spitfire_cloaked, Set(Certification.FortificationEngineering))
|
||||
ace.Modes(2).Item(DeployedItem.spitfire_aa, Set(Certification.FortificationEngineering))
|
||||
ace.Modes += new ConstructionFireMode
|
||||
ace.Modes(3).Item(DeployedItem.motionalarmsensor -> Set(CertificationType.CombatEngineering))
|
||||
ace
|
||||
.Modes(3)
|
||||
.Item(DeployedItem.sensor_shield -> Set(CertificationType.AdvancedHacking, CertificationType.CombatEngineering))
|
||||
ace.Modes(3).Item(DeployedItem.motionalarmsensor, Set(Certification.CombatEngineering))
|
||||
ace.Modes(3).Item(DeployedItem.sensor_shield, Set(Certification.AdvancedHacking, Certification.CombatEngineering))
|
||||
ace.Tile = InventoryTile.Tile33
|
||||
|
||||
advanced_ace.Name = "advanced_ace"
|
||||
advanced_ace.Size = EquipmentSize.Rifle
|
||||
advanced_ace.Modes += new ConstructionFireMode
|
||||
advanced_ace.Modes.head.Item(DeployedItem.tank_traps -> Set(CertificationType.FortificationEngineering))
|
||||
advanced_ace.Modes.head.Item(DeployedItem.tank_traps, Set(Certification.FortificationEngineering))
|
||||
advanced_ace.Modes += new ConstructionFireMode
|
||||
advanced_ace.Modes(1).Item(DeployedItem.portable_manned_turret -> Set(CertificationType.AssaultEngineering))
|
||||
advanced_ace.Modes(1).Item(DeployedItem.portable_manned_turret, Set(Certification.AssaultEngineering))
|
||||
advanced_ace.Modes += new ConstructionFireMode
|
||||
advanced_ace.Modes(2).Item(DeployedItem.deployable_shield_generator -> Set(CertificationType.AssaultEngineering))
|
||||
advanced_ace.Modes(2).Item(DeployedItem.deployable_shield_generator, Set(Certification.AssaultEngineering))
|
||||
advanced_ace.Tile = InventoryTile.Tile93
|
||||
|
||||
router_telepad.Name = "router_telepad"
|
||||
router_telepad.Size = EquipmentSize.Pistol
|
||||
router_telepad.Modes += new ConstructionFireMode
|
||||
router_telepad.Modes.head.Item(DeployedItem.router_telepad_deployable -> Set(CertificationType.GroundSupport))
|
||||
router_telepad.Modes.head.Item(DeployedItem.router_telepad_deployable, Set(Certification.GroundSupport))
|
||||
router_telepad.Tile = InventoryTile.Tile33
|
||||
router_telepad.Packet = new TelepadConverter
|
||||
|
||||
|
|
@ -6125,7 +6142,8 @@ object GlobalDefinitions {
|
|||
vulture.TrunkLocation = Vector3(-0.76f, -1.88f, 0f)
|
||||
vulture.AutoPilotSpeeds = (0, 4)
|
||||
vulture.Packet = variantConverter
|
||||
vulture.DestroyedModel = Some(DestroyedVehicle.Liberator) //add_property vulture destroyedphysics liberator_destroyed
|
||||
vulture.DestroyedModel =
|
||||
Some(DestroyedVehicle.Liberator) //add_property vulture destroyedphysics liberator_destroyed
|
||||
vulture.Subtract.Damage1 = 5
|
||||
vulture.JackingDuration = Array(0, 30, 10, 5)
|
||||
vulture.DamageUsing = DamageCalculations.AgainstAircraft
|
||||
|
|
@ -6599,7 +6617,9 @@ object GlobalDefinitions {
|
|||
vanu_equipment_term.Repairable = false
|
||||
|
||||
cert_terminal.Name = "cert_terminal"
|
||||
cert_terminal.Tab += 0 -> OrderTerminalDefinition.CertificationPage(CertTerminalDefinition.certs)
|
||||
val certs = Certification.values.filter(_.cost != 0)
|
||||
val page = OrderTerminalDefinition.CertificationPage(certs)
|
||||
cert_terminal.Tab += 0 -> page
|
||||
cert_terminal.MaxHealth = 500
|
||||
cert_terminal.Damageable = true
|
||||
cert_terminal.Repairable = true
|
||||
|
|
@ -6954,4 +6974,5 @@ object GlobalDefinitions {
|
|||
generator.RepairIfDestroyed = true
|
||||
generator.Subtract.Damage1 = 9
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,133 +0,0 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package net.psforever.objects
|
||||
|
||||
import net.psforever.objects.definition.ImplantDefinition
|
||||
import net.psforever.types.{ExoSuitType, ImplantType}
|
||||
|
||||
/**
|
||||
* A slot "on the player" into which an implant is installed.
|
||||
* In total, players have three implant slots.<br>
|
||||
* <br>
|
||||
* All implants slots start as "locked" and must be "unlocked" through battle rank advancement.
|
||||
* Only after it is "unlocked" may an implant be "installed" into the slot.
|
||||
* Upon installation, it undergoes an initialization period and then, after which, it is ready for user activation.
|
||||
* Being jammed de-activates the implant, put it into a state of "not being ready," and causes the initialization to repeat.
|
||||
*/
|
||||
class ImplantSlot {
|
||||
|
||||
/** is this slot available for holding an implant */
|
||||
private var unlocked: Boolean = false
|
||||
|
||||
/** whether this implant is ready for use */
|
||||
private var initialized: Boolean = false
|
||||
|
||||
/**
|
||||
*/
|
||||
private var initializeTime: Long = 0L
|
||||
|
||||
/** is this implant active */
|
||||
private var active: Boolean = false
|
||||
|
||||
/** what implant is currently installed in this slot; None if there is no implant currently installed */
|
||||
private var implant: Option[ImplantDefinition] = None
|
||||
|
||||
def InitializeTime: Long = initializeTime
|
||||
|
||||
def InitializeTime_=(time: Long): Long = {
|
||||
initializeTime = time
|
||||
InitializeTime
|
||||
}
|
||||
|
||||
def Unlocked: Boolean = unlocked
|
||||
|
||||
def Unlocked_=(lock: Boolean): Boolean = {
|
||||
unlocked = lock || unlocked //do not let re-lock
|
||||
Unlocked
|
||||
}
|
||||
|
||||
def Initialized: Boolean = initialized
|
||||
|
||||
def Initialized_=(init: Boolean): Boolean = {
|
||||
initialized = Installed.isDefined && init
|
||||
Active = Active && initialized //can not be active just yet
|
||||
Initialized
|
||||
}
|
||||
|
||||
def Active: Boolean = active
|
||||
|
||||
def Active_=(state: Boolean): Boolean = {
|
||||
active = Initialized && state
|
||||
Active
|
||||
}
|
||||
|
||||
def Implant: ImplantType.Value =
|
||||
Installed match {
|
||||
case Some(idef) =>
|
||||
idef.Type
|
||||
case None =>
|
||||
Active = false
|
||||
Initialized = false
|
||||
ImplantType.None
|
||||
}
|
||||
|
||||
def Implant_=(anImplant: ImplantDefinition): ImplantType.Value = {
|
||||
Implant_=(Some(anImplant))
|
||||
}
|
||||
|
||||
def Implant_=(anImplant: Option[ImplantDefinition]): ImplantType.Value = {
|
||||
if (Unlocked) {
|
||||
anImplant match {
|
||||
case Some(_) =>
|
||||
implant = anImplant
|
||||
case None =>
|
||||
implant = None
|
||||
}
|
||||
Active = false
|
||||
Initialized = false
|
||||
}
|
||||
Implant
|
||||
}
|
||||
|
||||
def Installed: Option[ImplantDefinition] = implant
|
||||
|
||||
def MaxTimer: Long =
|
||||
Implant match {
|
||||
case ImplantType.None =>
|
||||
-1L
|
||||
case _ =>
|
||||
Installed.get.InitializationDuration
|
||||
}
|
||||
|
||||
def ActivationCharge: Int = {
|
||||
if (Active) {
|
||||
Installed.get.ActivationStaminaCost
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the stamina consumption of the implant for any given moment of being active after its activation.
|
||||
* @param suit the exo-suit being worn
|
||||
* @return the amount of stamina (energy) that is consumed
|
||||
*/
|
||||
def Charge(suit: ExoSuitType.Value): Int = {
|
||||
if (Active) {
|
||||
val inst = Installed.get
|
||||
inst.StaminaCost
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
def Jammed(): Unit = {
|
||||
Active = false
|
||||
Initialized = false
|
||||
}
|
||||
}
|
||||
|
||||
object ImplantSlot {
|
||||
def apply(): ImplantSlot = {
|
||||
new ImplantSlot()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package net.psforever.objects
|
||||
|
||||
import net.psforever.objects.avatar.Avatar
|
||||
|
||||
import scala.collection.concurrent.{Map, TrieMap}
|
||||
|
||||
/**
|
||||
|
|
@ -25,13 +27,21 @@ private class LivePlayerList {
|
|||
}
|
||||
}
|
||||
|
||||
def Update(sessionId: Long, avatar: Avatar): Unit = {
|
||||
sessionMap.get(sessionId) match {
|
||||
case Some(_) =>
|
||||
sessionMap(sessionId) = avatar
|
||||
case None => ;
|
||||
}
|
||||
}
|
||||
|
||||
def Remove(sessionId: Long): Option[Avatar] = {
|
||||
sessionMap.remove(sessionId)
|
||||
}
|
||||
|
||||
def Shutdown: List[Avatar] = {
|
||||
val list = sessionMap.values.toList
|
||||
sessionMap.clear
|
||||
sessionMap.clear()
|
||||
list
|
||||
}
|
||||
}
|
||||
|
|
@ -68,15 +78,19 @@ object LivePlayerList {
|
|||
/**
|
||||
* Create a mapped entry between the user's session and a user's character.
|
||||
* Neither the player nor the session may exist in the current mappings if this is to work.
|
||||
*
|
||||
* @param sessionId the session
|
||||
* @param avatar the character
|
||||
* @param avatar the character
|
||||
* @return `true`, if the session was association was made; `false`, otherwise
|
||||
*/
|
||||
def Add(sessionId: Long, avatar: Avatar): Boolean = Instance.Add(sessionId, avatar)
|
||||
|
||||
def Update(sessionId: Long, avatar: Avatar): Unit = Instance.Update(sessionId, avatar)
|
||||
|
||||
/**
|
||||
* Remove all entries related to the given session identifier from the mappings.
|
||||
* The character no longer counts as "online."
|
||||
*
|
||||
* @param sessionId the session
|
||||
* @return any character that was afffected by the mapping removal
|
||||
*/
|
||||
|
|
@ -84,6 +98,7 @@ object LivePlayerList {
|
|||
|
||||
/**
|
||||
* Hastily remove all mappings and ids.
|
||||
*
|
||||
* @return an unsorted list of the characters that were still online
|
||||
*/
|
||||
def Shutdown: List[Avatar] = Instance.Shutdown
|
||||
|
|
|
|||
|
|
@ -70,14 +70,14 @@ trait NtuStorageBehavior extends Actor {
|
|||
def NtuStorageObject: NtuContainer = null
|
||||
|
||||
def storageBehavior: Receive = {
|
||||
case Ntu.Offer(src) => HandleNtuOffer(sender, src)
|
||||
case Ntu.Offer(src) => HandleNtuOffer(sender(), src)
|
||||
|
||||
case Ntu.Grant(_, 0) | Ntu.Request(0, 0) | TransferBehavior.Stopping() => StopNtuBehavior(sender)
|
||||
case Ntu.Grant(_, 0) | Ntu.Request(0, 0) | TransferBehavior.Stopping() => StopNtuBehavior(sender())
|
||||
|
||||
case Ntu.Request(min, max) => HandleNtuRequest(sender, min, max)
|
||||
case Ntu.Request(min, max) => HandleNtuRequest(sender(), min, max)
|
||||
|
||||
case Ntu.Grant(src, amount) => HandleNtuGrant(sender, src, amount)
|
||||
case NtuCommand.Grant(src, amount) => HandleNtuGrant(sender, src, amount)
|
||||
case Ntu.Grant(src, amount) => HandleNtuGrant(sender(), src, amount)
|
||||
case NtuCommand.Grant(src, amount) => HandleNtuGrant(sender(), src, amount)
|
||||
}
|
||||
|
||||
def HandleNtuOffer(sender: ActorRef, src: NtuContainer): Unit
|
||||
|
|
|
|||
|
|
@ -1,13 +1,8 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package net.psforever.objects
|
||||
|
||||
import net.psforever.objects.avatar.LoadoutManager
|
||||
import net.psforever.objects.definition.{
|
||||
AvatarDefinition,
|
||||
ExoSuitDefinition,
|
||||
ImplantDefinition,
|
||||
SpecialExoSuitDefinition
|
||||
}
|
||||
import net.psforever.objects.avatar.{Avatar, LoadoutManager}
|
||||
import net.psforever.objects.definition.{AvatarDefinition, ExoSuitDefinition, SpecialExoSuitDefinition}
|
||||
import net.psforever.objects.equipment.{Equipment, EquipmentSize, EquipmentSlot, JammableUnit}
|
||||
import net.psforever.objects.inventory.{Container, GridInventory, InventoryItem}
|
||||
import net.psforever.objects.serverobject.PlanetSideServerObject
|
||||
|
|
@ -15,13 +10,12 @@ import net.psforever.objects.serverobject.affinity.FactionAffinity
|
|||
import net.psforever.objects.vital.resistance.ResistanceProfile
|
||||
import net.psforever.objects.vital.{DamageResistanceModel, Vitality}
|
||||
import net.psforever.objects.zones.ZoneAware
|
||||
import net.psforever.packet.game.objectcreate.{Cosmetics, DetailedCharacterData, PersonalStyle}
|
||||
import net.psforever.types.{PlanetSideGUID, _}
|
||||
|
||||
import scala.annotation.tailrec
|
||||
import scala.util.{Success, Try}
|
||||
|
||||
class Player(private val core: Avatar)
|
||||
class Player(var avatar: Avatar)
|
||||
extends PlanetSideServerObject
|
||||
with FactionAffinity
|
||||
with Vitality
|
||||
|
|
@ -32,7 +26,6 @@ class Player(private val core: Avatar)
|
|||
Health = 0 //player health is artificially managed as a part of their lifecycle; start entity as dead
|
||||
Destroyed = true //see isAlive
|
||||
private var backpack: Boolean = false
|
||||
private var stamina: Int = 0
|
||||
private var armor: Int = 0
|
||||
|
||||
private var capacitor: Float = 0f
|
||||
|
|
@ -40,8 +33,6 @@ class Player(private val core: Avatar)
|
|||
private var capacitorLastUsedMillis: Long = 0
|
||||
private var capacitorLastChargedMillis: Long = 0
|
||||
|
||||
private var maxStamina: Int = 100 //does anything affect this?
|
||||
|
||||
private var exosuit: ExoSuitDefinition = GlobalDefinitions.Standard
|
||||
private val freeHand: EquipmentSlot = new OffhandEquipmentSlot(EquipmentSize.Inventory)
|
||||
private val holsters: Array[EquipmentSlot] = Array.fill[EquipmentSlot](5)(new EquipmentSlot)
|
||||
|
|
@ -54,9 +45,7 @@ class Player(private val core: Avatar)
|
|||
private var crouching: Boolean = false
|
||||
private var jumping: Boolean = false
|
||||
private var cloaked: Boolean = false
|
||||
private var fatigued: Boolean =
|
||||
false // If stamina drops to 0, player is fatigued until regenerating at least 20 stamina
|
||||
private var afk: Boolean = false
|
||||
private var afk: Boolean = false
|
||||
|
||||
private var vehicleSeated: Option[PlanetSideGUID] = None
|
||||
|
||||
|
|
@ -70,36 +59,35 @@ class Player(private val core: Avatar)
|
|||
|
||||
/** From PlanetsideAttributeMessage */
|
||||
var PlanetsideAttribute: Array[Long] = Array.ofDim(120)
|
||||
var skipStaminaRegenForTurns: Int = 0
|
||||
|
||||
val squadLoadouts = new LoadoutManager(10)
|
||||
|
||||
Player.SuitSetup(this, exosuit)
|
||||
|
||||
def CharId: Long = core.CharId
|
||||
def Definition: AvatarDefinition = avatar.definition
|
||||
|
||||
def Name: String = core.name
|
||||
def CharId: Long = avatar.id
|
||||
|
||||
def Faction: PlanetSideEmpire.Value = core.faction
|
||||
def Name: String = avatar.name
|
||||
|
||||
def Sex: CharacterGender.Value = core.sex
|
||||
def Faction: PlanetSideEmpire.Value = avatar.faction
|
||||
|
||||
def Head: Int = core.head
|
||||
def Sex: CharacterGender.Value = avatar.sex
|
||||
|
||||
def Voice: CharacterVoice.Value = core.voice
|
||||
def Head: Int = avatar.head
|
||||
|
||||
def LFS: Boolean = core.LFS
|
||||
def Voice: CharacterVoice.Value = avatar.voice
|
||||
|
||||
def isAlive: Boolean = !Destroyed
|
||||
|
||||
def isBackpack: Boolean = backpack
|
||||
|
||||
def Spawn: Boolean = {
|
||||
def Spawn(): Boolean = {
|
||||
if (!isAlive && !isBackpack) {
|
||||
Destroyed = false
|
||||
Health = Definition.DefaultHealth
|
||||
Stamina = MaxStamina
|
||||
Armor = MaxArmor
|
||||
Capacitor = 0
|
||||
ResetAllImplants()
|
||||
}
|
||||
isAlive
|
||||
}
|
||||
|
|
@ -107,7 +95,6 @@ class Player(private val core: Avatar)
|
|||
def Die: Boolean = {
|
||||
Destroyed = true
|
||||
Health = 0
|
||||
Stamina = 0
|
||||
false
|
||||
}
|
||||
|
||||
|
|
@ -126,21 +113,6 @@ class Player(private val core: Avatar)
|
|||
}
|
||||
}
|
||||
|
||||
def Stamina: Int = stamina
|
||||
|
||||
def Stamina_=(assignStamina: Int): Int = {
|
||||
stamina = if (isAlive) { math.min(math.max(0, assignStamina), MaxStamina) }
|
||||
else { 0 }
|
||||
Stamina
|
||||
}
|
||||
|
||||
def MaxStamina: Int = maxStamina
|
||||
|
||||
def MaxStamina_=(max: Int): Int = {
|
||||
maxStamina = math.min(math.max(0, max), 65535)
|
||||
MaxStamina
|
||||
}
|
||||
|
||||
def Armor: Int = armor
|
||||
|
||||
def Armor_=(assignArmor: Int): Int = {
|
||||
|
|
@ -199,7 +171,7 @@ class Player(private val core: Avatar)
|
|||
} else if (slot > -1 && slot < 5) {
|
||||
holsters(slot)
|
||||
} else if (slot == 5) {
|
||||
core.FifthSlot
|
||||
avatar.fifthSlot()
|
||||
} else if (slot == Player.FreeHandSlot) {
|
||||
freeHand
|
||||
} else {
|
||||
|
|
@ -211,10 +183,6 @@ class Player(private val core: Avatar)
|
|||
|
||||
def Inventory: GridInventory = inventory
|
||||
|
||||
def Locker: LockerContainer = core.Locker
|
||||
|
||||
def FifthSlot: EquipmentSlot = core.FifthSlot
|
||||
|
||||
override def Fit(obj: Equipment): Option[Int] = {
|
||||
recursiveHolsterFit(holsters.iterator, obj.Size) match {
|
||||
case Some(index) =>
|
||||
|
|
@ -238,7 +206,7 @@ class Player(private val core: Avatar)
|
|||
if (!iter.hasNext) {
|
||||
None
|
||||
} else {
|
||||
val slot = iter.next
|
||||
val slot = iter.next()
|
||||
if (slot.Equipment.isEmpty && slot.Size.equals(objSize)) {
|
||||
Some(index)
|
||||
} else {
|
||||
|
|
@ -278,7 +246,7 @@ class Player(private val core: Avatar)
|
|||
if (!iter.hasNext) {
|
||||
None
|
||||
} else {
|
||||
val slot = iter.next
|
||||
val slot = iter.next()
|
||||
if (slot.Equipment.isDefined && slot.Equipment.get.GUID == guid) {
|
||||
Some(index)
|
||||
} else {
|
||||
|
|
@ -343,40 +311,6 @@ class Player(private val core: Avatar)
|
|||
|
||||
def RadiationShielding = exosuit.RadiationShielding
|
||||
|
||||
def EquipmentLoadouts: LoadoutManager = core.EquipmentLoadouts
|
||||
|
||||
def SquadLoadouts: LoadoutManager = core.SquadLoadouts
|
||||
|
||||
def BEP: Long = core.BEP
|
||||
|
||||
def CEP: Long = core.CEP
|
||||
|
||||
def Certifications: Set[CertificationType.Value] = core.Certifications.toSet
|
||||
|
||||
/**
|
||||
* What kind of implant is installed into the given slot number?
|
||||
* @see `ImplantType`
|
||||
* @param slot the slot number
|
||||
* @return the tye of implant
|
||||
*/
|
||||
def Implant(slot: Int): ImplantType.Value = core.Implant(slot)
|
||||
|
||||
def ImplantSlot(slot: Int): ImplantSlot = core.Implants(slot)
|
||||
|
||||
/**
|
||||
* A read-only `Array` of tuples representing important information about all unlocked implant slots.
|
||||
* @return a maximum of three implant types, initialization times, and active flags
|
||||
*/
|
||||
def Implants: Array[(ImplantType.Value, Long, Boolean)] = {
|
||||
core.Implants.takeWhile(_.Unlocked).map(implant => { (implant.Implant, implant.MaxTimer, implant.Active) })
|
||||
}
|
||||
|
||||
def InstallImplant(implant: ImplantDefinition): Option[Int] = core.InstallImplant(implant)
|
||||
|
||||
def UninstallImplant(implant: ImplantType.Value): Option[Int] = core.UninstallImplant(implant)
|
||||
|
||||
def ResetAllImplants(): Unit = core.ResetAllImplants()
|
||||
|
||||
def FacingYawUpper: Float = facingYawUpper
|
||||
|
||||
def FacingYawUpper_=(facing: Float): Float = {
|
||||
|
|
@ -405,13 +339,6 @@ class Player(private val core: Avatar)
|
|||
Cloaked
|
||||
}
|
||||
|
||||
def Fatigued: Boolean = fatigued
|
||||
|
||||
def Fatigued_=(isFatigued: Boolean): Boolean = {
|
||||
fatigued = isFatigued
|
||||
Fatigued
|
||||
}
|
||||
|
||||
def AwayFromKeyboard: Boolean = afk
|
||||
|
||||
def AwayFromKeyboard_=(away: Boolean): Boolean = {
|
||||
|
|
@ -419,66 +346,6 @@ class Player(private val core: Avatar)
|
|||
AwayFromKeyboard
|
||||
}
|
||||
|
||||
def PersonalStyleFeatures: Option[Cosmetics] = core.PersonalStyleFeatures
|
||||
|
||||
def AddToPersonalStyle(value: PersonalStyle.Value): (Option[Cosmetics], Option[Cosmetics]) = {
|
||||
val original = core.PersonalStyleFeatures
|
||||
if (DetailedCharacterData.isBR24(core.BEP)) {
|
||||
core.PersonalStyleFeatures = original match {
|
||||
case Some(cosmetic) =>
|
||||
cosmetic + value
|
||||
case None =>
|
||||
Cosmetics(value)
|
||||
}
|
||||
(original, core.PersonalStyleFeatures)
|
||||
} else {
|
||||
(None, None)
|
||||
}
|
||||
}
|
||||
|
||||
def RemoveFromPersonalStyle(value: PersonalStyle.Value): (Option[Cosmetics], Option[Cosmetics]) = {
|
||||
val original = core.PersonalStyleFeatures
|
||||
original match {
|
||||
case Some(cosmetics) =>
|
||||
(original, core.PersonalStyleFeatures = cosmetics - value)
|
||||
case None =>
|
||||
(None, None)
|
||||
}
|
||||
}
|
||||
|
||||
private def BasicFeatureToggle(feature: PersonalStyle.Value): (Option[Cosmetics], Option[Cosmetics]) =
|
||||
core.PersonalStyleFeatures match {
|
||||
case Some(c: Cosmetics) =>
|
||||
if (c.Styles.contains(feature)) {
|
||||
RemoveFromPersonalStyle(feature)
|
||||
} else {
|
||||
AddToPersonalStyle(feature)
|
||||
}
|
||||
case None =>
|
||||
AddToPersonalStyle(feature)
|
||||
}
|
||||
|
||||
def ToggleHelmet: (Option[Cosmetics], Option[Cosmetics]) = BasicFeatureToggle(PersonalStyle.NoHelmet)
|
||||
|
||||
def ToggleShades: (Option[Cosmetics], Option[Cosmetics]) = BasicFeatureToggle(PersonalStyle.Sunglasses)
|
||||
|
||||
def ToggleEarpiece: (Option[Cosmetics], Option[Cosmetics]) = BasicFeatureToggle(PersonalStyle.Earpiece)
|
||||
|
||||
def ToggleHat: (Option[Cosmetics], Option[Cosmetics]) = {
|
||||
core.PersonalStyleFeatures match {
|
||||
case Some(c: Cosmetics) =>
|
||||
if (c.Styles.contains(PersonalStyle.BrimmedCap)) {
|
||||
(RemoveFromPersonalStyle(PersonalStyle.BrimmedCap)._1, AddToPersonalStyle(PersonalStyle.Beret)._2)
|
||||
} else if (c.Styles.contains(PersonalStyle.Beret)) {
|
||||
RemoveFromPersonalStyle(PersonalStyle.Beret)
|
||||
} else {
|
||||
AddToPersonalStyle(PersonalStyle.BrimmedCap)
|
||||
}
|
||||
case None =>
|
||||
AddToPersonalStyle(PersonalStyle.BrimmedCap)
|
||||
}
|
||||
}
|
||||
|
||||
private var usingSpecial: SpecialExoSuitDefinition.Mode.Value => SpecialExoSuitDefinition.Mode.Value =
|
||||
DefaultUsingSpecial
|
||||
|
||||
|
|
@ -602,8 +469,6 @@ class Player(private val core: Avatar)
|
|||
isBackpack && (backpackAccess.isEmpty || backpackAccess.contains(player.GUID))
|
||||
}
|
||||
|
||||
def FirstTimeEvents: List[String] = core.FirstTimeEvents
|
||||
|
||||
def VehicleSeated: Option[PlanetSideGUID] = vehicleSeated
|
||||
|
||||
def VehicleSeated_=(guid: PlanetSideGUID): Option[PlanetSideGUID] = VehicleSeated_=(Some(guid))
|
||||
|
|
@ -613,57 +478,31 @@ class Player(private val core: Avatar)
|
|||
VehicleSeated
|
||||
}
|
||||
|
||||
def VehicleOwned: Option[PlanetSideGUID] = core.VehicleOwned
|
||||
|
||||
def VehicleOwned_=(guid: PlanetSideGUID): Option[PlanetSideGUID] = core.VehicleOwned_=(Some(guid))
|
||||
|
||||
def VehicleOwned_=(guid: Option[PlanetSideGUID]): Option[PlanetSideGUID] = core.VehicleOwned_=(guid)
|
||||
|
||||
def GetLastUsedTime(code: Int): Long = core.GetLastUsedTime(code)
|
||||
|
||||
def GetLastUsedTime(code: ExoSuitType.Value): Long = core.GetLastUsedTime(code)
|
||||
|
||||
def GetLastUsedTime(code: ExoSuitType.Value, subtype: Int): Long = core.GetLastUsedTime(code, subtype)
|
||||
|
||||
def SetLastUsedTime(code: Int, time: Long): Unit = core.SetLastUsedTime(code, time)
|
||||
|
||||
def SetLastUsedTime(code: ExoSuitType.Value): Unit = core.SetLastUsedTime(code)
|
||||
|
||||
def SetLastUsedTime(code: ExoSuitType.Value, time: Long): Unit = core.SetLastUsedTime(code, time)
|
||||
|
||||
def SetLastUsedTime(code: ExoSuitType.Value, subtype: Int): Unit = core.SetLastUsedTime(code, subtype)
|
||||
|
||||
def SetLastUsedTime(code: ExoSuitType.Value, subtype: Int, time: Long): Unit =
|
||||
core.SetLastUsedTime(code, subtype, time)
|
||||
|
||||
def GetLastPurchaseTime(code: Int): Long = core.GetLastPurchaseTime(code)
|
||||
|
||||
def SetLastPurchaseTime(code: Int, time: Long): Unit = core.SetLastPurchaseTime(code, time)
|
||||
|
||||
def ObjectTypeNameReference(id: Long): String = core.ObjectTypeNameReference(id)
|
||||
|
||||
def ObjectTypeNameReference(id: Long, name: String): String = core.ObjectTypeNameReference(id, name)
|
||||
|
||||
def DamageModel = exosuit.asInstanceOf[DamageResistanceModel]
|
||||
|
||||
def Definition: AvatarDefinition = core.Definition
|
||||
|
||||
def canEqual(other: Any): Boolean = other.isInstanceOf[Player]
|
||||
|
||||
override def equals(other: Any): Boolean =
|
||||
other match {
|
||||
case that: Player =>
|
||||
(that canEqual this) &&
|
||||
core == that.core
|
||||
avatar == that.avatar
|
||||
case _ =>
|
||||
false
|
||||
}
|
||||
|
||||
override def hashCode(): Int = {
|
||||
core.hashCode()
|
||||
avatar.hashCode()
|
||||
}
|
||||
|
||||
override def toString: String = Player.toString(this)
|
||||
override def toString: String = {
|
||||
val guid = if (HasGUID) {
|
||||
s" ${Continent}-${GUID.guid}"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
s"${avatar.name}$guid ${Health}/${MaxHealth} ${Armor}/${MaxArmor}"
|
||||
}
|
||||
}
|
||||
|
||||
object Player {
|
||||
|
|
@ -672,16 +511,6 @@ object Player {
|
|||
final val HandsDownSlot: Int = 255
|
||||
|
||||
final case class Die()
|
||||
final case class ImplantActivation(slot: Int, status: Int)
|
||||
final case class ImplantInitializationStart(slot: Int)
|
||||
final case class UninitializeImplant(slot: Int)
|
||||
final case class ImplantInitializationComplete(slot: Int)
|
||||
final case class StaminaRegen()
|
||||
final case class StaminaChanged(currentStamina: Option[Int] = None)
|
||||
|
||||
object StaminaChanged {
|
||||
def apply(amount: Int): StaminaChanged = StaminaChanged(Some(amount))
|
||||
}
|
||||
|
||||
def apply(core: Avatar): Player = {
|
||||
new Player(core)
|
||||
|
|
@ -698,33 +527,11 @@ object Player {
|
|||
|
||||
def Respawn(player: Player): Player = {
|
||||
if (player.Release) {
|
||||
val obj = new Player(player.core)
|
||||
val obj = new Player(player.avatar)
|
||||
obj.Continent = player.Continent
|
||||
obj
|
||||
} else {
|
||||
player
|
||||
}
|
||||
}
|
||||
|
||||
def GetHackLevel(player: Player): Int = {
|
||||
if (
|
||||
player.Certifications.contains(CertificationType.ExpertHacking) || player.Certifications.contains(
|
||||
CertificationType.ElectronicsExpert
|
||||
)
|
||||
) {
|
||||
3
|
||||
} else if (player.Certifications.contains(CertificationType.AdvancedHacking)) {
|
||||
2
|
||||
} else if (player.Certifications.contains(CertificationType.Hacking)) {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
def toString(obj: Player): String = {
|
||||
val guid = if (obj.HasGUID) { s" ${obj.Continent}-${obj.GUID.guid}" }
|
||||
else { "" }
|
||||
s"${obj.core}$guid ${obj.Health}/${obj.MaxHealth} ${obj.Armor}/${obj.MaxArmor}"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ object Players {
|
|||
if (!iter.hasNext) {
|
||||
list
|
||||
} else {
|
||||
val slot = iter.next
|
||||
val slot = iter.next()
|
||||
slot.Equipment match {
|
||||
case Some(equipment) =>
|
||||
slot.Equipment = None
|
||||
|
|
@ -105,7 +105,7 @@ object Players {
|
|||
if (!iter.hasNext) {
|
||||
list
|
||||
} else {
|
||||
val slot = iter.next
|
||||
val slot = iter.next()
|
||||
if (slot.Equipment.isEmpty) {
|
||||
list.find(item => item.obj.Size == slot.Size) match {
|
||||
case Some(obj) =>
|
||||
|
|
@ -126,11 +126,11 @@ object Players {
|
|||
case Nil =>
|
||||
true
|
||||
case permissions if subtype != 0 =>
|
||||
val certs = player.Certifications
|
||||
val certs = player.avatar.certifications
|
||||
certs.intersect(permissions.toSet).nonEmpty &&
|
||||
certs.intersect(InfantryLoadout.DetermineSubtypeC(subtype)).nonEmpty
|
||||
case permissions =>
|
||||
player.Certifications.intersect(permissions.toSet).nonEmpty
|
||||
player.avatar.certifications.intersect(permissions.toSet).nonEmpty
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ class SensorDeployableControl(sensor: SensorDeployable)
|
|||
target match {
|
||||
case obj: PlanetSideServerObject if !jammedSound =>
|
||||
obj.Zone.VehicleEvents ! VehicleServiceMessage(
|
||||
obj.Zone.Id,
|
||||
obj.Zone.id,
|
||||
VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, obj.GUID, 54, 1)
|
||||
)
|
||||
super.StartJammeredSound(obj, dur)
|
||||
|
|
@ -83,7 +83,7 @@ class SensorDeployableControl(sensor: SensorDeployable)
|
|||
case obj: PlanetSideServerObject with JammableUnit if !obj.Jammed =>
|
||||
val zone = obj.Zone
|
||||
zone.LocalEvents ! LocalServiceMessage(
|
||||
zone.Id,
|
||||
zone.id,
|
||||
LocalAction.TriggerEffectInfo(Service.defaultPlayerGUID, "on", obj.GUID, false, 1000)
|
||||
)
|
||||
super.StartJammeredStatus(obj, dur)
|
||||
|
|
@ -95,7 +95,7 @@ class SensorDeployableControl(sensor: SensorDeployable)
|
|||
case obj: PlanetSideServerObject if jammedSound =>
|
||||
val zone = obj.Zone
|
||||
zone.VehicleEvents ! VehicleServiceMessage(
|
||||
zone.Id,
|
||||
zone.id,
|
||||
VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, obj.GUID, 54, 0)
|
||||
)
|
||||
case _ => ;
|
||||
|
|
@ -107,7 +107,7 @@ class SensorDeployableControl(sensor: SensorDeployable)
|
|||
target match {
|
||||
case obj: PlanetSideServerObject with JammableUnit if obj.Jammed =>
|
||||
sensor.Zone.LocalEvents ! LocalServiceMessage(
|
||||
sensor.Zone.Id,
|
||||
sensor.Zone.id,
|
||||
LocalAction.TriggerEffectInfo(Service.defaultPlayerGUID, "on", obj.GUID, true, 1000)
|
||||
)
|
||||
case _ => ;
|
||||
|
|
@ -127,7 +127,7 @@ object SensorDeployableControl {
|
|||
Deployables.AnnounceDestroyDeployable(target, Some(1 seconds))
|
||||
val zone = target.Zone
|
||||
zone.LocalEvents ! LocalServiceMessage(
|
||||
zone.Id,
|
||||
zone.id,
|
||||
LocalAction.TriggerEffectInfo(Service.defaultPlayerGUID, "on", target.GUID, false, 1000)
|
||||
)
|
||||
//position the explosion effect near the bulky area of the sensor stalk
|
||||
|
|
@ -143,7 +143,7 @@ object SensorDeployableControl {
|
|||
)
|
||||
}
|
||||
zone.LocalEvents ! LocalServiceMessage(
|
||||
zone.Id,
|
||||
zone.id,
|
||||
LocalAction.TriggerEffectLocation(Service.defaultPlayerGUID, "motion_sensor_destroyed", explosionPos, ang)
|
||||
)
|
||||
//TODO replaced by an alternate model (charred stub)?
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package net.psforever.objects
|
||||
|
||||
import net.psforever.objects.avatar.Avatar
|
||||
import net.psforever.objects.zones.{Zone, Zoning}
|
||||
import net.psforever.packet.game.DeadState
|
||||
|
||||
|
|
@ -9,7 +10,6 @@ case class Session(
|
|||
account: Account = null,
|
||||
player: Player = null,
|
||||
avatar: Avatar = null,
|
||||
admin: Boolean = false,
|
||||
zoningType: Zoning.Method.Value = Zoning.Method.None,
|
||||
deadState: DeadState.Value = DeadState.Alive,
|
||||
speed: Float = 1.0f,
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ class ShieldGeneratorControl(gen: ShieldGeneratorDeployable)
|
|||
target match {
|
||||
case obj: PlanetSideServerObject with JammableUnit if !obj.Jammed =>
|
||||
obj.Zone.VehicleEvents ! VehicleServiceMessage(
|
||||
obj.Zone.Id,
|
||||
obj.Zone.id,
|
||||
VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, obj.GUID, 27, 1)
|
||||
)
|
||||
super.StartJammeredStatus(obj, dur)
|
||||
|
|
@ -132,7 +132,7 @@ class ShieldGeneratorControl(gen: ShieldGeneratorDeployable)
|
|||
target match {
|
||||
case obj: PlanetSideServerObject with JammableUnit if obj.Jammed =>
|
||||
obj.Zone.VehicleEvents ! VehicleServiceMessage(
|
||||
obj.Zone.Id,
|
||||
obj.Zone.id,
|
||||
VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, obj.GUID, 27, 0)
|
||||
)
|
||||
case _ => ;
|
||||
|
|
@ -154,7 +154,7 @@ object ShieldGeneratorControl {
|
|||
if (damageToShields) {
|
||||
val zone = target.Zone
|
||||
zone.VehicleEvents ! VehicleServiceMessage(
|
||||
zone.Id,
|
||||
zone.id,
|
||||
VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, target.GUID, 68, target.Shields)
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -111,11 +111,11 @@ object SpawnPoint {
|
|||
val ori = target.Orientation
|
||||
val zrad = math.toRadians(ori.z)
|
||||
val radius =
|
||||
scala.math.random.toFloat * d.UseRadius / 2 + 20f //20 is definitely outside of the gating energy field
|
||||
scala.math.random().toFloat * d.UseRadius / 2 + 20f //20 is definitely outside of the gating energy field
|
||||
val shift = Vector3(math.sin(zrad).toFloat, math.cos(zrad).toFloat, 0) * radius
|
||||
val altitudeShift = target.Definition match {
|
||||
case vdef: VehicleDefinition if GlobalDefinitions.isFlightVehicle(vdef) =>
|
||||
Vector3.z(scala.math.random.toFloat * d.UseRadius / 4 + 20f)
|
||||
Vector3.z(scala.math.random().toFloat * d.UseRadius / 4 + 20f)
|
||||
case _ =>
|
||||
Vector3.Zero
|
||||
}
|
||||
|
|
|
|||
|
|
@ -145,7 +145,7 @@ object Tool {
|
|||
if (!iter.hasNext) {
|
||||
list
|
||||
} else {
|
||||
val index = iter.next
|
||||
val index = iter.next()
|
||||
fmodes.filter(fmode => fmode.AmmoSlotIndex == index) match {
|
||||
case fmode :: _ =>
|
||||
buildFireModes(tdef, iter, fmodes, list :+ new FireModeSlot(tdef, fmode))
|
||||
|
|
|
|||
|
|
@ -199,16 +199,16 @@ class Vehicle(private val vehicleDef: VehicleDefinition)
|
|||
Flying
|
||||
}
|
||||
|
||||
def NtuCapacitorScaled : Int = {
|
||||
if(Definition.MaxNtuCapacitor > 0) {
|
||||
def NtuCapacitorScaled: Int = {
|
||||
if (Definition.MaxNtuCapacitor > 0) {
|
||||
scala.math.ceil((NtuCapacitor.toFloat / Definition.MaxNtuCapacitor.toFloat) * 10).toInt
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
def Capacitor : Int = capacitor
|
||||
|
||||
def Capacitor: Int = capacitor
|
||||
|
||||
def Capacitor_=(value: Int): Int = {
|
||||
if (value > Definition.MaxCapacitor) {
|
||||
capacitor = Definition.MaxCapacitor
|
||||
|
|
@ -365,7 +365,7 @@ class Vehicle(private val vehicleDef: VehicleDefinition)
|
|||
if (!iter.hasNext) {
|
||||
None
|
||||
} else {
|
||||
val (seatNumber, seat) = iter.next
|
||||
val (seatNumber, seat) = iter.next()
|
||||
if (seat.Occupant.contains(player)) {
|
||||
Some(seatNumber)
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -36,10 +36,10 @@ object Vehicles {
|
|||
def Own(vehicle: Vehicle, playerOpt: Option[Player]): Option[Vehicle] = {
|
||||
playerOpt match {
|
||||
case Some(tplayer) =>
|
||||
tplayer.VehicleOwned = vehicle.GUID
|
||||
tplayer.avatar.vehicle = Some(vehicle.GUID)
|
||||
vehicle.AssignOwnership(playerOpt)
|
||||
vehicle.Zone.VehicleEvents ! VehicleServiceMessage(
|
||||
vehicle.Zone.Id,
|
||||
vehicle.Zone.id,
|
||||
VehicleAction.Ownership(tplayer.GUID, vehicle.GUID)
|
||||
)
|
||||
Vehicles.ReloadAccessPermissions(vehicle, tplayer.Name)
|
||||
|
|
@ -50,8 +50,9 @@ object Vehicles {
|
|||
}
|
||||
|
||||
/**
|
||||
* Disassociate a vehicle fromthe player who owns it.
|
||||
* @param guid the unique identifier for that vehicle
|
||||
* Disassociate a vehicle from the player who owns it.
|
||||
*
|
||||
* @param guid the unique identifier for that vehicle
|
||||
* @param vehicle the vehicle
|
||||
* @return the vehicle, if it had a previous owner;
|
||||
* `None`, otherwise
|
||||
|
|
@ -59,8 +60,8 @@ object Vehicles {
|
|||
def Disown(guid: PlanetSideGUID, vehicle: Vehicle): Option[Vehicle] =
|
||||
vehicle.Zone.GUID(vehicle.Owner) match {
|
||||
case Some(player: Player) =>
|
||||
if (player.VehicleOwned.contains(guid)) {
|
||||
player.VehicleOwned = None
|
||||
if (player.avatar.vehicle.contains(guid)) {
|
||||
player.avatar.vehicle = None
|
||||
vehicle.Zone.VehicleEvents ! VehicleServiceMessage(
|
||||
player.Name,
|
||||
VehicleAction.Ownership(player.GUID, PlanetSideGUID(0))
|
||||
|
|
@ -99,9 +100,9 @@ object Vehicles {
|
|||
* @param player the player
|
||||
*/
|
||||
def Disown(player: Player, zoneOpt: Option[Zone]): Option[Vehicle] = {
|
||||
player.VehicleOwned match {
|
||||
player.avatar.vehicle match {
|
||||
case Some(vehicle_guid) =>
|
||||
player.VehicleOwned = None
|
||||
player.avatar.vehicle = None
|
||||
zoneOpt.getOrElse(player.Zone).GUID(vehicle_guid) match {
|
||||
case Some(vehicle: Vehicle) =>
|
||||
Disown(player, vehicle)
|
||||
|
|
@ -231,7 +232,7 @@ object Vehicles {
|
|||
cargoHold.Occupant match {
|
||||
case Some(cargo: Vehicle) => {
|
||||
cargo.Seats(0).Occupant match {
|
||||
case Some(cargoDriver : Player) =>
|
||||
case Some(cargoDriver: Player) =>
|
||||
CargoBehavior.HandleVehicleCargoDismount(
|
||||
target.Zone,
|
||||
cargo.GUID,
|
||||
|
|
@ -255,7 +256,7 @@ object Vehicles {
|
|||
tplayer.VehicleSeated = None
|
||||
if (tplayer.HasGUID) {
|
||||
zone.VehicleEvents ! VehicleServiceMessage(
|
||||
zone.Id,
|
||||
zone.id,
|
||||
VehicleAction.KickPassenger(tplayer.GUID, 4, unk2 = false, target.GUID)
|
||||
)
|
||||
}
|
||||
|
|
@ -268,7 +269,7 @@ object Vehicles {
|
|||
target.Actor ! Vehicle.Deconstruct()
|
||||
} else { // Otherwise handle ownership transfer as normal
|
||||
// Remove ownership of our current vehicle, if we have one
|
||||
hacker.VehicleOwned match {
|
||||
hacker.avatar.vehicle match {
|
||||
case Some(guid: PlanetSideGUID) =>
|
||||
zone.GUID(guid) match {
|
||||
case Some(vehicle: Vehicle) =>
|
||||
|
|
@ -294,12 +295,12 @@ object Vehicles {
|
|||
//todo: Send HackMessage -> HackCleared to vehicle? can be found in packet captures. Not sure if necessary.
|
||||
// And broadcast the faction change to other clients
|
||||
zone.AvatarEvents ! AvatarServiceMessage(
|
||||
zone.Id,
|
||||
zone.id,
|
||||
AvatarAction.SetEmpire(Service.defaultPlayerGUID, target.GUID, hacker.Faction)
|
||||
)
|
||||
}
|
||||
zone.LocalEvents ! LocalServiceMessage(
|
||||
zone.Id,
|
||||
zone.id,
|
||||
LocalAction.TriggerSound(hacker.GUID, TriggeredSound.HackVehicle, target.Position, 30, 0.49803925f)
|
||||
)
|
||||
// Clean up after specific vehicles, e.g. remove router telepads
|
||||
|
|
@ -313,31 +314,41 @@ object Vehicles {
|
|||
}
|
||||
}
|
||||
|
||||
def FindANTChargingSource(obj : TransferContainer, ntuChargingTarget : Option[TransferContainer]) : Option[TransferContainer] = {
|
||||
def FindANTChargingSource(
|
||||
obj: TransferContainer,
|
||||
ntuChargingTarget: Option[TransferContainer]
|
||||
): Option[TransferContainer] = {
|
||||
//determine if we are close enough to charge from something
|
||||
(ntuChargingTarget match {
|
||||
case Some(target : WarpGate) if {
|
||||
val soiRadius = target.Definition.SOIRadius
|
||||
Vector3.DistanceSquared(obj.Position.xy, target.Position.xy) < soiRadius * soiRadius
|
||||
} =>
|
||||
case Some(target: WarpGate) if {
|
||||
val soiRadius = target.Definition.SOIRadius
|
||||
Vector3.DistanceSquared(obj.Position.xy, target.Position.xy) < soiRadius * soiRadius
|
||||
} =>
|
||||
Some(target.asInstanceOf[NtuContainer])
|
||||
case None =>
|
||||
None
|
||||
}).orElse {
|
||||
val position = obj.Position.xy
|
||||
obj.Zone.Buildings.values
|
||||
.collectFirst { case gate : WarpGate
|
||||
if { val soiRadius = gate.Definition.SOIRadius
|
||||
Vector3.DistanceSquared(position, gate.Position.xy) < soiRadius * soiRadius } => gate }
|
||||
.collectFirst {
|
||||
case gate: WarpGate if {
|
||||
val soiRadius = gate.Definition.SOIRadius
|
||||
Vector3.DistanceSquared(position, gate.Position.xy) < soiRadius * soiRadius
|
||||
} =>
|
||||
gate
|
||||
}
|
||||
.asInstanceOf[Option[NtuContainer]]
|
||||
}
|
||||
}
|
||||
|
||||
def FindANTDischargingTarget(obj : TransferContainer, ntuChargingTarget : Option[TransferContainer]) : Option[TransferContainer] = {
|
||||
def FindANTDischargingTarget(
|
||||
obj: TransferContainer,
|
||||
ntuChargingTarget: Option[TransferContainer]
|
||||
): Option[TransferContainer] = {
|
||||
(ntuChargingTarget match {
|
||||
case out @ Some(target : NtuContainer) if {
|
||||
Vector3.DistanceSquared(obj.Position.xy, target.Position.xy) < 400 //20m is generous ...
|
||||
} =>
|
||||
case out @ Some(target: NtuContainer) if {
|
||||
Vector3.DistanceSquared(obj.Position.xy, target.Position.xy) < 400 //20m is generous ...
|
||||
} =>
|
||||
out
|
||||
case _ =>
|
||||
None
|
||||
|
|
@ -352,8 +363,8 @@ object Vehicles {
|
|||
} match {
|
||||
case Some(building) =>
|
||||
building.Amenities
|
||||
.collect { case obj : NtuContainer => obj }
|
||||
.sortBy {o => Vector3.DistanceSquared(position, o.Position.xy) < 400 } //20m is generous ...
|
||||
.collect { case obj: NtuContainer => obj }
|
||||
.sortBy { o => Vector3.DistanceSquared(position, o.Position.xy) < 400 } //20m is generous ...
|
||||
.headOption
|
||||
case None =>
|
||||
None
|
||||
|
|
@ -363,9 +374,10 @@ object Vehicles {
|
|||
|
||||
/**
|
||||
* Before a vehicle is removed from the game world, the following actions must be performed.
|
||||
*
|
||||
* @param vehicle the vehicle
|
||||
*/
|
||||
def BeforeUnloadVehicle(vehicle : Vehicle, zone : Zone) : Unit = {
|
||||
def BeforeUnloadVehicle(vehicle: Vehicle, zone: Zone): Unit = {
|
||||
vehicle.Definition match {
|
||||
case GlobalDefinitions.ams =>
|
||||
vehicle.Actor ! Deployment.TryUndeploy(DriveState.Undeploying)
|
||||
|
|
@ -377,17 +389,17 @@ object Vehicles {
|
|||
}
|
||||
}
|
||||
|
||||
def RemoveTelepads(vehicle: Vehicle) : Unit = {
|
||||
def RemoveTelepads(vehicle: Vehicle): Unit = {
|
||||
val zone = vehicle.Zone
|
||||
(vehicle.Utility(UtilityType.internal_router_telepad_deployable) match {
|
||||
case Some(util : Utility.InternalTelepad) =>
|
||||
case Some(util: Utility.InternalTelepad) =>
|
||||
val telepad = util.Telepad
|
||||
util.Telepad = None
|
||||
zone.GUID(telepad)
|
||||
case _ =>
|
||||
None
|
||||
}) match {
|
||||
case Some(telepad : TelepadDeployable) =>
|
||||
case Some(telepad: TelepadDeployable) =>
|
||||
log.debug(s"BeforeUnload: deconstructing telepad $telepad that was linked to router $vehicle ...")
|
||||
telepad.Active = false
|
||||
zone.LocalEvents ! LocalServiceMessage.Deployables(RemoverActor.ClearSpecific(List(telepad), zone))
|
||||
|
|
|
|||
188
common/src/main/scala/net/psforever/objects/avatar/Avatar.scala
Normal file
188
common/src/main/scala/net/psforever/objects/avatar/Avatar.scala
Normal file
|
|
@ -0,0 +1,188 @@
|
|||
package net.psforever.objects.avatar
|
||||
|
||||
import net.psforever.objects.definition.{AvatarDefinition, BasicDefinition}
|
||||
import net.psforever.objects.equipment.{EquipmentSize, EquipmentSlot}
|
||||
import net.psforever.objects.loadouts.{Loadout, SquadLoadout}
|
||||
import net.psforever.objects.{GlobalDefinitions, LockerContainer, LockerEquipment, OffhandEquipmentSlot}
|
||||
import net.psforever.types._
|
||||
import org.joda.time.{LocalDateTime, Period}
|
||||
import scala.collection.immutable.Seq
|
||||
import scala.concurrent.duration.{FiniteDuration, _}
|
||||
|
||||
object Avatar {
|
||||
val purchaseCooldowns: Map[BasicDefinition, FiniteDuration] = Map(
|
||||
GlobalDefinitions.ams -> 5.minutes,
|
||||
GlobalDefinitions.ant -> 5.minutes,
|
||||
GlobalDefinitions.apc_nc -> 5.minutes,
|
||||
GlobalDefinitions.apc_tr -> 5.minutes,
|
||||
GlobalDefinitions.apc_vs -> 5.minutes,
|
||||
GlobalDefinitions.aurora -> 5.minutes,
|
||||
GlobalDefinitions.battlewagon -> 5.minutes,
|
||||
GlobalDefinitions.dropship -> 5.minutes,
|
||||
GlobalDefinitions.flail -> 5.minutes,
|
||||
GlobalDefinitions.fury -> 5.minutes,
|
||||
GlobalDefinitions.galaxy_gunship -> 10.minutes,
|
||||
GlobalDefinitions.lodestar -> 5.minutes,
|
||||
GlobalDefinitions.liberator -> 5.minutes,
|
||||
GlobalDefinitions.lightgunship -> 5.minutes,
|
||||
GlobalDefinitions.lightning -> 5.minutes,
|
||||
GlobalDefinitions.magrider -> 5.minutes,
|
||||
GlobalDefinitions.mediumtransport -> 5.minutes,
|
||||
GlobalDefinitions.mosquito -> 5.minutes,
|
||||
GlobalDefinitions.phantasm -> 5.minutes,
|
||||
GlobalDefinitions.prowler -> 5.minutes,
|
||||
GlobalDefinitions.quadassault -> 5.minutes,
|
||||
GlobalDefinitions.quadstealth -> 5.minutes,
|
||||
GlobalDefinitions.router -> 5.minutes,
|
||||
GlobalDefinitions.switchblade -> 5.minutes,
|
||||
GlobalDefinitions.skyguard -> 5.minutes,
|
||||
GlobalDefinitions.threemanheavybuggy -> 5.minutes,
|
||||
GlobalDefinitions.thunderer -> 5.minutes,
|
||||
GlobalDefinitions.two_man_assault_buggy -> 5.minutes,
|
||||
GlobalDefinitions.twomanhoverbuggy -> 5.minutes,
|
||||
GlobalDefinitions.twomanheavybuggy -> 5.minutes,
|
||||
GlobalDefinitions.vanguard -> 5.minutes,
|
||||
GlobalDefinitions.vulture -> 5.minutes,
|
||||
GlobalDefinitions.wasp -> 5.minutes,
|
||||
GlobalDefinitions.flamethrower -> 3.minutes,
|
||||
GlobalDefinitions.VSMAX -> 5.minutes,
|
||||
GlobalDefinitions.NCMAX -> 5.minutes,
|
||||
GlobalDefinitions.TRMAX -> 5.minutes,
|
||||
// TODO weapon based cooldown
|
||||
GlobalDefinitions.nchev_sparrow -> 5.minutes,
|
||||
GlobalDefinitions.nchev_falcon -> 5.minutes,
|
||||
GlobalDefinitions.nchev_scattercannon -> 5.minutes,
|
||||
GlobalDefinitions.vshev_comet -> 5.minutes,
|
||||
GlobalDefinitions.vshev_quasar -> 5.minutes,
|
||||
GlobalDefinitions.vshev_starfire -> 5.minutes,
|
||||
GlobalDefinitions.trhev_burster -> 5.minutes,
|
||||
GlobalDefinitions.trhev_dualcycler -> 5.minutes,
|
||||
GlobalDefinitions.trhev_pounder -> 5.minutes
|
||||
)
|
||||
|
||||
val useCooldowns: Map[BasicDefinition, FiniteDuration] = Map(
|
||||
GlobalDefinitions.medkit -> 5.seconds,
|
||||
GlobalDefinitions.super_armorkit -> 20.minutes,
|
||||
GlobalDefinitions.super_medkit -> 20.minutes,
|
||||
GlobalDefinitions.super_staminakit -> 20.minutes
|
||||
)
|
||||
}
|
||||
|
||||
case class Avatar(
|
||||
/** unique identifier corresponding to a database table row index */
|
||||
id: Int,
|
||||
name: String,
|
||||
faction: PlanetSideEmpire.Value,
|
||||
sex: CharacterGender.Value,
|
||||
head: Int,
|
||||
voice: CharacterVoice.Value,
|
||||
bep: Long = 0,
|
||||
cep: Long = 0,
|
||||
stamina: Int = 100,
|
||||
fatigued: Boolean = false,
|
||||
cosmetics: Option[Set[Cosmetic]] = None,
|
||||
certifications: Set[Certification] = Set(),
|
||||
loadouts: Seq[Option[Loadout]] = Seq.fill(15)(None),
|
||||
squadLoadouts: Seq[Option[SquadLoadout]] = Seq.fill(10)(None),
|
||||
implants: Seq[Option[Implant]] = Seq(None, None, None),
|
||||
locker: LockerContainer = new LockerContainer(), // TODO var bad
|
||||
deployables: DeployableToolbox = new DeployableToolbox(), // TODO var bad
|
||||
lookingForSquad: Boolean = false,
|
||||
var vehicle: Option[PlanetSideGUID] = None, // TODO var bad
|
||||
firstTimeEvents: Set[String] =
|
||||
FirstTimeEvents.Maps ++ FirstTimeEvents.Monoliths ++
|
||||
FirstTimeEvents.Standard.All ++ FirstTimeEvents.Cavern.All ++
|
||||
FirstTimeEvents.TR.All ++ FirstTimeEvents.NC.All ++ FirstTimeEvents.VS.All ++
|
||||
FirstTimeEvents.Generic,
|
||||
/** Timestamps of when a vehicle or equipment was last purchased */
|
||||
purchaseTimes: Map[String, LocalDateTime] = Map(),
|
||||
/** Timestamps of when a vehicle or equipment was last purchased */
|
||||
useTimes: Map[String, LocalDateTime] = Map()
|
||||
) {
|
||||
assert(bep >= 0)
|
||||
assert(cep >= 0)
|
||||
|
||||
val br: BattleRank = BattleRank.withExperience(bep)
|
||||
val cr: CommandRank = CommandRank.withExperience(cep)
|
||||
|
||||
private def cooldown(
|
||||
times: Map[String, LocalDateTime],
|
||||
cooldowns: Map[BasicDefinition, FiniteDuration],
|
||||
definition: BasicDefinition
|
||||
): Option[Period] = {
|
||||
times.get(definition.Name) match {
|
||||
case Some(purchaseTime) =>
|
||||
val secondsSincePurchase = new Period(purchaseTime, LocalDateTime.now()).toStandardSeconds.getSeconds
|
||||
cooldowns.get(definition) match {
|
||||
case Some(cooldown) if (cooldown.toSeconds - secondsSincePurchase) > 0 =>
|
||||
Some(Period.seconds(cooldown.toSeconds.toInt - secondsSincePurchase))
|
||||
case _ => None
|
||||
}
|
||||
case None =>
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns the remaining purchase cooldown or None if an object is not on cooldown */
|
||||
def purchaseCooldown(definition: BasicDefinition): Option[Period] = {
|
||||
cooldown(purchaseTimes, Avatar.purchaseCooldowns, definition)
|
||||
}
|
||||
|
||||
/** Returns the remaining use cooldown or None if an object is not on cooldown */
|
||||
def useCooldown(definition: BasicDefinition): Option[Period] = {
|
||||
cooldown(useTimes, Avatar.useCooldowns, definition)
|
||||
}
|
||||
|
||||
def fifthSlot(): EquipmentSlot = {
|
||||
new OffhandEquipmentSlot(EquipmentSize.Inventory) {
|
||||
val obj = new LockerEquipment(locker)
|
||||
Equipment = obj
|
||||
}
|
||||
}
|
||||
|
||||
val definition: AvatarDefinition = GlobalDefinitions.avatar
|
||||
|
||||
/** Returns numerical value from 0-3 that is the hacking skill level representation in packets */
|
||||
def hackingSkillLevel(): Int = {
|
||||
if (
|
||||
certifications.contains(Certification.ExpertHacking) || certifications.contains(Certification.ElectronicsExpert)
|
||||
) {
|
||||
3
|
||||
} else if (certifications.contains(Certification.AdvancedHacking)) {
|
||||
2
|
||||
} else if (certifications.contains(Certification.Hacking)) {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
/** The maximum stamina amount */
|
||||
val maxStamina: Int = 100
|
||||
|
||||
/** Return true if the stamina is at the maximum amount */
|
||||
def staminaFull: Boolean = {
|
||||
stamina == maxStamina
|
||||
}
|
||||
|
||||
def canEqual(other: Any): Boolean = other.isInstanceOf[Avatar]
|
||||
|
||||
override def equals(other: Any): Boolean =
|
||||
other match {
|
||||
case that: Avatar =>
|
||||
(that canEqual this) &&
|
||||
id == that.id
|
||||
case _ =>
|
||||
false
|
||||
}
|
||||
|
||||
/** Avatar assertions
|
||||
* These protect against programming errors by asserting avatar properties have correct values
|
||||
* They may or may not be disabled for live applications
|
||||
*/
|
||||
assert(stamina <= maxStamina && stamina >= 0)
|
||||
assert(head >= 0) // TODO what's the max value?
|
||||
assert(implants.length <= 3)
|
||||
assert(implants.flatten.map(_.definition.implantType).distinct.length == implants.flatten.length)
|
||||
assert(br.implantSlots >= implants.flatten.length)
|
||||
}
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
package net.psforever.objects.avatar
|
||||
|
||||
import enumeratum.values.{IntEnum, IntEnumEntry}
|
||||
import net.psforever.packet.game.objectcreate.UniformStyle
|
||||
|
||||
/** Battle ranks and their starting experience values
|
||||
* Source: http://wiki.psforever.net/wiki/Battle_Rank
|
||||
*/
|
||||
sealed abstract class BattleRank(val value: Int, val experience: Long) extends IntEnumEntry {
|
||||
def implantSlots: Int = {
|
||||
if (this.value >= BattleRank.BR18.value) {
|
||||
3
|
||||
} else if (this.value >= BattleRank.BR12.value) {
|
||||
2
|
||||
} else if (this.value >= BattleRank.BR6.value) {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
def uniformStyle: UniformStyle.Value = {
|
||||
if (this.value >= BattleRank.BR25.value) {
|
||||
UniformStyle.ThirdUpgrade
|
||||
} else if (this.value >= BattleRank.BR14.value) {
|
||||
UniformStyle.SecondUpgrade
|
||||
} else if (this.value >= BattleRank.BR7.value) {
|
||||
UniformStyle.FirstUpgrade
|
||||
} else {
|
||||
UniformStyle.Normal
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
case object BattleRank extends IntEnum[BattleRank] {
|
||||
|
||||
case object BR1 extends BattleRank(value = 1, experience = 0L)
|
||||
|
||||
case object BR2 extends BattleRank(value = 2, experience = 1000L)
|
||||
|
||||
case object BR3 extends BattleRank(value = 3, experience = 3000L)
|
||||
|
||||
case object BR4 extends BattleRank(value = 4, experience = 7500L)
|
||||
|
||||
case object BR5 extends BattleRank(value = 5, experience = 15000L)
|
||||
|
||||
case object BR6 extends BattleRank(value = 6, experience = 30000L)
|
||||
|
||||
case object BR7 extends BattleRank(value = 7, experience = 45000L)
|
||||
|
||||
case object BR8 extends BattleRank(value = 8, experience = 67500L)
|
||||
|
||||
case object BR9 extends BattleRank(value = 9, experience = 101250L)
|
||||
|
||||
case object BR10 extends BattleRank(value = 10, experience = 126563L)
|
||||
|
||||
case object BR11 extends BattleRank(value = 11, experience = 158203L)
|
||||
|
||||
case object BR12 extends BattleRank(value = 12, experience = 197754L)
|
||||
|
||||
case object BR13 extends BattleRank(value = 13, experience = 247192L)
|
||||
|
||||
case object BR14 extends BattleRank(value = 14, experience = 308990L)
|
||||
|
||||
case object BR15 extends BattleRank(value = 15, experience = 386239L)
|
||||
|
||||
case object BR16 extends BattleRank(value = 16, experience = 482798L)
|
||||
|
||||
case object BR17 extends BattleRank(value = 17, experience = 603497L)
|
||||
|
||||
case object BR18 extends BattleRank(value = 18, experience = 754371L)
|
||||
|
||||
case object BR19 extends BattleRank(value = 19, experience = 942964L)
|
||||
|
||||
case object BR20 extends BattleRank(value = 20, experience = 1178705L)
|
||||
|
||||
case object BR21 extends BattleRank(value = 21, experience = 1438020L)
|
||||
|
||||
case object BR22 extends BattleRank(value = 22, experience = 1710301L)
|
||||
|
||||
case object BR23 extends BattleRank(value = 23, experience = 1988027L)
|
||||
|
||||
case object BR24 extends BattleRank(value = 24, experience = 2286231L)
|
||||
|
||||
case object BR25 extends BattleRank(value = 25, experience = 2583441L)
|
||||
|
||||
val values: IndexedSeq[BattleRank] = findValues
|
||||
|
||||
/** Find BattleRank variant for given experience value */
|
||||
def withExperience(experience: Long): BattleRank = {
|
||||
withExperienceOpt(experience).get
|
||||
}
|
||||
|
||||
/** Find BattleRank variant for given experience value */
|
||||
def withExperienceOpt(experience: Long): Option[BattleRank] = {
|
||||
values.find(br =>
|
||||
this.withValueOpt(br.value + 1) match {
|
||||
case Some(nextBr) =>
|
||||
experience >= br.experience && experience < nextBr.experience
|
||||
case None =>
|
||||
experience >= br.experience
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,224 +1,219 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package net.psforever.objects.avatar
|
||||
|
||||
import net.psforever.types.CertificationType
|
||||
import enumeratum.values.{IntEnum, IntEnumEntry}
|
||||
import net.psforever.packet.PacketHelpers
|
||||
import scodec.Codec
|
||||
import scodec.codecs._
|
||||
|
||||
import scala.collection.mutable
|
||||
import scala.annotation.tailrec
|
||||
|
||||
object Certification {
|
||||
object Dependencies {
|
||||
sealed abstract class Certification(
|
||||
val value: Int,
|
||||
/** Name used in packets */
|
||||
val name: String,
|
||||
/** Certification point cost */
|
||||
val cost: Int,
|
||||
val requires: Set[Certification] = Set(),
|
||||
val replaces: Set[Certification] = Set()
|
||||
) extends IntEnumEntry
|
||||
|
||||
/**
|
||||
* Find the certifications that are immediately dependent on the target certification.
|
||||
* (For `A`, find all `B` that are `B ⇒ A`.)
|
||||
* @param certification the target certification
|
||||
* @return all connected certifications
|
||||
*/
|
||||
def From(certification: CertificationType.Value): Set[CertificationType.Value] = dependencies(certification).toSet
|
||||
case object Certification extends IntEnum[Certification] {
|
||||
|
||||
/**
|
||||
* Find all certifications that are dependent on the target certification.
|
||||
* (For `A`, find all `B...C` where `C ⇒ B` and `B ⇒ A`.)
|
||||
* @param certification the target certification
|
||||
* @return all connected certifications
|
||||
*/
|
||||
def FromAll(certification: CertificationType.Value): Set[CertificationType.Value] = {
|
||||
var available: List[CertificationType.Value] = List(certification)
|
||||
var allocated: mutable.ListBuffer[CertificationType.Value] = mutable.ListBuffer.empty[CertificationType.Value]
|
||||
do {
|
||||
available = available.flatMap(cert => dependencies(cert))
|
||||
allocated ++= available
|
||||
} while (available.nonEmpty)
|
||||
allocated.toSet
|
||||
}
|
||||
case object StandardAssault extends Certification(value = 0, name = "standard_assault", cost = 0)
|
||||
|
||||
/**
|
||||
* Find the certifications that are immediate dependencies of the target certification.
|
||||
* (For `A`, find all `B` where `A ⇒ B`.)
|
||||
* @param certification the target certification
|
||||
* @return all connected certifications
|
||||
*/
|
||||
def For(certification: CertificationType.Value): Set[CertificationType.Value] = {
|
||||
(for {
|
||||
(cert, certs) <- dependencies
|
||||
if certs contains certification
|
||||
} yield cert).toSet
|
||||
}
|
||||
case object MediumAssault extends Certification(value = 1, name = "medium_assault", cost = 2)
|
||||
|
||||
/**
|
||||
* Find all certifications that are dependencies of the target certification.
|
||||
* (For `A`, find all `B...C` where `A ⇒ B` and `B ⇒ C`.)
|
||||
* @param certification the target certification
|
||||
* @return all connected certifications
|
||||
*/
|
||||
def ForAll(certification: CertificationType.Value): Set[CertificationType.Value] = {
|
||||
var available: List[CertificationType.Value] = List(certification)
|
||||
var allocated: mutable.ListBuffer[CertificationType.Value] = mutable.ListBuffer.empty[CertificationType.Value]
|
||||
do {
|
||||
available = available.flatMap {
|
||||
For
|
||||
}
|
||||
allocated ++= available
|
||||
} while (available.nonEmpty)
|
||||
allocated.toSet
|
||||
}
|
||||
case object HeavyAssault
|
||||
extends Certification(value = 2, name = "heavy_assault", cost = 4, requires = Set(MediumAssault))
|
||||
|
||||
import CertificationType._
|
||||
case object SpecialAssault
|
||||
extends Certification(value = 3, name = "special_assault", cost = 3, requires = Set(MediumAssault))
|
||||
|
||||
/**
|
||||
* Find all certifications that are related but mutually exclusive with the target certification.
|
||||
* (For `A`, find all `B` that `B ⊃ A` but `A XOR B`.)
|
||||
* @param certification the target certification
|
||||
* @return all connected certifications
|
||||
*/
|
||||
def Like(certification: CertificationType.Value): Set[CertificationType.Value] =
|
||||
certification match {
|
||||
case AssaultBuggy =>
|
||||
Set(Harasser)
|
||||
case LightScout =>
|
||||
Set(AirCavalryScout, AssaultBuggy, Harasser)
|
||||
case UniMAX =>
|
||||
Set(AAMAX, AIMAX, AVMAX)
|
||||
case AdvancedEngineering =>
|
||||
Set(AssaultEngineering, FortificationEngineering)
|
||||
case ElectronicsExpert =>
|
||||
Set(DataCorruption, ExpertHacking)
|
||||
case _ =>
|
||||
Set.empty[CertificationType.Value]
|
||||
}
|
||||
case object AntiVehicular
|
||||
extends Certification(value = 4, name = "anti_vehicular", cost = 3, requires = Set(MediumAssault))
|
||||
|
||||
private val dependencies: Map[CertificationType.Value, List[CertificationType.Value]] = Map(
|
||||
StandardAssault -> List(),
|
||||
AgileExoSuit -> List(),
|
||||
ReinforcedExoSuit -> List(),
|
||||
InfiltrationSuit -> List(Phantasm),
|
||||
AIMAX -> List(),
|
||||
AVMAX -> List(),
|
||||
AAMAX -> List(),
|
||||
UniMAX -> List(),
|
||||
StandardAssault -> List(),
|
||||
MediumAssault -> List(AntiVehicular, HeavyAssault, Sniping, SpecialAssault),
|
||||
AntiVehicular -> List(),
|
||||
HeavyAssault -> List(),
|
||||
Sniping -> List(),
|
||||
SpecialAssault -> List(EliteAssault),
|
||||
EliteAssault -> List(),
|
||||
ATV -> List(Switchblade),
|
||||
Switchblade -> List(),
|
||||
Harasser -> List(),
|
||||
AssaultBuggy -> List(),
|
||||
LightScout -> List(AirCavalryAssault),
|
||||
GroundSupport -> List(),
|
||||
GroundTransport -> List(),
|
||||
ArmoredAssault1 -> List(ArmoredAssault2),
|
||||
ArmoredAssault2 -> List(BattleFrameRobotics, Flail),
|
||||
Flail -> List(),
|
||||
AirCavalryScout -> List(AirCavalryAssault),
|
||||
AirCavalryAssault -> List(AirCavalryInterceptor),
|
||||
AirCavalryInterceptor -> List(),
|
||||
AirSupport -> List(GalaxyGunship),
|
||||
GalaxyGunship -> List(),
|
||||
Phantasm -> List(),
|
||||
BattleFrameRobotics -> List(BFRAntiInfantry, BFRAntiAircraft),
|
||||
BFRAntiInfantry -> List(),
|
||||
BFRAntiAircraft -> List(),
|
||||
Medical -> List(AdvancedMedical),
|
||||
AdvancedMedical -> List(),
|
||||
Engineering -> List(CombatEngineering),
|
||||
CombatEngineering -> List(AdvancedEngineering, AssaultEngineering, FortificationEngineering),
|
||||
AdvancedEngineering -> List(),
|
||||
AssaultEngineering -> List(),
|
||||
FortificationEngineering -> List(),
|
||||
Hacking -> List(AdvancedHacking),
|
||||
AdvancedHacking -> List(DataCorruption, ElectronicsExpert, ExpertHacking),
|
||||
DataCorruption -> List(),
|
||||
ElectronicsExpert -> List(),
|
||||
ExpertHacking -> List()
|
||||
case object Sniping extends Certification(value = 5, name = "sniper", cost = 3, requires = Set(MediumAssault))
|
||||
|
||||
case object EliteAssault
|
||||
extends Certification(value = 6, name = "special_assault_2", cost = 1, requires = Set(SpecialAssault))
|
||||
|
||||
case object AirCavalryScout extends Certification(value = 7, name = "air_cavalry_scout", cost = 3)
|
||||
|
||||
case object AirCavalryInterceptor
|
||||
extends Certification(value = 8, name = "air_cavalry_interceptor", cost = 2, requires = Set(AirCavalryScout))
|
||||
|
||||
case object AirCavalryAssault
|
||||
extends Certification(
|
||||
value = 9,
|
||||
name = "air_cavalry_assault",
|
||||
cost = 2,
|
||||
requires = Set(AirCavalryScout)
|
||||
)
|
||||
|
||||
case object AirSupport extends Certification(value = 10, name = "air_support", cost = 3)
|
||||
|
||||
case object ATV extends Certification(value = 11, name = "quad_all", cost = 1)
|
||||
|
||||
case object LightScout
|
||||
extends Certification(
|
||||
value = 12,
|
||||
name = "light_scout",
|
||||
cost = 5,
|
||||
replaces = Set(AirCavalryScout, AssaultBuggy, Harasser)
|
||||
)
|
||||
|
||||
case object AssaultBuggy extends Certification(value = 13, name = "assault_buggy", cost = 3, replaces = Set(Harasser))
|
||||
|
||||
case object ArmoredAssault1 extends Certification(value = 14, name = "armored_assault1", cost = 2)
|
||||
|
||||
case object ArmoredAssault2
|
||||
extends Certification(value = 15, name = "armored_assault2", cost = 3, requires = Set(ArmoredAssault1))
|
||||
|
||||
case object GroundTransport extends Certification(value = 16, name = "ground_transport", cost = 2)
|
||||
|
||||
case object GroundSupport extends Certification(value = 17, name = "ground_support", cost = 2)
|
||||
|
||||
case object BattleFrameRobotics
|
||||
extends Certification(value = 18, name = "TODO2", cost = 4, requires = Set(ArmoredAssault2)) // TODO name
|
||||
|
||||
case object Flail extends Certification(value = 19, name = "flail", cost = 1, requires = Set(ArmoredAssault2))
|
||||
|
||||
case object Switchblade extends Certification(value = 20, name = "switchblade", cost = 1, requires = Set(ATV))
|
||||
|
||||
case object Harasser extends Certification(value = 21, name = "harasser", cost = 1)
|
||||
|
||||
case object Phantasm extends Certification(value = 22, name = "phantasm", cost = 3, requires = Set(InfiltrationSuit))
|
||||
|
||||
case object GalaxyGunship extends Certification(value = 23, name = "gunship", cost = 2, requires = Set(AirSupport))
|
||||
|
||||
case object BFRAntiAircraft
|
||||
extends Certification(value = 24, name = "TODO3", cost = 1, requires = Set(BattleFrameRobotics))
|
||||
|
||||
case object BFRAntiInfantry
|
||||
extends Certification(value = 25, name = "TODO4", cost = 1, requires = Set(BattleFrameRobotics)) // TODO name
|
||||
|
||||
case object StandardExoSuit extends Certification(value = 26, name = "TODO5", cost = 0)
|
||||
|
||||
case object AgileExoSuit extends Certification(value = 27, name = "agile_armor", cost = 0)
|
||||
|
||||
case object ReinforcedExoSuit extends Certification(value = 28, name = "reinforced_armor", cost = 3)
|
||||
|
||||
case object InfiltrationSuit extends Certification(value = 29, name = "infiltration_suit", cost = 2)
|
||||
|
||||
case object AAMAX extends Certification(value = 30, name = "max_anti_aircraft", cost = 2)
|
||||
|
||||
case object AIMAX extends Certification(value = 31, name = "max_anti_personnel", cost = 3)
|
||||
|
||||
case object AVMAX extends Certification(value = 32, name = "max_anti_vehicular", cost = 3)
|
||||
|
||||
case object UniMAX extends Certification(value = 33, name = "max_all", cost = 6, replaces = Set(AAMAX, AIMAX, AVMAX))
|
||||
|
||||
case object Medical extends Certification(value = 34, name = "Medical", cost = 3)
|
||||
|
||||
case object AdvancedMedical
|
||||
extends Certification(value = 35, name = "advanced_medical", cost = 2, requires = Set(Medical))
|
||||
|
||||
case object Hacking extends Certification(value = 36, name = "Hacking", cost = 3)
|
||||
|
||||
case object AdvancedHacking
|
||||
extends Certification(value = 37, name = "advanced_hacking", cost = 2, requires = Set(Hacking))
|
||||
|
||||
case object ExpertHacking
|
||||
extends Certification(value = 38, name = "expert_hacking", cost = 2, requires = Set(AdvancedHacking))
|
||||
|
||||
case object DataCorruption
|
||||
extends Certification(value = 39, name = "virus_hacking", cost = 3, requires = Set(AdvancedHacking))
|
||||
|
||||
case object ElectronicsExpert
|
||||
extends Certification(
|
||||
value = 40,
|
||||
name = "electronics_expert",
|
||||
cost = 4,
|
||||
requires = Set(AdvancedHacking),
|
||||
replaces = Set(DataCorruption, ExpertHacking)
|
||||
)
|
||||
|
||||
case object Engineering extends Certification(value = 41, name = "Repair", cost = 3)
|
||||
|
||||
case object CombatEngineering
|
||||
extends Certification(value = 42, name = "combat_engineering", cost = 2, requires = Set(Engineering))
|
||||
|
||||
case object FortificationEngineering
|
||||
extends Certification(value = 43, name = "ce_defense", cost = 3, requires = Set(CombatEngineering))
|
||||
|
||||
case object AssaultEngineering
|
||||
extends Certification(value = 44, name = "ce_offense", cost = 3, requires = Set(CombatEngineering))
|
||||
|
||||
case object AdvancedEngineering
|
||||
extends Certification(
|
||||
value = 45,
|
||||
name = "ce_advanced",
|
||||
cost = 5,
|
||||
requires = Set(CombatEngineering),
|
||||
replaces = Set(AssaultEngineering, FortificationEngineering)
|
||||
)
|
||||
|
||||
// https://github.com/lloydmeta/enumeratum/issues/86
|
||||
lazy val values: IndexedSeq[Certification] = findValues
|
||||
|
||||
implicit val codec: Codec[Certification] = PacketHelpers.createIntEnumCodec(this, uint8L)
|
||||
|
||||
/**
|
||||
* Certifications are often stored, in object form, as a 46-member collection.
|
||||
* Encode a subset of certification values for packet form.
|
||||
*
|
||||
* @return the certifications, as a single value
|
||||
*/
|
||||
def toEncodedLong(certs: Set[Certification]): Long = {
|
||||
certs
|
||||
.map { cert => math.pow(2, cert.value).toLong }
|
||||
.foldLeft(0L)(_ + _)
|
||||
}
|
||||
|
||||
/**
|
||||
* Certifications are often stored, in packet form, as an encoded little-endian `46u` value.
|
||||
* Decode a representative value into a subset of certification values.
|
||||
*
|
||||
* @see `ChangeSquadMemberRequirementsCertifications`
|
||||
* @see `changeSquadMemberRequirementsCertificationsCodec`
|
||||
* @see `fromEncodedLong(Long, Iterable[Long], Set[CertificationType.Value])`
|
||||
* @param certs the certifications, as a single value
|
||||
* @return the certifications, as a sequence of values
|
||||
*/
|
||||
def fromEncodedLong(certs: Long): Set[Certification] = {
|
||||
recursiveFromEncodedLong(
|
||||
certs,
|
||||
Certification.values.map { cert => math.pow(2, cert.value).toLong }.sorted
|
||||
)
|
||||
}
|
||||
|
||||
object Cost {
|
||||
|
||||
/**
|
||||
* For a certification, get its point cost.
|
||||
* @param certification the certification
|
||||
* @return the cost
|
||||
*/
|
||||
def Of(certification: CertificationType.Value): Int = points(certification)
|
||||
|
||||
/**
|
||||
* For a list of certifications, find the point cost of all unique certifications.
|
||||
* @see `Of(Set)`
|
||||
* @param certifications the certification list
|
||||
* @return the total cost
|
||||
*/
|
||||
def Of(certifications: List[CertificationType.Value]): Int = Of(certifications.toSet)
|
||||
|
||||
/**
|
||||
* For a set of certifications, find the point cost of all certifications.
|
||||
* @see `OfAll(List)`
|
||||
* @param certifications the certification list
|
||||
* @return the total cost
|
||||
*/
|
||||
def Of(certifications: Set[CertificationType.Value]): Int = OfAll(certifications.toList)
|
||||
|
||||
/**
|
||||
* For a list of certifications, find the point cost of all certifications, counting any duplicates.
|
||||
* @param certifications the certification list
|
||||
* @return the total cost
|
||||
*/
|
||||
def OfAll(certifications: List[CertificationType.Value]): Int = {
|
||||
certifications map points sum
|
||||
/**
|
||||
* Certifications are often stored, in packet form, as an encoded little-endian `46u` value.
|
||||
* Decode a representative value into a subset of certification values
|
||||
* by repeatedly finding the partition point of values less than a specific one,
|
||||
* providing for both the next lowest value (to subtract) and an index (of a certification).
|
||||
*
|
||||
* @see `ChangeSquadMemberRequirementsCertifications`
|
||||
* @see `changeSquadMemberRequirementsCertificationsCodec`
|
||||
* @see `fromEncodedLong(Long)`
|
||||
* @param certs the certifications, as a single value
|
||||
* @param splitList the available values to partition
|
||||
* @param out the accumulating certification values;
|
||||
* defaults to an empty set
|
||||
* @return the certifications, as a sequence of values
|
||||
*/
|
||||
@tailrec
|
||||
private def recursiveFromEncodedLong(
|
||||
certs: Long,
|
||||
splitList: Iterable[Long],
|
||||
out: Set[Certification] = Set.empty
|
||||
): Set[Certification] = {
|
||||
if (certs == 0 || splitList.isEmpty) {
|
||||
out
|
||||
} else {
|
||||
val (less, _) = splitList.partition(_ <= certs)
|
||||
recursiveFromEncodedLong(certs - less.last, less, out ++ Set(Certification.withValue(less.size - 1)))
|
||||
}
|
||||
|
||||
import CertificationType._
|
||||
private val points: Map[CertificationType.Value, Int] = Map(
|
||||
StandardExoSuit -> 0,
|
||||
AgileExoSuit -> 0,
|
||||
ReinforcedExoSuit -> 3,
|
||||
InfiltrationSuit -> 2,
|
||||
AAMAX -> 2,
|
||||
AIMAX -> 3,
|
||||
AVMAX -> 3,
|
||||
UniMAX -> 6,
|
||||
StandardAssault -> 0,
|
||||
MediumAssault -> 2,
|
||||
AntiVehicular -> 3,
|
||||
HeavyAssault -> 4,
|
||||
Sniping -> 3,
|
||||
SpecialAssault -> 3,
|
||||
EliteAssault -> 1,
|
||||
ATV -> 1,
|
||||
Switchblade -> 1,
|
||||
Harasser -> 1,
|
||||
AssaultBuggy -> 3,
|
||||
LightScout -> 5,
|
||||
GroundSupport -> 2,
|
||||
GroundTransport -> 2,
|
||||
ArmoredAssault1 -> 2,
|
||||
ArmoredAssault2 -> 3,
|
||||
Flail -> 1,
|
||||
AirCavalryScout -> 3,
|
||||
AirCavalryAssault -> 2,
|
||||
AirCavalryInterceptor -> 2,
|
||||
AirSupport -> 3,
|
||||
GalaxyGunship -> 2,
|
||||
Phantasm -> 3,
|
||||
BattleFrameRobotics -> 4,
|
||||
BFRAntiInfantry -> 1,
|
||||
BFRAntiAircraft -> 1,
|
||||
Medical -> 3,
|
||||
AdvancedMedical -> 2,
|
||||
Engineering -> 3,
|
||||
CombatEngineering -> 2,
|
||||
AdvancedEngineering -> 5,
|
||||
AssaultEngineering -> 3,
|
||||
FortificationEngineering -> 3,
|
||||
Hacking -> 3,
|
||||
AdvancedHacking -> 2,
|
||||
DataCorruption -> 3,
|
||||
ElectronicsExpert -> 4,
|
||||
ExpertHacking -> 2
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
package net.psforever.objects.avatar
|
||||
|
||||
import enumeratum.values.{IntEnum, IntEnumEntry}
|
||||
|
||||
/** Command ranks and their starting experience values */
|
||||
sealed abstract class CommandRank(val value: Int, val experience: Long) extends IntEnumEntry
|
||||
|
||||
case object CommandRank extends IntEnum[CommandRank] {
|
||||
|
||||
case object CR0 extends CommandRank(value = 0, experience = 0L)
|
||||
|
||||
case object CR1 extends CommandRank(value = 1, experience = 10000L)
|
||||
|
||||
case object CR2 extends CommandRank(value = 2, experience = 50000L)
|
||||
|
||||
case object CR3 extends CommandRank(value = 3, experience = 150000L)
|
||||
|
||||
case object CR4 extends CommandRank(value = 4, experience = 300000L)
|
||||
|
||||
case object CR5 extends CommandRank(value = 5, experience = 600000L)
|
||||
|
||||
val values: IndexedSeq[CommandRank] = findValues
|
||||
|
||||
/** Find CommandRank variant for given experience value */
|
||||
def withExperience(experience: Long): CommandRank = {
|
||||
withExperienceOpt(experience).get
|
||||
}
|
||||
|
||||
/** Find CommandRank variant for given experience value */
|
||||
def withExperienceOpt(experience: Long): Option[CommandRank] = {
|
||||
values.find(cr =>
|
||||
this.withValueOpt(cr.value + 1) match {
|
||||
case Some(nextCr) =>
|
||||
experience >= cr.experience && experience < nextCr.experience
|
||||
case None =>
|
||||
experience >= cr.experience
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -26,7 +26,7 @@ class CorpseControl(player: Player) extends Actor with ContainableBehavior {
|
|||
obj.Find(item) match {
|
||||
case Some(slot) =>
|
||||
obj.Zone.AvatarEvents ! AvatarServiceMessage(
|
||||
player.Zone.Id,
|
||||
player.Zone.id,
|
||||
AvatarAction.SendResponse(Service.defaultPlayerGUID, ObjectAttachMessage(obj.GUID, item.GUID, slot))
|
||||
)
|
||||
case None => ;
|
||||
|
|
@ -40,7 +40,7 @@ class CorpseControl(player: Player) extends Actor with ContainableBehavior {
|
|||
val zone = obj.Zone
|
||||
val events = zone.AvatarEvents
|
||||
item.Faction = PlanetSideEmpire.NEUTRAL
|
||||
events ! AvatarServiceMessage(zone.Id, AvatarAction.ObjectDelete(Service.defaultPlayerGUID, item.GUID))
|
||||
events ! AvatarServiceMessage(zone.id, AvatarAction.ObjectDelete(Service.defaultPlayerGUID, item.GUID))
|
||||
}
|
||||
|
||||
def PutItemInSlotCallback(item: Equipment, slot: Int): Unit = {
|
||||
|
|
@ -49,7 +49,7 @@ class CorpseControl(player: Player) extends Actor with ContainableBehavior {
|
|||
val events = zone.AvatarEvents
|
||||
val definition = item.Definition
|
||||
events ! AvatarServiceMessage(
|
||||
zone.Id,
|
||||
zone.id,
|
||||
AvatarAction.SendResponse(
|
||||
Service.defaultPlayerGUID,
|
||||
ObjectCreateDetailedMessage(
|
||||
|
|
@ -66,7 +66,7 @@ class CorpseControl(player: Player) extends Actor with ContainableBehavior {
|
|||
val obj = ContainerObject
|
||||
val zone = obj.Zone
|
||||
zone.AvatarEvents ! AvatarServiceMessage(
|
||||
zone.Id,
|
||||
zone.id,
|
||||
AvatarAction.SendResponse(
|
||||
Service.defaultPlayerGUID,
|
||||
ObjectDetachMessage(obj.GUID, item.GUID, Vector3.Zero, 0f)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,66 @@
|
|||
package net.psforever.objects.avatar
|
||||
|
||||
import enumeratum.values.{IntEnum, IntEnumEntry}
|
||||
import scodec.{Attempt, Codec}
|
||||
import scodec.codecs.uint
|
||||
|
||||
/** Avatar cosmetic options */
|
||||
sealed abstract class Cosmetic(val value: Int) extends IntEnumEntry
|
||||
|
||||
case object Cosmetic extends IntEnum[Cosmetic] {
|
||||
|
||||
case object BrimmedCap extends Cosmetic(value = 1)
|
||||
|
||||
case object Earpiece extends Cosmetic(value = 2)
|
||||
|
||||
case object Sunglasses extends Cosmetic(value = 4)
|
||||
|
||||
case object Beret extends Cosmetic(value = 8)
|
||||
|
||||
case object NoHelmet extends Cosmetic(value = 16)
|
||||
|
||||
val values: IndexedSeq[Cosmetic] = findValues
|
||||
|
||||
/** Get enum values from ObjectCreateMessage value */
|
||||
def valuesFromObjectCreateValue(value: Int): Set[Cosmetic] = {
|
||||
values.filter(c => (value & c.value) == c.value).toSet
|
||||
}
|
||||
|
||||
/** Serialize enum values to ObjectCreateMessage value */
|
||||
def valuesToObjectCreateValue(values: Set[Cosmetic]): Int = {
|
||||
values.foldLeft(0)(_ + _.value)
|
||||
}
|
||||
|
||||
/** Get enum values from AttributeMessage value
|
||||
* Attribute and object create messages use different indexes and the NoHelmet value becomes a YesHelmet value
|
||||
*/
|
||||
def valuesFromAttributeValue(value: Long): Set[Cosmetic] = {
|
||||
var values = Set[Cosmetic]()
|
||||
if (((value >> 4L) & 1L) == 1L) values += Cosmetic.Beret
|
||||
if (((value >> 3L) & 1L) == 1L) values += Cosmetic.Earpiece
|
||||
if (((value >> 2L) & 1L) == 1L) values += Cosmetic.Sunglasses
|
||||
if (((value >> 1L) & 1L) == 1L) values += Cosmetic.BrimmedCap
|
||||
if (((value >> 0L) & 1L) == 0L) values += Cosmetic.NoHelmet
|
||||
values
|
||||
}
|
||||
|
||||
/** Serialize enum values to AttributeMessage value
|
||||
* Attribute and object create messages use different indexes and the NoHelmet value becomes a YesHelmet value
|
||||
*/
|
||||
def valuesToAttributeValue(values: Set[Cosmetic]): Long = {
|
||||
values.foldLeft(1) {
|
||||
case (sum, NoHelmet) => sum - 1
|
||||
case (sum, BrimmedCap) => sum + 2
|
||||
case (sum, Sunglasses) => sum + 4
|
||||
case (sum, Earpiece) => sum + 8
|
||||
case (sum, Beret) => sum + 16
|
||||
}
|
||||
}
|
||||
|
||||
/** Codec for object create messages */
|
||||
implicit val codec: Codec[Set[Cosmetic]] = uint(5).exmap(
|
||||
value => Attempt.Successful(Cosmetic.valuesFromObjectCreateValue(value)),
|
||||
cosmetics => Attempt.Successful(Cosmetic.valuesToObjectCreateValue(cosmetics))
|
||||
)
|
||||
|
||||
}
|
||||
|
|
@ -3,8 +3,7 @@ package net.psforever.objects.avatar
|
|||
|
||||
import net.psforever.objects.PlanetSideGameObject
|
||||
import net.psforever.objects.ce.{Deployable, DeployableCategory, DeployedItem}
|
||||
import net.psforever.types.{CertificationType, PlanetSideGUID}
|
||||
|
||||
import net.psforever.types.PlanetSideGUID
|
||||
import scala.collection.mutable
|
||||
|
||||
/**
|
||||
|
|
@ -56,11 +55,12 @@ class DeployableToolbox {
|
|||
|
||||
/**
|
||||
* Set up the initial deployable counts by providing certification values to be used in category and unit selection.
|
||||
*
|
||||
* @param certifications a group of certifications for the initial values
|
||||
* @return `true`, if this is the first time and actual "initialization" is performed;
|
||||
* `false`, otherwise
|
||||
* `false`, otherwise
|
||||
*/
|
||||
def Initialize(certifications: Set[CertificationType.Value]): Boolean = {
|
||||
def Initialize(certifications: Set[Certification]): Boolean = {
|
||||
if (!initialized) {
|
||||
DeployableToolbox.Initialize(deployableCounts, categoryCounts, certifications)
|
||||
initialized = true
|
||||
|
|
@ -79,8 +79,8 @@ class DeployableToolbox {
|
|||
* the new certification should already have been added to this group
|
||||
*/
|
||||
def AddToDeployableQuantities(
|
||||
certification: CertificationType.Value,
|
||||
certificationSet: Set[CertificationType.Value]
|
||||
certification: Certification,
|
||||
certificationSet: Set[Certification]
|
||||
): Unit = {
|
||||
initialized = true
|
||||
DeployableToolbox.AddToDeployableQuantities(deployableCounts, categoryCounts, certification, certificationSet)
|
||||
|
|
@ -96,8 +96,8 @@ class DeployableToolbox {
|
|||
* the new certification should already have been excluded from this group
|
||||
*/
|
||||
def RemoveFromDeployableQuantities(
|
||||
certification: CertificationType.Value,
|
||||
certificationSet: Set[CertificationType.Value]
|
||||
certification: Certification,
|
||||
certificationSet: Set[Certification]
|
||||
): Unit = {
|
||||
initialized = true
|
||||
DeployableToolbox.RemoveFromDeployablesQuantities(deployableCounts, categoryCounts, certification, certificationSet)
|
||||
|
|
@ -137,8 +137,8 @@ class DeployableToolbox {
|
|||
* `false`, otherwise
|
||||
*/
|
||||
def Available(obj: DeployableToolbox.AcceptableDeployable): Boolean = {
|
||||
deployableCounts(DeployableToolbox.UnifiedType(obj.Definition.Item)).Available &&
|
||||
categoryCounts(obj.Definition.DeployCategory).Available
|
||||
deployableCounts(DeployableToolbox.UnifiedType(obj.Definition.Item)).Available() &&
|
||||
categoryCounts(obj.Definition.DeployCategory).Available()
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -331,8 +331,8 @@ class DeployableToolbox {
|
|||
|
||||
def UpdateUI(): List[(Int, Int, Int, Int)] = DeployedItem.values flatMap UpdateUIElement toList
|
||||
|
||||
def UpdateUI(entry: CertificationType.Value): List[(Int, Int, Int, Int)] = {
|
||||
import CertificationType._
|
||||
def UpdateUI(entry: Certification): List[(Int, Int, Int, Int)] = {
|
||||
import Certification._
|
||||
entry match {
|
||||
case AdvancedHacking =>
|
||||
UpdateUIElement(DeployedItem.sensor_shield)
|
||||
|
|
@ -371,7 +371,7 @@ class DeployableToolbox {
|
|||
}
|
||||
}
|
||||
|
||||
def UpdateUI(certifications: List[CertificationType.Value]): List[(Int, Int, Int, Int)] = {
|
||||
def UpdateUI(certifications: List[Certification]): List[(Int, Int, Int, Int)] = {
|
||||
certifications flatMap UpdateUI
|
||||
}
|
||||
|
||||
|
|
@ -404,7 +404,7 @@ class DeployableToolbox {
|
|||
deployableLists(category).clear()
|
||||
categoryCounts(category).Current = 0
|
||||
(Deployable.Category.Includes(category) map DeployableToolbox.UnifiedType toSet)
|
||||
.foreach({ item: DeployedItem.Value => deployableCounts(item).Current = 0 })
|
||||
.foreach({ item: DeployedItem.Value => deployableCounts(item).Current = 0 })
|
||||
out
|
||||
}
|
||||
|
||||
|
|
@ -481,9 +481,9 @@ object DeployableToolbox {
|
|||
private def Initialize(
|
||||
counts: Map[DeployedItem.Value, DeployableToolbox.Bin],
|
||||
categories: Map[DeployableCategory.Value, DeployableToolbox.Bin],
|
||||
certifications: Set[CertificationType.Value]
|
||||
certifications: Set[Certification]
|
||||
): Unit = {
|
||||
import CertificationType._
|
||||
import Certification._
|
||||
if (certifications.contains(AdvancedEngineering)) {
|
||||
counts(DeployedItem.boomer).Max = 25
|
||||
counts(DeployedItem.he_mine).Max = 25
|
||||
|
|
@ -548,7 +548,7 @@ object DeployableToolbox {
|
|||
counts(DeployedItem.sensor_shield).Max = 20
|
||||
}
|
||||
}
|
||||
if (certifications.contains(CertificationType.GroundSupport)) {
|
||||
if (certifications.contains(Certification.GroundSupport)) {
|
||||
counts(DeployedItem.router_telepad_deployable).Max = 1024
|
||||
categories(DeployableCategory.Telepads).Max = 1024
|
||||
}
|
||||
|
|
@ -564,10 +564,10 @@ object DeployableToolbox {
|
|||
def AddToDeployableQuantities(
|
||||
counts: Map[DeployedItem.Value, DeployableToolbox.Bin],
|
||||
categories: Map[DeployableCategory.Value, DeployableToolbox.Bin],
|
||||
certification: CertificationType.Value,
|
||||
certificationSet: Set[CertificationType.Value]
|
||||
certification: Certification,
|
||||
certificationSet: Set[Certification]
|
||||
): Unit = {
|
||||
import CertificationType._
|
||||
import Certification._
|
||||
if (certificationSet contains certification) {
|
||||
certification match {
|
||||
case AdvancedHacking =>
|
||||
|
|
@ -649,10 +649,10 @@ object DeployableToolbox {
|
|||
def RemoveFromDeployablesQuantities(
|
||||
counts: Map[DeployedItem.Value, DeployableToolbox.Bin],
|
||||
categories: Map[DeployableCategory.Value, DeployableToolbox.Bin],
|
||||
certification: CertificationType.Value,
|
||||
certificationSet: Set[CertificationType.Value]
|
||||
certification: Certification,
|
||||
certificationSet: Set[Certification]
|
||||
): Unit = {
|
||||
import CertificationType._
|
||||
import Certification._
|
||||
if (!certificationSet.contains(certification)) {
|
||||
certification match {
|
||||
case AdvancedHacking =>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
package net.psforever.objects.avatar
|
||||
|
||||
import net.psforever.objects.definition.ImplantDefinition
|
||||
import net.psforever.packet.game.objectcreate.ImplantEntry
|
||||
|
||||
case class Implant(
|
||||
definition: ImplantDefinition,
|
||||
active: Boolean = false,
|
||||
initialized: Boolean = false
|
||||
//initializationTime: FiniteDuration
|
||||
) {
|
||||
def toEntry: ImplantEntry = {
|
||||
// TODO initialization time?
|
||||
new ImplantEntry(definition.implantType, None, active)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,8 @@
|
|||
// Copyright (c) 2020 PSForever
|
||||
package net.psforever.objects.avatar
|
||||
|
||||
import akka.actor.{Actor, ActorRef, Cancellable, Props}
|
||||
import akka.actor.{Actor, ActorRef, Props}
|
||||
import net.psforever.actors.session.AvatarActor
|
||||
import net.psforever.objects.{Player, _}
|
||||
import net.psforever.objects.ballistics.{PlayerSource, ResolvedProjectile}
|
||||
import net.psforever.objects.equipment._
|
||||
|
|
@ -22,32 +23,26 @@ import net.psforever.types._
|
|||
import services.{RemoverActor, Service}
|
||||
import services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||
import services.local.{LocalAction, LocalServiceMessage}
|
||||
|
||||
import akka.actor.typed
|
||||
import scala.concurrent.duration._
|
||||
import scala.collection.mutable
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
|
||||
class PlayerControl(player: Player) extends Actor with JammableBehavior with Damageable with ContainableBehavior {
|
||||
def JammableObject = player
|
||||
class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Command])
|
||||
extends Actor
|
||||
with JammableBehavior
|
||||
with Damageable
|
||||
with ContainableBehavior {
|
||||
def JammableObject = player
|
||||
|
||||
def DamageableObject = player
|
||||
def ContainerObject = player
|
||||
|
||||
def ContainerObject = player
|
||||
|
||||
private[this] val log = org.log4s.getLogger(player.Name)
|
||||
private[this] val damageLog = org.log4s.getLogger(Damageable.LogChannel)
|
||||
|
||||
/** Stamina will be used. Stamina will be restored. */
|
||||
var staminaRegen: Cancellable = Default.Cancellable
|
||||
|
||||
/**
|
||||
* A collection of timers indexed for the implant in each slot.
|
||||
* Before an implant is ready, it serves as the initialization timer.
|
||||
* After being initialized, it is used as the stamina drain interval when the implant is active.
|
||||
*/
|
||||
val implantSlotTimers = mutable.HashMap(0 -> Default.Cancellable, 1 -> Default.Cancellable, 2 -> Default.Cancellable)
|
||||
|
||||
/** control agency for the player's locker container (dedicated inventory slot #5) */
|
||||
val lockerControlAgent: ActorRef = {
|
||||
val locker = player.Locker
|
||||
val locker = player.avatar.locker
|
||||
locker.Zone = player.Zone
|
||||
locker.Actor = context.actorOf(
|
||||
Props(classOf[LockerContainerControl], locker, player.Name),
|
||||
|
|
@ -57,9 +52,7 @@ class PlayerControl(player: Player) extends Actor with JammableBehavior with Dam
|
|||
|
||||
override def postStop(): Unit = {
|
||||
lockerControlAgent ! akka.actor.PoisonPill
|
||||
player.Locker.Actor = Default.Actor
|
||||
staminaRegen.cancel
|
||||
implantSlotTimers.values.foreach { _.cancel }
|
||||
player.avatar.locker.Actor = Default.Actor
|
||||
}
|
||||
|
||||
def receive: Receive =
|
||||
|
|
@ -67,47 +60,6 @@ class PlayerControl(player: Player) extends Actor with JammableBehavior with Dam
|
|||
.orElse(takesDamage)
|
||||
.orElse(containerBehavior)
|
||||
.orElse {
|
||||
case Player.ImplantActivation(slot: Int, status: Int) =>
|
||||
ImplantActivation(slot, status)
|
||||
|
||||
case Player.UninitializeImplant(slot: Int) =>
|
||||
UninitializeImplant(slot)
|
||||
|
||||
case Player.ImplantInitializationStart(slot: Int) =>
|
||||
ImplantInitializationStart(slot)
|
||||
|
||||
case Player.ImplantInitializationComplete(slot: Int) =>
|
||||
ImplantInitializationComplete(slot)
|
||||
|
||||
case Player.StaminaRegen() =>
|
||||
if (staminaRegen == Default.Cancellable) {
|
||||
staminaRegen.cancel
|
||||
staminaRegen =
|
||||
context.system.scheduler.scheduleOnce(delay = 500 milliseconds, self, PlayerControl.StaminaRegen())
|
||||
}
|
||||
|
||||
case PlayerControl.StaminaRegen() =>
|
||||
staminaRegen.cancel
|
||||
if (player.isAlive) {
|
||||
if (player.skipStaminaRegenForTurns > 0) {
|
||||
// Do not renew stamina for a while
|
||||
player.skipStaminaRegenForTurns -= 1
|
||||
} else if (
|
||||
(player.VehicleSeated.nonEmpty || !player.isMoving && !player.Jumping) && player.Stamina < player.MaxStamina
|
||||
) {
|
||||
// Regen stamina roughly every 500ms
|
||||
StaminaChanged(changeInStamina = 1)
|
||||
}
|
||||
}
|
||||
staminaRegen =
|
||||
context.system.scheduler.scheduleOnce(delay = 500 milliseconds, self, PlayerControl.StaminaRegen())
|
||||
|
||||
case Player.StaminaChanged(Some(changeInStamina)) =>
|
||||
StaminaChanged(changeInStamina)
|
||||
|
||||
case Player.StaminaChanged(None) =>
|
||||
UpdateStamina()
|
||||
|
||||
case Player.Die() =>
|
||||
if (player.isAlive) {
|
||||
DestructionAwareness(player, None)
|
||||
|
|
@ -138,7 +90,7 @@ class PlayerControl(player: Player) extends Actor with JammableBehavior with Dam
|
|||
InventoryStateMessage(item.AmmoSlot.Box.GUID, item.GUID, magazine.toLong)
|
||||
)
|
||||
)
|
||||
events ! AvatarServiceMessage(zone.Id, AvatarAction.PlanetsideAttributeToAll(guid, 0, newHealth))
|
||||
events ! AvatarServiceMessage(zone.id, AvatarAction.PlanetsideAttributeToAll(guid, 0, newHealth))
|
||||
player.History(
|
||||
HealFromEquipment(
|
||||
PlayerSource(player),
|
||||
|
|
@ -171,7 +123,7 @@ class PlayerControl(player: Player) extends Actor with JammableBehavior with Dam
|
|||
!player.isAlive && !player.isBackpack &&
|
||||
item.Magazine >= 25
|
||||
) {
|
||||
sender ! CommonMessages.Progress(
|
||||
sender() ! CommonMessages.Progress(
|
||||
4,
|
||||
Players.FinishRevivingPlayer(player, user.Name, item),
|
||||
Players.RevivingTickAction(player, user, item)
|
||||
|
|
@ -202,7 +154,7 @@ class PlayerControl(player: Player) extends Actor with JammableBehavior with Dam
|
|||
InventoryStateMessage(item.AmmoSlot.Box.GUID, item.GUID, magazine.toLong)
|
||||
)
|
||||
)
|
||||
events ! AvatarServiceMessage(zone.Id, AvatarAction.PlanetsideAttributeToAll(guid, 4, player.Armor))
|
||||
events ! AvatarServiceMessage(zone.id, AvatarAction.PlanetsideAttributeToAll(guid, 4, player.Armor))
|
||||
player.History(
|
||||
RepairFromEquipment(
|
||||
PlayerSource(player),
|
||||
|
|
@ -232,26 +184,29 @@ class PlayerControl(player: Player) extends Actor with JammableBehavior with Dam
|
|||
case Terminal.TerminalMessage(_, msg, order) =>
|
||||
order match {
|
||||
case Terminal.BuyExosuit(exosuit, subtype) =>
|
||||
val time = System.currentTimeMillis
|
||||
var toDelete: List[InventoryItem] = Nil
|
||||
val originalSuit = player.ExoSuit
|
||||
val originalSubtype = Loadout.DetermineSubtype(player)
|
||||
val requestToChangeArmor = originalSuit != exosuit || originalSubtype != subtype
|
||||
val allowedToChangeArmor = Players.CertificationToUseExoSuit(player, exosuit, subtype) &&
|
||||
(if (exosuit == ExoSuitType.MAX) {
|
||||
if (time - player.GetLastUsedTime(exosuit, subtype) < 300000L) {
|
||||
false
|
||||
} else {
|
||||
player.SetLastUsedTime(exosuit, subtype, time)
|
||||
true
|
||||
val definition = player.avatar.faction match {
|
||||
case PlanetSideEmpire.NC => GlobalDefinitions.NCMAX
|
||||
case PlanetSideEmpire.TR => GlobalDefinitions.TRMAX
|
||||
case PlanetSideEmpire.VS => GlobalDefinitions.VSMAX
|
||||
}
|
||||
player.avatar.purchaseCooldown(definition) match {
|
||||
case Some(_) =>
|
||||
false
|
||||
case None =>
|
||||
avatarActor ! AvatarActor.UpdatePurchaseTime(definition)
|
||||
true
|
||||
}
|
||||
} else {
|
||||
player.SetLastUsedTime(exosuit, subtype, time)
|
||||
true
|
||||
})
|
||||
val result = if (requestToChangeArmor && allowedToChangeArmor) {
|
||||
log.info(s"${player.Name} wants to change to a different exo-suit - $exosuit")
|
||||
player.SetLastUsedTime(exosuit, subtype, System.currentTimeMillis())
|
||||
val beforeHolsters = Players.clearHolsters(player.Holsters().iterator)
|
||||
val beforeInventory = player.Inventory.Clear()
|
||||
//change suit
|
||||
|
|
@ -314,18 +269,9 @@ class PlayerControl(player: Player) extends Actor with JammableBehavior with Dam
|
|||
player.Inventory.InsertQuickly(elem.start, elem.obj)
|
||||
}
|
||||
//deactivate non-passive implants
|
||||
implantSlotTimers.keys.foreach { index =>
|
||||
val implantSlot = player.ImplantSlot(index)
|
||||
if (
|
||||
implantSlot.Installed.nonEmpty && implantSlot.Active && (implantSlot.Charge(
|
||||
originalSuit
|
||||
) > 0 || implantSlot.Charge(exosuit) > 0)
|
||||
) {
|
||||
ImplantActivation(index, status = 0)
|
||||
}
|
||||
}
|
||||
avatarActor ! AvatarActor.DeactivateActiveImplants()
|
||||
player.Zone.AvatarEvents ! AvatarServiceMessage(
|
||||
player.Zone.Id,
|
||||
player.Zone.id,
|
||||
AvatarAction.ChangeExosuit(
|
||||
player.GUID,
|
||||
exosuit,
|
||||
|
|
@ -375,19 +321,22 @@ class PlayerControl(player: Player) extends Actor with JammableBehavior with Dam
|
|||
}) ++ dropHolsters ++ dropInventory
|
||||
//a loadout with a prohibited exo-suit type will result in the fallback exo-suit type
|
||||
//imposed 5min delay on mechanized exo-suit switches
|
||||
val time = System.currentTimeMillis()
|
||||
val (nextSuit, nextSubtype) =
|
||||
if (
|
||||
Players.CertificationToUseExoSuit(player, exosuit, subtype) &&
|
||||
(if (exosuit == ExoSuitType.MAX) {
|
||||
if (time - player.GetLastUsedTime(exosuit, subtype) < 300000L) {
|
||||
false
|
||||
} else {
|
||||
player.SetLastUsedTime(exosuit, subtype, time)
|
||||
true
|
||||
val definition = player.avatar.faction match {
|
||||
case PlanetSideEmpire.NC => GlobalDefinitions.NCMAX
|
||||
case PlanetSideEmpire.TR => GlobalDefinitions.TRMAX
|
||||
case PlanetSideEmpire.VS => GlobalDefinitions.VSMAX
|
||||
}
|
||||
player.avatar.purchaseCooldown(definition) match {
|
||||
case Some(_) => false
|
||||
case None =>
|
||||
avatarActor ! AvatarActor.UpdatePurchaseTime(definition)
|
||||
true
|
||||
}
|
||||
} else {
|
||||
player.SetLastUsedTime(exosuit, subtype, time)
|
||||
true
|
||||
})
|
||||
) {
|
||||
|
|
@ -396,7 +345,6 @@ class PlayerControl(player: Player) extends Actor with JammableBehavior with Dam
|
|||
log.warn(
|
||||
s"no longer has permission to wear the exo-suit type $exosuit; will wear $fallbackSuit instead"
|
||||
)
|
||||
player.SetLastUsedTime(fallbackSuit, fallbackSubtype, time)
|
||||
(fallbackSuit, fallbackSubtype)
|
||||
}
|
||||
//sanitize (incoming) inventory
|
||||
|
|
@ -458,18 +406,9 @@ class PlayerControl(player: Player) extends Actor with JammableBehavior with Dam
|
|||
(afterHolsters ++ afterInventory).foreach { entry => entry.obj.Faction = player.Faction }
|
||||
toDeleteOrDrop.foreach { entry => entry.obj.Faction = PlanetSideEmpire.NEUTRAL }
|
||||
//deactivate non-passive implants
|
||||
implantSlotTimers.keys.foreach { index =>
|
||||
val implantSlot = player.ImplantSlot(index)
|
||||
if (
|
||||
implantSlot.Installed.nonEmpty && implantSlot.Active && (implantSlot.Charge(
|
||||
originalSuit
|
||||
) > 0 || implantSlot.Charge(nextSuit) > 0)
|
||||
) {
|
||||
ImplantActivation(index, status = 0)
|
||||
}
|
||||
}
|
||||
avatarActor ! AvatarActor.DeactivateActiveImplants()
|
||||
player.Zone.AvatarEvents ! AvatarServiceMessage(
|
||||
player.Zone.Id,
|
||||
player.Zone.id,
|
||||
AvatarAction.ChangeLoadout(
|
||||
player.GUID,
|
||||
nextSuit,
|
||||
|
|
@ -487,103 +426,7 @@ class PlayerControl(player: Player) extends Actor with JammableBehavior with Dam
|
|||
player.Name,
|
||||
AvatarAction.TerminalOrderResult(msg.terminal_guid, msg.transaction_type, true)
|
||||
)
|
||||
|
||||
case Terminal.LearnImplant(implant) =>
|
||||
val zone = player.Zone
|
||||
val events = zone.AvatarEvents
|
||||
val playerChannel = player.Name
|
||||
val terminal_guid = msg.terminal_guid
|
||||
val implant_type = implant.Type
|
||||
val message = s"wants to learn $implant_type"
|
||||
val (interface, slotNumber) = player.VehicleSeated match {
|
||||
case Some(mech_guid) =>
|
||||
(
|
||||
zone.map.TerminalToInterface.get(mech_guid.guid),
|
||||
if (!player.Implants.exists({ case (implantType, _, _) => implantType == implant_type })) {
|
||||
//no duplicates
|
||||
player.InstallImplant(implant)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
)
|
||||
case _ =>
|
||||
(None, None)
|
||||
}
|
||||
val result = if (interface.contains(terminal_guid.guid) && slotNumber.isDefined) {
|
||||
val slot = slotNumber.get
|
||||
log.info(s"$message - put in slot $slot")
|
||||
events ! AvatarServiceMessage(
|
||||
playerChannel,
|
||||
AvatarAction.SendResponse(
|
||||
Service.defaultPlayerGUID,
|
||||
AvatarImplantMessage(player.GUID, ImplantAction.Add, slot, implant_type.id)
|
||||
)
|
||||
)
|
||||
ImplantInitializationStart(slot)
|
||||
true
|
||||
} else {
|
||||
if (interface.isEmpty) {
|
||||
log.warn(s"$message - not interacting with a terminal")
|
||||
} else if (!interface.contains(terminal_guid.guid)) {
|
||||
log.warn(s"$message - interacting with the wrong terminal, ${interface.get}")
|
||||
} else if (slotNumber.isEmpty) {
|
||||
log.warn(s"$message - already knows that implant")
|
||||
} else {
|
||||
log.warn(s"$message - forgot to sit at a terminal")
|
||||
}
|
||||
false
|
||||
}
|
||||
events ! AvatarServiceMessage(
|
||||
playerChannel,
|
||||
AvatarAction.TerminalOrderResult(terminal_guid, msg.transaction_type, result)
|
||||
)
|
||||
|
||||
case Terminal.SellImplant(implant) =>
|
||||
val zone = player.Zone
|
||||
val events = zone.AvatarEvents
|
||||
val playerChannel = player.Name
|
||||
val terminal_guid = msg.terminal_guid
|
||||
val implant_type = implant.Type
|
||||
val (interface, slotNumber) = player.VehicleSeated match {
|
||||
case Some(mech_guid) =>
|
||||
(
|
||||
zone.map.TerminalToInterface.get(mech_guid.guid),
|
||||
player.UninstallImplant(implant_type)
|
||||
)
|
||||
case None =>
|
||||
(None, None)
|
||||
}
|
||||
val result = if (interface.contains(terminal_guid.guid) && slotNumber.isDefined) {
|
||||
val slot = slotNumber.get
|
||||
log.info(s"is uninstalling $implant_type - take from slot $slot")
|
||||
UninitializeImplant(slot)
|
||||
events ! AvatarServiceMessage(
|
||||
playerChannel,
|
||||
AvatarAction.SendResponse(
|
||||
Service.defaultPlayerGUID,
|
||||
AvatarImplantMessage(player.GUID, ImplantAction.Remove, slot, 0)
|
||||
)
|
||||
)
|
||||
true
|
||||
} else {
|
||||
val message = s"${player.Name} can not sell $implant_type"
|
||||
if (interface.isEmpty) {
|
||||
log.warn(s"$message - not interacting with a terminal")
|
||||
} else if (!interface.contains(terminal_guid.guid)) {
|
||||
log.warn(s"$message - interacting with the wrong terminal, ${interface.get}")
|
||||
} else if (slotNumber.isEmpty) {
|
||||
log.warn(s"$message - does not know that implant")
|
||||
} else {
|
||||
log.warn(s"$message - forgot to sit at a terminal")
|
||||
}
|
||||
false
|
||||
}
|
||||
events ! AvatarServiceMessage(
|
||||
playerChannel,
|
||||
AvatarAction.TerminalOrderResult(terminal_guid, msg.transaction_type, result)
|
||||
)
|
||||
|
||||
case _ => ; //terminal messages not handled here
|
||||
case _ => assert(false, msg.toString)
|
||||
}
|
||||
|
||||
case Zone.Ground.ItemOnGround(item, _, _) =>
|
||||
|
|
@ -599,10 +442,10 @@ class PlayerControl(player: Player) extends Actor with JammableBehavior with Dam
|
|||
case (Some(boomer: BoomerDeployable), Some(avatar)) =>
|
||||
val guid = boomer.GUID
|
||||
val factionChannel = boomer.Faction.toString
|
||||
if (avatar.Deployables.Remove(boomer)) {
|
||||
if (avatar.deployables.Remove(boomer)) {
|
||||
boomer.Faction = PlanetSideEmpire.NEUTRAL
|
||||
boomer.AssignOwnership(None)
|
||||
avatar.Deployables.UpdateUIElement(boomer.Definition.Item).foreach {
|
||||
avatar.deployables.UpdateUIElement(boomer.Definition.Item).foreach {
|
||||
case (currElem, curr, maxElem, max) =>
|
||||
avatarEvents ! AvatarServiceMessage(
|
||||
name,
|
||||
|
|
@ -648,12 +491,12 @@ class PlayerControl(player: Player) extends Actor with JammableBehavior with Dam
|
|||
if (player.isAlive && !player.spectator) {
|
||||
val originalHealth = player.Health
|
||||
val originalArmor = player.Armor
|
||||
val originalStamina = player.Stamina
|
||||
val originalStamina = player.avatar.stamina
|
||||
val originalCapacitor = player.Capacitor.toInt
|
||||
val cause = applyDamageTo(player)
|
||||
val health = player.Health
|
||||
val armor = player.Armor
|
||||
val stamina = player.Stamina
|
||||
val stamina = player.avatar.stamina
|
||||
val capacitor = player.Capacitor.toInt
|
||||
val damageToHealth = originalHealth - health
|
||||
val damageToArmor = originalArmor - armor
|
||||
|
|
@ -682,7 +525,7 @@ class PlayerControl(player: Player) extends Actor with JammableBehavior with Dam
|
|||
): Unit = {
|
||||
val targetGUID = target.GUID
|
||||
val zone = target.Zone
|
||||
val zoneId = zone.Id
|
||||
val zoneId = zone.id
|
||||
val events = zone.AvatarEvents
|
||||
val health = target.Health
|
||||
if (damageToArmor > 0) {
|
||||
|
|
@ -701,7 +544,7 @@ class PlayerControl(player: Player) extends Actor with JammableBehavior with Dam
|
|||
events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(targetGUID, 0, health))
|
||||
}
|
||||
if (damageToStamina > 0) {
|
||||
UpdateStamina()
|
||||
avatarActor ! AvatarActor.ConsumeStamina(damageToStamina)
|
||||
}
|
||||
//activity on map
|
||||
zone.Activity ! Zone.HotSpot.Activity(cause.target, cause.projectile.owner, cause.hit_pos)
|
||||
|
|
@ -761,20 +604,13 @@ class PlayerControl(player: Player) extends Actor with JammableBehavior with Dam
|
|||
val zone = target.Zone
|
||||
val events = zone.AvatarEvents
|
||||
val nameChannel = target.Name
|
||||
val zoneChannel = zone.Id
|
||||
val zoneChannel = zone.id
|
||||
target.Die
|
||||
//unjam
|
||||
CancelJammeredSound(target)
|
||||
CancelJammeredStatus(target)
|
||||
//implants off
|
||||
target.Stamina = 0
|
||||
UpdateStamina() //turn off implants / OutOfStamina
|
||||
//uninitialize implants
|
||||
target.Implants.indices.foreach {
|
||||
case slot if target.Implant(slot) != ImplantType.None =>
|
||||
UninitializeImplant(slot)
|
||||
}
|
||||
target.ResetAllImplants() //anything else specific to the backend
|
||||
avatarActor ! AvatarActor.DeinitializeImplants()
|
||||
events ! AvatarServiceMessage(
|
||||
nameChannel,
|
||||
AvatarAction.Killed(player_guid, target.VehicleSeated)
|
||||
|
|
@ -873,7 +709,7 @@ class PlayerControl(player: Player) extends Actor with JammableBehavior with Dam
|
|||
target match {
|
||||
case obj: Player if !jammedSound =>
|
||||
obj.Zone.AvatarEvents ! AvatarServiceMessage(
|
||||
obj.Zone.Id,
|
||||
obj.Zone.id,
|
||||
AvatarAction.PlanetsideAttributeToAll(obj.GUID, 27, 1)
|
||||
)
|
||||
super.StartJammeredSound(obj, 3000)
|
||||
|
|
@ -890,25 +726,13 @@ class PlayerControl(player: Player) extends Actor with JammableBehavior with Dam
|
|||
* @param dur the duration of the timer, in milliseconds
|
||||
*/
|
||||
override def StartJammeredStatus(target: Any, dur: Int): Unit = {
|
||||
//TODO these features
|
||||
val zone = player.Zone
|
||||
player.Implants.indices.foreach { slot => // Deactivate & uninitialize all implants
|
||||
zone.AvatarEvents ! AvatarServiceMessage(
|
||||
zone.Id,
|
||||
AvatarAction.PlanetsideAttribute(player.GUID, 28, player.Implant(slot).id * 2)
|
||||
) // Deactivation sound / effect
|
||||
ImplantActivation(slot, status = 0)
|
||||
UninitializeImplant(slot)
|
||||
}
|
||||
player.skipStaminaRegenForTurns = math.max(player.skipStaminaRegenForTurns, 10)
|
||||
avatarActor ! AvatarActor.DeinitializeImplants()
|
||||
avatarActor ! AvatarActor.SuspendStaminaRegeneration(5 seconds)
|
||||
super.StartJammeredStatus(target, dur)
|
||||
}
|
||||
|
||||
override def CancelJammeredStatus(target: Any): Unit = {
|
||||
player.Implants.indices.foreach { slot => // Start reinitializing all implants
|
||||
player.ImplantSlot(slot).InitializeTime = 0 //setting time to 0 will restart implant initialization (eventually)
|
||||
ImplantInitializationStart(slot)
|
||||
}
|
||||
avatarActor ! AvatarActor.InitializeImplants(instant = true)
|
||||
super.CancelJammeredStatus(target)
|
||||
}
|
||||
|
||||
|
|
@ -921,7 +745,7 @@ class PlayerControl(player: Player) extends Actor with JammableBehavior with Dam
|
|||
target match {
|
||||
case obj: Player if jammedSound =>
|
||||
obj.Zone.AvatarEvents ! AvatarServiceMessage(
|
||||
obj.Zone.Id,
|
||||
obj.Zone.id,
|
||||
AvatarAction.PlanetsideAttributeToAll(obj.GUID, 27, 0)
|
||||
)
|
||||
super.CancelJammeredSound(obj)
|
||||
|
|
@ -956,7 +780,7 @@ class PlayerControl(player: Player) extends Actor with JammableBehavior with Dam
|
|||
val obj = ContainerObject
|
||||
val zone = obj.Zone
|
||||
val events = zone.AvatarEvents
|
||||
val toChannel = if (obj.VisibleSlots.contains(slot)) zone.Id else player.Name
|
||||
val toChannel = if (obj.VisibleSlots.contains(slot)) zone.id else player.Name
|
||||
item.Faction = PlanetSideEmpire.NEUTRAL
|
||||
if (slot == obj.DrawnSlot) {
|
||||
obj.DrawnSlot = Player.HandsDownSlot
|
||||
|
|
@ -986,7 +810,7 @@ class PlayerControl(player: Player) extends Actor with JammableBehavior with Dam
|
|||
)
|
||||
)
|
||||
if (obj.VisibleSlots.contains(slot)) {
|
||||
events ! AvatarServiceMessage(zone.Id, AvatarAction.EquipmentInHand(guid, guid, slot, item))
|
||||
events ! AvatarServiceMessage(zone.id, AvatarAction.EquipmentInHand(guid, guid, slot, item))
|
||||
}
|
||||
//handle specific types of items
|
||||
item match {
|
||||
|
|
@ -998,10 +822,10 @@ class PlayerControl(player: Player) extends Actor with JammableBehavior with Dam
|
|||
val bguid = boomer.GUID
|
||||
val faction = player.Faction
|
||||
val factionChannel = faction.toString
|
||||
if (avatar.Deployables.Add(boomer)) {
|
||||
if (avatar.deployables.Add(boomer)) {
|
||||
boomer.Faction = faction
|
||||
boomer.AssignOwnership(player)
|
||||
avatar.Deployables.UpdateUIElement(boomer.Definition.Item).foreach {
|
||||
avatar.deployables.UpdateUIElement(boomer.Definition.Item).foreach {
|
||||
case (currElem, curr, maxElem, max) =>
|
||||
events ! AvatarServiceMessage(
|
||||
name,
|
||||
|
|
@ -1045,250 +869,4 @@ class PlayerControl(player: Player) extends Actor with JammableBehavior with Dam
|
|||
AvatarAction.SendResponse(Service.defaultPlayerGUID, ObjectDetachMessage(obj.GUID, item.GUID, Vector3.Zero, 0f))
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* na
|
||||
* @param changeInStamina na
|
||||
*/
|
||||
def StaminaChanged(changeInStamina: Int): Unit = {
|
||||
val beforeStamina = player.Stamina
|
||||
val afterStamina = player.Stamina += changeInStamina
|
||||
if (beforeStamina != afterStamina) {
|
||||
UpdateStamina()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the current stamina value for this player requires a greater change in player states.
|
||||
* Losing all stamina and not yet being fatigued deactivates implants.
|
||||
* Having stamina of 20 points or greater and having previously been fatigued
|
||||
* allows implants to operate once again.
|
||||
* Initialization must be restarted manually for any implant that had not previously finished initializing.
|
||||
*/
|
||||
def UpdateStamina(): Unit = {
|
||||
val currentStamina = player.Stamina
|
||||
if (currentStamina == 0 && !player.Fatigued) { // Only be fatigued once even if loses all stamina again
|
||||
player.Fatigued = true
|
||||
player.skipStaminaRegenForTurns = math.max(player.skipStaminaRegenForTurns, 6)
|
||||
player.Implants.indices.foreach { slot => // Disable all implants
|
||||
ImplantActivation(slot, status = 0)
|
||||
player.Zone.AvatarEvents ! AvatarServiceMessage(
|
||||
player.Name,
|
||||
AvatarAction.SendResponse(
|
||||
Service.defaultPlayerGUID,
|
||||
AvatarImplantMessage(player.GUID, ImplantAction.OutOfStamina, slot, 1)
|
||||
)
|
||||
)
|
||||
}
|
||||
} else if (currentStamina >= 20) {
|
||||
val wasFatigued = player.Fatigued
|
||||
player.Fatigued = false
|
||||
if (wasFatigued) { //reactivate only if we were fatigued
|
||||
player.Implants.indices.foreach { slot => // Re-enable all implants
|
||||
player.Zone.AvatarEvents ! AvatarServiceMessage(
|
||||
player.Name,
|
||||
AvatarAction.SendResponse(
|
||||
Service.defaultPlayerGUID,
|
||||
AvatarImplantMessage(player.GUID, ImplantAction.OutOfStamina, slot, 0)
|
||||
)
|
||||
)
|
||||
if (!player.ImplantSlot(slot).Initialized) {
|
||||
ImplantInitializationStart(slot)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
player.Zone.AvatarEvents ! AvatarServiceMessage(
|
||||
player.Name,
|
||||
AvatarAction.PlanetsideAttributeToAll(player.GUID, 2, currentStamina)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* The process of starting an implant so that it can be activated is one that requires a matter of time.
|
||||
* If the implant should already have been started, then just switch to the proper state.
|
||||
* Always (check to) initialize implants when setting up an avatar or becoming fatigued or when revived.
|
||||
* @param slot the slot in which this implant is found
|
||||
*/
|
||||
def ImplantInitializationStart(slot: Int): Unit = {
|
||||
val implantSlot = player.ImplantSlot(slot)
|
||||
if (implantSlot.Installed.isDefined) {
|
||||
if (!implantSlot.Initialized) {
|
||||
val time = System.currentTimeMillis
|
||||
val initializationTime = if (implantSlot.InitializeTime == 0L) {
|
||||
implantSlot.InitializeTime = time
|
||||
time
|
||||
} else {
|
||||
implantSlot.InitializeTime
|
||||
}
|
||||
val maxInitializationTime = implantSlot.MaxTimer * 1000
|
||||
if (time - initializationTime > maxInitializationTime) {
|
||||
//this implant should have already been initialized
|
||||
ImplantInitializationComplete(slot)
|
||||
} else {
|
||||
// Start client side initialization timer
|
||||
// Check this along the bottom of the character information window
|
||||
//progress accumulates according to the client's knowledge of the implant initialization time
|
||||
//what is normally a 60s timer that is set to 120s on the server will still visually update as if 60s
|
||||
val percent = (100 * (time - initializationTime) / maxInitializationTime.toFloat).toInt
|
||||
player.Zone.AvatarEvents ! AvatarServiceMessage(
|
||||
player.Name,
|
||||
AvatarAction.SendResponse(Service.defaultPlayerGUID, ActionProgressMessage(slot + 6, percent))
|
||||
)
|
||||
// Callback after initialization timer to complete initialization
|
||||
implantSlotTimers(slot).cancel
|
||||
implantSlotTimers(slot) = context.system.scheduler.scheduleOnce(
|
||||
(maxInitializationTime - (time - initializationTime)) milliseconds,
|
||||
self,
|
||||
Player.ImplantInitializationComplete(slot)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
ImplantInitializationComplete(slot)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The implant is ready to be made available and active on selection.
|
||||
* The end result of a timed process, occasionally an implant will become "already active".
|
||||
* @param slot the slot in which this implant is found
|
||||
*/
|
||||
def ImplantInitializationComplete(slot: Int): Unit = {
|
||||
val implantSlot = player.ImplantSlot(slot)
|
||||
if (implantSlot.Installed.isDefined) {
|
||||
implantSlot.Initialized = true
|
||||
player.Zone.AvatarEvents ! AvatarServiceMessage(
|
||||
player.Name,
|
||||
AvatarAction.SendResponse(
|
||||
Service.defaultPlayerGUID,
|
||||
AvatarImplantMessage(player.GUID, ImplantAction.Initialization, slot, 1)
|
||||
)
|
||||
)
|
||||
implantSlotTimers(slot).cancel
|
||||
implantSlotTimers(slot) = Default.Cancellable
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not the implant is being used by the player who installed it.
|
||||
* If the implant has no business having its activation state changed yet, it (re)starts its initialization phase.
|
||||
* @param slot the slot in which this implant is found
|
||||
* @param status `1`, if the implant should become active;
|
||||
* `0`, if it should be deactivated
|
||||
*/
|
||||
def ImplantActivation(slot: Int, status: Int): Unit = {
|
||||
val implantSlot = player.ImplantSlot(slot)
|
||||
if (!implantSlot.Initialized && !player.Fatigued) {
|
||||
log.warn(s"implant in slot $slot is trying to (de)activate when not even initialized!")
|
||||
//we should not be activating or deactivataing, but initializing
|
||||
implantSlotTimers(slot).cancel
|
||||
implantSlotTimers(slot) = Default.Cancellable
|
||||
implantSlot.Active = false
|
||||
//normal deactivation
|
||||
player.Zone.AvatarEvents ! AvatarServiceMessage(
|
||||
player.Name,
|
||||
AvatarAction.DeactivateImplantSlot(player.GUID, slot)
|
||||
)
|
||||
//initialization process (from scratch)
|
||||
implantSlot.InitializeTime = 0
|
||||
ImplantInitializationStart(slot)
|
||||
} else if (status == 0 && implantSlot.Active) {
|
||||
implantSlotTimers(slot).cancel
|
||||
implantSlotTimers(slot) = Default.Cancellable
|
||||
implantSlot.Active = false
|
||||
player.Zone.AvatarEvents ! AvatarServiceMessage(
|
||||
player.Zone.Id,
|
||||
AvatarAction.PlanetsideAttribute(player.GUID, 28, player.Implant(slot).id * 2)
|
||||
) // Deactivation sound / effect
|
||||
player.Zone.AvatarEvents ! AvatarServiceMessage(
|
||||
player.Name,
|
||||
AvatarAction.DeactivateImplantSlot(player.GUID, slot)
|
||||
)
|
||||
} else if (status == 1 && implantSlot.Initialized && !player.Fatigued) {
|
||||
implantSlot.Installed match {
|
||||
case Some(implant)
|
||||
if (implant.Type == ImplantType.PersonalShield && player.ExoSuit == ExoSuitType.Infiltration) ||
|
||||
(implant.Type == ImplantType.Surge && player.ExoSuit == ExoSuitType.MAX) =>
|
||||
//TODO STILL NOT ALLOWED (but make it look normal)
|
||||
case Some(implant) =>
|
||||
if (implantSlot.Active) {
|
||||
// Some events such as zoning will reset the implant on the client side without sending a deactivation packet
|
||||
// But the implant will remain in an active state server side. For now, allow reactivation of the implant.
|
||||
log.warn(s"implant $slot is already active, but activating again")
|
||||
implantSlotTimers(slot).cancel
|
||||
implantSlotTimers(slot) = Default.Cancellable
|
||||
}
|
||||
val activationStaminaCost = implant.ActivationStaminaCost
|
||||
if (activationStaminaCost > 0) {
|
||||
player.Stamina -= activationStaminaCost // Activation stamina drain
|
||||
UpdateStamina()
|
||||
}
|
||||
if (!player.Fatigued) {
|
||||
implantSlot.Active = true
|
||||
val zone = player.Zone
|
||||
val drainInterval = implant.GetCostIntervalByExoSuit(player.ExoSuit)
|
||||
if (drainInterval > 0) { // Ongoing stamina drain, if applicable
|
||||
implantSlotTimers(slot).cancel
|
||||
implantSlotTimers(slot) = context.system.scheduler.scheduleWithFixedDelay(
|
||||
initialDelay = 0 seconds,
|
||||
drainInterval milliseconds,
|
||||
self,
|
||||
Player.StaminaChanged(-implant.StaminaCost)
|
||||
)
|
||||
}
|
||||
zone.AvatarEvents ! AvatarServiceMessage(
|
||||
zone.Id,
|
||||
AvatarAction.PlanetsideAttribute(player.GUID, 28, player.Implant(slot).id * 2 + 1)
|
||||
) // Activation sound / effect
|
||||
zone.AvatarEvents ! AvatarServiceMessage(player.Name, AvatarAction.ActivateImplantSlot(player.GUID, slot))
|
||||
}
|
||||
case _ =>
|
||||
//there should have been an implant here ...
|
||||
implantSlot.Active = false
|
||||
implantSlot.Initialized = false
|
||||
implantSlot.InitializeTime = 0L
|
||||
//todo: AvatarImplantMessage(tplayer.GUID, ImplantAction.Remove, slot, 0)?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The implant in this slot is no longer active and is no longer considered ready to activate.
|
||||
* @param slot the slot in which an implant could be found
|
||||
*/
|
||||
def UninitializeImplant(slot: Int): Unit = {
|
||||
implantSlotTimers(slot).cancel
|
||||
implantSlotTimers(slot) = Default.Cancellable
|
||||
val zone = player.Zone
|
||||
val guid = player.GUID
|
||||
val playerChannel = player.Name
|
||||
val zoneChannel = zone.Id
|
||||
val implantSlot = player.ImplantSlot(slot)
|
||||
// if(implantSlot.Active) {
|
||||
// zone.AvatarEvents ! AvatarServiceMessage(zoneChannel, AvatarAction.PlanetsideAttribute(guid, 28, player.Implant(slot).id * 2)) // Deactivation sound / effect
|
||||
// zone.AvatarEvents ! AvatarServiceMessage(playerChannel, AvatarAction.DeactivateImplantSlot(guid, slot))
|
||||
// }
|
||||
implantSlot.Active = false
|
||||
implantSlot.Initialized = false
|
||||
implantSlot.InitializeTime = 0L
|
||||
zone.AvatarEvents ! AvatarServiceMessage(
|
||||
playerChannel,
|
||||
AvatarAction.SendResponse(Service.defaultPlayerGUID, ActionProgressMessage(slot + 6, 100))
|
||||
)
|
||||
zone.AvatarEvents ! AvatarServiceMessage(
|
||||
zoneChannel,
|
||||
AvatarAction.SendResponse(
|
||||
Service.defaultPlayerGUID,
|
||||
AvatarImplantMessage(guid, ImplantAction.Initialization, slot, 0)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
object PlayerControl {
|
||||
|
||||
/**
|
||||
*/
|
||||
private case class StaminaRegen()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -73,12 +73,12 @@ final case class Projectile(
|
|||
object Projectile {
|
||||
|
||||
/** the first projectile GUID used by all clients internally */
|
||||
final val BaseUID: Int = 40100
|
||||
final val baseUID: Int = 40100
|
||||
|
||||
/** all clients progress through 40100 to 40124 normally, skipping only for long-lived projectiles
|
||||
* 40125 to 40149 are being reserved as a guard against undetected overflow
|
||||
*/
|
||||
final val RangeUID: Int = 40150
|
||||
final val rangeUID: Int = 40150
|
||||
|
||||
/**
|
||||
* Overloaded constructor for an `Unresolved` projectile.
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package net.psforever.objects.definition
|
||||
|
||||
import net.psforever.objects.avatar.Certification
|
||||
import net.psforever.objects.ce.DeployedItem
|
||||
import net.psforever.objects.definition.converter.ACEConverter
|
||||
import net.psforever.objects.equipment.CItem
|
||||
import net.psforever.types.CertificationType
|
||||
|
||||
import scala.collection.mutable.ListBuffer
|
||||
|
||||
|
|
@ -27,21 +27,20 @@ object ConstructionItemDefinition {
|
|||
}
|
||||
|
||||
class ConstructionFireMode {
|
||||
private val deployables: ListBuffer[DeployedItem.Value] = ListBuffer.empty
|
||||
private val permissions: ListBuffer[Set[CertificationType.Value]] = ListBuffer.empty
|
||||
private val deployables: ListBuffer[DeployedItem.Value] = ListBuffer.empty
|
||||
private val permissions: ListBuffer[Set[Certification]] = ListBuffer.empty
|
||||
|
||||
def Permissions: ListBuffer[Set[CertificationType.Value]] = permissions
|
||||
def Permissions: ListBuffer[Set[Certification]] = permissions
|
||||
|
||||
def Deployables: ListBuffer[DeployedItem.Value] = deployables
|
||||
|
||||
def Item(deployable: DeployedItem.Value): ListBuffer[DeployedItem.Value] = {
|
||||
deployables += deployable
|
||||
permissions += Set.empty[CertificationType.Value]
|
||||
permissions += Set.empty[Certification]
|
||||
deployables
|
||||
}
|
||||
|
||||
def Item(deployPair: (DeployedItem.Value, Set[CertificationType.Value])): ListBuffer[DeployedItem.Value] = {
|
||||
val (deployable, permission) = deployPair
|
||||
def Item(deployable: DeployedItem.Value, permission: Set[Certification]): ListBuffer[DeployedItem.Value] = {
|
||||
deployables += deployable
|
||||
permissions += permission
|
||||
deployables
|
||||
|
|
|
|||
|
|
@ -2,12 +2,13 @@
|
|||
package net.psforever.objects.definition
|
||||
|
||||
import net.psforever.objects.GlobalDefinitions
|
||||
import net.psforever.objects.avatar.Certification
|
||||
import net.psforever.objects.equipment.EquipmentSize
|
||||
import net.psforever.objects.inventory.InventoryTile
|
||||
import net.psforever.objects.vital._
|
||||
import net.psforever.objects.vital.damage.DamageCalculations
|
||||
import net.psforever.objects.vital.resistance.ResistanceProfileMutators
|
||||
import net.psforever.types.{CertificationType, ExoSuitType, PlanetSideEmpire}
|
||||
import net.psforever.types.{ExoSuitType, PlanetSideEmpire}
|
||||
|
||||
/**
|
||||
* A definition for producing the personal armor the player wears.
|
||||
|
|
@ -18,15 +19,15 @@ class ExoSuitDefinition(private val suitType: ExoSuitType.Value)
|
|||
extends BasicDefinition
|
||||
with ResistanceProfileMutators
|
||||
with DamageResistanceModel {
|
||||
protected var permissions: List[CertificationType.Value] = List.empty
|
||||
protected var maxArmor: Int = 0
|
||||
protected val holsters: Array[EquipmentSize.Value] = Array.fill[EquipmentSize.Value](5)(EquipmentSize.Blocked)
|
||||
protected var inventoryScale: InventoryTile = InventoryTile.Tile11 //override with custom InventoryTile
|
||||
protected var inventoryOffset: Int = 0
|
||||
protected var maxCapacitor: Int = 0
|
||||
protected var capacitorRechargeDelayMillis: Int = 0
|
||||
protected var capacitorRechargePerSecond: Int = 0
|
||||
protected var capacitorDrainPerSecond: Int = 0
|
||||
protected var permissions: List[Certification] = List.empty
|
||||
protected var maxArmor: Int = 0
|
||||
protected val holsters: Array[EquipmentSize.Value] = Array.fill[EquipmentSize.Value](5)(EquipmentSize.Blocked)
|
||||
protected var inventoryScale: InventoryTile = InventoryTile.Tile11 //override with custom InventoryTile
|
||||
protected var inventoryOffset: Int = 0
|
||||
protected var maxCapacitor: Int = 0
|
||||
protected var capacitorRechargeDelayMillis: Int = 0
|
||||
protected var capacitorRechargePerSecond: Int = 0
|
||||
protected var capacitorDrainPerSecond: Int = 0
|
||||
Name = "exo-suit"
|
||||
DamageUsing = DamageCalculations.AgainstExoSuit
|
||||
ResistUsing = StandardInfantryResistance
|
||||
|
|
@ -102,25 +103,15 @@ class ExoSuitDefinition(private val suitType: ExoSuitType.Value)
|
|||
}
|
||||
}
|
||||
|
||||
def Permissions: List[CertificationType.Value] = permissions
|
||||
def Permissions: List[Certification] = permissions
|
||||
|
||||
def Permissions_=(certs: List[CertificationType.Value]): List[CertificationType.Value] = {
|
||||
def Permissions_=(certs: List[Certification]): List[Certification] = {
|
||||
permissions = certs
|
||||
Permissions
|
||||
}
|
||||
|
||||
def Use: ExoSuitDefinition = this
|
||||
|
||||
def canEqual(other: Any): Boolean = other.isInstanceOf[ExoSuitDefinition]
|
||||
|
||||
override def equals(other: Any): Boolean =
|
||||
other match {
|
||||
case that: ExoSuitDefinition =>
|
||||
(that canEqual this) &&
|
||||
suitType == that.suitType
|
||||
case _ => false
|
||||
}
|
||||
|
||||
override def hashCode(): Int = {
|
||||
val state = Seq(suitType)
|
||||
state.map(_.hashCode()).foldLeft(0)((a, b) => 31 * a + b)
|
||||
|
|
|
|||
|
|
@ -14,11 +14,11 @@ import scala.collection.mutable
|
|||
* Passive implants are always active and thus have no cost.
|
||||
* After being activated, a non-passive implant consumes a specific amount of stamina at regular intervals
|
||||
* Some implants will specify a different interval for consuming stamina based on the exo-suit the player is wearing
|
||||
*
|
||||
* @param implantType the type of implant that is defined
|
||||
* @see `ImplantType`
|
||||
*/
|
||||
class ImplantDefinition(private val implantType: Int) extends BasicDefinition {
|
||||
ImplantType(implantType)
|
||||
class ImplantDefinition(val implantType: ImplantType) extends BasicDefinition {
|
||||
|
||||
/** how long it takes the implant to become ready for activation; is milliseconds */
|
||||
private var initializationDuration: Long = 0L
|
||||
|
|
@ -84,16 +84,4 @@ class ImplantDefinition(private val implantType: Int) extends BasicDefinition {
|
|||
def GetCostIntervalByExoSuit(exosuit: ExoSuitType.Value): Int =
|
||||
costIntervalByExoSuit.getOrElse(exosuit, CostIntervalDefault)
|
||||
def CostIntervalByExoSuitHashMap: mutable.Map[ExoSuitType.Value, Int] = costIntervalByExoSuit
|
||||
|
||||
def Type: ImplantType.Value = ImplantType(implantType)
|
||||
}
|
||||
|
||||
object ImplantDefinition {
|
||||
def apply(implantType: Int): ImplantDefinition = {
|
||||
new ImplantDefinition(implantType)
|
||||
}
|
||||
|
||||
def apply(implantType: ImplantType.Value): ImplantDefinition = {
|
||||
new ImplantDefinition(implantType.id)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ package net.psforever.objects.definition.converter
|
|||
import net.psforever.objects.Player
|
||||
import net.psforever.objects.equipment.{Equipment, EquipmentSlot}
|
||||
import net.psforever.packet.game.objectcreate._
|
||||
import net.psforever.types.{ExoSuitType, GrenadeState, ImplantType, PlanetSideGUID}
|
||||
import net.psforever.types.{ExoSuitType, GrenadeState, PlanetSideGUID}
|
||||
|
||||
import scala.annotation.tailrec
|
||||
import scala.util.{Success, Try}
|
||||
|
|
@ -96,13 +96,13 @@ object AvatarConverter {
|
|||
false,
|
||||
facingPitch = obj.Orientation.y,
|
||||
facingYawUpper = obj.FacingYawUpper,
|
||||
obj.LFS,
|
||||
obj.avatar.lookingForSquad,
|
||||
GrenadeState.None,
|
||||
obj.Cloaked,
|
||||
false,
|
||||
false,
|
||||
unk5 = false,
|
||||
unk6 = false,
|
||||
charging_pose = false,
|
||||
false,
|
||||
unk7 = false,
|
||||
on_zipline = None
|
||||
)
|
||||
CharacterAppearanceData(aa, ab, RibbonBars())
|
||||
|
|
@ -117,44 +117,43 @@ object AvatarConverter {
|
|||
} else {
|
||||
StatConverter.Health(obj.Armor, MaxArmor)
|
||||
},
|
||||
DressBattleRank(obj),
|
||||
obj.avatar.br.uniformStyle,
|
||||
0,
|
||||
DressCommandRank(obj),
|
||||
MakeImplantEffectList(obj.Implants.toIndexedSeq),
|
||||
MakeCosmetics(obj)
|
||||
obj.avatar.cr.value,
|
||||
obj.avatar.implants.flatten.filter(_.active).flatMap(_.definition.implantType.effect).toList,
|
||||
obj.avatar.cosmetics
|
||||
)
|
||||
}
|
||||
|
||||
def MakeDetailedCharacterData(obj: Player): Option[Int] => DetailedCharacterData = {
|
||||
val bep: Long = obj.BEP
|
||||
val maxOpt: Option[Long] = if (obj.ExoSuit == ExoSuitType.MAX) { Some(0L) }
|
||||
else { None }
|
||||
val ba: DetailedCharacterA = DetailedCharacterA(
|
||||
bep,
|
||||
obj.CEP,
|
||||
obj.avatar.bep,
|
||||
obj.avatar.cep,
|
||||
0L,
|
||||
0L,
|
||||
0L,
|
||||
obj.MaxHealth,
|
||||
obj.Health,
|
||||
false,
|
||||
unk4 = false,
|
||||
obj.Armor,
|
||||
0L,
|
||||
obj.MaxStamina,
|
||||
obj.Stamina,
|
||||
obj.avatar.maxStamina,
|
||||
obj.avatar.stamina,
|
||||
maxOpt,
|
||||
0,
|
||||
0,
|
||||
0L,
|
||||
List(0, 0, 0, 0, 0, 0),
|
||||
obj.Certifications.toList.sortBy(_.id) //TODO is sorting necessary?
|
||||
obj.avatar.certifications.toList.sortBy(_.value) //TODO is sorting necessary?
|
||||
)
|
||||
val bb: (Long, Option[Int]) => DetailedCharacterB = DetailedCharacterB(
|
||||
None,
|
||||
MakeImplantEntries(obj),
|
||||
obj.avatar.implants.flatten.map(_.toEntry).toList,
|
||||
Nil,
|
||||
Nil,
|
||||
obj.FirstTimeEvents,
|
||||
obj.avatar.firstTimeEvents.toList,
|
||||
tutorials = List.empty[String], //TODO tutorial list
|
||||
0L,
|
||||
0L,
|
||||
|
|
@ -165,9 +164,9 @@ object AvatarConverter {
|
|||
Nil,
|
||||
Nil,
|
||||
false,
|
||||
MakeCosmetics(obj)
|
||||
obj.avatar.cosmetics
|
||||
)
|
||||
pad_length: Option[Int] => DetailedCharacterData(ba, bb(bep, pad_length))(pad_length)
|
||||
pad_length: Option[Int] => DetailedCharacterData(ba, bb(obj.avatar.bep, pad_length))(pad_length)
|
||||
}
|
||||
|
||||
def MakeInventoryData(obj: Player): InventoryData = {
|
||||
|
|
@ -180,98 +179,6 @@ object AvatarConverter {
|
|||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Select the appropriate `UniformStyle` design for a player's accumulated battle experience points.
|
||||
* At certain battle ranks, all exo-suits undergo some form of coloration change.
|
||||
* @param obj the `Player` game object
|
||||
* @return the resulting uniform upgrade level
|
||||
*/
|
||||
private def DressBattleRank(obj: Player): UniformStyle.Value = {
|
||||
val bep: Long = obj.BEP
|
||||
if (bep > 2583440) { //BR25+
|
||||
UniformStyle.ThirdUpgrade
|
||||
} else if (bep > 308989) { //BR14+
|
||||
UniformStyle.SecondUpgrade
|
||||
} else if (bep > 44999) { //BR7+
|
||||
UniformStyle.FirstUpgrade
|
||||
} else { //BR1+
|
||||
UniformStyle.Normal
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Select the appropriate design for a player's accumulated command experience points.
|
||||
* Visual cues for command rank include armlets, anklets, and, finally, a backpack, awarded at different ranks.
|
||||
* @param obj the `Player` game object
|
||||
* @return the resulting uniform upgrade level
|
||||
*/
|
||||
private def DressCommandRank(obj: Player): Int = {
|
||||
val cep = obj.CEP
|
||||
if (cep > 599999) {
|
||||
5
|
||||
} else if (cep > 299999) {
|
||||
4
|
||||
} else if (cep > 149999) {
|
||||
3
|
||||
} else if (cep > 49999) {
|
||||
2
|
||||
} else if (cep > 9999) {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform an `Array` of `Implant` objects into a `List` of `ImplantEntry` objects suitable as packet data.
|
||||
* @param obj the `Player` game object
|
||||
* @return the resulting implant `List`
|
||||
* @see `ImplantEntry` in `DetailedCharacterData`
|
||||
*/
|
||||
private def MakeImplantEntries(obj: Player): List[ImplantEntry] = {
|
||||
//val numImplants : Int = DetailedCharacterData.numberOfImplantSlots(obj.BEP)
|
||||
//val implants = obj.Implants
|
||||
obj.Implants
|
||||
.map({
|
||||
case (implant, initialization, _) =>
|
||||
if (initialization == 0) {
|
||||
ImplantEntry(implant, None)
|
||||
} else {
|
||||
ImplantEntry(implant, Some(math.max(0, initialization).toInt))
|
||||
}
|
||||
})
|
||||
.toList
|
||||
}
|
||||
|
||||
/**
|
||||
* Find and encode implants whose effect will be displayed on this player.
|
||||
* @param implants a `Sequence` of `ImplantSlot` objects
|
||||
* @return the effect of an active implant
|
||||
*/
|
||||
private def MakeImplantEffectList(implants: Seq[(ImplantType.Value, Long, Boolean)]): List[ImplantEffects.Value] = {
|
||||
implants.collect {
|
||||
case (ImplantType.AdvancedRegen, _, true) => ImplantEffects.RegenEffects
|
||||
case (ImplantType.DarklightVision, _, true) => ImplantEffects.DarklightEffects
|
||||
case (ImplantType.PersonalShield, _, true) => ImplantEffects.PersonalShieldEffects
|
||||
case (ImplantType.Surge, _, true) => ImplantEffects.SurgeEffects
|
||||
}.toList
|
||||
}
|
||||
|
||||
/**
|
||||
* Should this player be of battle rank 24 or higher, they will have a mandatory cosmetics object in their bitstream.
|
||||
* Players that have not yet set any cosmetic personal effects will still have this field recorded as `None`
|
||||
* but it must be represented nonetheless.
|
||||
* @param obj the `Player` game object
|
||||
* @see `Cosmetics`
|
||||
* @return the `Cosmetics` options
|
||||
*/
|
||||
def MakeCosmetics(obj: Player): Option[Cosmetics] =
|
||||
if (DetailedCharacterData.isBR24(obj.BEP)) {
|
||||
obj.PersonalStyleFeatures.orElse(Some(Cosmetics()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a player with an inventory, convert the contents of that inventory into converted-decoded packet data.
|
||||
* The inventory is not represented in a `0x17` `Player`, so the conversion is only valid for `0x18` avatars.
|
||||
|
|
@ -363,7 +270,7 @@ object AvatarConverter {
|
|||
if (!iter.hasNext) {
|
||||
list
|
||||
} else {
|
||||
val slot: EquipmentSlot = iter.next
|
||||
val slot: EquipmentSlot = iter.next()
|
||||
if (slot.Equipment.isDefined) {
|
||||
val equip: Equipment = slot.Equipment.get
|
||||
recursiveMakeHolsters(
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package net.psforever.objects.definition.converter
|
||||
|
||||
import net.psforever.objects.avatar.Certification
|
||||
import net.psforever.objects.{Player, Tool}
|
||||
import net.psforever.objects.equipment.EquipmentSlot
|
||||
import net.psforever.packet.game.objectcreate._
|
||||
|
|
@ -42,10 +43,10 @@ class CharacterSelectConverter extends AvatarConverter {
|
|||
CommonFieldData(
|
||||
obj.Faction,
|
||||
bops = false,
|
||||
false,
|
||||
false,
|
||||
alternate = false,
|
||||
v1 = false,
|
||||
None,
|
||||
false,
|
||||
jammered = false,
|
||||
None,
|
||||
v5 = None,
|
||||
PlanetSideGUID(0)
|
||||
|
|
@ -62,38 +63,37 @@ class CharacterSelectConverter extends AvatarConverter {
|
|||
0L,
|
||||
outfit_name = "",
|
||||
outfit_logo = 0,
|
||||
false,
|
||||
unk1 = false,
|
||||
backpack = false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
unk2 = false,
|
||||
unk3 = false,
|
||||
unk4 = false,
|
||||
facingPitch = 0,
|
||||
facingYawUpper = 0,
|
||||
lfs = false,
|
||||
GrenadeState.None,
|
||||
obj.Cloaked,
|
||||
false,
|
||||
false,
|
||||
is_cloaking = obj.Cloaked,
|
||||
unk5 = false,
|
||||
unk6 = false,
|
||||
charging_pose = false,
|
||||
false,
|
||||
unk7 = false,
|
||||
on_zipline = None
|
||||
)
|
||||
CharacterAppearanceData(aa, ab, RibbonBars())
|
||||
}
|
||||
|
||||
private def MakeDetailedCharacterData(obj: Player): Option[Int] => DetailedCharacterData = {
|
||||
val bep: Long = obj.BEP
|
||||
val maxOpt: Option[Long] = if (obj.ExoSuit == ExoSuitType.MAX) { Some(0L) }
|
||||
else { None }
|
||||
val ba: DetailedCharacterA = DetailedCharacterA(
|
||||
bep,
|
||||
obj.CEP,
|
||||
obj.avatar.bep,
|
||||
obj.avatar.cep,
|
||||
0L,
|
||||
0L,
|
||||
0L,
|
||||
1,
|
||||
1,
|
||||
false,
|
||||
unk4 = false,
|
||||
0,
|
||||
0L,
|
||||
1,
|
||||
|
|
@ -103,11 +103,13 @@ class CharacterSelectConverter extends AvatarConverter {
|
|||
0,
|
||||
0L,
|
||||
List(0, 0, 0, 0, 0, 0),
|
||||
certs = List.empty[CertificationType.Value]
|
||||
certs = List.empty[Certification]
|
||||
)
|
||||
|
||||
val bb: (Long, Option[Int]) => DetailedCharacterB = DetailedCharacterB(
|
||||
None,
|
||||
MakeImplantEntries(obj), //necessary for correct stream length
|
||||
// necessary for correct stream length
|
||||
List.fill[ImplantEntry](obj.avatar.br.implantSlots)(ImplantEntry(ImplantType.None, None)),
|
||||
Nil,
|
||||
Nil,
|
||||
firstTimeEvents = List.empty[String],
|
||||
|
|
@ -120,20 +122,10 @@ class CharacterSelectConverter extends AvatarConverter {
|
|||
Some(DCDExtra2(0, 0)),
|
||||
Nil,
|
||||
Nil,
|
||||
false,
|
||||
AvatarConverter.MakeCosmetics(obj)
|
||||
unkC = false,
|
||||
obj.avatar.cosmetics
|
||||
)
|
||||
pad_length: Option[Int] => DetailedCharacterData(ba, bb(bep, pad_length))(pad_length)
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform an `Array` of `Implant` objects into a `List` of `ImplantEntry` objects suitable as packet data.
|
||||
* @param obj the `Player` game object
|
||||
* @return the resulting implant `List`
|
||||
* @see `ImplantEntry` in `DetailedCharacterData`
|
||||
*/
|
||||
private def MakeImplantEntries(obj: Player): List[ImplantEntry] = {
|
||||
List.fill[ImplantEntry](DetailedCharacterData.numberOfImplantSlots(obj.BEP))(ImplantEntry(ImplantType.None, None))
|
||||
pad_length: Option[Int] => DetailedCharacterData(ba, bb(obj.avatar.bep, pad_length))(pad_length)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -152,7 +144,7 @@ class CharacterSelectConverter extends AvatarConverter {
|
|||
if (!iter.hasNext) {
|
||||
list
|
||||
} else {
|
||||
val slot: EquipmentSlot = iter.next
|
||||
val slot: EquipmentSlot = iter.next()
|
||||
slot.Equipment match {
|
||||
case Some(equip: Tool) =>
|
||||
val jammed = equip.Jammed
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
package net.psforever.objects.definition.converter
|
||||
|
||||
import net.psforever.objects.Player
|
||||
import net.psforever.objects.avatar.Certification
|
||||
import net.psforever.objects.equipment.{Equipment, EquipmentSlot}
|
||||
import net.psforever.packet.game.objectcreate._
|
||||
import net.psforever.types.{PlanetSideGUID, _}
|
||||
|
|
@ -37,9 +38,9 @@ class CorpseConverter extends AvatarConverter {
|
|||
obj.Faction,
|
||||
bops = false,
|
||||
alternate = true,
|
||||
false,
|
||||
v1 = false,
|
||||
None,
|
||||
false,
|
||||
jammered = false,
|
||||
None,
|
||||
v5 = None,
|
||||
PlanetSideGUID(0)
|
||||
|
|
@ -56,20 +57,20 @@ class CorpseConverter extends AvatarConverter {
|
|||
0L,
|
||||
outfit_name = "",
|
||||
outfit_logo = 0,
|
||||
false,
|
||||
unk1 = false,
|
||||
backpack = true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
unk2 = false,
|
||||
unk3 = false,
|
||||
unk4 = false,
|
||||
facingPitch = 0,
|
||||
facingYawUpper = 0,
|
||||
lfs = false,
|
||||
GrenadeState.None,
|
||||
is_cloaking = false,
|
||||
false,
|
||||
false,
|
||||
unk5 = false,
|
||||
unk6 = false,
|
||||
charging_pose = false,
|
||||
false,
|
||||
unk7 = false,
|
||||
on_zipline = None
|
||||
)
|
||||
CharacterAppearanceData(aa, ab, RibbonBars())
|
||||
|
|
@ -86,7 +87,7 @@ class CorpseConverter extends AvatarConverter {
|
|||
0L,
|
||||
0,
|
||||
0,
|
||||
false,
|
||||
unk4 = false,
|
||||
0,
|
||||
0L,
|
||||
0,
|
||||
|
|
@ -96,7 +97,7 @@ class CorpseConverter extends AvatarConverter {
|
|||
0,
|
||||
0L,
|
||||
List(0, 0, 0, 0, 0, 0),
|
||||
certs = List.empty[CertificationType.Value]
|
||||
certs = List.empty[Certification]
|
||||
)
|
||||
val bb: (Long, Option[Int]) => DetailedCharacterB = DetailedCharacterB(
|
||||
None,
|
||||
|
|
@ -113,7 +114,7 @@ class CorpseConverter extends AvatarConverter {
|
|||
Some(DCDExtra2(0, 0)),
|
||||
Nil,
|
||||
Nil,
|
||||
false,
|
||||
unkC = false,
|
||||
cosmetics = None
|
||||
)
|
||||
(pad_length: Option[Int]) => DetailedCharacterData(ba, bb(0, pad_length))(pad_length)
|
||||
|
|
@ -161,7 +162,7 @@ class CorpseConverter extends AvatarConverter {
|
|||
if (!iter.hasNext) {
|
||||
list
|
||||
} else {
|
||||
val slot: EquipmentSlot = iter.next
|
||||
val slot: EquipmentSlot = iter.next()
|
||||
if (slot.Equipment.isDefined) {
|
||||
val equip: Equipment = slot.Equipment.get
|
||||
recursiveMakeHolsters(
|
||||
|
|
|
|||
|
|
@ -159,7 +159,7 @@ trait JammableBehavior {
|
|||
if (!jammedSound) {
|
||||
jammedSound = true
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
jammeredSoundTimer.cancel
|
||||
jammeredSoundTimer.cancel()
|
||||
jammeredSoundTimer =
|
||||
context.system.scheduler.scheduleOnce(dur milliseconds, self, JammableUnit.ClearJammeredSound())
|
||||
}
|
||||
|
|
@ -174,7 +174,7 @@ trait JammableBehavior {
|
|||
*/
|
||||
def StartJammeredStatus(target: Any, dur: Int): Unit = {
|
||||
JammableObject.Jammed = true
|
||||
jammeredStatusTimer.cancel
|
||||
jammeredStatusTimer.cancel()
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
jammeredStatusTimer =
|
||||
context.system.scheduler.scheduleOnce(dur milliseconds, self, JammableUnit.ClearJammeredStatus())
|
||||
|
|
@ -188,7 +188,7 @@ trait JammableBehavior {
|
|||
*/
|
||||
def CancelJammeredSound(target: Any): Unit = {
|
||||
jammedSound = false
|
||||
jammeredSoundTimer.cancel
|
||||
jammeredSoundTimer.cancel()
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -199,7 +199,7 @@ trait JammableBehavior {
|
|||
*/
|
||||
def CancelJammeredStatus(target: Any): Unit = {
|
||||
JammableObject.Jammed = false
|
||||
jammeredStatusTimer.cancel
|
||||
jammeredStatusTimer.cancel()
|
||||
}
|
||||
|
||||
val jammableBehavior: Receive = {
|
||||
|
|
@ -230,7 +230,7 @@ trait JammableMountedWeapons extends JammableBehavior {
|
|||
target match {
|
||||
case obj: PlanetSideServerObject with MountedWeapons with JammableUnit if !jammedSound =>
|
||||
obj.Zone.VehicleEvents ! VehicleServiceMessage(
|
||||
obj.Zone.Id,
|
||||
obj.Zone.id,
|
||||
VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, obj.GUID, 27, 1)
|
||||
)
|
||||
super.StartJammeredSound(target, dur)
|
||||
|
|
@ -251,7 +251,7 @@ trait JammableMountedWeapons extends JammableBehavior {
|
|||
target match {
|
||||
case obj: PlanetSideServerObject if jammedSound =>
|
||||
obj.Zone.VehicleEvents ! VehicleServiceMessage(
|
||||
obj.Zone.Id,
|
||||
obj.Zone.id,
|
||||
VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, obj.GUID, 27, 0)
|
||||
)
|
||||
case _ => ;
|
||||
|
|
@ -280,7 +280,7 @@ object JammableMountedWeapons {
|
|||
*/
|
||||
def JammeredStatus(target: PlanetSideServerObject with MountedWeapons, statusCode: Int): Unit = {
|
||||
val zone = target.Zone
|
||||
val zoneId = zone.Id
|
||||
val zoneId = zone.id
|
||||
target.Weapons.values
|
||||
.map { _.Equipment }
|
||||
.collect {
|
||||
|
|
|
|||
|
|
@ -153,7 +153,7 @@ object GUIDTask {
|
|||
*/
|
||||
def RegisterAvatar(tplayer: Player)(implicit guid: ActorRef): TaskResolver.GiveTask = {
|
||||
val holsterTasks = VisibleSlotTaskBuilding(tplayer.Holsters(), RegisterEquipment)
|
||||
val lockerTask = List(RegisterLocker(tplayer.Locker))
|
||||
val lockerTask = List(RegisterLocker(tplayer.avatar.locker))
|
||||
val inventoryTasks = RegisterInventory(tplayer)
|
||||
TaskResolver.GiveTask(RegisterObjectTask(tplayer).task, holsterTasks ++ lockerTask ++ inventoryTasks)
|
||||
}
|
||||
|
|
@ -311,7 +311,7 @@ object GUIDTask {
|
|||
*/
|
||||
def UnregisterAvatar(tplayer: Player)(implicit guid: ActorRef): TaskResolver.GiveTask = {
|
||||
val holsterTasks = VisibleSlotTaskBuilding(tplayer.Holsters(), UnregisterEquipment)
|
||||
val lockerTask = List(UnregisterLocker(tplayer.Locker))
|
||||
val lockerTask = List(UnregisterLocker(tplayer.avatar.locker))
|
||||
val inventoryTasks = UnregisterInventory(tplayer)
|
||||
TaskResolver.GiveTask(UnregisterObjectTask(tplayer).task, holsterTasks ++ lockerTask ++ inventoryTasks)
|
||||
}
|
||||
|
|
@ -392,7 +392,7 @@ object GUIDTask {
|
|||
if (!iter.hasNext) {
|
||||
list
|
||||
} else {
|
||||
iter.next.Equipment match {
|
||||
iter.next().Equipment match {
|
||||
case Some(item) =>
|
||||
recursiveVisibleSlotTaskBuilding(iter, func, list :+ func(item))
|
||||
case None =>
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ class TaskResolver() extends Actor {
|
|||
TimeoutCleanup()
|
||||
|
||||
case msg =>
|
||||
log.warn(s"$self received an unexpected message $msg from $sender")
|
||||
log.warn(s"$self received an unexpected message $msg from ${sender()}")
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -408,7 +408,7 @@ object TaskResolver {
|
|||
if (!iter.hasNext) {
|
||||
None
|
||||
} else {
|
||||
val index: Int = iter.next
|
||||
val index: Int = iter.next()
|
||||
if (tasks(index).task.isComplete == resolution) {
|
||||
Some(index)
|
||||
} else {
|
||||
|
|
@ -428,7 +428,7 @@ object TaskResolver {
|
|||
if (!iter.hasNext) {
|
||||
true
|
||||
} else {
|
||||
if (iter.next.isComplete == resolution) {
|
||||
if (iter.next().isComplete == resolution) {
|
||||
filterCompletionMatch(iter, resolution)
|
||||
} else {
|
||||
false
|
||||
|
|
@ -452,7 +452,7 @@ object TaskResolver {
|
|||
if (!iter.hasNext) {
|
||||
indexList
|
||||
} else {
|
||||
val index: Int = iter.next
|
||||
val index: Int = iter.next()
|
||||
val taskEntry = tasks(index)
|
||||
if (
|
||||
taskEntry.Executing && taskEntry.task.isComplete == Task.Resolution.Incomplete && now - taskEntry.Start > taskEntry.task.Timeout
|
||||
|
|
@ -476,7 +476,7 @@ object TaskResolver {
|
|||
if (!iter.hasNext) {
|
||||
None
|
||||
} else {
|
||||
if (iter.next.task == target) {
|
||||
if (iter.next().task == target) {
|
||||
Some(index)
|
||||
} else {
|
||||
findTask(iter, target, index + 1)
|
||||
|
|
@ -496,7 +496,7 @@ object TaskResolver {
|
|||
if (!iter.hasNext) {
|
||||
None
|
||||
} else {
|
||||
val tEntry = iter.next
|
||||
val tEntry = iter.next()
|
||||
if (tEntry.subtasks.contains(target)) {
|
||||
Some(index)
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ class NumberPoolActor(pool: NumberPool) extends Actor {
|
|||
|
||||
def receive: Receive = {
|
||||
case NumberPoolActor.GetAnyNumber(id) =>
|
||||
sender ! (pool.Get() match {
|
||||
sender() ! (pool.Get() match {
|
||||
case Success(value) =>
|
||||
NumberPoolActor.GiveNumber(value, id)
|
||||
case Failure(ex) => ;
|
||||
|
|
@ -30,7 +30,7 @@ class NumberPoolActor(pool: NumberPool) extends Actor {
|
|||
})
|
||||
|
||||
case NumberPoolActor.GetSpecificNumber(number, id) =>
|
||||
sender ! (NumberPoolActor.GetSpecificNumber(pool, number) match {
|
||||
sender() ! (NumberPoolActor.GetSpecificNumber(pool, number) match {
|
||||
case Success(value) =>
|
||||
NumberPoolActor.GiveNumber(value, id)
|
||||
case Failure(ex) => ;
|
||||
|
|
@ -41,7 +41,7 @@ class NumberPoolActor(pool: NumberPool) extends Actor {
|
|||
val result = pool.Return(number)
|
||||
val ex: Option[Throwable] = if (!result) { Some(new Exception("number was not returned")) }
|
||||
else { None }
|
||||
sender ! NumberPoolActor.ReturnNumberResult(number, ex, id)
|
||||
sender() ! NumberPoolActor.ReturnNumberResult(number, ex, id)
|
||||
|
||||
case msg =>
|
||||
log.info(s"received an unexpected message - ${msg.toString}")
|
||||
|
|
|
|||
|
|
@ -126,11 +126,11 @@ class UniqueNumberSystem(private val guid: NumberPoolHub, private val poolActors
|
|||
case Some(entry) =>
|
||||
entry.replyTo ! Failure(new Exception(s"for ${entry.target} with number $number, ${ex.getMessage}"))
|
||||
case None => ;
|
||||
log.error(s"could not find original request $nid that caused error $ex, but pool was $sender")
|
||||
log.error(s"could not find original request $nid that caused error $ex, but pool was ${sender()}")
|
||||
//no callback is possible
|
||||
}
|
||||
case _ => ;
|
||||
log.error(s"could not find original request $id that caused error $ex, but pool was $sender")
|
||||
log.error(s"could not find original request $id that caused error $ex, but pool was ${sender()}")
|
||||
//no callback is possible
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -287,7 +287,7 @@ class GridInventory extends Container {
|
|||
if (!cells.hasNext) {
|
||||
None
|
||||
} else {
|
||||
val index = cells.next + offset
|
||||
val index = cells.next() + offset
|
||||
CheckCollisionsAsGrid(index, tWidth, tHeight) match {
|
||||
case Success(Nil) =>
|
||||
Some(index)
|
||||
|
|
@ -466,7 +466,7 @@ class GridInventory extends Container {
|
|||
if (!iter.hasNext) {
|
||||
None
|
||||
} else {
|
||||
val index = iter.next
|
||||
val index = iter.next()
|
||||
if (items(index).obj.GUID == guid) {
|
||||
Some(index)
|
||||
} else {
|
||||
|
|
@ -567,7 +567,7 @@ class GridInventory extends Container {
|
|||
updated: List[List[Int]]
|
||||
): List[List[Int]] = {
|
||||
if (original.hasNext) {
|
||||
val target = original.next
|
||||
val target = original.next()
|
||||
val filtered = updated.filterNot(item => item.equals(target))
|
||||
val newupdated = if (filtered.size == updated.size) {
|
||||
updated //the lists are the same size, nothing was filtered
|
||||
|
|
@ -588,7 +588,7 @@ class GridInventory extends Container {
|
|||
*/
|
||||
def Clear(): List[InventoryItem] = {
|
||||
val list = items.values.toList
|
||||
items.clear
|
||||
items.clear()
|
||||
entryIndex.set(0)
|
||||
grid = SetCellsOnlyNoOffset(0, width, height)
|
||||
list
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package net.psforever.objects.loadouts
|
||||
|
||||
import net.psforever.types.{CertificationType, ExoSuitType}
|
||||
import net.psforever.objects.avatar.Certification
|
||||
import net.psforever.types.ExoSuitType
|
||||
|
||||
/**
|
||||
* A blueprint of a player's uniform, their holster items, and their inventory items, saved in a specific state.
|
||||
|
|
@ -12,6 +13,7 @@ import net.psforever.types.{CertificationType, ExoSuitType}
|
|||
* The ten-long list is initialized with `FavoritesMessage` packets assigned to the "Infantry" list.
|
||||
* Specific entries are added or removed using `FavoritesRequest` packets,
|
||||
* re-established using other conventional game packets.
|
||||
*
|
||||
* @param label the name by which this inventory will be known when displayed in a Favorites list;
|
||||
* field gets inherited
|
||||
* @param visible_slots simplified representation of the `Equipment` that can see "seen" on the target;
|
||||
|
|
@ -106,15 +108,16 @@ object InfantryLoadout {
|
|||
* Assuming the exo-suit is a mechanized assault type,
|
||||
* use the subtype to determine what certifications would be valid for permitted access to that specific exo-suit.
|
||||
* The "C" does not stand for "certification."
|
||||
*
|
||||
* @see `CertificationType`
|
||||
* @param subtype the numeric subtype
|
||||
* @return a `Set` of all certifications that would grant access to the mechanized assault exo-suit subtype
|
||||
*/
|
||||
def DetermineSubtypeC(subtype: Int): Set[CertificationType.Value] =
|
||||
def DetermineSubtypeC(subtype: Int): Set[Certification] =
|
||||
subtype match {
|
||||
case 1 => Set(CertificationType.AIMAX, CertificationType.UniMAX)
|
||||
case 2 => Set(CertificationType.AVMAX, CertificationType.UniMAX)
|
||||
case 3 => Set(CertificationType.AAMAX, CertificationType.UniMAX)
|
||||
case _ => Set.empty[CertificationType.Value]
|
||||
case 1 => Set(Certification.AIMAX, Certification.UniMAX)
|
||||
case 2 => Set(Certification.AVMAX, Certification.UniMAX)
|
||||
case 3 => Set(Certification.AAMAX, Certification.UniMAX)
|
||||
case _ => Set.empty[Certification]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -203,7 +203,7 @@ object Loadout {
|
|||
if (!iter.hasNext) {
|
||||
list
|
||||
} else {
|
||||
val entry = iter.next
|
||||
val entry = iter.next()
|
||||
entry.Equipment match {
|
||||
case Some(obj) =>
|
||||
recursiveHolsterSimplifications(iter, index + 1, list :+ SimplifiedEntry(buildSimplification(obj), index))
|
||||
|
|
@ -231,7 +231,7 @@ object Loadout {
|
|||
if (!iter.hasNext) {
|
||||
list
|
||||
} else {
|
||||
val entry = iter.next
|
||||
val entry = iter.next()
|
||||
val fmodeSimp = if (entry.Box.AmmoType == entry.AmmoType) {
|
||||
ShorthandAmmoSlot(
|
||||
entry.AmmoTypeIndex,
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
// Copyright (c) 2019 PSForever
|
||||
package net.psforever.objects.loadouts
|
||||
|
||||
import net.psforever.types.CertificationType
|
||||
import net.psforever.objects.avatar.Certification
|
||||
|
||||
final case class SquadPositionLoadout(
|
||||
index: Int,
|
||||
role: String,
|
||||
orders: String,
|
||||
requirements: Set[CertificationType.Value]
|
||||
requirements: Set[Certification]
|
||||
)
|
||||
|
||||
final case class SquadLoadout(task: String, zone_id: Option[Int], members: List[SquadPositionLoadout])
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ object FactionAffinityBehavior {
|
|||
val convertBehavior: Receive = {
|
||||
case FactionAffinity.ConvertFactionAffinity(faction) =>
|
||||
FactionObject.Faction = faction
|
||||
sender ! FactionAffinity.AssertFactionAffinity(FactionObject, faction)
|
||||
sender() ! FactionAffinity.AssertFactionAffinity(FactionObject, faction)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -36,7 +36,7 @@ object FactionAffinityBehavior {
|
|||
|
||||
val checkBehavior: Receive = {
|
||||
case FactionAffinity.ConfirmFactionAffinity() | FactionAffinity.AssertFactionAffinity(_, _) =>
|
||||
sender ! FactionAffinity.AssertFactionAffinity(FactionObject, FactionObject.Faction)
|
||||
sender() ! FactionAffinity.AssertFactionAffinity(FactionObject, FactionObject.Faction)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -59,36 +59,36 @@ trait ContainableBehavior {
|
|||
|
||||
case msg: ContainableMsg if waitOnMoveItemOps == 2 =>
|
||||
//all standard messages are blocked
|
||||
RepeatMessageLater(ContainableBehavior.Defer(msg, sender))
|
||||
RepeatMessageLater(ContainableBehavior.Defer(msg, sender()))
|
||||
MessageDeferredCallback(msg)
|
||||
|
||||
case msg: DeferrableMsg if waitOnMoveItemOps == 1 =>
|
||||
//insertion messages not related to an item move attempt are blocked
|
||||
RepeatMessageLater(ContainableBehavior.Defer(msg, sender))
|
||||
RepeatMessageLater(ContainableBehavior.Defer(msg, sender()))
|
||||
MessageDeferredCallback(msg)
|
||||
|
||||
/* normal messages */
|
||||
case Containable.RemoveItemFromSlot(None, Some(slot)) =>
|
||||
sender ! LocalRemoveItemFromSlot(slot)
|
||||
sender() ! LocalRemoveItemFromSlot(slot)
|
||||
|
||||
case Containable.RemoveItemFromSlot(Some(item), _) =>
|
||||
sender ! LocalRemoveItemFromSlot(item)
|
||||
sender() ! LocalRemoveItemFromSlot(item)
|
||||
|
||||
case Containable.PutItemInSlot(item, dest) =>
|
||||
/* can be deferred */
|
||||
sender ! LocalPutItemInSlot(item, dest)
|
||||
sender() ! LocalPutItemInSlot(item, dest)
|
||||
|
||||
case Containable.PutItemInSlotOnly(item, dest) =>
|
||||
/* can be deferred */
|
||||
sender ! LocalPutItemInSlotOnly(item, dest)
|
||||
sender() ! LocalPutItemInSlotOnly(item, dest)
|
||||
|
||||
case Containable.PutItemAway(item) =>
|
||||
/* can be deferred */
|
||||
sender ! LocalPutItemAway(item)
|
||||
sender() ! LocalPutItemAway(item)
|
||||
|
||||
case Containable.PutItemInSlotOrAway(item, dest) =>
|
||||
/* can be deferred */
|
||||
sender ! LocalPutItemInSlotOrAway(item, dest)
|
||||
sender() ! LocalPutItemInSlotOrAway(item, dest)
|
||||
|
||||
case msg @ Containable.MoveItem(destination, equipment, destSlot) =>
|
||||
/* can be deferred */
|
||||
|
|
@ -146,10 +146,10 @@ trait ContainableBehavior {
|
|||
}
|
||||
|
||||
case ContainableBehavior.MoveItemPutItemInSlot(item, dest) =>
|
||||
sender ! LocalPutItemInSlot(item, dest)
|
||||
sender() ! LocalPutItemInSlot(item, dest)
|
||||
|
||||
case ContainableBehavior.MoveItemPutItemInSlotOrAway(item, dest) =>
|
||||
sender ! LocalPutItemInSlotOrAway(item, dest)
|
||||
sender() ! LocalPutItemInSlotOrAway(item, dest)
|
||||
}
|
||||
|
||||
/* Functions (message control) */
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ object DamageableAmenity {
|
|||
*/
|
||||
def DestructionAwareness(target: Damageable.Target, cause: ResolvedProjectile): Unit = {
|
||||
val zone = target.Zone
|
||||
val zoneId = zone.Id
|
||||
val zoneId = zone.id
|
||||
val events = zone.AvatarEvents
|
||||
val targetGUID = target.GUID
|
||||
events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(targetGUID, 50, 1))
|
||||
|
|
|
|||
|
|
@ -167,7 +167,7 @@ object DamageableEntity {
|
|||
if (!target.Destroyed) {
|
||||
val tguid = target.GUID
|
||||
zone.AvatarEvents ! AvatarServiceMessage(
|
||||
zone.Id,
|
||||
zone.id,
|
||||
AvatarAction.PlanetsideAttributeToAll(tguid, 0, target.Health)
|
||||
)
|
||||
}
|
||||
|
|
@ -195,7 +195,7 @@ object DamageableEntity {
|
|||
target.Actor ! JammableUnit.ClearJammeredStatus()
|
||||
//
|
||||
val zone = target.Zone
|
||||
val zoneId = zone.Id
|
||||
val zoneId = zone.id
|
||||
val tguid = target.GUID
|
||||
val attribution = target.Zone.LivePlayers.find { p => cause.projectile.owner.Name.equals(p.Name) } match {
|
||||
case Some(player) => player.GUID
|
||||
|
|
|
|||
|
|
@ -180,7 +180,7 @@ object DamageableVehicle {
|
|||
if (target.Shields > 0) {
|
||||
target.Shields = 0
|
||||
zone.VehicleEvents ! VehicleServiceMessage(
|
||||
zone.Id,
|
||||
zone.id,
|
||||
VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, target.GUID, 68, 0)
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ object DamageableWeaponTurret {
|
|||
def DestructionAwareness(target: Damageable.Target with MountedWeapons, cause: ResolvedProjectile): Unit = {
|
||||
//wreckage has no (visible) mounted weapons
|
||||
val zone = target.Zone
|
||||
val zoneId = zone.Id
|
||||
val zoneId = zone.id
|
||||
val avatarEvents = zone.AvatarEvents
|
||||
target.Weapons.values
|
||||
.filter {
|
||||
|
|
|
|||
|
|
@ -25,114 +25,133 @@ trait DeploymentBehavior {
|
|||
|
||||
val deployBehavior: Receive = {
|
||||
case Deployment.TryDeploymentChange(state) =>
|
||||
sender ! TryDeploymentStateChange(state)
|
||||
sender() ! TryDeploymentStateChange(state)
|
||||
|
||||
case Deployment.TryDeploy(state) =>
|
||||
sender ! TryDeployStateChange(state)
|
||||
sender() ! TryDeployStateChange(state)
|
||||
|
||||
case Deployment.TryUndeploy(state) =>
|
||||
sender ! TryUndeployStateChange(state)
|
||||
sender() ! TryUndeployStateChange(state)
|
||||
}
|
||||
|
||||
def TryDeploymentStateChange(state : DriveState.Value) : Any = {
|
||||
val obj = DeploymentObject
|
||||
def TryDeploymentStateChange(state: DriveState.Value): Any = {
|
||||
val obj = DeploymentObject
|
||||
val prevState = obj.DeploymentState
|
||||
if(TryDeploymentChange(obj, state)) {
|
||||
if(Deployment.CheckForDeployState(state)) {
|
||||
if (TryDeploymentChange(obj, state)) {
|
||||
if (Deployment.CheckForDeployState(state)) {
|
||||
DeploymentAction(obj, state, prevState)
|
||||
Deployment.CanDeploy(obj, state)
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
UndeploymentAction(obj, state, prevState)
|
||||
Deployment.CanUndeploy(obj, state)
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
Deployment.CanNotChangeDeployment(obj, state, "incorrect transition state")
|
||||
}
|
||||
}
|
||||
|
||||
def TryDeployStateChange(state : DriveState.Value) : Any = {
|
||||
val obj = DeploymentObject
|
||||
def TryDeployStateChange(state: DriveState.Value): Any = {
|
||||
val obj = DeploymentObject
|
||||
val prevState = obj.DeploymentState
|
||||
if(Deployment.CheckForDeployState(state) && TryDeploymentChange(obj, state)) {
|
||||
if (Deployment.CheckForDeployState(state) && TryDeploymentChange(obj, state)) {
|
||||
DeploymentAction(obj, state, prevState)
|
||||
Deployment.CanDeploy(obj, state)
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
Deployment.CanNotChangeDeployment(obj, state, "incorrect deploy transition state")
|
||||
}
|
||||
}
|
||||
|
||||
def TryUndeployStateChange(state : DriveState.Value) : Any = {
|
||||
val obj = DeploymentObject
|
||||
def TryUndeployStateChange(state: DriveState.Value): Any = {
|
||||
val obj = DeploymentObject
|
||||
val prevState = obj.DeploymentState
|
||||
if(Deployment.CheckForUndeployState(state) && TryUndeploymentChange(obj, state)) {
|
||||
if (Deployment.CheckForUndeployState(state) && TryUndeploymentChange(obj, state)) {
|
||||
UndeploymentAction(obj, state, prevState)
|
||||
Deployment.CanUndeploy(obj, state)
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
Deployment.CanNotChangeDeployment(obj, state, "incorrect undeploy transition state")
|
||||
}
|
||||
}
|
||||
|
||||
def TryDeploymentChange(obj : Deployment.DeploymentObject, state : DriveState.Value) : Boolean = {
|
||||
def TryDeploymentChange(obj: Deployment.DeploymentObject, state: DriveState.Value): Boolean = {
|
||||
DeploymentBehavior.TryDeploymentChange(obj, state)
|
||||
}
|
||||
|
||||
def TryUndeploymentChange(obj : Deployment.DeploymentObject, state : DriveState.Value) : Boolean = {
|
||||
def TryUndeploymentChange(obj: Deployment.DeploymentObject, state: DriveState.Value): Boolean = {
|
||||
DeploymentBehavior.TryDeploymentChange(obj, state)
|
||||
}
|
||||
|
||||
def DeploymentAction(obj : Deployment.DeploymentObject, state : DriveState.Value, prevState : DriveState.Value) : DriveState.Value = {
|
||||
val guid = obj.GUID
|
||||
val zone = obj.Zone
|
||||
val zoneChannel = zone.Id
|
||||
val GUID0 = Service.defaultPlayerGUID
|
||||
def DeploymentAction(
|
||||
obj: Deployment.DeploymentObject,
|
||||
state: DriveState.Value,
|
||||
prevState: DriveState.Value
|
||||
): DriveState.Value = {
|
||||
val guid = obj.GUID
|
||||
val zone = obj.Zone
|
||||
val zoneChannel = zone.id
|
||||
val GUID0 = Service.defaultPlayerGUID
|
||||
//TODO remove this arbitrary allowance angle when no longer helpful
|
||||
if(obj.Orientation.x > 30 && obj.Orientation.x < 330) {
|
||||
if (obj.Orientation.x > 30 && obj.Orientation.x < 330) {
|
||||
obj.DeploymentState = prevState
|
||||
prevState
|
||||
}
|
||||
else if(state == DriveState.Deploying) {
|
||||
} else if (state == DriveState.Deploying) {
|
||||
obj.Velocity = Some(Vector3.Zero) //no velocity
|
||||
zone.VehicleEvents ! VehicleServiceMessage(zoneChannel, VehicleAction.DeployRequest(GUID0, guid, state, 0, false, Vector3.Zero))
|
||||
context.system.scheduler.scheduleOnce(obj.DeployTime milliseconds, obj.Actor, Deployment.TryDeploy(DriveState.Deployed))
|
||||
zone.VehicleEvents ! VehicleServiceMessage(
|
||||
zoneChannel,
|
||||
VehicleAction.DeployRequest(GUID0, guid, state, 0, false, Vector3.Zero)
|
||||
)
|
||||
context.system.scheduler.scheduleOnce(
|
||||
obj.DeployTime milliseconds,
|
||||
obj.Actor,
|
||||
Deployment.TryDeploy(DriveState.Deployed)
|
||||
)
|
||||
state
|
||||
}
|
||||
else if(state == DriveState.Deployed) {
|
||||
} else if (state == DriveState.Deployed) {
|
||||
obj.Velocity = Some(Vector3.Zero) //no velocity
|
||||
zone.VehicleEvents ! VehicleServiceMessage(zoneChannel, VehicleAction.DeployRequest(GUID0, guid, state, 0, false, Vector3.Zero))
|
||||
zone.VehicleEvents ! VehicleServiceMessage(
|
||||
zoneChannel,
|
||||
VehicleAction.DeployRequest(GUID0, guid, state, 0, false, Vector3.Zero)
|
||||
)
|
||||
state
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
prevState
|
||||
}
|
||||
}
|
||||
|
||||
def UndeploymentAction(obj : Deployment.DeploymentObject, state : DriveState.Value, prevState : DriveState.Value) : DriveState.Value = {
|
||||
val guid = obj.GUID
|
||||
val zone = obj.Zone
|
||||
val zoneChannel = zone.Id
|
||||
val GUID0 = Service.defaultPlayerGUID
|
||||
if(state == DriveState.Undeploying) {
|
||||
zone.VehicleEvents ! VehicleServiceMessage(zoneChannel, VehicleAction.DeployRequest(GUID0, guid, state, 0, false, Vector3.Zero))
|
||||
def UndeploymentAction(
|
||||
obj: Deployment.DeploymentObject,
|
||||
state: DriveState.Value,
|
||||
prevState: DriveState.Value
|
||||
): DriveState.Value = {
|
||||
val guid = obj.GUID
|
||||
val zone = obj.Zone
|
||||
val zoneChannel = zone.id
|
||||
val GUID0 = Service.defaultPlayerGUID
|
||||
if (state == DriveState.Undeploying) {
|
||||
zone.VehicleEvents ! VehicleServiceMessage(
|
||||
zoneChannel,
|
||||
VehicleAction.DeployRequest(GUID0, guid, state, 0, false, Vector3.Zero)
|
||||
)
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
context.system.scheduler.scheduleOnce(obj.UndeployTime milliseconds, obj.Actor, Deployment.TryUndeploy(DriveState.Mobile))
|
||||
context.system.scheduler.scheduleOnce(
|
||||
obj.UndeployTime milliseconds,
|
||||
obj.Actor,
|
||||
Deployment.TryUndeploy(DriveState.Mobile)
|
||||
)
|
||||
state
|
||||
}
|
||||
else if(state == DriveState.Mobile) {
|
||||
zone.VehicleEvents ! VehicleServiceMessage(zoneChannel, VehicleAction.DeployRequest(GUID0, guid, state, 0, false, Vector3.Zero))
|
||||
} else if (state == DriveState.Mobile) {
|
||||
zone.VehicleEvents ! VehicleServiceMessage(
|
||||
zoneChannel,
|
||||
VehicleAction.DeployRequest(GUID0, guid, state, 0, false, Vector3.Zero)
|
||||
)
|
||||
state
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
prevState
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object DeploymentBehavior {
|
||||
def TryDeploymentChange(obj : Deployment.DeploymentObject, state : DriveState.Value) : Boolean = {
|
||||
def TryDeploymentChange(obj: Deployment.DeploymentObject, state: DriveState.Value): Boolean = {
|
||||
Deployment.NextState(obj.DeploymentState) == state && (obj.DeploymentState = state) == state
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,9 +14,9 @@ class DoorControl(door: Door) extends Actor with FactionAffinityBehavior.Check {
|
|||
def receive: Receive =
|
||||
checkBehavior.orElse {
|
||||
case Door.Use(player, msg) =>
|
||||
sender ! Door.DoorMessage(player, msg, door.Use(player, msg))
|
||||
sender() ! Door.DoorMessage(player, msg, door.Use(player, msg))
|
||||
|
||||
case _ =>
|
||||
sender ! Door.NoEvent()
|
||||
sender() ! Door.NoEvent()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ class GeneratorControl(gen: Generator)
|
|||
GeneratorControl.UpdateOwner(gen)
|
||||
//kaboom
|
||||
zone.AvatarEvents ! AvatarServiceMessage(
|
||||
zone.Id,
|
||||
zone.id,
|
||||
AvatarAction.SendResponse(
|
||||
Service.defaultPlayerGUID,
|
||||
TriggerEffectMessage(gen.GUID, "explosion_generator", None, None)
|
||||
|
|
|
|||
|
|
@ -21,19 +21,19 @@ object GenericHackables {
|
|||
* @return the percentage amount of progress per tick
|
||||
*/
|
||||
def GetHackSpeed(player: Player, obj: PlanetSideServerObject): Float = {
|
||||
val playerHackLevel = Player.GetHackLevel(player)
|
||||
val playerHackLevel = player.avatar.hackingSkillLevel()
|
||||
val timeToHack = obj match {
|
||||
case vehicle: Vehicle => vehicle.JackingDuration(playerHackLevel).toFloat
|
||||
case hackable: Hackable => hackable.HackDuration(playerHackLevel).toFloat
|
||||
case _ =>
|
||||
log.warn(
|
||||
s"${player.Name} tried to hack an object that has no hack time defined - ${obj.Definition.Name}#${obj.GUID} on ${obj.Zone.Id}"
|
||||
s"${player.Name} tried to hack an object that has no hack time defined - ${obj.Definition.Name}#${obj.GUID} on ${obj.Zone.id}"
|
||||
)
|
||||
0f
|
||||
}
|
||||
if (timeToHack == 0) {
|
||||
log.warn(
|
||||
s"${player.Name} tried to hack an object that they don't have the correct hacking level for - ${obj.Definition.Name}#${obj.GUID} on ${obj.Zone.Id}"
|
||||
s"${player.Name} tried to hack an object that they don't have the correct hacking level for - ${obj.Definition.Name}#${obj.GUID} on ${obj.Zone.id}"
|
||||
)
|
||||
0f
|
||||
} else {
|
||||
|
|
@ -110,7 +110,7 @@ object GenericHackables {
|
|||
ask(target.Actor, CommonMessages.Hack(tplayer, target))(1 second).mapTo[Boolean].onComplete {
|
||||
case Success(_) =>
|
||||
val zone = target.Zone
|
||||
val zoneId = zone.Id
|
||||
val zoneId = zone.id
|
||||
val pguid = tplayer.GUID
|
||||
zone.LocalEvents ! LocalServiceMessage(
|
||||
zoneId,
|
||||
|
|
@ -118,7 +118,8 @@ object GenericHackables {
|
|||
)
|
||||
zone.LocalEvents ! LocalServiceMessage(
|
||||
zoneId,
|
||||
LocalAction.HackTemporarily(pguid, zone, target, unk, target.HackEffectDuration(Player.GetHackLevel(user)))
|
||||
LocalAction
|
||||
.HackTemporarily(pguid, zone, target, unk, target.HackEffectDuration(user.avatar.hackingSkillLevel()))
|
||||
)
|
||||
case Failure(_) => log.warn(s"Hack message failed on target guid: ${target.GUID}")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ object HackableBehavior {
|
|||
case CommonMessages.Hack(player, _, _) =>
|
||||
val obj = HackableObject
|
||||
obj.HackedBy = player
|
||||
sender ! true
|
||||
sender() ! true
|
||||
|
||||
case CommonMessages.ClearHack() =>
|
||||
val obj = HackableObject
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ class ImplantTerminalMechControl(mech: ImplantTerminalMech)
|
|||
//TODO setup certifications check
|
||||
mech.Owner match {
|
||||
case b: Building if (b.Faction != player.Faction || b.CaptureTerminalIsHacked) && mech.HackedBy.isEmpty =>
|
||||
sender ! CommonMessages.Progress(
|
||||
sender() ! CommonMessages.Progress(
|
||||
GenericHackables.GetHackSpeed(player, mech),
|
||||
GenericHackables.FinishHacking(mech, player, 3212836864L),
|
||||
GenericHackables.HackingTickAction(progressType = 1, player, mech, item.GUID)
|
||||
|
|
@ -60,7 +60,7 @@ class ImplantTerminalMechControl(mech: ImplantTerminalMech)
|
|||
player: Player
|
||||
): Boolean = {
|
||||
val zone = obj.Zone
|
||||
zone.map.TerminalToInterface.get(obj.GUID.guid) match {
|
||||
zone.map.terminalToInterface.get(obj.GUID.guid) match {
|
||||
case Some(interface_guid) =>
|
||||
(zone.GUID(interface_guid) match {
|
||||
case Some(interface) => !interface.Destroyed
|
||||
|
|
|
|||
|
|
@ -26,13 +26,13 @@ class IFFLockControl(lock: IFFLock)
|
|||
case CommonMessages.Use(player, Some(item: SimpleItem))
|
||||
if item.Definition == GlobalDefinitions.remote_electronics_kit =>
|
||||
if (lock.Faction != player.Faction && lock.HackedBy.isEmpty) {
|
||||
sender ! CommonMessages.Progress(
|
||||
sender() ! CommonMessages.Progress(
|
||||
GenericHackables.GetHackSpeed(player, lock),
|
||||
GenericHackables.FinishHacking(lock, player, 1114636288L),
|
||||
GenericHackables.HackingTickAction(progressType = 1, player, lock, item.GUID)
|
||||
)
|
||||
} else if (lock.Faction == player.Faction && lock.HackedBy.nonEmpty) {
|
||||
sender ! CommonMessages.Progress(
|
||||
sender() ! CommonMessages.Progress(
|
||||
GenericHackables.GetHackSpeed(player, lock),
|
||||
IFFLocks.FinishResecuringIFFLock(lock),
|
||||
GenericHackables.HackingTickAction(progressType = 1, player, lock, item.GUID)
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ object IFFLocks {
|
|||
def FinishResecuringIFFLock(lock: IFFLock)(): Unit = {
|
||||
val zone = lock.Zone
|
||||
lock.Zone.LocalEvents ! LocalServiceMessage(
|
||||
zone.Id,
|
||||
zone.id,
|
||||
LocalAction.ClearTemporaryHack(Service.defaultPlayerGUID, lock)
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue