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:
Jakob Gillich 2020-08-01 12:25:03 +02:00
parent 1efbedcf8e
commit 3bdc681c9d
267 changed files with 476963 additions and 133957 deletions

View file

@ -1,7 +1,7 @@
name: CI name: CI
on: [push, pull_request] on: [push, pull_request]
jobs: jobs:
build: test:
runs-on: ubuntu-latest runs-on: ubuntu-latest
services: services:
postgres: postgres:
@ -43,4 +43,23 @@ jobs:
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v2
with: with:
name: pslogin.zip 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

View file

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

View file

@ -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" ":")

View file

@ -3,7 +3,7 @@ import xerial.sbt.pack.PackPlugin._
lazy val commonSettings = Seq( lazy val commonSettings = Seq(
organization := "net.psforever", organization := "net.psforever",
version := "1.0.2-SNAPSHOT", version := "1.0.2-SNAPSHOT",
scalaVersion := "2.13.2", scalaVersion := "2.13.3",
Global / cancelable := false, Global / cancelable := false,
semanticdbEnabled := true, semanticdbEnabled := true,
semanticdbVersion := scalafixSemanticdb.revision, semanticdbVersion := scalafixSemanticdb.revision,
@ -43,40 +43,43 @@ lazy val commonSettings = Seq(
classLoaderLayeringStrategy := ClassLoaderLayeringStrategy.Flat, classLoaderLayeringStrategy := ClassLoaderLayeringStrategy.Flat,
resolvers += "Sonatype OSS Snapshots" at "https://oss.sonatype.org/content/repositories/snapshots", resolvers += "Sonatype OSS Snapshots" at "https://oss.sonatype.org/content/repositories/snapshots",
libraryDependencies ++= Seq( libraryDependencies ++= Seq(
"com.typesafe.akka" %% "akka-actor" % "2.6.6", "com.typesafe.akka" %% "akka-actor" % "2.6.6",
"com.typesafe.akka" %% "akka-slf4j" % "2.6.6", "com.typesafe.akka" %% "akka-slf4j" % "2.6.6",
"com.typesafe.akka" %% "akka-protobuf-v3" % "2.6.6", "com.typesafe.akka" %% "akka-protobuf-v3" % "2.6.6",
"com.typesafe.akka" %% "akka-stream" % "2.6.6", "com.typesafe.akka" %% "akka-stream" % "2.6.6",
"com.typesafe.akka" %% "akka-testkit" % "2.6.6" % "test", "com.typesafe.akka" %% "akka-testkit" % "2.6.6" % "test",
"com.typesafe.akka" %% "akka-actor-typed" % "2.6.6", "com.typesafe.akka" %% "akka-actor-typed" % "2.6.6",
"com.typesafe.akka" %% "akka-cluster-typed" % "2.6.6", "com.typesafe.akka" %% "akka-cluster-typed" % "2.6.6",
"com.typesafe.scala-logging" %% "scala-logging" % "3.9.2", "com.typesafe.scala-logging" %% "scala-logging" % "3.9.2",
"org.specs2" %% "specs2-core" % "4.9.4" % "test", "org.specs2" %% "specs2-core" % "4.9.4" % "test",
"org.scalatest" %% "scalatest" % "3.1.2" % "test", "org.scalatest" %% "scalatest" % "3.1.2" % "test",
"org.scodec" %% "scodec-core" % "1.11.7", "org.scodec" %% "scodec-core" % "1.11.7",
"net.java.dev.jna" % "jna" % "5.5.0", "net.java.dev.jna" % "jna" % "5.5.0",
"com.typesafe.akka" %% "akka-slf4j" % "2.6.5", "com.typesafe.akka" %% "akka-slf4j" % "2.6.5",
"ch.qos.logback" % "logback-classic" % "1.2.3", "ch.qos.logback" % "logback-classic" % "1.2.3",
"org.log4s" %% "log4s" % "1.8.2", "org.log4s" %% "log4s" % "1.8.2",
"org.fusesource.jansi" % "jansi" % "1.12", "org.fusesource.jansi" % "jansi" % "1.12",
"org.scoverage" %% "scalac-scoverage-plugin" % "1.4.1", "org.scoverage" %% "scalac-scoverage-plugin" % "1.4.1",
"com.github.nscala-time" %% "nscala-time" % "2.24.0", "com.github.nscala-time" %% "nscala-time" % "2.24.0",
"com.github.t3hnar" %% "scala-bcrypt" % "4.1", "com.github.t3hnar" %% "scala-bcrypt" % "4.1",
"org.scala-graph" %% "graph-core" % "1.13.1", "org.scala-graph" %% "graph-core" % "1.13.1",
"io.kamon" %% "kamon-bundle" % "2.1.0", "io.kamon" %% "kamon-bundle" % "2.1.0",
"io.kamon" %% "kamon-apm-reporter" % "2.1.0", "io.kamon" %% "kamon-apm-reporter" % "2.1.0",
"org.json4s" %% "json4s-native" % "3.6.8", "org.json4s" %% "json4s-native" % "3.6.8",
"com.typesafe.akka" %% "akka-stream" % "2.6.5", "io.getquill" %% "quill-jasync-postgres" % "3.5.2",
"io.getquill" %% "quill-jasync-postgres" % "3.5.2", "org.flywaydb" % "flyway-core" % "6.5.0",
"org.flywaydb" % "flyway-core" % "6.5.0", "org.postgresql" % "postgresql" % "42.2.14",
"org.postgresql" % "postgresql" % "42.2.14", "com.typesafe" % "config" % "1.4.0",
"com.typesafe" % "config" % "1.4.0", "com.github.pureconfig" %% "pureconfig" % "0.13.0",
"com.github.pureconfig" %% "pureconfig" % "0.13.0", "com.beachape" %% "enumeratum" % "1.6.1",
"com.beachape" %% "enumeratum" % "1.6.1", "joda-time" % "joda-time" % "2.10.6",
"joda-time" % "joda-time" % "2.10.6", "commons-io" % "commons-io" % "2.6",
"commons-io" % "commons-io" % "2.6", "com.github.scopt" %% "scopt" % "4.0.0-RC2",
"com.github.scopt" %% "scopt" % "4.0.0-RC2", "io.sentry" % "sentry-logback" % "1.7.30",
"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 // 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.*" coverageExcludedPackages := "net\\.psforever\\.actors\\.session\\.SessionActor.*;net\\.psforever\\.zones\\.zonemaps.*"

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -140,7 +140,7 @@ class BuildingActor(
} }
building.Faction = faction building.Faction = faction
galaxyService ! GalaxyServiceMessage(GalaxyAction.MapUpdate(building.infoUpdateMessage())) 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 Behaviors.same
case MapUpdate() => case MapUpdate() =>

View file

@ -9,10 +9,12 @@ import net.psforever.objects.serverobject.structures.StructureType
import net.psforever.objects.zones.Zone import net.psforever.objects.zones.Zone
import net.psforever.objects.{ConstructionItem, PlanetSideGameObject, Player, Vehicle} import net.psforever.objects.{ConstructionItem, PlanetSideGameObject, Player, Vehicle}
import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID, Vector3} import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID, Vector3}
import scala.collection.mutable.ListBuffer import scala.collection.mutable.ListBuffer
import akka.actor.typed.scaladsl.adapter._ import akka.actor.typed.scaladsl.adapter._
import net.psforever.util.Database._ import net.psforever.util.Database._
import net.psforever.persistence import net.psforever.persistence
import scala.util.{Failure, Success} import scala.util.{Failure, Success}
import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.ExecutionContext.Implicits.global
@ -29,7 +31,11 @@ object ZoneActor {
final case class GetZone(replyTo: ActorRef[ZoneResponse]) extends Command final case class GetZone(replyTo: ActorRef[ZoneResponse]) extends Command
final case class ZoneResponse(zone: Zone) 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 AddPlayer(player: Player) extends Command
final case class RemovePlayer(player: Player) extends Command final case class RemovePlayer(player: Player) extends Command

View file

@ -57,7 +57,7 @@ class CryptoSessionActor extends Actor with MDCContextAware {
this.sessionId = sharedSessionId this.sessionId = sharedSessionId
leftRef = sender() leftRef = sender()
if (pipe.hasNext) { 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) rightRef !> HelloFriend(sessionId, pipe)
} else { } else {
rightRef = sender() rightRef = sender()

View file

@ -62,7 +62,7 @@ class LoginSessionActor extends Actor with MDCContextAware {
this.sessionId = aSessionId this.sessionId = aSessionId
leftRef = sender() leftRef = sender()
if (pipe.hasNext) { if (pipe.hasNext) {
rightRef = pipe.next rightRef = pipe.next()
rightRef !> HelloFriend(aSessionId, pipe) rightRef !> HelloFriend(aSessionId, pipe)
} else { } else {
rightRef = sender() rightRef = sender()
@ -174,7 +174,7 @@ class LoginSessionActor extends Actor with MDCContextAware {
log.info(s"$account") log.info(s"$account")
(account.inactive, password.isBcrypted(account.passhash)) match { (account.inactive, password.isBcrypted(account.passhash)) match {
case (false, true) => 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( val future = ctx.run(
query[persistence.Login].insert( query[persistence.Login].insert(
_.accountId -> lift(account.id), _.accountId -> lift(account.id),
@ -280,7 +280,7 @@ class LoginSessionActor extends Actor with MDCContextAware {
val r = new scala.util.Random val r = new scala.util.Random
val sb = new StringBuilder val sb = new StringBuilder
for (_ <- 1 to 31) { for (_ <- 1 to 31) {
sb.append(r.nextPrintableChar) sb.append(r.nextPrintableChar())
} }
sb.toString sb.toString
} }

View file

@ -77,7 +77,7 @@ class PacketCodingActor extends Actor with MDCContextAware {
this.sessionId = sharedSessionId this.sessionId = sharedSessionId
leftRef = sender() leftRef = sender()
if (pipe.hasNext) { if (pipe.hasNext) {
rightRef = pipe.next rightRef = pipe.next()
rightRef !> HelloFriend(sessionId, pipe) rightRef !> HelloFriend(sessionId, pipe)
} else { } else {
rightRef = sender() rightRef = sender()
@ -127,14 +127,14 @@ class PacketCodingActor extends Actor with MDCContextAware {
relatedALog.clear() relatedALog.clear()
} }
case RawPacket(msg) => 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) mtuLimit(msg)
} else { //from network, to LSA, WSA, etc. - decode } else { //from network, to LSA, WSA, etc. - decode
UnmarshalInnerPacket(msg, "a packet") UnmarshalInnerPacket(msg, "a packet")
} }
//known elevated packet type //known elevated packet type
case ctrl @ ControlPacket(_, packet) => 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 { PacketCoding.EncodePacket(packet) match {
case Successful(data) => case Successful(data) =>
mtuLimit(data.toByteVector) mtuLimit(data.toByteVector)
@ -148,7 +148,7 @@ class PacketCodingActor extends Actor with MDCContextAware {
} }
//known elevated packet type //known elevated packet type
case game @ GamePacket(_, _, packet) => 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 { PacketCoding.EncodePacket(packet) match {
case Successful(data) => case Successful(data) =>
mtuLimit(data.toByteVector) mtuLimit(data.toByteVector)
@ -166,7 +166,7 @@ class PacketCodingActor extends Actor with MDCContextAware {
handleBundlePacket(list) handleBundlePacket(list)
//etc //etc
case msg => case msg =>
if (sender == rightRef) { if (sender() == rightRef) {
log.trace(s"BASE CASE PACKET SEND, LEFT: $msg") log.trace(s"BASE CASE PACKET SEND, LEFT: $msg")
MDC("sessionId") = sessionId.toString MDC("sessionId") = sessionId.toString
leftRef !> msg leftRef !> msg
@ -421,7 +421,7 @@ class PacketCodingActor extends Actor with MDCContextAware {
out out
} else { } else {
import net.psforever.packet.{PlanetSideControlPacket, PlanetSideGamePacket} import net.psforever.packet.{PlanetSideControlPacket, PlanetSideGamePacket}
iter.next match { iter.next() match {
case msg: PlanetSideGamePacket => case msg: PlanetSideGamePacket =>
PacketCoding.EncodePacket(msg) match { PacketCoding.EncodePacket(msg) match {
case Successful(bytecode) => case Successful(bytecode) =>
@ -462,7 +462,7 @@ class PacketCodingActor extends Actor with MDCContextAware {
if (!iter.hasNext) { if (!iter.hasNext) {
out out
} else { } else {
val data = iter.next val data = iter.next()
var len = data.length.toInt var len = data.length.toInt
len = len + (if (len < 256) { 1 } len = len + (if (len < 256) { 1 }
else if (len < 65536) { 2 } else if (len < 65536) { 2 }

View file

@ -26,8 +26,8 @@ class Session(
) { ) {
var state: SessionState = New() var state: SessionState = New()
val sessionCreatedTime: DateTime = DateTime.now val sessionCreatedTime: DateTime = DateTime.now()
var sessionEndedTime: DateTime = DateTime.now var sessionEndedTime: DateTime = DateTime.now()
val pipeline = sessionPipeline.map { actor => val pipeline = sessionPipeline.map { actor =>
val a = context.actorOf(actor.props, actor.nameTemplate + sessionId.toString) val a = context.actorOf(actor.props, actor.nameTemplate + sessionId.toString)
@ -37,7 +37,7 @@ class Session(
val pipelineIter = pipeline.iterator val pipelineIter = pipeline.iterator
if (pipelineIter.hasNext) { if (pipelineIter.hasNext) {
pipelineIter.next ! HelloFriend(sessionId, pipelineIter) pipelineIter.next() ! HelloFriend(sessionId, pipelineIter)
} }
// statistics // statistics
@ -74,7 +74,7 @@ class Session(
pipeline.foreach(context.unwatch) pipeline.foreach(context.unwatch)
pipeline.foreach(_ ! PoisonPill) pipeline.foreach(_ ! PoisonPill)
sessionEndedTime = DateTime.now sessionEndedTime = DateTime.now()
setState(Closed()) setState(Closed())
} }

View file

@ -54,7 +54,7 @@ class SessionRouter(role: String, pipeline: List[SessionPipeline]) extends Actor
override def supervisorStrategy = OneForOneStrategy() { case _ => Stop } override def supervisorStrategy = OneForOneStrategy() { case _ => Stop }
override def preStart = { override def preStart() = {
log.info(s"SessionRouter (for ${role}s) initializing ...") log.info(s"SessionRouter (for ${role}s) initializing ...")
} }

View file

@ -64,7 +64,7 @@ class UdpNetworkSimulator(server: ActorRef, params: NetworkSimulatorParameters)
// this packet needs to be sent within 20 milliseconds or more // this packet needs to be sent within 20 milliseconds or more
if (lastTime >= 20000000) { if (lastTime >= 20000000) {
server.tell(inPacketQueue.dequeue._1, interface) server.tell(inPacketQueue.dequeue()._1, interface)
} else { } else {
schedule(lastTime.nanoseconds, outbound = false) schedule(lastTime.nanoseconds, outbound = false)
exit = true exit = true
@ -79,7 +79,7 @@ class UdpNetworkSimulator(server: ActorRef, params: NetworkSimulatorParameters)
// this packet needs to be sent within 20 milliseconds or more // this packet needs to be sent within 20 milliseconds or more
if (lastTime >= 20000000) { if (lastTime >= 20000000) {
interface.tell(outPacketQueue.dequeue._1, server) interface.tell(outPacketQueue.dequeue()._1, server)
} else { } else {
schedule(lastTime.nanoseconds, outbound = true) schedule(lastTime.nanoseconds, outbound = true)
exit = true exit = true

View file

@ -328,13 +328,13 @@ object WorldSession {
) )
) )
localZone.AvatarEvents ! AvatarServiceMessage( localZone.AvatarEvents ! AvatarServiceMessage(
localZone.Id, localZone.id,
AvatarAction.ObjectHeld(localGUID, localPlayer.LastDrawnSlot) AvatarAction.ObjectHeld(localGUID, localPlayer.LastDrawnSlot)
) )
} }
localPlayer.DrawnSlot = localSlot localPlayer.DrawnSlot = localSlot
localZone.AvatarEvents ! AvatarServiceMessage( localZone.AvatarEvents ! AvatarServiceMessage(
localZone.Id, localZone.id,
AvatarAction.SendResponse(Service.defaultPlayerGUID, ObjectHeldMessage(localGUID, localSlot, false)) AvatarAction.SendResponse(Service.defaultPlayerGUID, ObjectHeldMessage(localGUID, localSlot, false))
) )
} }
@ -380,7 +380,7 @@ object WorldSession {
case Some(_) => ; case Some(_) => ;
case None => //acting on old data? case None => //acting on old data?
localZone.AvatarEvents ! AvatarServiceMessage( localZone.AvatarEvents ! AvatarServiceMessage(
localZone.Id, localZone.id,
AvatarAction.ObjectDelete(Service.defaultPlayerGUID, item_guid) AvatarAction.ObjectDelete(Service.defaultPlayerGUID, item_guid)
) )
} }

View file

@ -9,7 +9,7 @@ import akka.actor.typed.scaladsl.adapter._
class CmdListPlayers(args: Array[String], services: Map[String, ActorRef]) extends Actor { class CmdListPlayers(args: Array[String], services: Map[String, ActorRef]) extends Actor {
private[this] val log = org.log4s.getLogger(self.path.name) private[this] val log = org.log4s.getLogger(self.path.name)
override def preStart = { override def preStart() = {
ServiceManager.receptionist ! Receptionist.Find( ServiceManager.receptionist ! Receptionist.Find(
InterstellarClusterService.InterstellarClusterServiceKey, InterstellarClusterService.InterstellarClusterServiceKey,
context.self context.self

View file

@ -5,7 +5,7 @@ import akka.actor.{Actor, ActorRef}
import scala.collection.mutable.Map import scala.collection.mutable.Map
class CmdShutdown(args: Array[String], services: Map[String, ActorRef]) extends Actor { class CmdShutdown(args: Array[String], services: Map[String, ActorRef]) extends Actor {
override def preStart = { override def preStart() = {
var data = Map[String, Any]() var data = Map[String, Any]()
context.parent ! CommandGoodResponse("Shutting down", data) context.parent ! CommandGoodResponse("Shutting down", data)
context.system.terminate() context.system.terminate()

View file

@ -1,8 +1,7 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects package net.psforever.objects
class Account(private val accountId: Int, private val username: String, private val gm: Boolean = false) { case class Account(
def AccountId: Int = accountId id: Int,
def Username: String = username name: String,
def GM: Boolean = gm gm: Boolean = false
} )

View file

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

View file

@ -1,10 +1,10 @@
// Copyright (c) 2017 PSForever // Copyright (c) 2017 PSForever
package net.psforever.objects package net.psforever.objects
import net.psforever.objects.avatar.Certification
import net.psforever.objects.ce.DeployedItem import net.psforever.objects.ce.DeployedItem
import net.psforever.objects.definition.{ConstructionFireMode, ConstructionItemDefinition} import net.psforever.objects.definition.{ConstructionFireMode, ConstructionItemDefinition}
import net.psforever.objects.equipment.{Equipment, FireModeSwitch} 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> * 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) FireMode.Deployables(ammoTypeIndex)
} }
def ModePermissions: Set[CertificationType.Value] = FireMode.Permissions(ammoTypeIndex) def ModePermissions: Set[Certification] = FireMode.Permissions(ammoTypeIndex)
def Definition: ConstructionItemDefinition = cItemDef def Definition: ConstructionItemDefinition = cItemDef
} }

View file

@ -5,7 +5,8 @@ object Default {
//cancellable //cancellable
import akka.actor.Cancellable import akka.actor.Cancellable
protected class InternalCancellable extends Cancellable { protected class InternalCancellable extends Cancellable {
override def cancel: Boolean = true override def cancel(): Boolean = true
override def isCancelled: Boolean = true override def isCancelled: Boolean = true
} }
private val cancellable: Cancellable = new InternalCancellable private val cancellable: Cancellable = new InternalCancellable
@ -28,7 +29,7 @@ object Default {
*/ */
private class DefaultActor extends AkkaActor { private class DefaultActor extends AkkaActor {
def receive: Receive = { 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 private var defaultRef: ActorRef = ActorRef.noSender
@ -40,7 +41,7 @@ object Default {
*/ */
def apply(sys: ActorSystem): ActorRef = { def apply(sys: ActorSystem): ActorRef = {
if (defaultRef == ActorRef.noSender) { 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 defaultRef
} }

View file

@ -2,12 +2,13 @@
package net.psforever.objects package net.psforever.objects
import akka.actor.ActorRef import akka.actor.ActorRef
import net.psforever.objects.avatar.{Avatar, Certification}
import scala.concurrent.duration._ import scala.concurrent.duration._
import net.psforever.objects.ce.{Deployable, DeployedItem} import net.psforever.objects.ce.{Deployable, DeployedItem}
import net.psforever.objects.zones.Zone import net.psforever.objects.zones.Zone
import net.psforever.packet.game.{DeployableInfo, DeploymentAction} import net.psforever.packet.game.{DeployableInfo, DeploymentAction}
import net.psforever.types.{CertificationType, PlanetSideGUID} import net.psforever.types.PlanetSideGUID
import services.RemoverActor import services.RemoverActor
import services.local.{LocalAction, LocalServiceMessage} import services.local.{LocalAction, LocalServiceMessage}
@ -100,7 +101,7 @@ object Deployables {
*/ */
def Disown(zone: Zone, avatar: Avatar, replyTo: ActorRef): List[PlanetSideGameObject with Deployable] = { def Disown(zone: Zone, avatar: Avatar, replyTo: ActorRef): List[PlanetSideGameObject with Deployable] = {
val (boomers, deployables) = val (boomers, deployables) =
avatar.Deployables avatar.deployables
.Clear() .Clear()
.map(zone.GUID) .map(zone.GUID)
.collect { case Some(obj) => obj.asInstanceOf[PlanetSideGameObject with Deployable] } .collect { case Some(obj) => obj.asInstanceOf[PlanetSideGameObject with Deployable] }
@ -128,7 +129,7 @@ object Deployables {
*/ */
def InitializeDeployableQuantities(avatar: Avatar): Boolean = { def InitializeDeployableQuantities(avatar: Avatar): Boolean = {
log.info("Setting up combat engineering ...") 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)] = { def InitializeDeployableUIElements(avatar: Avatar): List[(Int, Int, Int, Int)] = {
log.info("Setting up combat engineering UI ...") log.info("Setting up combat engineering UI ...")
avatar.Deployables.UpdateUI() avatar.deployables.UpdateUI()
} }
/** /**
@ -149,11 +150,11 @@ object Deployables {
*/ */
def AddToDeployableQuantities( def AddToDeployableQuantities(
avatar: Avatar, avatar: Avatar,
certification: CertificationType.Value, certification: Certification,
certificationSet: Set[CertificationType.Value] certificationSet: Set[Certification]
): List[(Int, Int, Int, Int)] = { ): List[(Int, Int, Int, Int)] = {
avatar.Deployables.AddToDeployableQuantities(certification, certificationSet) avatar.deployables.AddToDeployableQuantities(certification, certificationSet)
avatar.Deployables.UpdateUI(certification) avatar.deployables.UpdateUI(certification)
} }
/** /**
@ -165,10 +166,10 @@ object Deployables {
*/ */
def RemoveFromDeployableQuantities( def RemoveFromDeployableQuantities(
avatar: Avatar, avatar: Avatar,
certification: CertificationType.Value, certification: Certification,
certificationSet: Set[CertificationType.Value] certificationSet: Set[Certification]
): List[(Int, Int, Int, Int)] = { ): List[(Int, Int, Int, Int)] = {
avatar.Deployables.RemoveFromDeployableQuantities(certification, certificationSet) avatar.deployables.RemoveFromDeployableQuantities(certification, certificationSet)
avatar.Deployables.UpdateUI(certification) avatar.deployables.UpdateUI(certification)
} }
} }

View file

@ -93,7 +93,7 @@ object ExplosiveDeployableControl {
if (target.Definition.DetonateOnJamming) { if (target.Definition.DetonateOnJamming) {
val zone = target.Zone val zone = target.Zone
zone.Activity ! Zone.HotSpot.Activity(cause.target, cause.projectile.owner, cause.hit_pos) 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) DestructionAwareness(target, cause)
} }
@ -114,12 +114,12 @@ object ExplosiveDeployableControl {
target.Destroyed = true target.Destroyed = true
Deployables.AnnounceDestroyDeployable(target, Some(if (target.Jammed) 0 seconds else 500 milliseconds)) Deployables.AnnounceDestroyDeployable(target, Some(if (target.Jammed) 0 seconds else 500 milliseconds))
zone.AvatarEvents ! AvatarServiceMessage( zone.AvatarEvents ! AvatarServiceMessage(
zone.Id, zone.id,
AvatarAction.Destroy(target.GUID, attribution, Service.defaultPlayerGUID, target.Position) AvatarAction.Destroy(target.GUID, attribution, Service.defaultPlayerGUID, target.Position)
) )
if (target.Health == 0) { if (target.Health == 0) {
zone.LocalEvents ! LocalServiceMessage( zone.LocalEvents ! LocalServiceMessage(
zone.Id, zone.id,
LocalAction.TriggerEffect(Service.defaultPlayerGUID, "detonate_damaged_mine", target.GUID) LocalAction.TriggerEffect(Service.defaultPlayerGUID, "detonate_damaged_mine", target.GUID)
) )
} }

View file

@ -1,6 +1,7 @@
// Copyright (c) 2017 PSForever // Copyright (c) 2017 PSForever
package net.psforever.objects package net.psforever.objects
import net.psforever.objects.avatar.Certification
import net.psforever.objects.ballistics.Projectiles import net.psforever.objects.ballistics.Projectiles
import net.psforever.objects.ce.{DeployableCategory, DeployedItem} import net.psforever.objects.ce.{DeployableCategory, DeployedItem}
import net.psforever.objects.definition._ 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.vehicles.{DestroyedVehicle, InternalTelepadDefinition, SeatArmorRestriction, UtilityType}
import net.psforever.objects.vital.damage.{DamageCalculations, DamageModifiers} import net.psforever.objects.vital.damage.{DamageCalculations, DamageModifiers}
import net.psforever.objects.vital.{DamageType, StandardResolutions} 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.collection.mutable
import scala.concurrent.duration._ import scala.concurrent.duration._
@ -53,47 +53,67 @@ object GlobalDefinitions {
/* /*
Implants Implants
*/ */
val advanced_regen = ImplantDefinition(0) val advanced_regen = new ImplantDefinition(ImplantType.AdvancedRegen) {
Name = "advanced_regen"
}
advanced_regen.InitializationDuration = 120 advanced_regen.InitializationDuration = 120
advanced_regen.StaminaCost = 2 advanced_regen.StaminaCost = 2
advanced_regen.CostIntervalDefault = 500 advanced_regen.CostIntervalDefault = 500
val targeting = ImplantDefinition(1) val targeting = new ImplantDefinition(ImplantType.Targeting) {
Name = "targeting"
}
targeting.InitializationDuration = 60 targeting.InitializationDuration = 60
val audio_amplifier = ImplantDefinition(2) val audio_amplifier = new ImplantDefinition(ImplantType.AudioAmplifier) {
Name = "audio_amplifier"
}
audio_amplifier.InitializationDuration = 60 audio_amplifier.InitializationDuration = 60
audio_amplifier.StaminaCost = 1 audio_amplifier.StaminaCost = 1
audio_amplifier.CostIntervalDefault = 1000 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.InitializationDuration = 60
darklight_vision.ActivationStaminaCost = 3 darklight_vision.ActivationStaminaCost = 3
darklight_vision.StaminaCost = 1 darklight_vision.StaminaCost = 1
darklight_vision.CostIntervalDefault = 500 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.InitializationDuration = 120
melee_booster.StaminaCost = 10 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.InitializationDuration = 120
personal_shield.StaminaCost = 1 personal_shield.StaminaCost = 1
personal_shield.CostIntervalDefault = 600 personal_shield.CostIntervalDefault = 600
val range_magnifier = ImplantDefinition(6) val range_magnifier = new ImplantDefinition(ImplantType.RangeMagnifier) {
Name = "range_magnifier"
}
range_magnifier.InitializationDuration = 60 range_magnifier.InitializationDuration = 60
val second_wind = ImplantDefinition(7) val second_wind = new ImplantDefinition(ImplantType.SecondWind) {
Name = "second_wind"
}
second_wind.InitializationDuration = 180 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.InitializationDuration = 90
silent_run.StaminaCost = 1 silent_run.StaminaCost = 1
silent_run.CostIntervalDefault = 333 silent_run.CostIntervalDefault = 333
silent_run.CostIntervalByExoSuitHashMap(ExoSuitType.Agile) = 1000 silent_run.CostIntervalByExoSuitHashMap(ExoSuitType.Agile) = 1000
val surge = ImplantDefinition(9) val surge = new ImplantDefinition(ImplantType.Surge) {
Name = "surge"
}
surge.InitializationDuration = 90 surge.InitializationDuration = 90
surge.StaminaCost = 1 surge.StaminaCost = 1
surge.CostIntervalDefault = 1000 surge.CostIntervalDefault = 1000
@ -1633,7 +1653,7 @@ object GlobalDefinitions {
Reinforced.Name = "med_armor" Reinforced.Name = "med_armor"
Reinforced.Descriptor = "reinforced" Reinforced.Descriptor = "reinforced"
Reinforced.Permissions = List(CertificationType.ReinforcedExoSuit) Reinforced.Permissions = List(Certification.ReinforcedExoSuit)
Reinforced.MaxArmor = 200 Reinforced.MaxArmor = 200
Reinforced.InventoryScale = InventoryTile.Tile1209 Reinforced.InventoryScale = InventoryTile.Tile1209
Reinforced.InventoryOffset = 6 Reinforced.InventoryOffset = 6
@ -1647,7 +1667,7 @@ object GlobalDefinitions {
Reinforced.ResistanceAggravated = 12 Reinforced.ResistanceAggravated = 12
Infiltration.Name = "infiltration_suit" Infiltration.Name = "infiltration_suit"
Infiltration.Permissions = List(CertificationType.InfiltrationSuit) Infiltration.Permissions = List(Certification.InfiltrationSuit)
Infiltration.MaxArmor = 0 Infiltration.MaxArmor = 0
Infiltration.InventoryScale = InventoryTile.Tile66 Infiltration.InventoryScale = InventoryTile.Tile66
Infiltration.InventoryOffset = 6 Infiltration.InventoryOffset = 6
@ -1655,8 +1675,7 @@ object GlobalDefinitions {
Infiltration.Holster(4, EquipmentSize.Melee) Infiltration.Holster(4, EquipmentSize.Melee)
def CommonMaxConfig(max: SpecialExoSuitDefinition): Unit = { def CommonMaxConfig(max: SpecialExoSuitDefinition): Unit = {
max.Permissions = max.Permissions = List(Certification.AIMAX, Certification.AVMAX, Certification.AAMAX, Certification.UniMAX)
List(CertificationType.AIMAX, CertificationType.AVMAX, CertificationType.AAMAX, CertificationType.UniMAX)
max.MaxArmor = 650 max.MaxArmor = 650
max.InventoryScale = InventoryTile.Tile1612 max.InventoryScale = InventoryTile.Tile1612
max.InventoryOffset = 6 max.InventoryOffset = 6
@ -4720,35 +4739,33 @@ object GlobalDefinitions {
ace.Name = "ace" ace.Name = "ace"
ace.Size = EquipmentSize.Pistol ace.Size = EquipmentSize.Pistol
ace.Modes += new ConstructionFireMode 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 += new ConstructionFireMode
ace.Modes(1).Item(DeployedItem.he_mine -> Set(CertificationType.CombatEngineering)) ace.Modes(1).Item(DeployedItem.he_mine, Set(Certification.CombatEngineering))
ace.Modes(1).Item(DeployedItem.jammer_mine -> Set(CertificationType.AssaultEngineering)) ace.Modes(1).Item(DeployedItem.jammer_mine, Set(Certification.AssaultEngineering))
ace.Modes += new ConstructionFireMode ace.Modes += new ConstructionFireMode
ace.Modes(2).Item(DeployedItem.spitfire_turret -> Set(CertificationType.CombatEngineering)) ace.Modes(2).Item(DeployedItem.spitfire_turret, Set(Certification.CombatEngineering))
ace.Modes(2).Item(DeployedItem.spitfire_cloaked -> Set(CertificationType.FortificationEngineering)) ace.Modes(2).Item(DeployedItem.spitfire_cloaked, Set(Certification.FortificationEngineering))
ace.Modes(2).Item(DeployedItem.spitfire_aa -> Set(CertificationType.FortificationEngineering)) ace.Modes(2).Item(DeployedItem.spitfire_aa, Set(Certification.FortificationEngineering))
ace.Modes += new ConstructionFireMode ace.Modes += new ConstructionFireMode
ace.Modes(3).Item(DeployedItem.motionalarmsensor -> Set(CertificationType.CombatEngineering)) ace.Modes(3).Item(DeployedItem.motionalarmsensor, Set(Certification.CombatEngineering))
ace ace.Modes(3).Item(DeployedItem.sensor_shield, Set(Certification.AdvancedHacking, Certification.CombatEngineering))
.Modes(3)
.Item(DeployedItem.sensor_shield -> Set(CertificationType.AdvancedHacking, CertificationType.CombatEngineering))
ace.Tile = InventoryTile.Tile33 ace.Tile = InventoryTile.Tile33
advanced_ace.Name = "advanced_ace" advanced_ace.Name = "advanced_ace"
advanced_ace.Size = EquipmentSize.Rifle advanced_ace.Size = EquipmentSize.Rifle
advanced_ace.Modes += new ConstructionFireMode 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 += 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 += 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 advanced_ace.Tile = InventoryTile.Tile93
router_telepad.Name = "router_telepad" router_telepad.Name = "router_telepad"
router_telepad.Size = EquipmentSize.Pistol router_telepad.Size = EquipmentSize.Pistol
router_telepad.Modes += new ConstructionFireMode 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.Tile = InventoryTile.Tile33
router_telepad.Packet = new TelepadConverter router_telepad.Packet = new TelepadConverter
@ -6125,7 +6142,8 @@ object GlobalDefinitions {
vulture.TrunkLocation = Vector3(-0.76f, -1.88f, 0f) vulture.TrunkLocation = Vector3(-0.76f, -1.88f, 0f)
vulture.AutoPilotSpeeds = (0, 4) vulture.AutoPilotSpeeds = (0, 4)
vulture.Packet = variantConverter 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.Subtract.Damage1 = 5
vulture.JackingDuration = Array(0, 30, 10, 5) vulture.JackingDuration = Array(0, 30, 10, 5)
vulture.DamageUsing = DamageCalculations.AgainstAircraft vulture.DamageUsing = DamageCalculations.AgainstAircraft
@ -6599,7 +6617,9 @@ object GlobalDefinitions {
vanu_equipment_term.Repairable = false vanu_equipment_term.Repairable = false
cert_terminal.Name = "cert_terminal" 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.MaxHealth = 500
cert_terminal.Damageable = true cert_terminal.Damageable = true
cert_terminal.Repairable = true cert_terminal.Repairable = true
@ -6954,4 +6974,5 @@ object GlobalDefinitions {
generator.RepairIfDestroyed = true generator.RepairIfDestroyed = true
generator.Subtract.Damage1 = 9 generator.Subtract.Damage1 = 9
} }
} }

View file

@ -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()
}
}

View file

@ -1,6 +1,8 @@
// Copyright (c) 2017 PSForever // Copyright (c) 2017 PSForever
package net.psforever.objects package net.psforever.objects
import net.psforever.objects.avatar.Avatar
import scala.collection.concurrent.{Map, TrieMap} 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] = { def Remove(sessionId: Long): Option[Avatar] = {
sessionMap.remove(sessionId) sessionMap.remove(sessionId)
} }
def Shutdown: List[Avatar] = { def Shutdown: List[Avatar] = {
val list = sessionMap.values.toList val list = sessionMap.values.toList
sessionMap.clear sessionMap.clear()
list list
} }
} }
@ -68,15 +78,19 @@ object LivePlayerList {
/** /**
* Create a mapped entry between the user's session and a user's character. * 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. * Neither the player nor the session may exist in the current mappings if this is to work.
*
* @param sessionId the session * @param sessionId the session
* @param avatar the character * @param avatar the character
* @return `true`, if the session was association was made; `false`, otherwise * @return `true`, if the session was association was made; `false`, otherwise
*/ */
def Add(sessionId: Long, avatar: Avatar): Boolean = Instance.Add(sessionId, avatar) 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. * Remove all entries related to the given session identifier from the mappings.
* The character no longer counts as "online." * The character no longer counts as "online."
*
* @param sessionId the session * @param sessionId the session
* @return any character that was afffected by the mapping removal * @return any character that was afffected by the mapping removal
*/ */
@ -84,6 +98,7 @@ object LivePlayerList {
/** /**
* Hastily remove all mappings and ids. * Hastily remove all mappings and ids.
*
* @return an unsorted list of the characters that were still online * @return an unsorted list of the characters that were still online
*/ */
def Shutdown: List[Avatar] = Instance.Shutdown def Shutdown: List[Avatar] = Instance.Shutdown

View file

@ -70,14 +70,14 @@ trait NtuStorageBehavior extends Actor {
def NtuStorageObject: NtuContainer = null def NtuStorageObject: NtuContainer = null
def storageBehavior: Receive = { 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 Ntu.Grant(src, amount) => HandleNtuGrant(sender(), src, amount)
case NtuCommand.Grant(src, amount) => HandleNtuGrant(sender, src, amount) case NtuCommand.Grant(src, amount) => HandleNtuGrant(sender(), src, amount)
} }
def HandleNtuOffer(sender: ActorRef, src: NtuContainer): Unit def HandleNtuOffer(sender: ActorRef, src: NtuContainer): Unit

View file

@ -1,13 +1,8 @@
// Copyright (c) 2017 PSForever // Copyright (c) 2017 PSForever
package net.psforever.objects package net.psforever.objects
import net.psforever.objects.avatar.LoadoutManager import net.psforever.objects.avatar.{Avatar, LoadoutManager}
import net.psforever.objects.definition.{ import net.psforever.objects.definition.{AvatarDefinition, ExoSuitDefinition, SpecialExoSuitDefinition}
AvatarDefinition,
ExoSuitDefinition,
ImplantDefinition,
SpecialExoSuitDefinition
}
import net.psforever.objects.equipment.{Equipment, EquipmentSize, EquipmentSlot, JammableUnit} import net.psforever.objects.equipment.{Equipment, EquipmentSize, EquipmentSlot, JammableUnit}
import net.psforever.objects.inventory.{Container, GridInventory, InventoryItem} import net.psforever.objects.inventory.{Container, GridInventory, InventoryItem}
import net.psforever.objects.serverobject.PlanetSideServerObject 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.resistance.ResistanceProfile
import net.psforever.objects.vital.{DamageResistanceModel, Vitality} import net.psforever.objects.vital.{DamageResistanceModel, Vitality}
import net.psforever.objects.zones.ZoneAware import net.psforever.objects.zones.ZoneAware
import net.psforever.packet.game.objectcreate.{Cosmetics, DetailedCharacterData, PersonalStyle}
import net.psforever.types.{PlanetSideGUID, _} import net.psforever.types.{PlanetSideGUID, _}
import scala.annotation.tailrec import scala.annotation.tailrec
import scala.util.{Success, Try} import scala.util.{Success, Try}
class Player(private val core: Avatar) class Player(var avatar: Avatar)
extends PlanetSideServerObject extends PlanetSideServerObject
with FactionAffinity with FactionAffinity
with Vitality 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 Health = 0 //player health is artificially managed as a part of their lifecycle; start entity as dead
Destroyed = true //see isAlive Destroyed = true //see isAlive
private var backpack: Boolean = false private var backpack: Boolean = false
private var stamina: Int = 0
private var armor: Int = 0 private var armor: Int = 0
private var capacitor: Float = 0f private var capacitor: Float = 0f
@ -40,8 +33,6 @@ class Player(private val core: Avatar)
private var capacitorLastUsedMillis: Long = 0 private var capacitorLastUsedMillis: Long = 0
private var capacitorLastChargedMillis: Long = 0 private var capacitorLastChargedMillis: Long = 0
private var maxStamina: Int = 100 //does anything affect this?
private var exosuit: ExoSuitDefinition = GlobalDefinitions.Standard private var exosuit: ExoSuitDefinition = GlobalDefinitions.Standard
private val freeHand: EquipmentSlot = new OffhandEquipmentSlot(EquipmentSize.Inventory) private val freeHand: EquipmentSlot = new OffhandEquipmentSlot(EquipmentSize.Inventory)
private val holsters: Array[EquipmentSlot] = Array.fill[EquipmentSlot](5)(new EquipmentSlot) 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 crouching: Boolean = false
private var jumping: Boolean = false private var jumping: Boolean = false
private var cloaked: Boolean = false private var cloaked: Boolean = false
private var fatigued: Boolean = private var afk: Boolean = false
false // If stamina drops to 0, player is fatigued until regenerating at least 20 stamina
private var afk: Boolean = false
private var vehicleSeated: Option[PlanetSideGUID] = None private var vehicleSeated: Option[PlanetSideGUID] = None
@ -70,36 +59,35 @@ class Player(private val core: Avatar)
/** From PlanetsideAttributeMessage */ /** From PlanetsideAttributeMessage */
var PlanetsideAttribute: Array[Long] = Array.ofDim(120) var PlanetsideAttribute: Array[Long] = Array.ofDim(120)
var skipStaminaRegenForTurns: Int = 0
val squadLoadouts = new LoadoutManager(10)
Player.SuitSetup(this, exosuit) 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 isAlive: Boolean = !Destroyed
def isBackpack: Boolean = backpack def isBackpack: Boolean = backpack
def Spawn: Boolean = { def Spawn(): Boolean = {
if (!isAlive && !isBackpack) { if (!isAlive && !isBackpack) {
Destroyed = false Destroyed = false
Health = Definition.DefaultHealth Health = Definition.DefaultHealth
Stamina = MaxStamina
Armor = MaxArmor Armor = MaxArmor
Capacitor = 0 Capacitor = 0
ResetAllImplants()
} }
isAlive isAlive
} }
@ -107,7 +95,6 @@ class Player(private val core: Avatar)
def Die: Boolean = { def Die: Boolean = {
Destroyed = true Destroyed = true
Health = 0 Health = 0
Stamina = 0
false 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: Int = armor
def Armor_=(assignArmor: Int): Int = { def Armor_=(assignArmor: Int): Int = {
@ -199,7 +171,7 @@ class Player(private val core: Avatar)
} else if (slot > -1 && slot < 5) { } else if (slot > -1 && slot < 5) {
holsters(slot) holsters(slot)
} else if (slot == 5) { } else if (slot == 5) {
core.FifthSlot avatar.fifthSlot()
} else if (slot == Player.FreeHandSlot) { } else if (slot == Player.FreeHandSlot) {
freeHand freeHand
} else { } else {
@ -211,10 +183,6 @@ class Player(private val core: Avatar)
def Inventory: GridInventory = inventory def Inventory: GridInventory = inventory
def Locker: LockerContainer = core.Locker
def FifthSlot: EquipmentSlot = core.FifthSlot
override def Fit(obj: Equipment): Option[Int] = { override def Fit(obj: Equipment): Option[Int] = {
recursiveHolsterFit(holsters.iterator, obj.Size) match { recursiveHolsterFit(holsters.iterator, obj.Size) match {
case Some(index) => case Some(index) =>
@ -238,7 +206,7 @@ class Player(private val core: Avatar)
if (!iter.hasNext) { if (!iter.hasNext) {
None None
} else { } else {
val slot = iter.next val slot = iter.next()
if (slot.Equipment.isEmpty && slot.Size.equals(objSize)) { if (slot.Equipment.isEmpty && slot.Size.equals(objSize)) {
Some(index) Some(index)
} else { } else {
@ -278,7 +246,7 @@ class Player(private val core: Avatar)
if (!iter.hasNext) { if (!iter.hasNext) {
None None
} else { } else {
val slot = iter.next val slot = iter.next()
if (slot.Equipment.isDefined && slot.Equipment.get.GUID == guid) { if (slot.Equipment.isDefined && slot.Equipment.get.GUID == guid) {
Some(index) Some(index)
} else { } else {
@ -343,40 +311,6 @@ class Player(private val core: Avatar)
def RadiationShielding = exosuit.RadiationShielding 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: Float = facingYawUpper
def FacingYawUpper_=(facing: Float): Float = { def FacingYawUpper_=(facing: Float): Float = {
@ -405,13 +339,6 @@ class Player(private val core: Avatar)
Cloaked Cloaked
} }
def Fatigued: Boolean = fatigued
def Fatigued_=(isFatigued: Boolean): Boolean = {
fatigued = isFatigued
Fatigued
}
def AwayFromKeyboard: Boolean = afk def AwayFromKeyboard: Boolean = afk
def AwayFromKeyboard_=(away: Boolean): Boolean = { def AwayFromKeyboard_=(away: Boolean): Boolean = {
@ -419,66 +346,6 @@ class Player(private val core: Avatar)
AwayFromKeyboard 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 = private var usingSpecial: SpecialExoSuitDefinition.Mode.Value => SpecialExoSuitDefinition.Mode.Value =
DefaultUsingSpecial DefaultUsingSpecial
@ -602,8 +469,6 @@ class Player(private val core: Avatar)
isBackpack && (backpackAccess.isEmpty || backpackAccess.contains(player.GUID)) isBackpack && (backpackAccess.isEmpty || backpackAccess.contains(player.GUID))
} }
def FirstTimeEvents: List[String] = core.FirstTimeEvents
def VehicleSeated: Option[PlanetSideGUID] = vehicleSeated def VehicleSeated: Option[PlanetSideGUID] = vehicleSeated
def VehicleSeated_=(guid: PlanetSideGUID): Option[PlanetSideGUID] = VehicleSeated_=(Some(guid)) def VehicleSeated_=(guid: PlanetSideGUID): Option[PlanetSideGUID] = VehicleSeated_=(Some(guid))
@ -613,57 +478,31 @@ class Player(private val core: Avatar)
VehicleSeated 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 DamageModel = exosuit.asInstanceOf[DamageResistanceModel]
def Definition: AvatarDefinition = core.Definition
def canEqual(other: Any): Boolean = other.isInstanceOf[Player] def canEqual(other: Any): Boolean = other.isInstanceOf[Player]
override def equals(other: Any): Boolean = override def equals(other: Any): Boolean =
other match { other match {
case that: Player => case that: Player =>
(that canEqual this) && (that canEqual this) &&
core == that.core avatar == that.avatar
case _ => case _ =>
false false
} }
override def hashCode(): Int = { 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 { object Player {
@ -672,16 +511,6 @@ object Player {
final val HandsDownSlot: Int = 255 final val HandsDownSlot: Int = 255
final case class Die() 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 = { def apply(core: Avatar): Player = {
new Player(core) new Player(core)
@ -698,33 +527,11 @@ object Player {
def Respawn(player: Player): Player = { def Respawn(player: Player): Player = {
if (player.Release) { if (player.Release) {
val obj = new Player(player.core) val obj = new Player(player.avatar)
obj.Continent = player.Continent obj.Continent = player.Continent
obj obj
} else { } else {
player 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}"
}
} }

View file

@ -82,7 +82,7 @@ object Players {
if (!iter.hasNext) { if (!iter.hasNext) {
list list
} else { } else {
val slot = iter.next val slot = iter.next()
slot.Equipment match { slot.Equipment match {
case Some(equipment) => case Some(equipment) =>
slot.Equipment = None slot.Equipment = None
@ -105,7 +105,7 @@ object Players {
if (!iter.hasNext) { if (!iter.hasNext) {
list list
} else { } else {
val slot = iter.next val slot = iter.next()
if (slot.Equipment.isEmpty) { if (slot.Equipment.isEmpty) {
list.find(item => item.obj.Size == slot.Size) match { list.find(item => item.obj.Size == slot.Size) match {
case Some(obj) => case Some(obj) =>
@ -126,11 +126,11 @@ object Players {
case Nil => case Nil =>
true true
case permissions if subtype != 0 => case permissions if subtype != 0 =>
val certs = player.Certifications val certs = player.avatar.certifications
certs.intersect(permissions.toSet).nonEmpty && certs.intersect(permissions.toSet).nonEmpty &&
certs.intersect(InfantryLoadout.DetermineSubtypeC(subtype)).nonEmpty certs.intersect(InfantryLoadout.DetermineSubtypeC(subtype)).nonEmpty
case permissions => case permissions =>
player.Certifications.intersect(permissions.toSet).nonEmpty player.avatar.certifications.intersect(permissions.toSet).nonEmpty
} }
} }
} }

View file

@ -71,7 +71,7 @@ class SensorDeployableControl(sensor: SensorDeployable)
target match { target match {
case obj: PlanetSideServerObject if !jammedSound => case obj: PlanetSideServerObject if !jammedSound =>
obj.Zone.VehicleEvents ! VehicleServiceMessage( obj.Zone.VehicleEvents ! VehicleServiceMessage(
obj.Zone.Id, obj.Zone.id,
VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, obj.GUID, 54, 1) VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, obj.GUID, 54, 1)
) )
super.StartJammeredSound(obj, dur) super.StartJammeredSound(obj, dur)
@ -83,7 +83,7 @@ class SensorDeployableControl(sensor: SensorDeployable)
case obj: PlanetSideServerObject with JammableUnit if !obj.Jammed => case obj: PlanetSideServerObject with JammableUnit if !obj.Jammed =>
val zone = obj.Zone val zone = obj.Zone
zone.LocalEvents ! LocalServiceMessage( zone.LocalEvents ! LocalServiceMessage(
zone.Id, zone.id,
LocalAction.TriggerEffectInfo(Service.defaultPlayerGUID, "on", obj.GUID, false, 1000) LocalAction.TriggerEffectInfo(Service.defaultPlayerGUID, "on", obj.GUID, false, 1000)
) )
super.StartJammeredStatus(obj, dur) super.StartJammeredStatus(obj, dur)
@ -95,7 +95,7 @@ class SensorDeployableControl(sensor: SensorDeployable)
case obj: PlanetSideServerObject if jammedSound => case obj: PlanetSideServerObject if jammedSound =>
val zone = obj.Zone val zone = obj.Zone
zone.VehicleEvents ! VehicleServiceMessage( zone.VehicleEvents ! VehicleServiceMessage(
zone.Id, zone.id,
VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, obj.GUID, 54, 0) VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, obj.GUID, 54, 0)
) )
case _ => ; case _ => ;
@ -107,7 +107,7 @@ class SensorDeployableControl(sensor: SensorDeployable)
target match { target match {
case obj: PlanetSideServerObject with JammableUnit if obj.Jammed => case obj: PlanetSideServerObject with JammableUnit if obj.Jammed =>
sensor.Zone.LocalEvents ! LocalServiceMessage( sensor.Zone.LocalEvents ! LocalServiceMessage(
sensor.Zone.Id, sensor.Zone.id,
LocalAction.TriggerEffectInfo(Service.defaultPlayerGUID, "on", obj.GUID, true, 1000) LocalAction.TriggerEffectInfo(Service.defaultPlayerGUID, "on", obj.GUID, true, 1000)
) )
case _ => ; case _ => ;
@ -127,7 +127,7 @@ object SensorDeployableControl {
Deployables.AnnounceDestroyDeployable(target, Some(1 seconds)) Deployables.AnnounceDestroyDeployable(target, Some(1 seconds))
val zone = target.Zone val zone = target.Zone
zone.LocalEvents ! LocalServiceMessage( zone.LocalEvents ! LocalServiceMessage(
zone.Id, zone.id,
LocalAction.TriggerEffectInfo(Service.defaultPlayerGUID, "on", target.GUID, false, 1000) LocalAction.TriggerEffectInfo(Service.defaultPlayerGUID, "on", target.GUID, false, 1000)
) )
//position the explosion effect near the bulky area of the sensor stalk //position the explosion effect near the bulky area of the sensor stalk
@ -143,7 +143,7 @@ object SensorDeployableControl {
) )
} }
zone.LocalEvents ! LocalServiceMessage( zone.LocalEvents ! LocalServiceMessage(
zone.Id, zone.id,
LocalAction.TriggerEffectLocation(Service.defaultPlayerGUID, "motion_sensor_destroyed", explosionPos, ang) LocalAction.TriggerEffectLocation(Service.defaultPlayerGUID, "motion_sensor_destroyed", explosionPos, ang)
) )
//TODO replaced by an alternate model (charred stub)? //TODO replaced by an alternate model (charred stub)?

View file

@ -1,5 +1,6 @@
package net.psforever.objects package net.psforever.objects
import net.psforever.objects.avatar.Avatar
import net.psforever.objects.zones.{Zone, Zoning} import net.psforever.objects.zones.{Zone, Zoning}
import net.psforever.packet.game.DeadState import net.psforever.packet.game.DeadState
@ -9,7 +10,6 @@ case class Session(
account: Account = null, account: Account = null,
player: Player = null, player: Player = null,
avatar: Avatar = null, avatar: Avatar = null,
admin: Boolean = false,
zoningType: Zoning.Method.Value = Zoning.Method.None, zoningType: Zoning.Method.Value = Zoning.Method.None,
deadState: DeadState.Value = DeadState.Alive, deadState: DeadState.Value = DeadState.Alive,
speed: Float = 1.0f, speed: Float = 1.0f,

View file

@ -119,7 +119,7 @@ class ShieldGeneratorControl(gen: ShieldGeneratorDeployable)
target match { target match {
case obj: PlanetSideServerObject with JammableUnit if !obj.Jammed => case obj: PlanetSideServerObject with JammableUnit if !obj.Jammed =>
obj.Zone.VehicleEvents ! VehicleServiceMessage( obj.Zone.VehicleEvents ! VehicleServiceMessage(
obj.Zone.Id, obj.Zone.id,
VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, obj.GUID, 27, 1) VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, obj.GUID, 27, 1)
) )
super.StartJammeredStatus(obj, dur) super.StartJammeredStatus(obj, dur)
@ -132,7 +132,7 @@ class ShieldGeneratorControl(gen: ShieldGeneratorDeployable)
target match { target match {
case obj: PlanetSideServerObject with JammableUnit if obj.Jammed => case obj: PlanetSideServerObject with JammableUnit if obj.Jammed =>
obj.Zone.VehicleEvents ! VehicleServiceMessage( obj.Zone.VehicleEvents ! VehicleServiceMessage(
obj.Zone.Id, obj.Zone.id,
VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, obj.GUID, 27, 0) VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, obj.GUID, 27, 0)
) )
case _ => ; case _ => ;
@ -154,7 +154,7 @@ object ShieldGeneratorControl {
if (damageToShields) { if (damageToShields) {
val zone = target.Zone val zone = target.Zone
zone.VehicleEvents ! VehicleServiceMessage( zone.VehicleEvents ! VehicleServiceMessage(
zone.Id, zone.id,
VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, target.GUID, 68, target.Shields) VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, target.GUID, 68, target.Shields)
) )
} }

View file

@ -111,11 +111,11 @@ object SpawnPoint {
val ori = target.Orientation val ori = target.Orientation
val zrad = math.toRadians(ori.z) val zrad = math.toRadians(ori.z)
val radius = 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 shift = Vector3(math.sin(zrad).toFloat, math.cos(zrad).toFloat, 0) * radius
val altitudeShift = target.Definition match { val altitudeShift = target.Definition match {
case vdef: VehicleDefinition if GlobalDefinitions.isFlightVehicle(vdef) => 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 _ => case _ =>
Vector3.Zero Vector3.Zero
} }

View file

@ -145,7 +145,7 @@ object Tool {
if (!iter.hasNext) { if (!iter.hasNext) {
list list
} else { } else {
val index = iter.next val index = iter.next()
fmodes.filter(fmode => fmode.AmmoSlotIndex == index) match { fmodes.filter(fmode => fmode.AmmoSlotIndex == index) match {
case fmode :: _ => case fmode :: _ =>
buildFireModes(tdef, iter, fmodes, list :+ new FireModeSlot(tdef, fmode)) buildFireModes(tdef, iter, fmodes, list :+ new FireModeSlot(tdef, fmode))

View file

@ -199,16 +199,16 @@ class Vehicle(private val vehicleDef: VehicleDefinition)
Flying Flying
} }
def NtuCapacitorScaled : Int = { def NtuCapacitorScaled: Int = {
if(Definition.MaxNtuCapacitor > 0) { if (Definition.MaxNtuCapacitor > 0) {
scala.math.ceil((NtuCapacitor.toFloat / Definition.MaxNtuCapacitor.toFloat) * 10).toInt scala.math.ceil((NtuCapacitor.toFloat / Definition.MaxNtuCapacitor.toFloat) * 10).toInt
} else { } else {
0 0
} }
} }
def Capacitor : Int = capacitor def Capacitor: Int = capacitor
def Capacitor_=(value: Int): Int = { def Capacitor_=(value: Int): Int = {
if (value > Definition.MaxCapacitor) { if (value > Definition.MaxCapacitor) {
capacitor = Definition.MaxCapacitor capacitor = Definition.MaxCapacitor
@ -365,7 +365,7 @@ class Vehicle(private val vehicleDef: VehicleDefinition)
if (!iter.hasNext) { if (!iter.hasNext) {
None None
} else { } else {
val (seatNumber, seat) = iter.next val (seatNumber, seat) = iter.next()
if (seat.Occupant.contains(player)) { if (seat.Occupant.contains(player)) {
Some(seatNumber) Some(seatNumber)
} else { } else {

View file

@ -36,10 +36,10 @@ object Vehicles {
def Own(vehicle: Vehicle, playerOpt: Option[Player]): Option[Vehicle] = { def Own(vehicle: Vehicle, playerOpt: Option[Player]): Option[Vehicle] = {
playerOpt match { playerOpt match {
case Some(tplayer) => case Some(tplayer) =>
tplayer.VehicleOwned = vehicle.GUID tplayer.avatar.vehicle = Some(vehicle.GUID)
vehicle.AssignOwnership(playerOpt) vehicle.AssignOwnership(playerOpt)
vehicle.Zone.VehicleEvents ! VehicleServiceMessage( vehicle.Zone.VehicleEvents ! VehicleServiceMessage(
vehicle.Zone.Id, vehicle.Zone.id,
VehicleAction.Ownership(tplayer.GUID, vehicle.GUID) VehicleAction.Ownership(tplayer.GUID, vehicle.GUID)
) )
Vehicles.ReloadAccessPermissions(vehicle, tplayer.Name) Vehicles.ReloadAccessPermissions(vehicle, tplayer.Name)
@ -50,8 +50,9 @@ object Vehicles {
} }
/** /**
* Disassociate a vehicle fromthe player who owns it. * Disassociate a vehicle from the player who owns it.
* @param guid the unique identifier for that vehicle *
* @param guid the unique identifier for that vehicle
* @param vehicle the vehicle * @param vehicle the vehicle
* @return the vehicle, if it had a previous owner; * @return the vehicle, if it had a previous owner;
* `None`, otherwise * `None`, otherwise
@ -59,8 +60,8 @@ object Vehicles {
def Disown(guid: PlanetSideGUID, vehicle: Vehicle): Option[Vehicle] = def Disown(guid: PlanetSideGUID, vehicle: Vehicle): Option[Vehicle] =
vehicle.Zone.GUID(vehicle.Owner) match { vehicle.Zone.GUID(vehicle.Owner) match {
case Some(player: Player) => case Some(player: Player) =>
if (player.VehicleOwned.contains(guid)) { if (player.avatar.vehicle.contains(guid)) {
player.VehicleOwned = None player.avatar.vehicle = None
vehicle.Zone.VehicleEvents ! VehicleServiceMessage( vehicle.Zone.VehicleEvents ! VehicleServiceMessage(
player.Name, player.Name,
VehicleAction.Ownership(player.GUID, PlanetSideGUID(0)) VehicleAction.Ownership(player.GUID, PlanetSideGUID(0))
@ -99,9 +100,9 @@ object Vehicles {
* @param player the player * @param player the player
*/ */
def Disown(player: Player, zoneOpt: Option[Zone]): Option[Vehicle] = { def Disown(player: Player, zoneOpt: Option[Zone]): Option[Vehicle] = {
player.VehicleOwned match { player.avatar.vehicle match {
case Some(vehicle_guid) => case Some(vehicle_guid) =>
player.VehicleOwned = None player.avatar.vehicle = None
zoneOpt.getOrElse(player.Zone).GUID(vehicle_guid) match { zoneOpt.getOrElse(player.Zone).GUID(vehicle_guid) match {
case Some(vehicle: Vehicle) => case Some(vehicle: Vehicle) =>
Disown(player, vehicle) Disown(player, vehicle)
@ -231,7 +232,7 @@ object Vehicles {
cargoHold.Occupant match { cargoHold.Occupant match {
case Some(cargo: Vehicle) => { case Some(cargo: Vehicle) => {
cargo.Seats(0).Occupant match { cargo.Seats(0).Occupant match {
case Some(cargoDriver : Player) => case Some(cargoDriver: Player) =>
CargoBehavior.HandleVehicleCargoDismount( CargoBehavior.HandleVehicleCargoDismount(
target.Zone, target.Zone,
cargo.GUID, cargo.GUID,
@ -255,7 +256,7 @@ object Vehicles {
tplayer.VehicleSeated = None tplayer.VehicleSeated = None
if (tplayer.HasGUID) { if (tplayer.HasGUID) {
zone.VehicleEvents ! VehicleServiceMessage( zone.VehicleEvents ! VehicleServiceMessage(
zone.Id, zone.id,
VehicleAction.KickPassenger(tplayer.GUID, 4, unk2 = false, target.GUID) VehicleAction.KickPassenger(tplayer.GUID, 4, unk2 = false, target.GUID)
) )
} }
@ -268,7 +269,7 @@ object Vehicles {
target.Actor ! Vehicle.Deconstruct() target.Actor ! Vehicle.Deconstruct()
} else { // Otherwise handle ownership transfer as normal } else { // Otherwise handle ownership transfer as normal
// Remove ownership of our current vehicle, if we have one // Remove ownership of our current vehicle, if we have one
hacker.VehicleOwned match { hacker.avatar.vehicle match {
case Some(guid: PlanetSideGUID) => case Some(guid: PlanetSideGUID) =>
zone.GUID(guid) match { zone.GUID(guid) match {
case Some(vehicle: Vehicle) => 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. //todo: Send HackMessage -> HackCleared to vehicle? can be found in packet captures. Not sure if necessary.
// And broadcast the faction change to other clients // And broadcast the faction change to other clients
zone.AvatarEvents ! AvatarServiceMessage( zone.AvatarEvents ! AvatarServiceMessage(
zone.Id, zone.id,
AvatarAction.SetEmpire(Service.defaultPlayerGUID, target.GUID, hacker.Faction) AvatarAction.SetEmpire(Service.defaultPlayerGUID, target.GUID, hacker.Faction)
) )
} }
zone.LocalEvents ! LocalServiceMessage( zone.LocalEvents ! LocalServiceMessage(
zone.Id, zone.id,
LocalAction.TriggerSound(hacker.GUID, TriggeredSound.HackVehicle, target.Position, 30, 0.49803925f) LocalAction.TriggerSound(hacker.GUID, TriggeredSound.HackVehicle, target.Position, 30, 0.49803925f)
) )
// Clean up after specific vehicles, e.g. remove router telepads // 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 //determine if we are close enough to charge from something
(ntuChargingTarget match { (ntuChargingTarget match {
case Some(target : WarpGate) if { case Some(target: WarpGate) if {
val soiRadius = target.Definition.SOIRadius val soiRadius = target.Definition.SOIRadius
Vector3.DistanceSquared(obj.Position.xy, target.Position.xy) < soiRadius * soiRadius Vector3.DistanceSquared(obj.Position.xy, target.Position.xy) < soiRadius * soiRadius
} => } =>
Some(target.asInstanceOf[NtuContainer]) Some(target.asInstanceOf[NtuContainer])
case None => case None =>
None None
}).orElse { }).orElse {
val position = obj.Position.xy val position = obj.Position.xy
obj.Zone.Buildings.values obj.Zone.Buildings.values
.collectFirst { case gate : WarpGate .collectFirst {
if { val soiRadius = gate.Definition.SOIRadius case gate: WarpGate if {
Vector3.DistanceSquared(position, gate.Position.xy) < soiRadius * soiRadius } => gate } val soiRadius = gate.Definition.SOIRadius
Vector3.DistanceSquared(position, gate.Position.xy) < soiRadius * soiRadius
} =>
gate
}
.asInstanceOf[Option[NtuContainer]] .asInstanceOf[Option[NtuContainer]]
} }
} }
def FindANTDischargingTarget(obj : TransferContainer, ntuChargingTarget : Option[TransferContainer]) : Option[TransferContainer] = { def FindANTDischargingTarget(
obj: TransferContainer,
ntuChargingTarget: Option[TransferContainer]
): Option[TransferContainer] = {
(ntuChargingTarget match { (ntuChargingTarget match {
case out @ Some(target : NtuContainer) if { case out @ Some(target: NtuContainer) if {
Vector3.DistanceSquared(obj.Position.xy, target.Position.xy) < 400 //20m is generous ... Vector3.DistanceSquared(obj.Position.xy, target.Position.xy) < 400 //20m is generous ...
} => } =>
out out
case _ => case _ =>
None None
@ -352,8 +363,8 @@ object Vehicles {
} match { } match {
case Some(building) => case Some(building) =>
building.Amenities building.Amenities
.collect { case obj : NtuContainer => obj } .collect { case obj: NtuContainer => obj }
.sortBy {o => Vector3.DistanceSquared(position, o.Position.xy) < 400 } //20m is generous ... .sortBy { o => Vector3.DistanceSquared(position, o.Position.xy) < 400 } //20m is generous ...
.headOption .headOption
case None => case None =>
None None
@ -363,9 +374,10 @@ object Vehicles {
/** /**
* Before a vehicle is removed from the game world, the following actions must be performed. * Before a vehicle is removed from the game world, the following actions must be performed.
*
* @param vehicle the vehicle * @param vehicle the vehicle
*/ */
def BeforeUnloadVehicle(vehicle : Vehicle, zone : Zone) : Unit = { def BeforeUnloadVehicle(vehicle: Vehicle, zone: Zone): Unit = {
vehicle.Definition match { vehicle.Definition match {
case GlobalDefinitions.ams => case GlobalDefinitions.ams =>
vehicle.Actor ! Deployment.TryUndeploy(DriveState.Undeploying) 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 val zone = vehicle.Zone
(vehicle.Utility(UtilityType.internal_router_telepad_deployable) match { (vehicle.Utility(UtilityType.internal_router_telepad_deployable) match {
case Some(util : Utility.InternalTelepad) => case Some(util: Utility.InternalTelepad) =>
val telepad = util.Telepad val telepad = util.Telepad
util.Telepad = None util.Telepad = None
zone.GUID(telepad) zone.GUID(telepad)
case _ => case _ =>
None None
}) match { }) match {
case Some(telepad : TelepadDeployable) => case Some(telepad: TelepadDeployable) =>
log.debug(s"BeforeUnload: deconstructing telepad $telepad that was linked to router $vehicle ...") log.debug(s"BeforeUnload: deconstructing telepad $telepad that was linked to router $vehicle ...")
telepad.Active = false telepad.Active = false
zone.LocalEvents ! LocalServiceMessage.Deployables(RemoverActor.ClearSpecific(List(telepad), zone)) zone.LocalEvents ! LocalServiceMessage.Deployables(RemoverActor.ClearSpecific(List(telepad), zone))

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

View file

@ -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
}
)
}
}

View file

@ -1,224 +1,219 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.avatar 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 { sealed abstract class Certification(
object Dependencies { 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
/** case object Certification extends IntEnum[Certification] {
* 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 StandardAssault extends Certification(value = 0, name = "standard_assault", cost = 0)
* 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 MediumAssault extends Certification(value = 1, name = "medium_assault", cost = 2)
* 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 HeavyAssault
* Find all certifications that are dependencies of the target certification. extends Certification(value = 2, name = "heavy_assault", cost = 4, requires = Set(MediumAssault))
* (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
}
import CertificationType._ case object SpecialAssault
extends Certification(value = 3, name = "special_assault", cost = 3, requires = Set(MediumAssault))
/** case object AntiVehicular
* Find all certifications that are related but mutually exclusive with the target certification. extends Certification(value = 4, name = "anti_vehicular", cost = 3, requires = Set(MediumAssault))
* (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]
}
private val dependencies: Map[CertificationType.Value, List[CertificationType.Value]] = Map( case object Sniping extends Certification(value = 5, name = "sniper", cost = 3, requires = Set(MediumAssault))
StandardAssault -> List(),
AgileExoSuit -> List(), case object EliteAssault
ReinforcedExoSuit -> List(), extends Certification(value = 6, name = "special_assault_2", cost = 1, requires = Set(SpecialAssault))
InfiltrationSuit -> List(Phantasm),
AIMAX -> List(), case object AirCavalryScout extends Certification(value = 7, name = "air_cavalry_scout", cost = 3)
AVMAX -> List(),
AAMAX -> List(), case object AirCavalryInterceptor
UniMAX -> List(), extends Certification(value = 8, name = "air_cavalry_interceptor", cost = 2, requires = Set(AirCavalryScout))
StandardAssault -> List(),
MediumAssault -> List(AntiVehicular, HeavyAssault, Sniping, SpecialAssault), case object AirCavalryAssault
AntiVehicular -> List(), extends Certification(
HeavyAssault -> List(), value = 9,
Sniping -> List(), name = "air_cavalry_assault",
SpecialAssault -> List(EliteAssault), cost = 2,
EliteAssault -> List(), requires = Set(AirCavalryScout)
ATV -> List(Switchblade), )
Switchblade -> List(),
Harasser -> List(), case object AirSupport extends Certification(value = 10, name = "air_support", cost = 3)
AssaultBuggy -> List(),
LightScout -> List(AirCavalryAssault), case object ATV extends Certification(value = 11, name = "quad_all", cost = 1)
GroundSupport -> List(),
GroundTransport -> List(), case object LightScout
ArmoredAssault1 -> List(ArmoredAssault2), extends Certification(
ArmoredAssault2 -> List(BattleFrameRobotics, Flail), value = 12,
Flail -> List(), name = "light_scout",
AirCavalryScout -> List(AirCavalryAssault), cost = 5,
AirCavalryAssault -> List(AirCavalryInterceptor), replaces = Set(AirCavalryScout, AssaultBuggy, Harasser)
AirCavalryInterceptor -> List(), )
AirSupport -> List(GalaxyGunship),
GalaxyGunship -> List(), case object AssaultBuggy extends Certification(value = 13, name = "assault_buggy", cost = 3, replaces = Set(Harasser))
Phantasm -> List(),
BattleFrameRobotics -> List(BFRAntiInfantry, BFRAntiAircraft), case object ArmoredAssault1 extends Certification(value = 14, name = "armored_assault1", cost = 2)
BFRAntiInfantry -> List(),
BFRAntiAircraft -> List(), case object ArmoredAssault2
Medical -> List(AdvancedMedical), extends Certification(value = 15, name = "armored_assault2", cost = 3, requires = Set(ArmoredAssault1))
AdvancedMedical -> List(),
Engineering -> List(CombatEngineering), case object GroundTransport extends Certification(value = 16, name = "ground_transport", cost = 2)
CombatEngineering -> List(AdvancedEngineering, AssaultEngineering, FortificationEngineering),
AdvancedEngineering -> List(), case object GroundSupport extends Certification(value = 17, name = "ground_support", cost = 2)
AssaultEngineering -> List(),
FortificationEngineering -> List(), case object BattleFrameRobotics
Hacking -> List(AdvancedHacking), extends Certification(value = 18, name = "TODO2", cost = 4, requires = Set(ArmoredAssault2)) // TODO name
AdvancedHacking -> List(DataCorruption, ElectronicsExpert, ExpertHacking),
DataCorruption -> List(), case object Flail extends Certification(value = 19, name = "flail", cost = 1, requires = Set(ArmoredAssault2))
ElectronicsExpert -> List(),
ExpertHacking -> List() 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 { /**
* Certifications are often stored, in packet form, as an encoded little-endian `46u` value.
/** * Decode a representative value into a subset of certification values
* For a certification, get its point cost. * by repeatedly finding the partition point of values less than a specific one,
* @param certification the certification * providing for both the next lowest value (to subtract) and an index (of a certification).
* @return the cost *
*/ * @see `ChangeSquadMemberRequirementsCertifications`
def Of(certification: CertificationType.Value): Int = points(certification) * @see `changeSquadMemberRequirementsCertificationsCodec`
* @see `fromEncodedLong(Long)`
/** * @param certs the certifications, as a single value
* For a list of certifications, find the point cost of all unique certifications. * @param splitList the available values to partition
* @see `Of(Set)` * @param out the accumulating certification values;
* @param certifications the certification list * defaults to an empty set
* @return the total cost * @return the certifications, as a sequence of values
*/ */
def Of(certifications: List[CertificationType.Value]): Int = Of(certifications.toSet) @tailrec
private def recursiveFromEncodedLong(
/** certs: Long,
* For a set of certifications, find the point cost of all certifications. splitList: Iterable[Long],
* @see `OfAll(List)` out: Set[Certification] = Set.empty
* @param certifications the certification list ): Set[Certification] = {
* @return the total cost if (certs == 0 || splitList.isEmpty) {
*/ out
def Of(certifications: Set[CertificationType.Value]): Int = OfAll(certifications.toList) } else {
val (less, _) = splitList.partition(_ <= certs)
/** recursiveFromEncodedLong(certs - less.last, less, out ++ Set(Certification.withValue(less.size - 1)))
* 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
} }
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
)
} }
} }

View file

@ -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
}
)
}
}

View file

@ -26,7 +26,7 @@ class CorpseControl(player: Player) extends Actor with ContainableBehavior {
obj.Find(item) match { obj.Find(item) match {
case Some(slot) => case Some(slot) =>
obj.Zone.AvatarEvents ! AvatarServiceMessage( obj.Zone.AvatarEvents ! AvatarServiceMessage(
player.Zone.Id, player.Zone.id,
AvatarAction.SendResponse(Service.defaultPlayerGUID, ObjectAttachMessage(obj.GUID, item.GUID, slot)) AvatarAction.SendResponse(Service.defaultPlayerGUID, ObjectAttachMessage(obj.GUID, item.GUID, slot))
) )
case None => ; case None => ;
@ -40,7 +40,7 @@ class CorpseControl(player: Player) extends Actor with ContainableBehavior {
val zone = obj.Zone val zone = obj.Zone
val events = zone.AvatarEvents val events = zone.AvatarEvents
item.Faction = PlanetSideEmpire.NEUTRAL 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 = { def PutItemInSlotCallback(item: Equipment, slot: Int): Unit = {
@ -49,7 +49,7 @@ class CorpseControl(player: Player) extends Actor with ContainableBehavior {
val events = zone.AvatarEvents val events = zone.AvatarEvents
val definition = item.Definition val definition = item.Definition
events ! AvatarServiceMessage( events ! AvatarServiceMessage(
zone.Id, zone.id,
AvatarAction.SendResponse( AvatarAction.SendResponse(
Service.defaultPlayerGUID, Service.defaultPlayerGUID,
ObjectCreateDetailedMessage( ObjectCreateDetailedMessage(
@ -66,7 +66,7 @@ class CorpseControl(player: Player) extends Actor with ContainableBehavior {
val obj = ContainerObject val obj = ContainerObject
val zone = obj.Zone val zone = obj.Zone
zone.AvatarEvents ! AvatarServiceMessage( zone.AvatarEvents ! AvatarServiceMessage(
zone.Id, zone.id,
AvatarAction.SendResponse( AvatarAction.SendResponse(
Service.defaultPlayerGUID, Service.defaultPlayerGUID,
ObjectDetachMessage(obj.GUID, item.GUID, Vector3.Zero, 0f) ObjectDetachMessage(obj.GUID, item.GUID, Vector3.Zero, 0f)

View file

@ -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))
)
}

View file

@ -3,8 +3,7 @@ package net.psforever.objects.avatar
import net.psforever.objects.PlanetSideGameObject import net.psforever.objects.PlanetSideGameObject
import net.psforever.objects.ce.{Deployable, DeployableCategory, DeployedItem} import net.psforever.objects.ce.{Deployable, DeployableCategory, DeployedItem}
import net.psforever.types.{CertificationType, PlanetSideGUID} import net.psforever.types.PlanetSideGUID
import scala.collection.mutable 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. * 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 * @param certifications a group of certifications for the initial values
* @return `true`, if this is the first time and actual "initialization" is performed; * @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) { if (!initialized) {
DeployableToolbox.Initialize(deployableCounts, categoryCounts, certifications) DeployableToolbox.Initialize(deployableCounts, categoryCounts, certifications)
initialized = true initialized = true
@ -79,8 +79,8 @@ class DeployableToolbox {
* the new certification should already have been added to this group * the new certification should already have been added to this group
*/ */
def AddToDeployableQuantities( def AddToDeployableQuantities(
certification: CertificationType.Value, certification: Certification,
certificationSet: Set[CertificationType.Value] certificationSet: Set[Certification]
): Unit = { ): Unit = {
initialized = true initialized = true
DeployableToolbox.AddToDeployableQuantities(deployableCounts, categoryCounts, certification, certificationSet) DeployableToolbox.AddToDeployableQuantities(deployableCounts, categoryCounts, certification, certificationSet)
@ -96,8 +96,8 @@ class DeployableToolbox {
* the new certification should already have been excluded from this group * the new certification should already have been excluded from this group
*/ */
def RemoveFromDeployableQuantities( def RemoveFromDeployableQuantities(
certification: CertificationType.Value, certification: Certification,
certificationSet: Set[CertificationType.Value] certificationSet: Set[Certification]
): Unit = { ): Unit = {
initialized = true initialized = true
DeployableToolbox.RemoveFromDeployablesQuantities(deployableCounts, categoryCounts, certification, certificationSet) DeployableToolbox.RemoveFromDeployablesQuantities(deployableCounts, categoryCounts, certification, certificationSet)
@ -137,8 +137,8 @@ class DeployableToolbox {
* `false`, otherwise * `false`, otherwise
*/ */
def Available(obj: DeployableToolbox.AcceptableDeployable): Boolean = { def Available(obj: DeployableToolbox.AcceptableDeployable): Boolean = {
deployableCounts(DeployableToolbox.UnifiedType(obj.Definition.Item)).Available && deployableCounts(DeployableToolbox.UnifiedType(obj.Definition.Item)).Available() &&
categoryCounts(obj.Definition.DeployCategory).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(): List[(Int, Int, Int, Int)] = DeployedItem.values flatMap UpdateUIElement toList
def UpdateUI(entry: CertificationType.Value): List[(Int, Int, Int, Int)] = { def UpdateUI(entry: Certification): List[(Int, Int, Int, Int)] = {
import CertificationType._ import Certification._
entry match { entry match {
case AdvancedHacking => case AdvancedHacking =>
UpdateUIElement(DeployedItem.sensor_shield) 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 certifications flatMap UpdateUI
} }
@ -404,7 +404,7 @@ class DeployableToolbox {
deployableLists(category).clear() deployableLists(category).clear()
categoryCounts(category).Current = 0 categoryCounts(category).Current = 0
(Deployable.Category.Includes(category) map DeployableToolbox.UnifiedType toSet) (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 out
} }
@ -481,9 +481,9 @@ object DeployableToolbox {
private def Initialize( private def Initialize(
counts: Map[DeployedItem.Value, DeployableToolbox.Bin], counts: Map[DeployedItem.Value, DeployableToolbox.Bin],
categories: Map[DeployableCategory.Value, DeployableToolbox.Bin], categories: Map[DeployableCategory.Value, DeployableToolbox.Bin],
certifications: Set[CertificationType.Value] certifications: Set[Certification]
): Unit = { ): Unit = {
import CertificationType._ import Certification._
if (certifications.contains(AdvancedEngineering)) { if (certifications.contains(AdvancedEngineering)) {
counts(DeployedItem.boomer).Max = 25 counts(DeployedItem.boomer).Max = 25
counts(DeployedItem.he_mine).Max = 25 counts(DeployedItem.he_mine).Max = 25
@ -548,7 +548,7 @@ object DeployableToolbox {
counts(DeployedItem.sensor_shield).Max = 20 counts(DeployedItem.sensor_shield).Max = 20
} }
} }
if (certifications.contains(CertificationType.GroundSupport)) { if (certifications.contains(Certification.GroundSupport)) {
counts(DeployedItem.router_telepad_deployable).Max = 1024 counts(DeployedItem.router_telepad_deployable).Max = 1024
categories(DeployableCategory.Telepads).Max = 1024 categories(DeployableCategory.Telepads).Max = 1024
} }
@ -564,10 +564,10 @@ object DeployableToolbox {
def AddToDeployableQuantities( def AddToDeployableQuantities(
counts: Map[DeployedItem.Value, DeployableToolbox.Bin], counts: Map[DeployedItem.Value, DeployableToolbox.Bin],
categories: Map[DeployableCategory.Value, DeployableToolbox.Bin], categories: Map[DeployableCategory.Value, DeployableToolbox.Bin],
certification: CertificationType.Value, certification: Certification,
certificationSet: Set[CertificationType.Value] certificationSet: Set[Certification]
): Unit = { ): Unit = {
import CertificationType._ import Certification._
if (certificationSet contains certification) { if (certificationSet contains certification) {
certification match { certification match {
case AdvancedHacking => case AdvancedHacking =>
@ -649,10 +649,10 @@ object DeployableToolbox {
def RemoveFromDeployablesQuantities( def RemoveFromDeployablesQuantities(
counts: Map[DeployedItem.Value, DeployableToolbox.Bin], counts: Map[DeployedItem.Value, DeployableToolbox.Bin],
categories: Map[DeployableCategory.Value, DeployableToolbox.Bin], categories: Map[DeployableCategory.Value, DeployableToolbox.Bin],
certification: CertificationType.Value, certification: Certification,
certificationSet: Set[CertificationType.Value] certificationSet: Set[Certification]
): Unit = { ): Unit = {
import CertificationType._ import Certification._
if (!certificationSet.contains(certification)) { if (!certificationSet.contains(certification)) {
certification match { certification match {
case AdvancedHacking => case AdvancedHacking =>

View file

@ -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)
}
}

View file

@ -1,7 +1,8 @@
// Copyright (c) 2020 PSForever // Copyright (c) 2020 PSForever
package net.psforever.objects.avatar 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.{Player, _}
import net.psforever.objects.ballistics.{PlayerSource, ResolvedProjectile} import net.psforever.objects.ballistics.{PlayerSource, ResolvedProjectile}
import net.psforever.objects.equipment._ import net.psforever.objects.equipment._
@ -22,32 +23,26 @@ import net.psforever.types._
import services.{RemoverActor, Service} import services.{RemoverActor, Service}
import services.avatar.{AvatarAction, AvatarServiceMessage} import services.avatar.{AvatarAction, AvatarServiceMessage}
import services.local.{LocalAction, LocalServiceMessage} import services.local.{LocalAction, LocalServiceMessage}
import akka.actor.typed
import scala.concurrent.duration._ 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 { class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Command])
def JammableObject = player extends Actor
with JammableBehavior
with Damageable
with ContainableBehavior {
def JammableObject = player
def DamageableObject = player def DamageableObject = player
def ContainerObject = player
def ContainerObject = player
private[this] val log = org.log4s.getLogger(player.Name) private[this] val log = org.log4s.getLogger(player.Name)
private[this] val damageLog = org.log4s.getLogger(Damageable.LogChannel) 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) */ /** control agency for the player's locker container (dedicated inventory slot #5) */
val lockerControlAgent: ActorRef = { val lockerControlAgent: ActorRef = {
val locker = player.Locker val locker = player.avatar.locker
locker.Zone = player.Zone locker.Zone = player.Zone
locker.Actor = context.actorOf( locker.Actor = context.actorOf(
Props(classOf[LockerContainerControl], locker, player.Name), Props(classOf[LockerContainerControl], locker, player.Name),
@ -57,9 +52,7 @@ class PlayerControl(player: Player) extends Actor with JammableBehavior with Dam
override def postStop(): Unit = { override def postStop(): Unit = {
lockerControlAgent ! akka.actor.PoisonPill lockerControlAgent ! akka.actor.PoisonPill
player.Locker.Actor = Default.Actor player.avatar.locker.Actor = Default.Actor
staminaRegen.cancel
implantSlotTimers.values.foreach { _.cancel }
} }
def receive: Receive = def receive: Receive =
@ -67,47 +60,6 @@ class PlayerControl(player: Player) extends Actor with JammableBehavior with Dam
.orElse(takesDamage) .orElse(takesDamage)
.orElse(containerBehavior) .orElse(containerBehavior)
.orElse { .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() => case Player.Die() =>
if (player.isAlive) { if (player.isAlive) {
DestructionAwareness(player, None) 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) 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( player.History(
HealFromEquipment( HealFromEquipment(
PlayerSource(player), PlayerSource(player),
@ -171,7 +123,7 @@ class PlayerControl(player: Player) extends Actor with JammableBehavior with Dam
!player.isAlive && !player.isBackpack && !player.isAlive && !player.isBackpack &&
item.Magazine >= 25 item.Magazine >= 25
) { ) {
sender ! CommonMessages.Progress( sender() ! CommonMessages.Progress(
4, 4,
Players.FinishRevivingPlayer(player, user.Name, item), Players.FinishRevivingPlayer(player, user.Name, item),
Players.RevivingTickAction(player, user, 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) 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( player.History(
RepairFromEquipment( RepairFromEquipment(
PlayerSource(player), PlayerSource(player),
@ -232,26 +184,29 @@ class PlayerControl(player: Player) extends Actor with JammableBehavior with Dam
case Terminal.TerminalMessage(_, msg, order) => case Terminal.TerminalMessage(_, msg, order) =>
order match { order match {
case Terminal.BuyExosuit(exosuit, subtype) => case Terminal.BuyExosuit(exosuit, subtype) =>
val time = System.currentTimeMillis
var toDelete: List[InventoryItem] = Nil var toDelete: List[InventoryItem] = Nil
val originalSuit = player.ExoSuit val originalSuit = player.ExoSuit
val originalSubtype = Loadout.DetermineSubtype(player) val originalSubtype = Loadout.DetermineSubtype(player)
val requestToChangeArmor = originalSuit != exosuit || originalSubtype != subtype val requestToChangeArmor = originalSuit != exosuit || originalSubtype != subtype
val allowedToChangeArmor = Players.CertificationToUseExoSuit(player, exosuit, subtype) && val allowedToChangeArmor = Players.CertificationToUseExoSuit(player, exosuit, subtype) &&
(if (exosuit == ExoSuitType.MAX) { (if (exosuit == ExoSuitType.MAX) {
if (time - player.GetLastUsedTime(exosuit, subtype) < 300000L) { val definition = player.avatar.faction match {
false case PlanetSideEmpire.NC => GlobalDefinitions.NCMAX
} else { case PlanetSideEmpire.TR => GlobalDefinitions.TRMAX
player.SetLastUsedTime(exosuit, subtype, time) case PlanetSideEmpire.VS => GlobalDefinitions.VSMAX
true }
player.avatar.purchaseCooldown(definition) match {
case Some(_) =>
false
case None =>
avatarActor ! AvatarActor.UpdatePurchaseTime(definition)
true
} }
} else { } else {
player.SetLastUsedTime(exosuit, subtype, time)
true true
}) })
val result = if (requestToChangeArmor && allowedToChangeArmor) { val result = if (requestToChangeArmor && allowedToChangeArmor) {
log.info(s"${player.Name} wants to change to a different exo-suit - $exosuit") 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 beforeHolsters = Players.clearHolsters(player.Holsters().iterator)
val beforeInventory = player.Inventory.Clear() val beforeInventory = player.Inventory.Clear()
//change suit //change suit
@ -314,18 +269,9 @@ class PlayerControl(player: Player) extends Actor with JammableBehavior with Dam
player.Inventory.InsertQuickly(elem.start, elem.obj) player.Inventory.InsertQuickly(elem.start, elem.obj)
} }
//deactivate non-passive implants //deactivate non-passive implants
implantSlotTimers.keys.foreach { index => avatarActor ! AvatarActor.DeactivateActiveImplants()
val implantSlot = player.ImplantSlot(index)
if (
implantSlot.Installed.nonEmpty && implantSlot.Active && (implantSlot.Charge(
originalSuit
) > 0 || implantSlot.Charge(exosuit) > 0)
) {
ImplantActivation(index, status = 0)
}
}
player.Zone.AvatarEvents ! AvatarServiceMessage( player.Zone.AvatarEvents ! AvatarServiceMessage(
player.Zone.Id, player.Zone.id,
AvatarAction.ChangeExosuit( AvatarAction.ChangeExosuit(
player.GUID, player.GUID,
exosuit, exosuit,
@ -375,19 +321,22 @@ class PlayerControl(player: Player) extends Actor with JammableBehavior with Dam
}) ++ dropHolsters ++ dropInventory }) ++ dropHolsters ++ dropInventory
//a loadout with a prohibited exo-suit type will result in the fallback exo-suit type //a loadout with a prohibited exo-suit type will result in the fallback exo-suit type
//imposed 5min delay on mechanized exo-suit switches //imposed 5min delay on mechanized exo-suit switches
val time = System.currentTimeMillis()
val (nextSuit, nextSubtype) = val (nextSuit, nextSubtype) =
if ( if (
Players.CertificationToUseExoSuit(player, exosuit, subtype) && Players.CertificationToUseExoSuit(player, exosuit, subtype) &&
(if (exosuit == ExoSuitType.MAX) { (if (exosuit == ExoSuitType.MAX) {
if (time - player.GetLastUsedTime(exosuit, subtype) < 300000L) { val definition = player.avatar.faction match {
false case PlanetSideEmpire.NC => GlobalDefinitions.NCMAX
} else { case PlanetSideEmpire.TR => GlobalDefinitions.TRMAX
player.SetLastUsedTime(exosuit, subtype, time) case PlanetSideEmpire.VS => GlobalDefinitions.VSMAX
true }
player.avatar.purchaseCooldown(definition) match {
case Some(_) => false
case None =>
avatarActor ! AvatarActor.UpdatePurchaseTime(definition)
true
} }
} else { } else {
player.SetLastUsedTime(exosuit, subtype, time)
true true
}) })
) { ) {
@ -396,7 +345,6 @@ class PlayerControl(player: Player) extends Actor with JammableBehavior with Dam
log.warn( log.warn(
s"no longer has permission to wear the exo-suit type $exosuit; will wear $fallbackSuit instead" s"no longer has permission to wear the exo-suit type $exosuit; will wear $fallbackSuit instead"
) )
player.SetLastUsedTime(fallbackSuit, fallbackSubtype, time)
(fallbackSuit, fallbackSubtype) (fallbackSuit, fallbackSubtype)
} }
//sanitize (incoming) inventory //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 } (afterHolsters ++ afterInventory).foreach { entry => entry.obj.Faction = player.Faction }
toDeleteOrDrop.foreach { entry => entry.obj.Faction = PlanetSideEmpire.NEUTRAL } toDeleteOrDrop.foreach { entry => entry.obj.Faction = PlanetSideEmpire.NEUTRAL }
//deactivate non-passive implants //deactivate non-passive implants
implantSlotTimers.keys.foreach { index => avatarActor ! AvatarActor.DeactivateActiveImplants()
val implantSlot = player.ImplantSlot(index)
if (
implantSlot.Installed.nonEmpty && implantSlot.Active && (implantSlot.Charge(
originalSuit
) > 0 || implantSlot.Charge(nextSuit) > 0)
) {
ImplantActivation(index, status = 0)
}
}
player.Zone.AvatarEvents ! AvatarServiceMessage( player.Zone.AvatarEvents ! AvatarServiceMessage(
player.Zone.Id, player.Zone.id,
AvatarAction.ChangeLoadout( AvatarAction.ChangeLoadout(
player.GUID, player.GUID,
nextSuit, nextSuit,
@ -487,103 +426,7 @@ class PlayerControl(player: Player) extends Actor with JammableBehavior with Dam
player.Name, player.Name,
AvatarAction.TerminalOrderResult(msg.terminal_guid, msg.transaction_type, true) AvatarAction.TerminalOrderResult(msg.terminal_guid, msg.transaction_type, true)
) )
case _ => assert(false, msg.toString)
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 Zone.Ground.ItemOnGround(item, _, _) => 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)) => case (Some(boomer: BoomerDeployable), Some(avatar)) =>
val guid = boomer.GUID val guid = boomer.GUID
val factionChannel = boomer.Faction.toString val factionChannel = boomer.Faction.toString
if (avatar.Deployables.Remove(boomer)) { if (avatar.deployables.Remove(boomer)) {
boomer.Faction = PlanetSideEmpire.NEUTRAL boomer.Faction = PlanetSideEmpire.NEUTRAL
boomer.AssignOwnership(None) boomer.AssignOwnership(None)
avatar.Deployables.UpdateUIElement(boomer.Definition.Item).foreach { avatar.deployables.UpdateUIElement(boomer.Definition.Item).foreach {
case (currElem, curr, maxElem, max) => case (currElem, curr, maxElem, max) =>
avatarEvents ! AvatarServiceMessage( avatarEvents ! AvatarServiceMessage(
name, name,
@ -648,12 +491,12 @@ class PlayerControl(player: Player) extends Actor with JammableBehavior with Dam
if (player.isAlive && !player.spectator) { if (player.isAlive && !player.spectator) {
val originalHealth = player.Health val originalHealth = player.Health
val originalArmor = player.Armor val originalArmor = player.Armor
val originalStamina = player.Stamina val originalStamina = player.avatar.stamina
val originalCapacitor = player.Capacitor.toInt val originalCapacitor = player.Capacitor.toInt
val cause = applyDamageTo(player) val cause = applyDamageTo(player)
val health = player.Health val health = player.Health
val armor = player.Armor val armor = player.Armor
val stamina = player.Stamina val stamina = player.avatar.stamina
val capacitor = player.Capacitor.toInt val capacitor = player.Capacitor.toInt
val damageToHealth = originalHealth - health val damageToHealth = originalHealth - health
val damageToArmor = originalArmor - armor val damageToArmor = originalArmor - armor
@ -682,7 +525,7 @@ class PlayerControl(player: Player) extends Actor with JammableBehavior with Dam
): Unit = { ): Unit = {
val targetGUID = target.GUID val targetGUID = target.GUID
val zone = target.Zone val zone = target.Zone
val zoneId = zone.Id val zoneId = zone.id
val events = zone.AvatarEvents val events = zone.AvatarEvents
val health = target.Health val health = target.Health
if (damageToArmor > 0) { 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)) events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(targetGUID, 0, health))
} }
if (damageToStamina > 0) { if (damageToStamina > 0) {
UpdateStamina() avatarActor ! AvatarActor.ConsumeStamina(damageToStamina)
} }
//activity on map //activity on map
zone.Activity ! Zone.HotSpot.Activity(cause.target, cause.projectile.owner, cause.hit_pos) 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 zone = target.Zone
val events = zone.AvatarEvents val events = zone.AvatarEvents
val nameChannel = target.Name val nameChannel = target.Name
val zoneChannel = zone.Id val zoneChannel = zone.id
target.Die target.Die
//unjam //unjam
CancelJammeredSound(target) CancelJammeredSound(target)
CancelJammeredStatus(target) CancelJammeredStatus(target)
//implants off
target.Stamina = 0
UpdateStamina() //turn off implants / OutOfStamina
//uninitialize implants //uninitialize implants
target.Implants.indices.foreach { avatarActor ! AvatarActor.DeinitializeImplants()
case slot if target.Implant(slot) != ImplantType.None =>
UninitializeImplant(slot)
}
target.ResetAllImplants() //anything else specific to the backend
events ! AvatarServiceMessage( events ! AvatarServiceMessage(
nameChannel, nameChannel,
AvatarAction.Killed(player_guid, target.VehicleSeated) AvatarAction.Killed(player_guid, target.VehicleSeated)
@ -873,7 +709,7 @@ class PlayerControl(player: Player) extends Actor with JammableBehavior with Dam
target match { target match {
case obj: Player if !jammedSound => case obj: Player if !jammedSound =>
obj.Zone.AvatarEvents ! AvatarServiceMessage( obj.Zone.AvatarEvents ! AvatarServiceMessage(
obj.Zone.Id, obj.Zone.id,
AvatarAction.PlanetsideAttributeToAll(obj.GUID, 27, 1) AvatarAction.PlanetsideAttributeToAll(obj.GUID, 27, 1)
) )
super.StartJammeredSound(obj, 3000) 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 * @param dur the duration of the timer, in milliseconds
*/ */
override def StartJammeredStatus(target: Any, dur: Int): Unit = { override def StartJammeredStatus(target: Any, dur: Int): Unit = {
//TODO these features avatarActor ! AvatarActor.DeinitializeImplants()
val zone = player.Zone avatarActor ! AvatarActor.SuspendStaminaRegeneration(5 seconds)
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)
super.StartJammeredStatus(target, dur) super.StartJammeredStatus(target, dur)
} }
override def CancelJammeredStatus(target: Any): Unit = { override def CancelJammeredStatus(target: Any): Unit = {
player.Implants.indices.foreach { slot => // Start reinitializing all implants avatarActor ! AvatarActor.InitializeImplants(instant = true)
player.ImplantSlot(slot).InitializeTime = 0 //setting time to 0 will restart implant initialization (eventually)
ImplantInitializationStart(slot)
}
super.CancelJammeredStatus(target) super.CancelJammeredStatus(target)
} }
@ -921,7 +745,7 @@ class PlayerControl(player: Player) extends Actor with JammableBehavior with Dam
target match { target match {
case obj: Player if jammedSound => case obj: Player if jammedSound =>
obj.Zone.AvatarEvents ! AvatarServiceMessage( obj.Zone.AvatarEvents ! AvatarServiceMessage(
obj.Zone.Id, obj.Zone.id,
AvatarAction.PlanetsideAttributeToAll(obj.GUID, 27, 0) AvatarAction.PlanetsideAttributeToAll(obj.GUID, 27, 0)
) )
super.CancelJammeredSound(obj) super.CancelJammeredSound(obj)
@ -956,7 +780,7 @@ class PlayerControl(player: Player) extends Actor with JammableBehavior with Dam
val obj = ContainerObject val obj = ContainerObject
val zone = obj.Zone val zone = obj.Zone
val events = zone.AvatarEvents 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 item.Faction = PlanetSideEmpire.NEUTRAL
if (slot == obj.DrawnSlot) { if (slot == obj.DrawnSlot) {
obj.DrawnSlot = Player.HandsDownSlot obj.DrawnSlot = Player.HandsDownSlot
@ -986,7 +810,7 @@ class PlayerControl(player: Player) extends Actor with JammableBehavior with Dam
) )
) )
if (obj.VisibleSlots.contains(slot)) { 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 //handle specific types of items
item match { item match {
@ -998,10 +822,10 @@ class PlayerControl(player: Player) extends Actor with JammableBehavior with Dam
val bguid = boomer.GUID val bguid = boomer.GUID
val faction = player.Faction val faction = player.Faction
val factionChannel = faction.toString val factionChannel = faction.toString
if (avatar.Deployables.Add(boomer)) { if (avatar.deployables.Add(boomer)) {
boomer.Faction = faction boomer.Faction = faction
boomer.AssignOwnership(player) boomer.AssignOwnership(player)
avatar.Deployables.UpdateUIElement(boomer.Definition.Item).foreach { avatar.deployables.UpdateUIElement(boomer.Definition.Item).foreach {
case (currElem, curr, maxElem, max) => case (currElem, curr, maxElem, max) =>
events ! AvatarServiceMessage( events ! AvatarServiceMessage(
name, 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)) 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()
} }

View file

@ -73,12 +73,12 @@ final case class Projectile(
object Projectile { object Projectile {
/** the first projectile GUID used by all clients internally */ /** 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 /** 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 * 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. * Overloaded constructor for an `Unresolved` projectile.

View file

@ -1,10 +1,10 @@
// Copyright (c) 2017 PSForever // Copyright (c) 2017 PSForever
package net.psforever.objects.definition package net.psforever.objects.definition
import net.psforever.objects.avatar.Certification
import net.psforever.objects.ce.DeployedItem import net.psforever.objects.ce.DeployedItem
import net.psforever.objects.definition.converter.ACEConverter import net.psforever.objects.definition.converter.ACEConverter
import net.psforever.objects.equipment.CItem import net.psforever.objects.equipment.CItem
import net.psforever.types.CertificationType
import scala.collection.mutable.ListBuffer import scala.collection.mutable.ListBuffer
@ -27,21 +27,20 @@ object ConstructionItemDefinition {
} }
class ConstructionFireMode { class ConstructionFireMode {
private val deployables: ListBuffer[DeployedItem.Value] = ListBuffer.empty private val deployables: ListBuffer[DeployedItem.Value] = ListBuffer.empty
private val permissions: ListBuffer[Set[CertificationType.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 Deployables: ListBuffer[DeployedItem.Value] = deployables
def Item(deployable: DeployedItem.Value): ListBuffer[DeployedItem.Value] = { def Item(deployable: DeployedItem.Value): ListBuffer[DeployedItem.Value] = {
deployables += deployable deployables += deployable
permissions += Set.empty[CertificationType.Value] permissions += Set.empty[Certification]
deployables deployables
} }
def Item(deployPair: (DeployedItem.Value, Set[CertificationType.Value])): ListBuffer[DeployedItem.Value] = { def Item(deployable: DeployedItem.Value, permission: Set[Certification]): ListBuffer[DeployedItem.Value] = {
val (deployable, permission) = deployPair
deployables += deployable deployables += deployable
permissions += permission permissions += permission
deployables deployables

View file

@ -2,12 +2,13 @@
package net.psforever.objects.definition package net.psforever.objects.definition
import net.psforever.objects.GlobalDefinitions import net.psforever.objects.GlobalDefinitions
import net.psforever.objects.avatar.Certification
import net.psforever.objects.equipment.EquipmentSize import net.psforever.objects.equipment.EquipmentSize
import net.psforever.objects.inventory.InventoryTile import net.psforever.objects.inventory.InventoryTile
import net.psforever.objects.vital._ import net.psforever.objects.vital._
import net.psforever.objects.vital.damage.DamageCalculations import net.psforever.objects.vital.damage.DamageCalculations
import net.psforever.objects.vital.resistance.ResistanceProfileMutators 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. * A definition for producing the personal armor the player wears.
@ -18,15 +19,15 @@ class ExoSuitDefinition(private val suitType: ExoSuitType.Value)
extends BasicDefinition extends BasicDefinition
with ResistanceProfileMutators with ResistanceProfileMutators
with DamageResistanceModel { with DamageResistanceModel {
protected var permissions: List[CertificationType.Value] = List.empty protected var permissions: List[Certification] = List.empty
protected var maxArmor: Int = 0 protected var maxArmor: Int = 0
protected val holsters: Array[EquipmentSize.Value] = Array.fill[EquipmentSize.Value](5)(EquipmentSize.Blocked) 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 inventoryScale: InventoryTile = InventoryTile.Tile11 //override with custom InventoryTile
protected var inventoryOffset: Int = 0 protected var inventoryOffset: Int = 0
protected var maxCapacitor: Int = 0 protected var maxCapacitor: Int = 0
protected var capacitorRechargeDelayMillis: Int = 0 protected var capacitorRechargeDelayMillis: Int = 0
protected var capacitorRechargePerSecond: Int = 0 protected var capacitorRechargePerSecond: Int = 0
protected var capacitorDrainPerSecond: Int = 0 protected var capacitorDrainPerSecond: Int = 0
Name = "exo-suit" Name = "exo-suit"
DamageUsing = DamageCalculations.AgainstExoSuit DamageUsing = DamageCalculations.AgainstExoSuit
ResistUsing = StandardInfantryResistance 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 = certs
Permissions Permissions
} }
def Use: ExoSuitDefinition = this 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 = { override def hashCode(): Int = {
val state = Seq(suitType) val state = Seq(suitType)
state.map(_.hashCode()).foldLeft(0)((a, b) => 31 * a + b) state.map(_.hashCode()).foldLeft(0)((a, b) => 31 * a + b)

View file

@ -14,11 +14,11 @@ import scala.collection.mutable
* Passive implants are always active and thus have no cost. * 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 * 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 * 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 * @param implantType the type of implant that is defined
* @see `ImplantType` * @see `ImplantType`
*/ */
class ImplantDefinition(private val implantType: Int) extends BasicDefinition { class ImplantDefinition(val implantType: ImplantType) extends BasicDefinition {
ImplantType(implantType)
/** how long it takes the implant to become ready for activation; is milliseconds */ /** how long it takes the implant to become ready for activation; is milliseconds */
private var initializationDuration: Long = 0L private var initializationDuration: Long = 0L
@ -84,16 +84,4 @@ class ImplantDefinition(private val implantType: Int) extends BasicDefinition {
def GetCostIntervalByExoSuit(exosuit: ExoSuitType.Value): Int = def GetCostIntervalByExoSuit(exosuit: ExoSuitType.Value): Int =
costIntervalByExoSuit.getOrElse(exosuit, CostIntervalDefault) costIntervalByExoSuit.getOrElse(exosuit, CostIntervalDefault)
def CostIntervalByExoSuitHashMap: mutable.Map[ExoSuitType.Value, Int] = costIntervalByExoSuit 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)
}
} }

View file

@ -4,7 +4,7 @@ package net.psforever.objects.definition.converter
import net.psforever.objects.Player import net.psforever.objects.Player
import net.psforever.objects.equipment.{Equipment, EquipmentSlot} import net.psforever.objects.equipment.{Equipment, EquipmentSlot}
import net.psforever.packet.game.objectcreate._ 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.annotation.tailrec
import scala.util.{Success, Try} import scala.util.{Success, Try}
@ -96,13 +96,13 @@ object AvatarConverter {
false, false,
facingPitch = obj.Orientation.y, facingPitch = obj.Orientation.y,
facingYawUpper = obj.FacingYawUpper, facingYawUpper = obj.FacingYawUpper,
obj.LFS, obj.avatar.lookingForSquad,
GrenadeState.None, GrenadeState.None,
obj.Cloaked, obj.Cloaked,
false, unk5 = false,
false, unk6 = false,
charging_pose = false, charging_pose = false,
false, unk7 = false,
on_zipline = None on_zipline = None
) )
CharacterAppearanceData(aa, ab, RibbonBars()) CharacterAppearanceData(aa, ab, RibbonBars())
@ -117,44 +117,43 @@ object AvatarConverter {
} else { } else {
StatConverter.Health(obj.Armor, MaxArmor) StatConverter.Health(obj.Armor, MaxArmor)
}, },
DressBattleRank(obj), obj.avatar.br.uniformStyle,
0, 0,
DressCommandRank(obj), obj.avatar.cr.value,
MakeImplantEffectList(obj.Implants.toIndexedSeq), obj.avatar.implants.flatten.filter(_.active).flatMap(_.definition.implantType.effect).toList,
MakeCosmetics(obj) obj.avatar.cosmetics
) )
} }
def MakeDetailedCharacterData(obj: Player): Option[Int] => DetailedCharacterData = { def MakeDetailedCharacterData(obj: Player): Option[Int] => DetailedCharacterData = {
val bep: Long = obj.BEP
val maxOpt: Option[Long] = if (obj.ExoSuit == ExoSuitType.MAX) { Some(0L) } val maxOpt: Option[Long] = if (obj.ExoSuit == ExoSuitType.MAX) { Some(0L) }
else { None } else { None }
val ba: DetailedCharacterA = DetailedCharacterA( val ba: DetailedCharacterA = DetailedCharacterA(
bep, obj.avatar.bep,
obj.CEP, obj.avatar.cep,
0L, 0L,
0L, 0L,
0L, 0L,
obj.MaxHealth, obj.MaxHealth,
obj.Health, obj.Health,
false, unk4 = false,
obj.Armor, obj.Armor,
0L, 0L,
obj.MaxStamina, obj.avatar.maxStamina,
obj.Stamina, obj.avatar.stamina,
maxOpt, maxOpt,
0, 0,
0, 0,
0L, 0L,
List(0, 0, 0, 0, 0, 0), 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( val bb: (Long, Option[Int]) => DetailedCharacterB = DetailedCharacterB(
None, None,
MakeImplantEntries(obj), obj.avatar.implants.flatten.map(_.toEntry).toList,
Nil, Nil,
Nil, Nil,
obj.FirstTimeEvents, obj.avatar.firstTimeEvents.toList,
tutorials = List.empty[String], //TODO tutorial list tutorials = List.empty[String], //TODO tutorial list
0L, 0L,
0L, 0L,
@ -165,9 +164,9 @@ object AvatarConverter {
Nil, Nil,
Nil, Nil,
false, 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 = { 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. * 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. * 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) { if (!iter.hasNext) {
list list
} else { } else {
val slot: EquipmentSlot = iter.next val slot: EquipmentSlot = iter.next()
if (slot.Equipment.isDefined) { if (slot.Equipment.isDefined) {
val equip: Equipment = slot.Equipment.get val equip: Equipment = slot.Equipment.get
recursiveMakeHolsters( recursiveMakeHolsters(

View file

@ -1,6 +1,7 @@
// Copyright (c) 2017 PSForever // Copyright (c) 2017 PSForever
package net.psforever.objects.definition.converter package net.psforever.objects.definition.converter
import net.psforever.objects.avatar.Certification
import net.psforever.objects.{Player, Tool} import net.psforever.objects.{Player, Tool}
import net.psforever.objects.equipment.EquipmentSlot import net.psforever.objects.equipment.EquipmentSlot
import net.psforever.packet.game.objectcreate._ import net.psforever.packet.game.objectcreate._
@ -42,10 +43,10 @@ class CharacterSelectConverter extends AvatarConverter {
CommonFieldData( CommonFieldData(
obj.Faction, obj.Faction,
bops = false, bops = false,
false, alternate = false,
false, v1 = false,
None, None,
false, jammered = false,
None, None,
v5 = None, v5 = None,
PlanetSideGUID(0) PlanetSideGUID(0)
@ -62,38 +63,37 @@ class CharacterSelectConverter extends AvatarConverter {
0L, 0L,
outfit_name = "", outfit_name = "",
outfit_logo = 0, outfit_logo = 0,
false, unk1 = false,
backpack = false, backpack = false,
false, unk2 = false,
false, unk3 = false,
false, unk4 = false,
facingPitch = 0, facingPitch = 0,
facingYawUpper = 0, facingYawUpper = 0,
lfs = false, lfs = false,
GrenadeState.None, GrenadeState.None,
obj.Cloaked, is_cloaking = obj.Cloaked,
false, unk5 = false,
false, unk6 = false,
charging_pose = false, charging_pose = false,
false, unk7 = false,
on_zipline = None on_zipline = None
) )
CharacterAppearanceData(aa, ab, RibbonBars()) CharacterAppearanceData(aa, ab, RibbonBars())
} }
private def MakeDetailedCharacterData(obj: Player): Option[Int] => DetailedCharacterData = { private def MakeDetailedCharacterData(obj: Player): Option[Int] => DetailedCharacterData = {
val bep: Long = obj.BEP
val maxOpt: Option[Long] = if (obj.ExoSuit == ExoSuitType.MAX) { Some(0L) } val maxOpt: Option[Long] = if (obj.ExoSuit == ExoSuitType.MAX) { Some(0L) }
else { None } else { None }
val ba: DetailedCharacterA = DetailedCharacterA( val ba: DetailedCharacterA = DetailedCharacterA(
bep, obj.avatar.bep,
obj.CEP, obj.avatar.cep,
0L, 0L,
0L, 0L,
0L, 0L,
1, 1,
1, 1,
false, unk4 = false,
0, 0,
0L, 0L,
1, 1,
@ -103,11 +103,13 @@ class CharacterSelectConverter extends AvatarConverter {
0, 0,
0L, 0L,
List(0, 0, 0, 0, 0, 0), List(0, 0, 0, 0, 0, 0),
certs = List.empty[CertificationType.Value] certs = List.empty[Certification]
) )
val bb: (Long, Option[Int]) => DetailedCharacterB = DetailedCharacterB( val bb: (Long, Option[Int]) => DetailedCharacterB = DetailedCharacterB(
None, 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,
Nil, Nil,
firstTimeEvents = List.empty[String], firstTimeEvents = List.empty[String],
@ -120,20 +122,10 @@ class CharacterSelectConverter extends AvatarConverter {
Some(DCDExtra2(0, 0)), Some(DCDExtra2(0, 0)),
Nil, Nil,
Nil, Nil,
false, unkC = false,
AvatarConverter.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)
}
/**
* 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))
} }
/** /**
@ -152,7 +144,7 @@ class CharacterSelectConverter extends AvatarConverter {
if (!iter.hasNext) { if (!iter.hasNext) {
list list
} else { } else {
val slot: EquipmentSlot = iter.next val slot: EquipmentSlot = iter.next()
slot.Equipment match { slot.Equipment match {
case Some(equip: Tool) => case Some(equip: Tool) =>
val jammed = equip.Jammed val jammed = equip.Jammed

View file

@ -2,6 +2,7 @@
package net.psforever.objects.definition.converter package net.psforever.objects.definition.converter
import net.psforever.objects.Player import net.psforever.objects.Player
import net.psforever.objects.avatar.Certification
import net.psforever.objects.equipment.{Equipment, EquipmentSlot} import net.psforever.objects.equipment.{Equipment, EquipmentSlot}
import net.psforever.packet.game.objectcreate._ import net.psforever.packet.game.objectcreate._
import net.psforever.types.{PlanetSideGUID, _} import net.psforever.types.{PlanetSideGUID, _}
@ -37,9 +38,9 @@ class CorpseConverter extends AvatarConverter {
obj.Faction, obj.Faction,
bops = false, bops = false,
alternate = true, alternate = true,
false, v1 = false,
None, None,
false, jammered = false,
None, None,
v5 = None, v5 = None,
PlanetSideGUID(0) PlanetSideGUID(0)
@ -56,20 +57,20 @@ class CorpseConverter extends AvatarConverter {
0L, 0L,
outfit_name = "", outfit_name = "",
outfit_logo = 0, outfit_logo = 0,
false, unk1 = false,
backpack = true, backpack = true,
false, unk2 = false,
false, unk3 = false,
false, unk4 = false,
facingPitch = 0, facingPitch = 0,
facingYawUpper = 0, facingYawUpper = 0,
lfs = false, lfs = false,
GrenadeState.None, GrenadeState.None,
is_cloaking = false, is_cloaking = false,
false, unk5 = false,
false, unk6 = false,
charging_pose = false, charging_pose = false,
false, unk7 = false,
on_zipline = None on_zipline = None
) )
CharacterAppearanceData(aa, ab, RibbonBars()) CharacterAppearanceData(aa, ab, RibbonBars())
@ -86,7 +87,7 @@ class CorpseConverter extends AvatarConverter {
0L, 0L,
0, 0,
0, 0,
false, unk4 = false,
0, 0,
0L, 0L,
0, 0,
@ -96,7 +97,7 @@ class CorpseConverter extends AvatarConverter {
0, 0,
0L, 0L,
List(0, 0, 0, 0, 0, 0), List(0, 0, 0, 0, 0, 0),
certs = List.empty[CertificationType.Value] certs = List.empty[Certification]
) )
val bb: (Long, Option[Int]) => DetailedCharacterB = DetailedCharacterB( val bb: (Long, Option[Int]) => DetailedCharacterB = DetailedCharacterB(
None, None,
@ -113,7 +114,7 @@ class CorpseConverter extends AvatarConverter {
Some(DCDExtra2(0, 0)), Some(DCDExtra2(0, 0)),
Nil, Nil,
Nil, Nil,
false, unkC = false,
cosmetics = None cosmetics = None
) )
(pad_length: Option[Int]) => DetailedCharacterData(ba, bb(0, pad_length))(pad_length) (pad_length: Option[Int]) => DetailedCharacterData(ba, bb(0, pad_length))(pad_length)
@ -161,7 +162,7 @@ class CorpseConverter extends AvatarConverter {
if (!iter.hasNext) { if (!iter.hasNext) {
list list
} else { } else {
val slot: EquipmentSlot = iter.next val slot: EquipmentSlot = iter.next()
if (slot.Equipment.isDefined) { if (slot.Equipment.isDefined) {
val equip: Equipment = slot.Equipment.get val equip: Equipment = slot.Equipment.get
recursiveMakeHolsters( recursiveMakeHolsters(

View file

@ -159,7 +159,7 @@ trait JammableBehavior {
if (!jammedSound) { if (!jammedSound) {
jammedSound = true jammedSound = true
import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.ExecutionContext.Implicits.global
jammeredSoundTimer.cancel jammeredSoundTimer.cancel()
jammeredSoundTimer = jammeredSoundTimer =
context.system.scheduler.scheduleOnce(dur milliseconds, self, JammableUnit.ClearJammeredSound()) context.system.scheduler.scheduleOnce(dur milliseconds, self, JammableUnit.ClearJammeredSound())
} }
@ -174,7 +174,7 @@ trait JammableBehavior {
*/ */
def StartJammeredStatus(target: Any, dur: Int): Unit = { def StartJammeredStatus(target: Any, dur: Int): Unit = {
JammableObject.Jammed = true JammableObject.Jammed = true
jammeredStatusTimer.cancel jammeredStatusTimer.cancel()
import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.ExecutionContext.Implicits.global
jammeredStatusTimer = jammeredStatusTimer =
context.system.scheduler.scheduleOnce(dur milliseconds, self, JammableUnit.ClearJammeredStatus()) context.system.scheduler.scheduleOnce(dur milliseconds, self, JammableUnit.ClearJammeredStatus())
@ -188,7 +188,7 @@ trait JammableBehavior {
*/ */
def CancelJammeredSound(target: Any): Unit = { def CancelJammeredSound(target: Any): Unit = {
jammedSound = false jammedSound = false
jammeredSoundTimer.cancel jammeredSoundTimer.cancel()
} }
/** /**
@ -199,7 +199,7 @@ trait JammableBehavior {
*/ */
def CancelJammeredStatus(target: Any): Unit = { def CancelJammeredStatus(target: Any): Unit = {
JammableObject.Jammed = false JammableObject.Jammed = false
jammeredStatusTimer.cancel jammeredStatusTimer.cancel()
} }
val jammableBehavior: Receive = { val jammableBehavior: Receive = {
@ -230,7 +230,7 @@ trait JammableMountedWeapons extends JammableBehavior {
target match { target match {
case obj: PlanetSideServerObject with MountedWeapons with JammableUnit if !jammedSound => case obj: PlanetSideServerObject with MountedWeapons with JammableUnit if !jammedSound =>
obj.Zone.VehicleEvents ! VehicleServiceMessage( obj.Zone.VehicleEvents ! VehicleServiceMessage(
obj.Zone.Id, obj.Zone.id,
VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, obj.GUID, 27, 1) VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, obj.GUID, 27, 1)
) )
super.StartJammeredSound(target, dur) super.StartJammeredSound(target, dur)
@ -251,7 +251,7 @@ trait JammableMountedWeapons extends JammableBehavior {
target match { target match {
case obj: PlanetSideServerObject if jammedSound => case obj: PlanetSideServerObject if jammedSound =>
obj.Zone.VehicleEvents ! VehicleServiceMessage( obj.Zone.VehicleEvents ! VehicleServiceMessage(
obj.Zone.Id, obj.Zone.id,
VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, obj.GUID, 27, 0) VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, obj.GUID, 27, 0)
) )
case _ => ; case _ => ;
@ -280,7 +280,7 @@ object JammableMountedWeapons {
*/ */
def JammeredStatus(target: PlanetSideServerObject with MountedWeapons, statusCode: Int): Unit = { def JammeredStatus(target: PlanetSideServerObject with MountedWeapons, statusCode: Int): Unit = {
val zone = target.Zone val zone = target.Zone
val zoneId = zone.Id val zoneId = zone.id
target.Weapons.values target.Weapons.values
.map { _.Equipment } .map { _.Equipment }
.collect { .collect {

View file

@ -153,7 +153,7 @@ object GUIDTask {
*/ */
def RegisterAvatar(tplayer: Player)(implicit guid: ActorRef): TaskResolver.GiveTask = { def RegisterAvatar(tplayer: Player)(implicit guid: ActorRef): TaskResolver.GiveTask = {
val holsterTasks = VisibleSlotTaskBuilding(tplayer.Holsters(), RegisterEquipment) val holsterTasks = VisibleSlotTaskBuilding(tplayer.Holsters(), RegisterEquipment)
val lockerTask = List(RegisterLocker(tplayer.Locker)) val lockerTask = List(RegisterLocker(tplayer.avatar.locker))
val inventoryTasks = RegisterInventory(tplayer) val inventoryTasks = RegisterInventory(tplayer)
TaskResolver.GiveTask(RegisterObjectTask(tplayer).task, holsterTasks ++ lockerTask ++ inventoryTasks) TaskResolver.GiveTask(RegisterObjectTask(tplayer).task, holsterTasks ++ lockerTask ++ inventoryTasks)
} }
@ -311,7 +311,7 @@ object GUIDTask {
*/ */
def UnregisterAvatar(tplayer: Player)(implicit guid: ActorRef): TaskResolver.GiveTask = { def UnregisterAvatar(tplayer: Player)(implicit guid: ActorRef): TaskResolver.GiveTask = {
val holsterTasks = VisibleSlotTaskBuilding(tplayer.Holsters(), UnregisterEquipment) val holsterTasks = VisibleSlotTaskBuilding(tplayer.Holsters(), UnregisterEquipment)
val lockerTask = List(UnregisterLocker(tplayer.Locker)) val lockerTask = List(UnregisterLocker(tplayer.avatar.locker))
val inventoryTasks = UnregisterInventory(tplayer) val inventoryTasks = UnregisterInventory(tplayer)
TaskResolver.GiveTask(UnregisterObjectTask(tplayer).task, holsterTasks ++ lockerTask ++ inventoryTasks) TaskResolver.GiveTask(UnregisterObjectTask(tplayer).task, holsterTasks ++ lockerTask ++ inventoryTasks)
} }
@ -392,7 +392,7 @@ object GUIDTask {
if (!iter.hasNext) { if (!iter.hasNext) {
list list
} else { } else {
iter.next.Equipment match { iter.next().Equipment match {
case Some(item) => case Some(item) =>
recursiveVisibleSlotTaskBuilding(iter, func, list :+ func(item)) recursiveVisibleSlotTaskBuilding(iter, func, list :+ func(item))
case None => case None =>

View file

@ -76,7 +76,7 @@ class TaskResolver() extends Actor {
TimeoutCleanup() TimeoutCleanup()
case msg => 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) { if (!iter.hasNext) {
None None
} else { } else {
val index: Int = iter.next val index: Int = iter.next()
if (tasks(index).task.isComplete == resolution) { if (tasks(index).task.isComplete == resolution) {
Some(index) Some(index)
} else { } else {
@ -428,7 +428,7 @@ object TaskResolver {
if (!iter.hasNext) { if (!iter.hasNext) {
true true
} else { } else {
if (iter.next.isComplete == resolution) { if (iter.next().isComplete == resolution) {
filterCompletionMatch(iter, resolution) filterCompletionMatch(iter, resolution)
} else { } else {
false false
@ -452,7 +452,7 @@ object TaskResolver {
if (!iter.hasNext) { if (!iter.hasNext) {
indexList indexList
} else { } else {
val index: Int = iter.next val index: Int = iter.next()
val taskEntry = tasks(index) val taskEntry = tasks(index)
if ( if (
taskEntry.Executing && taskEntry.task.isComplete == Task.Resolution.Incomplete && now - taskEntry.Start > taskEntry.task.Timeout taskEntry.Executing && taskEntry.task.isComplete == Task.Resolution.Incomplete && now - taskEntry.Start > taskEntry.task.Timeout
@ -476,7 +476,7 @@ object TaskResolver {
if (!iter.hasNext) { if (!iter.hasNext) {
None None
} else { } else {
if (iter.next.task == target) { if (iter.next().task == target) {
Some(index) Some(index)
} else { } else {
findTask(iter, target, index + 1) findTask(iter, target, index + 1)
@ -496,7 +496,7 @@ object TaskResolver {
if (!iter.hasNext) { if (!iter.hasNext) {
None None
} else { } else {
val tEntry = iter.next val tEntry = iter.next()
if (tEntry.subtasks.contains(target)) { if (tEntry.subtasks.contains(target)) {
Some(index) Some(index)
} else { } else {

View file

@ -22,7 +22,7 @@ class NumberPoolActor(pool: NumberPool) extends Actor {
def receive: Receive = { def receive: Receive = {
case NumberPoolActor.GetAnyNumber(id) => case NumberPoolActor.GetAnyNumber(id) =>
sender ! (pool.Get() match { sender() ! (pool.Get() match {
case Success(value) => case Success(value) =>
NumberPoolActor.GiveNumber(value, id) NumberPoolActor.GiveNumber(value, id)
case Failure(ex) => ; case Failure(ex) => ;
@ -30,7 +30,7 @@ class NumberPoolActor(pool: NumberPool) extends Actor {
}) })
case NumberPoolActor.GetSpecificNumber(number, id) => case NumberPoolActor.GetSpecificNumber(number, id) =>
sender ! (NumberPoolActor.GetSpecificNumber(pool, number) match { sender() ! (NumberPoolActor.GetSpecificNumber(pool, number) match {
case Success(value) => case Success(value) =>
NumberPoolActor.GiveNumber(value, id) NumberPoolActor.GiveNumber(value, id)
case Failure(ex) => ; case Failure(ex) => ;
@ -41,7 +41,7 @@ class NumberPoolActor(pool: NumberPool) extends Actor {
val result = pool.Return(number) val result = pool.Return(number)
val ex: Option[Throwable] = if (!result) { Some(new Exception("number was not returned")) } val ex: Option[Throwable] = if (!result) { Some(new Exception("number was not returned")) }
else { None } else { None }
sender ! NumberPoolActor.ReturnNumberResult(number, ex, id) sender() ! NumberPoolActor.ReturnNumberResult(number, ex, id)
case msg => case msg =>
log.info(s"received an unexpected message - ${msg.toString}") log.info(s"received an unexpected message - ${msg.toString}")

View file

@ -126,11 +126,11 @@ class UniqueNumberSystem(private val guid: NumberPoolHub, private val poolActors
case Some(entry) => case Some(entry) =>
entry.replyTo ! Failure(new Exception(s"for ${entry.target} with number $number, ${ex.getMessage}")) entry.replyTo ! Failure(new Exception(s"for ${entry.target} with number $number, ${ex.getMessage}"))
case None => ; 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 //no callback is possible
} }
case _ => ; 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 //no callback is possible
} }

View file

@ -287,7 +287,7 @@ class GridInventory extends Container {
if (!cells.hasNext) { if (!cells.hasNext) {
None None
} else { } else {
val index = cells.next + offset val index = cells.next() + offset
CheckCollisionsAsGrid(index, tWidth, tHeight) match { CheckCollisionsAsGrid(index, tWidth, tHeight) match {
case Success(Nil) => case Success(Nil) =>
Some(index) Some(index)
@ -466,7 +466,7 @@ class GridInventory extends Container {
if (!iter.hasNext) { if (!iter.hasNext) {
None None
} else { } else {
val index = iter.next val index = iter.next()
if (items(index).obj.GUID == guid) { if (items(index).obj.GUID == guid) {
Some(index) Some(index)
} else { } else {
@ -567,7 +567,7 @@ class GridInventory extends Container {
updated: List[List[Int]] updated: List[List[Int]]
): List[List[Int]] = { ): List[List[Int]] = {
if (original.hasNext) { if (original.hasNext) {
val target = original.next val target = original.next()
val filtered = updated.filterNot(item => item.equals(target)) val filtered = updated.filterNot(item => item.equals(target))
val newupdated = if (filtered.size == updated.size) { val newupdated = if (filtered.size == updated.size) {
updated //the lists are the same size, nothing was filtered updated //the lists are the same size, nothing was filtered
@ -588,7 +588,7 @@ class GridInventory extends Container {
*/ */
def Clear(): List[InventoryItem] = { def Clear(): List[InventoryItem] = {
val list = items.values.toList val list = items.values.toList
items.clear items.clear()
entryIndex.set(0) entryIndex.set(0)
grid = SetCellsOnlyNoOffset(0, width, height) grid = SetCellsOnlyNoOffset(0, width, height)
list list

View file

@ -1,7 +1,8 @@
// Copyright (c) 2017 PSForever // Copyright (c) 2017 PSForever
package net.psforever.objects.loadouts 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. * 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. * The ten-long list is initialized with `FavoritesMessage` packets assigned to the "Infantry" list.
* Specific entries are added or removed using `FavoritesRequest` packets, * Specific entries are added or removed using `FavoritesRequest` packets,
* re-established using other conventional game 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; * @param label the name by which this inventory will be known when displayed in a Favorites list;
* field gets inherited * field gets inherited
* @param visible_slots simplified representation of the `Equipment` that can see "seen" on the target; * @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, * 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. * 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." * The "C" does not stand for "certification."
*
* @see `CertificationType` * @see `CertificationType`
* @param subtype the numeric subtype * @param subtype the numeric subtype
* @return a `Set` of all certifications that would grant access to the mechanized assault exo-suit 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 { subtype match {
case 1 => Set(CertificationType.AIMAX, CertificationType.UniMAX) case 1 => Set(Certification.AIMAX, Certification.UniMAX)
case 2 => Set(CertificationType.AVMAX, CertificationType.UniMAX) case 2 => Set(Certification.AVMAX, Certification.UniMAX)
case 3 => Set(CertificationType.AAMAX, CertificationType.UniMAX) case 3 => Set(Certification.AAMAX, Certification.UniMAX)
case _ => Set.empty[CertificationType.Value] case _ => Set.empty[Certification]
} }
} }

View file

@ -203,7 +203,7 @@ object Loadout {
if (!iter.hasNext) { if (!iter.hasNext) {
list list
} else { } else {
val entry = iter.next val entry = iter.next()
entry.Equipment match { entry.Equipment match {
case Some(obj) => case Some(obj) =>
recursiveHolsterSimplifications(iter, index + 1, list :+ SimplifiedEntry(buildSimplification(obj), index)) recursiveHolsterSimplifications(iter, index + 1, list :+ SimplifiedEntry(buildSimplification(obj), index))
@ -231,7 +231,7 @@ object Loadout {
if (!iter.hasNext) { if (!iter.hasNext) {
list list
} else { } else {
val entry = iter.next val entry = iter.next()
val fmodeSimp = if (entry.Box.AmmoType == entry.AmmoType) { val fmodeSimp = if (entry.Box.AmmoType == entry.AmmoType) {
ShorthandAmmoSlot( ShorthandAmmoSlot(
entry.AmmoTypeIndex, entry.AmmoTypeIndex,

View file

@ -1,13 +1,13 @@
// Copyright (c) 2019 PSForever // Copyright (c) 2019 PSForever
package net.psforever.objects.loadouts package net.psforever.objects.loadouts
import net.psforever.types.CertificationType import net.psforever.objects.avatar.Certification
final case class SquadPositionLoadout( final case class SquadPositionLoadout(
index: Int, index: Int,
role: String, role: String,
orders: String, orders: String,
requirements: Set[CertificationType.Value] requirements: Set[Certification]
) )
final case class SquadLoadout(task: String, zone_id: Option[Int], members: List[SquadPositionLoadout]) final case class SquadLoadout(task: String, zone_id: Option[Int], members: List[SquadPositionLoadout])

View file

@ -22,7 +22,7 @@ object FactionAffinityBehavior {
val convertBehavior: Receive = { val convertBehavior: Receive = {
case FactionAffinity.ConvertFactionAffinity(faction) => case FactionAffinity.ConvertFactionAffinity(faction) =>
FactionObject.Faction = faction FactionObject.Faction = faction
sender ! FactionAffinity.AssertFactionAffinity(FactionObject, faction) sender() ! FactionAffinity.AssertFactionAffinity(FactionObject, faction)
} }
} }
@ -36,7 +36,7 @@ object FactionAffinityBehavior {
val checkBehavior: Receive = { val checkBehavior: Receive = {
case FactionAffinity.ConfirmFactionAffinity() | FactionAffinity.AssertFactionAffinity(_, _) => case FactionAffinity.ConfirmFactionAffinity() | FactionAffinity.AssertFactionAffinity(_, _) =>
sender ! FactionAffinity.AssertFactionAffinity(FactionObject, FactionObject.Faction) sender() ! FactionAffinity.AssertFactionAffinity(FactionObject, FactionObject.Faction)
} }
} }
} }

View file

@ -59,36 +59,36 @@ trait ContainableBehavior {
case msg: ContainableMsg if waitOnMoveItemOps == 2 => case msg: ContainableMsg if waitOnMoveItemOps == 2 =>
//all standard messages are blocked //all standard messages are blocked
RepeatMessageLater(ContainableBehavior.Defer(msg, sender)) RepeatMessageLater(ContainableBehavior.Defer(msg, sender()))
MessageDeferredCallback(msg) MessageDeferredCallback(msg)
case msg: DeferrableMsg if waitOnMoveItemOps == 1 => case msg: DeferrableMsg if waitOnMoveItemOps == 1 =>
//insertion messages not related to an item move attempt are blocked //insertion messages not related to an item move attempt are blocked
RepeatMessageLater(ContainableBehavior.Defer(msg, sender)) RepeatMessageLater(ContainableBehavior.Defer(msg, sender()))
MessageDeferredCallback(msg) MessageDeferredCallback(msg)
/* normal messages */ /* normal messages */
case Containable.RemoveItemFromSlot(None, Some(slot)) => case Containable.RemoveItemFromSlot(None, Some(slot)) =>
sender ! LocalRemoveItemFromSlot(slot) sender() ! LocalRemoveItemFromSlot(slot)
case Containable.RemoveItemFromSlot(Some(item), _) => case Containable.RemoveItemFromSlot(Some(item), _) =>
sender ! LocalRemoveItemFromSlot(item) sender() ! LocalRemoveItemFromSlot(item)
case Containable.PutItemInSlot(item, dest) => case Containable.PutItemInSlot(item, dest) =>
/* can be deferred */ /* can be deferred */
sender ! LocalPutItemInSlot(item, dest) sender() ! LocalPutItemInSlot(item, dest)
case Containable.PutItemInSlotOnly(item, dest) => case Containable.PutItemInSlotOnly(item, dest) =>
/* can be deferred */ /* can be deferred */
sender ! LocalPutItemInSlotOnly(item, dest) sender() ! LocalPutItemInSlotOnly(item, dest)
case Containable.PutItemAway(item) => case Containable.PutItemAway(item) =>
/* can be deferred */ /* can be deferred */
sender ! LocalPutItemAway(item) sender() ! LocalPutItemAway(item)
case Containable.PutItemInSlotOrAway(item, dest) => case Containable.PutItemInSlotOrAway(item, dest) =>
/* can be deferred */ /* can be deferred */
sender ! LocalPutItemInSlotOrAway(item, dest) sender() ! LocalPutItemInSlotOrAway(item, dest)
case msg @ Containable.MoveItem(destination, equipment, destSlot) => case msg @ Containable.MoveItem(destination, equipment, destSlot) =>
/* can be deferred */ /* can be deferred */
@ -146,10 +146,10 @@ trait ContainableBehavior {
} }
case ContainableBehavior.MoveItemPutItemInSlot(item, dest) => case ContainableBehavior.MoveItemPutItemInSlot(item, dest) =>
sender ! LocalPutItemInSlot(item, dest) sender() ! LocalPutItemInSlot(item, dest)
case ContainableBehavior.MoveItemPutItemInSlotOrAway(item, dest) => case ContainableBehavior.MoveItemPutItemInSlotOrAway(item, dest) =>
sender ! LocalPutItemInSlotOrAway(item, dest) sender() ! LocalPutItemInSlotOrAway(item, dest)
} }
/* Functions (message control) */ /* Functions (message control) */

View file

@ -34,7 +34,7 @@ object DamageableAmenity {
*/ */
def DestructionAwareness(target: Damageable.Target, cause: ResolvedProjectile): Unit = { def DestructionAwareness(target: Damageable.Target, cause: ResolvedProjectile): Unit = {
val zone = target.Zone val zone = target.Zone
val zoneId = zone.Id val zoneId = zone.id
val events = zone.AvatarEvents val events = zone.AvatarEvents
val targetGUID = target.GUID val targetGUID = target.GUID
events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(targetGUID, 50, 1)) events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(targetGUID, 50, 1))

View file

@ -167,7 +167,7 @@ object DamageableEntity {
if (!target.Destroyed) { if (!target.Destroyed) {
val tguid = target.GUID val tguid = target.GUID
zone.AvatarEvents ! AvatarServiceMessage( zone.AvatarEvents ! AvatarServiceMessage(
zone.Id, zone.id,
AvatarAction.PlanetsideAttributeToAll(tguid, 0, target.Health) AvatarAction.PlanetsideAttributeToAll(tguid, 0, target.Health)
) )
} }
@ -195,7 +195,7 @@ object DamageableEntity {
target.Actor ! JammableUnit.ClearJammeredStatus() target.Actor ! JammableUnit.ClearJammeredStatus()
// //
val zone = target.Zone val zone = target.Zone
val zoneId = zone.Id val zoneId = zone.id
val tguid = target.GUID val tguid = target.GUID
val attribution = target.Zone.LivePlayers.find { p => cause.projectile.owner.Name.equals(p.Name) } match { val attribution = target.Zone.LivePlayers.find { p => cause.projectile.owner.Name.equals(p.Name) } match {
case Some(player) => player.GUID case Some(player) => player.GUID

View file

@ -180,7 +180,7 @@ object DamageableVehicle {
if (target.Shields > 0) { if (target.Shields > 0) {
target.Shields = 0 target.Shields = 0
zone.VehicleEvents ! VehicleServiceMessage( zone.VehicleEvents ! VehicleServiceMessage(
zone.Id, zone.id,
VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, target.GUID, 68, 0) VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, target.GUID, 68, 0)
) )
} }

View file

@ -56,7 +56,7 @@ object DamageableWeaponTurret {
def DestructionAwareness(target: Damageable.Target with MountedWeapons, cause: ResolvedProjectile): Unit = { def DestructionAwareness(target: Damageable.Target with MountedWeapons, cause: ResolvedProjectile): Unit = {
//wreckage has no (visible) mounted weapons //wreckage has no (visible) mounted weapons
val zone = target.Zone val zone = target.Zone
val zoneId = zone.Id val zoneId = zone.id
val avatarEvents = zone.AvatarEvents val avatarEvents = zone.AvatarEvents
target.Weapons.values target.Weapons.values
.filter { .filter {

View file

@ -25,114 +25,133 @@ trait DeploymentBehavior {
val deployBehavior: Receive = { val deployBehavior: Receive = {
case Deployment.TryDeploymentChange(state) => case Deployment.TryDeploymentChange(state) =>
sender ! TryDeploymentStateChange(state) sender() ! TryDeploymentStateChange(state)
case Deployment.TryDeploy(state) => case Deployment.TryDeploy(state) =>
sender ! TryDeployStateChange(state) sender() ! TryDeployStateChange(state)
case Deployment.TryUndeploy(state) => case Deployment.TryUndeploy(state) =>
sender ! TryUndeployStateChange(state) sender() ! TryUndeployStateChange(state)
} }
def TryDeploymentStateChange(state : DriveState.Value) : Any = { def TryDeploymentStateChange(state: DriveState.Value): Any = {
val obj = DeploymentObject val obj = DeploymentObject
val prevState = obj.DeploymentState val prevState = obj.DeploymentState
if(TryDeploymentChange(obj, state)) { if (TryDeploymentChange(obj, state)) {
if(Deployment.CheckForDeployState(state)) { if (Deployment.CheckForDeployState(state)) {
DeploymentAction(obj, state, prevState) DeploymentAction(obj, state, prevState)
Deployment.CanDeploy(obj, state) Deployment.CanDeploy(obj, state)
} } else {
else {
UndeploymentAction(obj, state, prevState) UndeploymentAction(obj, state, prevState)
Deployment.CanUndeploy(obj, state) Deployment.CanUndeploy(obj, state)
} }
} } else {
else {
Deployment.CanNotChangeDeployment(obj, state, "incorrect transition state") Deployment.CanNotChangeDeployment(obj, state, "incorrect transition state")
} }
} }
def TryDeployStateChange(state : DriveState.Value) : Any = { def TryDeployStateChange(state: DriveState.Value): Any = {
val obj = DeploymentObject val obj = DeploymentObject
val prevState = obj.DeploymentState val prevState = obj.DeploymentState
if(Deployment.CheckForDeployState(state) && TryDeploymentChange(obj, state)) { if (Deployment.CheckForDeployState(state) && TryDeploymentChange(obj, state)) {
DeploymentAction(obj, state, prevState) DeploymentAction(obj, state, prevState)
Deployment.CanDeploy(obj, state) Deployment.CanDeploy(obj, state)
} } else {
else {
Deployment.CanNotChangeDeployment(obj, state, "incorrect deploy transition state") Deployment.CanNotChangeDeployment(obj, state, "incorrect deploy transition state")
} }
} }
def TryUndeployStateChange(state : DriveState.Value) : Any = { def TryUndeployStateChange(state: DriveState.Value): Any = {
val obj = DeploymentObject val obj = DeploymentObject
val prevState = obj.DeploymentState val prevState = obj.DeploymentState
if(Deployment.CheckForUndeployState(state) && TryUndeploymentChange(obj, state)) { if (Deployment.CheckForUndeployState(state) && TryUndeploymentChange(obj, state)) {
UndeploymentAction(obj, state, prevState) UndeploymentAction(obj, state, prevState)
Deployment.CanUndeploy(obj, state) Deployment.CanUndeploy(obj, state)
} } else {
else {
Deployment.CanNotChangeDeployment(obj, state, "incorrect undeploy transition state") 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) 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) DeploymentBehavior.TryDeploymentChange(obj, state)
} }
def DeploymentAction(obj : Deployment.DeploymentObject, state : DriveState.Value, prevState : DriveState.Value) : DriveState.Value = { def DeploymentAction(
val guid = obj.GUID obj: Deployment.DeploymentObject,
val zone = obj.Zone state: DriveState.Value,
val zoneChannel = zone.Id prevState: DriveState.Value
val GUID0 = Service.defaultPlayerGUID ): 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 //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 obj.DeploymentState = prevState
prevState prevState
} } else if (state == DriveState.Deploying) {
else if(state == DriveState.Deploying) {
obj.Velocity = Some(Vector3.Zero) //no velocity obj.Velocity = Some(Vector3.Zero) //no velocity
zone.VehicleEvents ! VehicleServiceMessage(zoneChannel, VehicleAction.DeployRequest(GUID0, guid, state, 0, false, Vector3.Zero)) zone.VehicleEvents ! VehicleServiceMessage(
context.system.scheduler.scheduleOnce(obj.DeployTime milliseconds, obj.Actor, Deployment.TryDeploy(DriveState.Deployed)) zoneChannel,
VehicleAction.DeployRequest(GUID0, guid, state, 0, false, Vector3.Zero)
)
context.system.scheduler.scheduleOnce(
obj.DeployTime milliseconds,
obj.Actor,
Deployment.TryDeploy(DriveState.Deployed)
)
state state
} } else if (state == DriveState.Deployed) {
else if(state == DriveState.Deployed) {
obj.Velocity = Some(Vector3.Zero) //no velocity 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 state
} } else {
else {
prevState prevState
} }
} }
def UndeploymentAction(obj : Deployment.DeploymentObject, state : DriveState.Value, prevState : DriveState.Value) : DriveState.Value = { def UndeploymentAction(
val guid = obj.GUID obj: Deployment.DeploymentObject,
val zone = obj.Zone state: DriveState.Value,
val zoneChannel = zone.Id prevState: DriveState.Value
val GUID0 = Service.defaultPlayerGUID ): DriveState.Value = {
if(state == DriveState.Undeploying) { val guid = obj.GUID
zone.VehicleEvents ! VehicleServiceMessage(zoneChannel, VehicleAction.DeployRequest(GUID0, guid, state, 0, false, Vector3.Zero)) 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 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 state
} } else if (state == DriveState.Mobile) {
else if(state == DriveState.Mobile) { zone.VehicleEvents ! VehicleServiceMessage(
zone.VehicleEvents ! VehicleServiceMessage(zoneChannel, VehicleAction.DeployRequest(GUID0, guid, state, 0, false, Vector3.Zero)) zoneChannel,
VehicleAction.DeployRequest(GUID0, guid, state, 0, false, Vector3.Zero)
)
state state
} } else {
else {
prevState prevState
} }
} }
} }
object DeploymentBehavior { 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 Deployment.NextState(obj.DeploymentState) == state && (obj.DeploymentState = state) == state
} }
} }

View file

@ -14,9 +14,9 @@ class DoorControl(door: Door) extends Actor with FactionAffinityBehavior.Check {
def receive: Receive = def receive: Receive =
checkBehavior.orElse { checkBehavior.orElse {
case Door.Use(player, msg) => case Door.Use(player, msg) =>
sender ! Door.DoorMessage(player, msg, door.Use(player, msg)) sender() ! Door.DoorMessage(player, msg, door.Use(player, msg))
case _ => case _ =>
sender ! Door.NoEvent() sender() ! Door.NoEvent()
} }
} }

View file

@ -46,7 +46,7 @@ class GeneratorControl(gen: Generator)
GeneratorControl.UpdateOwner(gen) GeneratorControl.UpdateOwner(gen)
//kaboom //kaboom
zone.AvatarEvents ! AvatarServiceMessage( zone.AvatarEvents ! AvatarServiceMessage(
zone.Id, zone.id,
AvatarAction.SendResponse( AvatarAction.SendResponse(
Service.defaultPlayerGUID, Service.defaultPlayerGUID,
TriggerEffectMessage(gen.GUID, "explosion_generator", None, None) TriggerEffectMessage(gen.GUID, "explosion_generator", None, None)

View file

@ -21,19 +21,19 @@ object GenericHackables {
* @return the percentage amount of progress per tick * @return the percentage amount of progress per tick
*/ */
def GetHackSpeed(player: Player, obj: PlanetSideServerObject): Float = { def GetHackSpeed(player: Player, obj: PlanetSideServerObject): Float = {
val playerHackLevel = Player.GetHackLevel(player) val playerHackLevel = player.avatar.hackingSkillLevel()
val timeToHack = obj match { val timeToHack = obj match {
case vehicle: Vehicle => vehicle.JackingDuration(playerHackLevel).toFloat case vehicle: Vehicle => vehicle.JackingDuration(playerHackLevel).toFloat
case hackable: Hackable => hackable.HackDuration(playerHackLevel).toFloat case hackable: Hackable => hackable.HackDuration(playerHackLevel).toFloat
case _ => case _ =>
log.warn( 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 0f
} }
if (timeToHack == 0) { if (timeToHack == 0) {
log.warn( 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 0f
} else { } else {
@ -110,7 +110,7 @@ object GenericHackables {
ask(target.Actor, CommonMessages.Hack(tplayer, target))(1 second).mapTo[Boolean].onComplete { ask(target.Actor, CommonMessages.Hack(tplayer, target))(1 second).mapTo[Boolean].onComplete {
case Success(_) => case Success(_) =>
val zone = target.Zone val zone = target.Zone
val zoneId = zone.Id val zoneId = zone.id
val pguid = tplayer.GUID val pguid = tplayer.GUID
zone.LocalEvents ! LocalServiceMessage( zone.LocalEvents ! LocalServiceMessage(
zoneId, zoneId,
@ -118,7 +118,8 @@ object GenericHackables {
) )
zone.LocalEvents ! LocalServiceMessage( zone.LocalEvents ! LocalServiceMessage(
zoneId, 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}") case Failure(_) => log.warn(s"Hack message failed on target guid: ${target.GUID}")
} }

View file

@ -19,7 +19,7 @@ object HackableBehavior {
case CommonMessages.Hack(player, _, _) => case CommonMessages.Hack(player, _, _) =>
val obj = HackableObject val obj = HackableObject
obj.HackedBy = player obj.HackedBy = player
sender ! true sender() ! true
case CommonMessages.ClearHack() => case CommonMessages.ClearHack() =>
val obj = HackableObject val obj = HackableObject

View file

@ -44,7 +44,7 @@ class ImplantTerminalMechControl(mech: ImplantTerminalMech)
//TODO setup certifications check //TODO setup certifications check
mech.Owner match { mech.Owner match {
case b: Building if (b.Faction != player.Faction || b.CaptureTerminalIsHacked) && mech.HackedBy.isEmpty => case b: Building if (b.Faction != player.Faction || b.CaptureTerminalIsHacked) && mech.HackedBy.isEmpty =>
sender ! CommonMessages.Progress( sender() ! CommonMessages.Progress(
GenericHackables.GetHackSpeed(player, mech), GenericHackables.GetHackSpeed(player, mech),
GenericHackables.FinishHacking(mech, player, 3212836864L), GenericHackables.FinishHacking(mech, player, 3212836864L),
GenericHackables.HackingTickAction(progressType = 1, player, mech, item.GUID) GenericHackables.HackingTickAction(progressType = 1, player, mech, item.GUID)
@ -60,7 +60,7 @@ class ImplantTerminalMechControl(mech: ImplantTerminalMech)
player: Player player: Player
): Boolean = { ): Boolean = {
val zone = obj.Zone val zone = obj.Zone
zone.map.TerminalToInterface.get(obj.GUID.guid) match { zone.map.terminalToInterface.get(obj.GUID.guid) match {
case Some(interface_guid) => case Some(interface_guid) =>
(zone.GUID(interface_guid) match { (zone.GUID(interface_guid) match {
case Some(interface) => !interface.Destroyed case Some(interface) => !interface.Destroyed

View file

@ -26,13 +26,13 @@ class IFFLockControl(lock: IFFLock)
case CommonMessages.Use(player, Some(item: SimpleItem)) case CommonMessages.Use(player, Some(item: SimpleItem))
if item.Definition == GlobalDefinitions.remote_electronics_kit => if item.Definition == GlobalDefinitions.remote_electronics_kit =>
if (lock.Faction != player.Faction && lock.HackedBy.isEmpty) { if (lock.Faction != player.Faction && lock.HackedBy.isEmpty) {
sender ! CommonMessages.Progress( sender() ! CommonMessages.Progress(
GenericHackables.GetHackSpeed(player, lock), GenericHackables.GetHackSpeed(player, lock),
GenericHackables.FinishHacking(lock, player, 1114636288L), GenericHackables.FinishHacking(lock, player, 1114636288L),
GenericHackables.HackingTickAction(progressType = 1, player, lock, item.GUID) GenericHackables.HackingTickAction(progressType = 1, player, lock, item.GUID)
) )
} else if (lock.Faction == player.Faction && lock.HackedBy.nonEmpty) { } else if (lock.Faction == player.Faction && lock.HackedBy.nonEmpty) {
sender ! CommonMessages.Progress( sender() ! CommonMessages.Progress(
GenericHackables.GetHackSpeed(player, lock), GenericHackables.GetHackSpeed(player, lock),
IFFLocks.FinishResecuringIFFLock(lock), IFFLocks.FinishResecuringIFFLock(lock),
GenericHackables.HackingTickAction(progressType = 1, player, lock, item.GUID) GenericHackables.HackingTickAction(progressType = 1, player, lock, item.GUID)

View file

@ -14,7 +14,7 @@ object IFFLocks {
def FinishResecuringIFFLock(lock: IFFLock)(): Unit = { def FinishResecuringIFFLock(lock: IFFLock)(): Unit = {
val zone = lock.Zone val zone = lock.Zone
lock.Zone.LocalEvents ! LocalServiceMessage( lock.Zone.LocalEvents ! LocalServiceMessage(
zone.Id, zone.id,
LocalAction.ClearTemporaryHack(Service.defaultPlayerGUID, lock) LocalAction.ClearTemporaryHack(Service.defaultPlayerGUID, lock)
) )
} }

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