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
on: [push, pull_request]
jobs:
build:
test:
runs-on: ubuntu-latest
services:
postgres:
@ -43,4 +43,23 @@ jobs:
uses: actions/upload-artifact@v2
with:
name: pslogin.zip
path: target/pslogin*.zip
path: target/pslogin*.zip
docker:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Inject slug/short variables
uses: rlespinasse/github-slug-action@v2.x
- name: Set variables
run: |
echo "::set-env name=REPOSITORY::$(echo $GITHUB_REPOSITORY | tr '[A-Z]' '[a-z]')"
- name: Build and push Docker image
uses: docker/build-push-action@v1.1.0
with:
username: ${{ github.actor }}
password: ${{ github.token }}
registry: docker.pkg.github.com
repository: ${{ env.REPOSITORY }}/server
tag_with_sha: true
tag_with_ref: true

View file

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

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
galaxyService ! GalaxyServiceMessage(GalaxyAction.MapUpdate(building.infoUpdateMessage()))
zone.LocalEvents ! LocalServiceMessage(zone.Id, LocalAction.SetEmpire(building.GUID, faction))
zone.LocalEvents ! LocalServiceMessage(zone.id, LocalAction.SetEmpire(building.GUID, faction))
Behaviors.same
case MapUpdate() =>

View file

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

View file

@ -57,7 +57,7 @@ class CryptoSessionActor extends Actor with MDCContextAware {
this.sessionId = sharedSessionId
leftRef = sender()
if (pipe.hasNext) {
rightRef = pipe.next // who ever we send to has to send something back to us
rightRef = pipe.next() // who ever we send to has to send something back to us
rightRef !> HelloFriend(sessionId, pipe)
} else {
rightRef = sender()

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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
package net.psforever.objects
import net.psforever.objects.avatar.Certification
import net.psforever.objects.ce.DeployedItem
import net.psforever.objects.definition.{ConstructionFireMode, ConstructionItemDefinition}
import net.psforever.objects.equipment.{Equipment, FireModeSwitch}
import net.psforever.types.CertificationType
/**
* A type of `Equipment` that can be wielded and applied to the game world to produce other game objects.<br>
@ -55,7 +55,7 @@ class ConstructionItem(private val cItemDef: ConstructionItemDefinition)
FireMode.Deployables(ammoTypeIndex)
}
def ModePermissions: Set[CertificationType.Value] = FireMode.Permissions(ammoTypeIndex)
def ModePermissions: Set[Certification] = FireMode.Permissions(ammoTypeIndex)
def Definition: ConstructionItemDefinition = cItemDef
}

View file

@ -5,7 +5,8 @@ object Default {
//cancellable
import akka.actor.Cancellable
protected class InternalCancellable extends Cancellable {
override def cancel: Boolean = true
override def cancel(): Boolean = true
override def isCancelled: Boolean = true
}
private val cancellable: Cancellable = new InternalCancellable
@ -28,7 +29,7 @@ object Default {
*/
private class DefaultActor extends AkkaActor {
def receive: Receive = {
case msg => context.system.deadLetters ! DeadLetter(msg, sender, self)
case msg => context.system.deadLetters ! DeadLetter(msg, sender(), self)
}
}
private var defaultRef: ActorRef = ActorRef.noSender
@ -40,7 +41,7 @@ object Default {
*/
def apply(sys: ActorSystem): ActorRef = {
if (defaultRef == ActorRef.noSender) {
defaultRef = sys.actorOf(Props[DefaultActor], name = s"system-default-actor")
defaultRef = sys.actorOf(Props[DefaultActor](), name = s"system-default-actor")
}
defaultRef
}

View file

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

View file

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

View file

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

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

View file

@ -70,14 +70,14 @@ trait NtuStorageBehavior extends Actor {
def NtuStorageObject: NtuContainer = null
def storageBehavior: Receive = {
case Ntu.Offer(src) => HandleNtuOffer(sender, src)
case Ntu.Offer(src) => HandleNtuOffer(sender(), src)
case Ntu.Grant(_, 0) | Ntu.Request(0, 0) | TransferBehavior.Stopping() => StopNtuBehavior(sender)
case Ntu.Grant(_, 0) | Ntu.Request(0, 0) | TransferBehavior.Stopping() => StopNtuBehavior(sender())
case Ntu.Request(min, max) => HandleNtuRequest(sender, min, max)
case Ntu.Request(min, max) => HandleNtuRequest(sender(), min, max)
case Ntu.Grant(src, amount) => HandleNtuGrant(sender, src, amount)
case NtuCommand.Grant(src, amount) => HandleNtuGrant(sender, src, amount)
case Ntu.Grant(src, amount) => HandleNtuGrant(sender(), src, amount)
case NtuCommand.Grant(src, amount) => HandleNtuGrant(sender(), src, amount)
}
def HandleNtuOffer(sender: ActorRef, src: NtuContainer): Unit

View file

@ -1,13 +1,8 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects
import net.psforever.objects.avatar.LoadoutManager
import net.psforever.objects.definition.{
AvatarDefinition,
ExoSuitDefinition,
ImplantDefinition,
SpecialExoSuitDefinition
}
import net.psforever.objects.avatar.{Avatar, LoadoutManager}
import net.psforever.objects.definition.{AvatarDefinition, ExoSuitDefinition, SpecialExoSuitDefinition}
import net.psforever.objects.equipment.{Equipment, EquipmentSize, EquipmentSlot, JammableUnit}
import net.psforever.objects.inventory.{Container, GridInventory, InventoryItem}
import net.psforever.objects.serverobject.PlanetSideServerObject
@ -15,13 +10,12 @@ import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.vital.resistance.ResistanceProfile
import net.psforever.objects.vital.{DamageResistanceModel, Vitality}
import net.psforever.objects.zones.ZoneAware
import net.psforever.packet.game.objectcreate.{Cosmetics, DetailedCharacterData, PersonalStyle}
import net.psforever.types.{PlanetSideGUID, _}
import scala.annotation.tailrec
import scala.util.{Success, Try}
class Player(private val core: Avatar)
class Player(var avatar: Avatar)
extends PlanetSideServerObject
with FactionAffinity
with Vitality
@ -32,7 +26,6 @@ class Player(private val core: Avatar)
Health = 0 //player health is artificially managed as a part of their lifecycle; start entity as dead
Destroyed = true //see isAlive
private var backpack: Boolean = false
private var stamina: Int = 0
private var armor: Int = 0
private var capacitor: Float = 0f
@ -40,8 +33,6 @@ class Player(private val core: Avatar)
private var capacitorLastUsedMillis: Long = 0
private var capacitorLastChargedMillis: Long = 0
private var maxStamina: Int = 100 //does anything affect this?
private var exosuit: ExoSuitDefinition = GlobalDefinitions.Standard
private val freeHand: EquipmentSlot = new OffhandEquipmentSlot(EquipmentSize.Inventory)
private val holsters: Array[EquipmentSlot] = Array.fill[EquipmentSlot](5)(new EquipmentSlot)
@ -54,9 +45,7 @@ class Player(private val core: Avatar)
private var crouching: Boolean = false
private var jumping: Boolean = false
private var cloaked: Boolean = false
private var fatigued: Boolean =
false // If stamina drops to 0, player is fatigued until regenerating at least 20 stamina
private var afk: Boolean = false
private var afk: Boolean = false
private var vehicleSeated: Option[PlanetSideGUID] = None
@ -70,36 +59,35 @@ class Player(private val core: Avatar)
/** From PlanetsideAttributeMessage */
var PlanetsideAttribute: Array[Long] = Array.ofDim(120)
var skipStaminaRegenForTurns: Int = 0
val squadLoadouts = new LoadoutManager(10)
Player.SuitSetup(this, exosuit)
def CharId: Long = core.CharId
def Definition: AvatarDefinition = avatar.definition
def Name: String = core.name
def CharId: Long = avatar.id
def Faction: PlanetSideEmpire.Value = core.faction
def Name: String = avatar.name
def Sex: CharacterGender.Value = core.sex
def Faction: PlanetSideEmpire.Value = avatar.faction
def Head: Int = core.head
def Sex: CharacterGender.Value = avatar.sex
def Voice: CharacterVoice.Value = core.voice
def Head: Int = avatar.head
def LFS: Boolean = core.LFS
def Voice: CharacterVoice.Value = avatar.voice
def isAlive: Boolean = !Destroyed
def isBackpack: Boolean = backpack
def Spawn: Boolean = {
def Spawn(): Boolean = {
if (!isAlive && !isBackpack) {
Destroyed = false
Health = Definition.DefaultHealth
Stamina = MaxStamina
Armor = MaxArmor
Capacitor = 0
ResetAllImplants()
}
isAlive
}
@ -107,7 +95,6 @@ class Player(private val core: Avatar)
def Die: Boolean = {
Destroyed = true
Health = 0
Stamina = 0
false
}
@ -126,21 +113,6 @@ class Player(private val core: Avatar)
}
}
def Stamina: Int = stamina
def Stamina_=(assignStamina: Int): Int = {
stamina = if (isAlive) { math.min(math.max(0, assignStamina), MaxStamina) }
else { 0 }
Stamina
}
def MaxStamina: Int = maxStamina
def MaxStamina_=(max: Int): Int = {
maxStamina = math.min(math.max(0, max), 65535)
MaxStamina
}
def Armor: Int = armor
def Armor_=(assignArmor: Int): Int = {
@ -199,7 +171,7 @@ class Player(private val core: Avatar)
} else if (slot > -1 && slot < 5) {
holsters(slot)
} else if (slot == 5) {
core.FifthSlot
avatar.fifthSlot()
} else if (slot == Player.FreeHandSlot) {
freeHand
} else {
@ -211,10 +183,6 @@ class Player(private val core: Avatar)
def Inventory: GridInventory = inventory
def Locker: LockerContainer = core.Locker
def FifthSlot: EquipmentSlot = core.FifthSlot
override def Fit(obj: Equipment): Option[Int] = {
recursiveHolsterFit(holsters.iterator, obj.Size) match {
case Some(index) =>
@ -238,7 +206,7 @@ class Player(private val core: Avatar)
if (!iter.hasNext) {
None
} else {
val slot = iter.next
val slot = iter.next()
if (slot.Equipment.isEmpty && slot.Size.equals(objSize)) {
Some(index)
} else {
@ -278,7 +246,7 @@ class Player(private val core: Avatar)
if (!iter.hasNext) {
None
} else {
val slot = iter.next
val slot = iter.next()
if (slot.Equipment.isDefined && slot.Equipment.get.GUID == guid) {
Some(index)
} else {
@ -343,40 +311,6 @@ class Player(private val core: Avatar)
def RadiationShielding = exosuit.RadiationShielding
def EquipmentLoadouts: LoadoutManager = core.EquipmentLoadouts
def SquadLoadouts: LoadoutManager = core.SquadLoadouts
def BEP: Long = core.BEP
def CEP: Long = core.CEP
def Certifications: Set[CertificationType.Value] = core.Certifications.toSet
/**
* What kind of implant is installed into the given slot number?
* @see `ImplantType`
* @param slot the slot number
* @return the tye of implant
*/
def Implant(slot: Int): ImplantType.Value = core.Implant(slot)
def ImplantSlot(slot: Int): ImplantSlot = core.Implants(slot)
/**
* A read-only `Array` of tuples representing important information about all unlocked implant slots.
* @return a maximum of three implant types, initialization times, and active flags
*/
def Implants: Array[(ImplantType.Value, Long, Boolean)] = {
core.Implants.takeWhile(_.Unlocked).map(implant => { (implant.Implant, implant.MaxTimer, implant.Active) })
}
def InstallImplant(implant: ImplantDefinition): Option[Int] = core.InstallImplant(implant)
def UninstallImplant(implant: ImplantType.Value): Option[Int] = core.UninstallImplant(implant)
def ResetAllImplants(): Unit = core.ResetAllImplants()
def FacingYawUpper: Float = facingYawUpper
def FacingYawUpper_=(facing: Float): Float = {
@ -405,13 +339,6 @@ class Player(private val core: Avatar)
Cloaked
}
def Fatigued: Boolean = fatigued
def Fatigued_=(isFatigued: Boolean): Boolean = {
fatigued = isFatigued
Fatigued
}
def AwayFromKeyboard: Boolean = afk
def AwayFromKeyboard_=(away: Boolean): Boolean = {
@ -419,66 +346,6 @@ class Player(private val core: Avatar)
AwayFromKeyboard
}
def PersonalStyleFeatures: Option[Cosmetics] = core.PersonalStyleFeatures
def AddToPersonalStyle(value: PersonalStyle.Value): (Option[Cosmetics], Option[Cosmetics]) = {
val original = core.PersonalStyleFeatures
if (DetailedCharacterData.isBR24(core.BEP)) {
core.PersonalStyleFeatures = original match {
case Some(cosmetic) =>
cosmetic + value
case None =>
Cosmetics(value)
}
(original, core.PersonalStyleFeatures)
} else {
(None, None)
}
}
def RemoveFromPersonalStyle(value: PersonalStyle.Value): (Option[Cosmetics], Option[Cosmetics]) = {
val original = core.PersonalStyleFeatures
original match {
case Some(cosmetics) =>
(original, core.PersonalStyleFeatures = cosmetics - value)
case None =>
(None, None)
}
}
private def BasicFeatureToggle(feature: PersonalStyle.Value): (Option[Cosmetics], Option[Cosmetics]) =
core.PersonalStyleFeatures match {
case Some(c: Cosmetics) =>
if (c.Styles.contains(feature)) {
RemoveFromPersonalStyle(feature)
} else {
AddToPersonalStyle(feature)
}
case None =>
AddToPersonalStyle(feature)
}
def ToggleHelmet: (Option[Cosmetics], Option[Cosmetics]) = BasicFeatureToggle(PersonalStyle.NoHelmet)
def ToggleShades: (Option[Cosmetics], Option[Cosmetics]) = BasicFeatureToggle(PersonalStyle.Sunglasses)
def ToggleEarpiece: (Option[Cosmetics], Option[Cosmetics]) = BasicFeatureToggle(PersonalStyle.Earpiece)
def ToggleHat: (Option[Cosmetics], Option[Cosmetics]) = {
core.PersonalStyleFeatures match {
case Some(c: Cosmetics) =>
if (c.Styles.contains(PersonalStyle.BrimmedCap)) {
(RemoveFromPersonalStyle(PersonalStyle.BrimmedCap)._1, AddToPersonalStyle(PersonalStyle.Beret)._2)
} else if (c.Styles.contains(PersonalStyle.Beret)) {
RemoveFromPersonalStyle(PersonalStyle.Beret)
} else {
AddToPersonalStyle(PersonalStyle.BrimmedCap)
}
case None =>
AddToPersonalStyle(PersonalStyle.BrimmedCap)
}
}
private var usingSpecial: SpecialExoSuitDefinition.Mode.Value => SpecialExoSuitDefinition.Mode.Value =
DefaultUsingSpecial
@ -602,8 +469,6 @@ class Player(private val core: Avatar)
isBackpack && (backpackAccess.isEmpty || backpackAccess.contains(player.GUID))
}
def FirstTimeEvents: List[String] = core.FirstTimeEvents
def VehicleSeated: Option[PlanetSideGUID] = vehicleSeated
def VehicleSeated_=(guid: PlanetSideGUID): Option[PlanetSideGUID] = VehicleSeated_=(Some(guid))
@ -613,57 +478,31 @@ class Player(private val core: Avatar)
VehicleSeated
}
def VehicleOwned: Option[PlanetSideGUID] = core.VehicleOwned
def VehicleOwned_=(guid: PlanetSideGUID): Option[PlanetSideGUID] = core.VehicleOwned_=(Some(guid))
def VehicleOwned_=(guid: Option[PlanetSideGUID]): Option[PlanetSideGUID] = core.VehicleOwned_=(guid)
def GetLastUsedTime(code: Int): Long = core.GetLastUsedTime(code)
def GetLastUsedTime(code: ExoSuitType.Value): Long = core.GetLastUsedTime(code)
def GetLastUsedTime(code: ExoSuitType.Value, subtype: Int): Long = core.GetLastUsedTime(code, subtype)
def SetLastUsedTime(code: Int, time: Long): Unit = core.SetLastUsedTime(code, time)
def SetLastUsedTime(code: ExoSuitType.Value): Unit = core.SetLastUsedTime(code)
def SetLastUsedTime(code: ExoSuitType.Value, time: Long): Unit = core.SetLastUsedTime(code, time)
def SetLastUsedTime(code: ExoSuitType.Value, subtype: Int): Unit = core.SetLastUsedTime(code, subtype)
def SetLastUsedTime(code: ExoSuitType.Value, subtype: Int, time: Long): Unit =
core.SetLastUsedTime(code, subtype, time)
def GetLastPurchaseTime(code: Int): Long = core.GetLastPurchaseTime(code)
def SetLastPurchaseTime(code: Int, time: Long): Unit = core.SetLastPurchaseTime(code, time)
def ObjectTypeNameReference(id: Long): String = core.ObjectTypeNameReference(id)
def ObjectTypeNameReference(id: Long, name: String): String = core.ObjectTypeNameReference(id, name)
def DamageModel = exosuit.asInstanceOf[DamageResistanceModel]
def Definition: AvatarDefinition = core.Definition
def canEqual(other: Any): Boolean = other.isInstanceOf[Player]
override def equals(other: Any): Boolean =
other match {
case that: Player =>
(that canEqual this) &&
core == that.core
avatar == that.avatar
case _ =>
false
}
override def hashCode(): Int = {
core.hashCode()
avatar.hashCode()
}
override def toString: String = Player.toString(this)
override def toString: String = {
val guid = if (HasGUID) {
s" ${Continent}-${GUID.guid}"
} else {
""
}
s"${avatar.name}$guid ${Health}/${MaxHealth} ${Armor}/${MaxArmor}"
}
}
object Player {
@ -672,16 +511,6 @@ object Player {
final val HandsDownSlot: Int = 255
final case class Die()
final case class ImplantActivation(slot: Int, status: Int)
final case class ImplantInitializationStart(slot: Int)
final case class UninitializeImplant(slot: Int)
final case class ImplantInitializationComplete(slot: Int)
final case class StaminaRegen()
final case class StaminaChanged(currentStamina: Option[Int] = None)
object StaminaChanged {
def apply(amount: Int): StaminaChanged = StaminaChanged(Some(amount))
}
def apply(core: Avatar): Player = {
new Player(core)
@ -698,33 +527,11 @@ object Player {
def Respawn(player: Player): Player = {
if (player.Release) {
val obj = new Player(player.core)
val obj = new Player(player.avatar)
obj.Continent = player.Continent
obj
} else {
player
}
}
def GetHackLevel(player: Player): Int = {
if (
player.Certifications.contains(CertificationType.ExpertHacking) || player.Certifications.contains(
CertificationType.ElectronicsExpert
)
) {
3
} else if (player.Certifications.contains(CertificationType.AdvancedHacking)) {
2
} else if (player.Certifications.contains(CertificationType.Hacking)) {
1
} else {
0
}
}
def toString(obj: Player): String = {
val guid = if (obj.HasGUID) { s" ${obj.Continent}-${obj.GUID.guid}" }
else { "" }
s"${obj.core}$guid ${obj.Health}/${obj.MaxHealth} ${obj.Armor}/${obj.MaxArmor}"
}
}

View file

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

View file

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

View file

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

View file

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

View file

@ -111,11 +111,11 @@ object SpawnPoint {
val ori = target.Orientation
val zrad = math.toRadians(ori.z)
val radius =
scala.math.random.toFloat * d.UseRadius / 2 + 20f //20 is definitely outside of the gating energy field
scala.math.random().toFloat * d.UseRadius / 2 + 20f //20 is definitely outside of the gating energy field
val shift = Vector3(math.sin(zrad).toFloat, math.cos(zrad).toFloat, 0) * radius
val altitudeShift = target.Definition match {
case vdef: VehicleDefinition if GlobalDefinitions.isFlightVehicle(vdef) =>
Vector3.z(scala.math.random.toFloat * d.UseRadius / 4 + 20f)
Vector3.z(scala.math.random().toFloat * d.UseRadius / 4 + 20f)
case _ =>
Vector3.Zero
}

View file

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

View file

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

View file

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

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
import net.psforever.types.CertificationType
import enumeratum.values.{IntEnum, IntEnumEntry}
import net.psforever.packet.PacketHelpers
import scodec.Codec
import scodec.codecs._
import scala.collection.mutable
import scala.annotation.tailrec
object Certification {
object Dependencies {
sealed abstract class Certification(
val value: Int,
/** Name used in packets */
val name: String,
/** Certification point cost */
val cost: Int,
val requires: Set[Certification] = Set(),
val replaces: Set[Certification] = Set()
) extends IntEnumEntry
/**
* Find the certifications that are immediately dependent on the target certification.
* (For `A`, find all `B` that are `B ⇒ A`.)
* @param certification the target certification
* @return all connected certifications
*/
def From(certification: CertificationType.Value): Set[CertificationType.Value] = dependencies(certification).toSet
case object Certification extends IntEnum[Certification] {
/**
* Find all certifications that are dependent on the target certification.
* (For `A`, find all `B...C` where `C ⇒ B` and `B ⇒ A`.)
* @param certification the target certification
* @return all connected certifications
*/
def FromAll(certification: CertificationType.Value): Set[CertificationType.Value] = {
var available: List[CertificationType.Value] = List(certification)
var allocated: mutable.ListBuffer[CertificationType.Value] = mutable.ListBuffer.empty[CertificationType.Value]
do {
available = available.flatMap(cert => dependencies(cert))
allocated ++= available
} while (available.nonEmpty)
allocated.toSet
}
case object StandardAssault extends Certification(value = 0, name = "standard_assault", cost = 0)
/**
* Find the certifications that are immediate dependencies of the target certification.
* (For `A`, find all `B` where `A ⇒ B`.)
* @param certification the target certification
* @return all connected certifications
*/
def For(certification: CertificationType.Value): Set[CertificationType.Value] = {
(for {
(cert, certs) <- dependencies
if certs contains certification
} yield cert).toSet
}
case object MediumAssault extends Certification(value = 1, name = "medium_assault", cost = 2)
/**
* Find all certifications that are dependencies of the target certification.
* (For `A`, find all `B...C` where `A ⇒ B` and `B ⇒ C`.)
* @param certification the target certification
* @return all connected certifications
*/
def ForAll(certification: CertificationType.Value): Set[CertificationType.Value] = {
var available: List[CertificationType.Value] = List(certification)
var allocated: mutable.ListBuffer[CertificationType.Value] = mutable.ListBuffer.empty[CertificationType.Value]
do {
available = available.flatMap {
For
}
allocated ++= available
} while (available.nonEmpty)
allocated.toSet
}
case object HeavyAssault
extends Certification(value = 2, name = "heavy_assault", cost = 4, requires = Set(MediumAssault))
import CertificationType._
case object SpecialAssault
extends Certification(value = 3, name = "special_assault", cost = 3, requires = Set(MediumAssault))
/**
* Find all certifications that are related but mutually exclusive with the target certification.
* (For `A`, find all `B` that `B ⊃ A` but `A XOR B`.)
* @param certification the target certification
* @return all connected certifications
*/
def Like(certification: CertificationType.Value): Set[CertificationType.Value] =
certification match {
case AssaultBuggy =>
Set(Harasser)
case LightScout =>
Set(AirCavalryScout, AssaultBuggy, Harasser)
case UniMAX =>
Set(AAMAX, AIMAX, AVMAX)
case AdvancedEngineering =>
Set(AssaultEngineering, FortificationEngineering)
case ElectronicsExpert =>
Set(DataCorruption, ExpertHacking)
case _ =>
Set.empty[CertificationType.Value]
}
case object AntiVehicular
extends Certification(value = 4, name = "anti_vehicular", cost = 3, requires = Set(MediumAssault))
private val dependencies: Map[CertificationType.Value, List[CertificationType.Value]] = Map(
StandardAssault -> List(),
AgileExoSuit -> List(),
ReinforcedExoSuit -> List(),
InfiltrationSuit -> List(Phantasm),
AIMAX -> List(),
AVMAX -> List(),
AAMAX -> List(),
UniMAX -> List(),
StandardAssault -> List(),
MediumAssault -> List(AntiVehicular, HeavyAssault, Sniping, SpecialAssault),
AntiVehicular -> List(),
HeavyAssault -> List(),
Sniping -> List(),
SpecialAssault -> List(EliteAssault),
EliteAssault -> List(),
ATV -> List(Switchblade),
Switchblade -> List(),
Harasser -> List(),
AssaultBuggy -> List(),
LightScout -> List(AirCavalryAssault),
GroundSupport -> List(),
GroundTransport -> List(),
ArmoredAssault1 -> List(ArmoredAssault2),
ArmoredAssault2 -> List(BattleFrameRobotics, Flail),
Flail -> List(),
AirCavalryScout -> List(AirCavalryAssault),
AirCavalryAssault -> List(AirCavalryInterceptor),
AirCavalryInterceptor -> List(),
AirSupport -> List(GalaxyGunship),
GalaxyGunship -> List(),
Phantasm -> List(),
BattleFrameRobotics -> List(BFRAntiInfantry, BFRAntiAircraft),
BFRAntiInfantry -> List(),
BFRAntiAircraft -> List(),
Medical -> List(AdvancedMedical),
AdvancedMedical -> List(),
Engineering -> List(CombatEngineering),
CombatEngineering -> List(AdvancedEngineering, AssaultEngineering, FortificationEngineering),
AdvancedEngineering -> List(),
AssaultEngineering -> List(),
FortificationEngineering -> List(),
Hacking -> List(AdvancedHacking),
AdvancedHacking -> List(DataCorruption, ElectronicsExpert, ExpertHacking),
DataCorruption -> List(),
ElectronicsExpert -> List(),
ExpertHacking -> List()
case object Sniping extends Certification(value = 5, name = "sniper", cost = 3, requires = Set(MediumAssault))
case object EliteAssault
extends Certification(value = 6, name = "special_assault_2", cost = 1, requires = Set(SpecialAssault))
case object AirCavalryScout extends Certification(value = 7, name = "air_cavalry_scout", cost = 3)
case object AirCavalryInterceptor
extends Certification(value = 8, name = "air_cavalry_interceptor", cost = 2, requires = Set(AirCavalryScout))
case object AirCavalryAssault
extends Certification(
value = 9,
name = "air_cavalry_assault",
cost = 2,
requires = Set(AirCavalryScout)
)
case object AirSupport extends Certification(value = 10, name = "air_support", cost = 3)
case object ATV extends Certification(value = 11, name = "quad_all", cost = 1)
case object LightScout
extends Certification(
value = 12,
name = "light_scout",
cost = 5,
replaces = Set(AirCavalryScout, AssaultBuggy, Harasser)
)
case object AssaultBuggy extends Certification(value = 13, name = "assault_buggy", cost = 3, replaces = Set(Harasser))
case object ArmoredAssault1 extends Certification(value = 14, name = "armored_assault1", cost = 2)
case object ArmoredAssault2
extends Certification(value = 15, name = "armored_assault2", cost = 3, requires = Set(ArmoredAssault1))
case object GroundTransport extends Certification(value = 16, name = "ground_transport", cost = 2)
case object GroundSupport extends Certification(value = 17, name = "ground_support", cost = 2)
case object BattleFrameRobotics
extends Certification(value = 18, name = "TODO2", cost = 4, requires = Set(ArmoredAssault2)) // TODO name
case object Flail extends Certification(value = 19, name = "flail", cost = 1, requires = Set(ArmoredAssault2))
case object Switchblade extends Certification(value = 20, name = "switchblade", cost = 1, requires = Set(ATV))
case object Harasser extends Certification(value = 21, name = "harasser", cost = 1)
case object Phantasm extends Certification(value = 22, name = "phantasm", cost = 3, requires = Set(InfiltrationSuit))
case object GalaxyGunship extends Certification(value = 23, name = "gunship", cost = 2, requires = Set(AirSupport))
case object BFRAntiAircraft
extends Certification(value = 24, name = "TODO3", cost = 1, requires = Set(BattleFrameRobotics))
case object BFRAntiInfantry
extends Certification(value = 25, name = "TODO4", cost = 1, requires = Set(BattleFrameRobotics)) // TODO name
case object StandardExoSuit extends Certification(value = 26, name = "TODO5", cost = 0)
case object AgileExoSuit extends Certification(value = 27, name = "agile_armor", cost = 0)
case object ReinforcedExoSuit extends Certification(value = 28, name = "reinforced_armor", cost = 3)
case object InfiltrationSuit extends Certification(value = 29, name = "infiltration_suit", cost = 2)
case object AAMAX extends Certification(value = 30, name = "max_anti_aircraft", cost = 2)
case object AIMAX extends Certification(value = 31, name = "max_anti_personnel", cost = 3)
case object AVMAX extends Certification(value = 32, name = "max_anti_vehicular", cost = 3)
case object UniMAX extends Certification(value = 33, name = "max_all", cost = 6, replaces = Set(AAMAX, AIMAX, AVMAX))
case object Medical extends Certification(value = 34, name = "Medical", cost = 3)
case object AdvancedMedical
extends Certification(value = 35, name = "advanced_medical", cost = 2, requires = Set(Medical))
case object Hacking extends Certification(value = 36, name = "Hacking", cost = 3)
case object AdvancedHacking
extends Certification(value = 37, name = "advanced_hacking", cost = 2, requires = Set(Hacking))
case object ExpertHacking
extends Certification(value = 38, name = "expert_hacking", cost = 2, requires = Set(AdvancedHacking))
case object DataCorruption
extends Certification(value = 39, name = "virus_hacking", cost = 3, requires = Set(AdvancedHacking))
case object ElectronicsExpert
extends Certification(
value = 40,
name = "electronics_expert",
cost = 4,
requires = Set(AdvancedHacking),
replaces = Set(DataCorruption, ExpertHacking)
)
case object Engineering extends Certification(value = 41, name = "Repair", cost = 3)
case object CombatEngineering
extends Certification(value = 42, name = "combat_engineering", cost = 2, requires = Set(Engineering))
case object FortificationEngineering
extends Certification(value = 43, name = "ce_defense", cost = 3, requires = Set(CombatEngineering))
case object AssaultEngineering
extends Certification(value = 44, name = "ce_offense", cost = 3, requires = Set(CombatEngineering))
case object AdvancedEngineering
extends Certification(
value = 45,
name = "ce_advanced",
cost = 5,
requires = Set(CombatEngineering),
replaces = Set(AssaultEngineering, FortificationEngineering)
)
// https://github.com/lloydmeta/enumeratum/issues/86
lazy val values: IndexedSeq[Certification] = findValues
implicit val codec: Codec[Certification] = PacketHelpers.createIntEnumCodec(this, uint8L)
/**
* Certifications are often stored, in object form, as a 46-member collection.
* Encode a subset of certification values for packet form.
*
* @return the certifications, as a single value
*/
def toEncodedLong(certs: Set[Certification]): Long = {
certs
.map { cert => math.pow(2, cert.value).toLong }
.foldLeft(0L)(_ + _)
}
/**
* Certifications are often stored, in packet form, as an encoded little-endian `46u` value.
* Decode a representative value into a subset of certification values.
*
* @see `ChangeSquadMemberRequirementsCertifications`
* @see `changeSquadMemberRequirementsCertificationsCodec`
* @see `fromEncodedLong(Long, Iterable[Long], Set[CertificationType.Value])`
* @param certs the certifications, as a single value
* @return the certifications, as a sequence of values
*/
def fromEncodedLong(certs: Long): Set[Certification] = {
recursiveFromEncodedLong(
certs,
Certification.values.map { cert => math.pow(2, cert.value).toLong }.sorted
)
}
object Cost {
/**
* For a certification, get its point cost.
* @param certification the certification
* @return the cost
*/
def Of(certification: CertificationType.Value): Int = points(certification)
/**
* For a list of certifications, find the point cost of all unique certifications.
* @see `Of(Set)`
* @param certifications the certification list
* @return the total cost
*/
def Of(certifications: List[CertificationType.Value]): Int = Of(certifications.toSet)
/**
* For a set of certifications, find the point cost of all certifications.
* @see `OfAll(List)`
* @param certifications the certification list
* @return the total cost
*/
def Of(certifications: Set[CertificationType.Value]): Int = OfAll(certifications.toList)
/**
* For a list of certifications, find the point cost of all certifications, counting any duplicates.
* @param certifications the certification list
* @return the total cost
*/
def OfAll(certifications: List[CertificationType.Value]): Int = {
certifications map points sum
/**
* Certifications are often stored, in packet form, as an encoded little-endian `46u` value.
* Decode a representative value into a subset of certification values
* by repeatedly finding the partition point of values less than a specific one,
* providing for both the next lowest value (to subtract) and an index (of a certification).
*
* @see `ChangeSquadMemberRequirementsCertifications`
* @see `changeSquadMemberRequirementsCertificationsCodec`
* @see `fromEncodedLong(Long)`
* @param certs the certifications, as a single value
* @param splitList the available values to partition
* @param out the accumulating certification values;
* defaults to an empty set
* @return the certifications, as a sequence of values
*/
@tailrec
private def recursiveFromEncodedLong(
certs: Long,
splitList: Iterable[Long],
out: Set[Certification] = Set.empty
): Set[Certification] = {
if (certs == 0 || splitList.isEmpty) {
out
} else {
val (less, _) = splitList.partition(_ <= certs)
recursiveFromEncodedLong(certs - less.last, less, out ++ Set(Certification.withValue(less.size - 1)))
}
import CertificationType._
private val points: Map[CertificationType.Value, Int] = Map(
StandardExoSuit -> 0,
AgileExoSuit -> 0,
ReinforcedExoSuit -> 3,
InfiltrationSuit -> 2,
AAMAX -> 2,
AIMAX -> 3,
AVMAX -> 3,
UniMAX -> 6,
StandardAssault -> 0,
MediumAssault -> 2,
AntiVehicular -> 3,
HeavyAssault -> 4,
Sniping -> 3,
SpecialAssault -> 3,
EliteAssault -> 1,
ATV -> 1,
Switchblade -> 1,
Harasser -> 1,
AssaultBuggy -> 3,
LightScout -> 5,
GroundSupport -> 2,
GroundTransport -> 2,
ArmoredAssault1 -> 2,
ArmoredAssault2 -> 3,
Flail -> 1,
AirCavalryScout -> 3,
AirCavalryAssault -> 2,
AirCavalryInterceptor -> 2,
AirSupport -> 3,
GalaxyGunship -> 2,
Phantasm -> 3,
BattleFrameRobotics -> 4,
BFRAntiInfantry -> 1,
BFRAntiAircraft -> 1,
Medical -> 3,
AdvancedMedical -> 2,
Engineering -> 3,
CombatEngineering -> 2,
AdvancedEngineering -> 5,
AssaultEngineering -> 3,
FortificationEngineering -> 3,
Hacking -> 3,
AdvancedHacking -> 2,
DataCorruption -> 3,
ElectronicsExpert -> 4,
ExpertHacking -> 2
)
}
}

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

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

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
package net.psforever.objects.avatar
import akka.actor.{Actor, ActorRef, Cancellable, Props}
import akka.actor.{Actor, ActorRef, Props}
import net.psforever.actors.session.AvatarActor
import net.psforever.objects.{Player, _}
import net.psforever.objects.ballistics.{PlayerSource, ResolvedProjectile}
import net.psforever.objects.equipment._
@ -22,32 +23,26 @@ import net.psforever.types._
import services.{RemoverActor, Service}
import services.avatar.{AvatarAction, AvatarServiceMessage}
import services.local.{LocalAction, LocalServiceMessage}
import akka.actor.typed
import scala.concurrent.duration._
import scala.collection.mutable
import scala.concurrent.ExecutionContext.Implicits.global
class PlayerControl(player: Player) extends Actor with JammableBehavior with Damageable with ContainableBehavior {
def JammableObject = player
class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Command])
extends Actor
with JammableBehavior
with Damageable
with ContainableBehavior {
def JammableObject = player
def DamageableObject = player
def ContainerObject = player
def ContainerObject = player
private[this] val log = org.log4s.getLogger(player.Name)
private[this] val damageLog = org.log4s.getLogger(Damageable.LogChannel)
/** Stamina will be used. Stamina will be restored. */
var staminaRegen: Cancellable = Default.Cancellable
/**
* A collection of timers indexed for the implant in each slot.
* Before an implant is ready, it serves as the initialization timer.
* After being initialized, it is used as the stamina drain interval when the implant is active.
*/
val implantSlotTimers = mutable.HashMap(0 -> Default.Cancellable, 1 -> Default.Cancellable, 2 -> Default.Cancellable)
/** control agency for the player's locker container (dedicated inventory slot #5) */
val lockerControlAgent: ActorRef = {
val locker = player.Locker
val locker = player.avatar.locker
locker.Zone = player.Zone
locker.Actor = context.actorOf(
Props(classOf[LockerContainerControl], locker, player.Name),
@ -57,9 +52,7 @@ class PlayerControl(player: Player) extends Actor with JammableBehavior with Dam
override def postStop(): Unit = {
lockerControlAgent ! akka.actor.PoisonPill
player.Locker.Actor = Default.Actor
staminaRegen.cancel
implantSlotTimers.values.foreach { _.cancel }
player.avatar.locker.Actor = Default.Actor
}
def receive: Receive =
@ -67,47 +60,6 @@ class PlayerControl(player: Player) extends Actor with JammableBehavior with Dam
.orElse(takesDamage)
.orElse(containerBehavior)
.orElse {
case Player.ImplantActivation(slot: Int, status: Int) =>
ImplantActivation(slot, status)
case Player.UninitializeImplant(slot: Int) =>
UninitializeImplant(slot)
case Player.ImplantInitializationStart(slot: Int) =>
ImplantInitializationStart(slot)
case Player.ImplantInitializationComplete(slot: Int) =>
ImplantInitializationComplete(slot)
case Player.StaminaRegen() =>
if (staminaRegen == Default.Cancellable) {
staminaRegen.cancel
staminaRegen =
context.system.scheduler.scheduleOnce(delay = 500 milliseconds, self, PlayerControl.StaminaRegen())
}
case PlayerControl.StaminaRegen() =>
staminaRegen.cancel
if (player.isAlive) {
if (player.skipStaminaRegenForTurns > 0) {
// Do not renew stamina for a while
player.skipStaminaRegenForTurns -= 1
} else if (
(player.VehicleSeated.nonEmpty || !player.isMoving && !player.Jumping) && player.Stamina < player.MaxStamina
) {
// Regen stamina roughly every 500ms
StaminaChanged(changeInStamina = 1)
}
}
staminaRegen =
context.system.scheduler.scheduleOnce(delay = 500 milliseconds, self, PlayerControl.StaminaRegen())
case Player.StaminaChanged(Some(changeInStamina)) =>
StaminaChanged(changeInStamina)
case Player.StaminaChanged(None) =>
UpdateStamina()
case Player.Die() =>
if (player.isAlive) {
DestructionAwareness(player, None)
@ -138,7 +90,7 @@ class PlayerControl(player: Player) extends Actor with JammableBehavior with Dam
InventoryStateMessage(item.AmmoSlot.Box.GUID, item.GUID, magazine.toLong)
)
)
events ! AvatarServiceMessage(zone.Id, AvatarAction.PlanetsideAttributeToAll(guid, 0, newHealth))
events ! AvatarServiceMessage(zone.id, AvatarAction.PlanetsideAttributeToAll(guid, 0, newHealth))
player.History(
HealFromEquipment(
PlayerSource(player),
@ -171,7 +123,7 @@ class PlayerControl(player: Player) extends Actor with JammableBehavior with Dam
!player.isAlive && !player.isBackpack &&
item.Magazine >= 25
) {
sender ! CommonMessages.Progress(
sender() ! CommonMessages.Progress(
4,
Players.FinishRevivingPlayer(player, user.Name, item),
Players.RevivingTickAction(player, user, item)
@ -202,7 +154,7 @@ class PlayerControl(player: Player) extends Actor with JammableBehavior with Dam
InventoryStateMessage(item.AmmoSlot.Box.GUID, item.GUID, magazine.toLong)
)
)
events ! AvatarServiceMessage(zone.Id, AvatarAction.PlanetsideAttributeToAll(guid, 4, player.Armor))
events ! AvatarServiceMessage(zone.id, AvatarAction.PlanetsideAttributeToAll(guid, 4, player.Armor))
player.History(
RepairFromEquipment(
PlayerSource(player),
@ -232,26 +184,29 @@ class PlayerControl(player: Player) extends Actor with JammableBehavior with Dam
case Terminal.TerminalMessage(_, msg, order) =>
order match {
case Terminal.BuyExosuit(exosuit, subtype) =>
val time = System.currentTimeMillis
var toDelete: List[InventoryItem] = Nil
val originalSuit = player.ExoSuit
val originalSubtype = Loadout.DetermineSubtype(player)
val requestToChangeArmor = originalSuit != exosuit || originalSubtype != subtype
val allowedToChangeArmor = Players.CertificationToUseExoSuit(player, exosuit, subtype) &&
(if (exosuit == ExoSuitType.MAX) {
if (time - player.GetLastUsedTime(exosuit, subtype) < 300000L) {
false
} else {
player.SetLastUsedTime(exosuit, subtype, time)
true
val definition = player.avatar.faction match {
case PlanetSideEmpire.NC => GlobalDefinitions.NCMAX
case PlanetSideEmpire.TR => GlobalDefinitions.TRMAX
case PlanetSideEmpire.VS => GlobalDefinitions.VSMAX
}
player.avatar.purchaseCooldown(definition) match {
case Some(_) =>
false
case None =>
avatarActor ! AvatarActor.UpdatePurchaseTime(definition)
true
}
} else {
player.SetLastUsedTime(exosuit, subtype, time)
true
})
val result = if (requestToChangeArmor && allowedToChangeArmor) {
log.info(s"${player.Name} wants to change to a different exo-suit - $exosuit")
player.SetLastUsedTime(exosuit, subtype, System.currentTimeMillis())
val beforeHolsters = Players.clearHolsters(player.Holsters().iterator)
val beforeInventory = player.Inventory.Clear()
//change suit
@ -314,18 +269,9 @@ class PlayerControl(player: Player) extends Actor with JammableBehavior with Dam
player.Inventory.InsertQuickly(elem.start, elem.obj)
}
//deactivate non-passive implants
implantSlotTimers.keys.foreach { index =>
val implantSlot = player.ImplantSlot(index)
if (
implantSlot.Installed.nonEmpty && implantSlot.Active && (implantSlot.Charge(
originalSuit
) > 0 || implantSlot.Charge(exosuit) > 0)
) {
ImplantActivation(index, status = 0)
}
}
avatarActor ! AvatarActor.DeactivateActiveImplants()
player.Zone.AvatarEvents ! AvatarServiceMessage(
player.Zone.Id,
player.Zone.id,
AvatarAction.ChangeExosuit(
player.GUID,
exosuit,
@ -375,19 +321,22 @@ class PlayerControl(player: Player) extends Actor with JammableBehavior with Dam
}) ++ dropHolsters ++ dropInventory
//a loadout with a prohibited exo-suit type will result in the fallback exo-suit type
//imposed 5min delay on mechanized exo-suit switches
val time = System.currentTimeMillis()
val (nextSuit, nextSubtype) =
if (
Players.CertificationToUseExoSuit(player, exosuit, subtype) &&
(if (exosuit == ExoSuitType.MAX) {
if (time - player.GetLastUsedTime(exosuit, subtype) < 300000L) {
false
} else {
player.SetLastUsedTime(exosuit, subtype, time)
true
val definition = player.avatar.faction match {
case PlanetSideEmpire.NC => GlobalDefinitions.NCMAX
case PlanetSideEmpire.TR => GlobalDefinitions.TRMAX
case PlanetSideEmpire.VS => GlobalDefinitions.VSMAX
}
player.avatar.purchaseCooldown(definition) match {
case Some(_) => false
case None =>
avatarActor ! AvatarActor.UpdatePurchaseTime(definition)
true
}
} else {
player.SetLastUsedTime(exosuit, subtype, time)
true
})
) {
@ -396,7 +345,6 @@ class PlayerControl(player: Player) extends Actor with JammableBehavior with Dam
log.warn(
s"no longer has permission to wear the exo-suit type $exosuit; will wear $fallbackSuit instead"
)
player.SetLastUsedTime(fallbackSuit, fallbackSubtype, time)
(fallbackSuit, fallbackSubtype)
}
//sanitize (incoming) inventory
@ -458,18 +406,9 @@ class PlayerControl(player: Player) extends Actor with JammableBehavior with Dam
(afterHolsters ++ afterInventory).foreach { entry => entry.obj.Faction = player.Faction }
toDeleteOrDrop.foreach { entry => entry.obj.Faction = PlanetSideEmpire.NEUTRAL }
//deactivate non-passive implants
implantSlotTimers.keys.foreach { index =>
val implantSlot = player.ImplantSlot(index)
if (
implantSlot.Installed.nonEmpty && implantSlot.Active && (implantSlot.Charge(
originalSuit
) > 0 || implantSlot.Charge(nextSuit) > 0)
) {
ImplantActivation(index, status = 0)
}
}
avatarActor ! AvatarActor.DeactivateActiveImplants()
player.Zone.AvatarEvents ! AvatarServiceMessage(
player.Zone.Id,
player.Zone.id,
AvatarAction.ChangeLoadout(
player.GUID,
nextSuit,
@ -487,103 +426,7 @@ class PlayerControl(player: Player) extends Actor with JammableBehavior with Dam
player.Name,
AvatarAction.TerminalOrderResult(msg.terminal_guid, msg.transaction_type, true)
)
case Terminal.LearnImplant(implant) =>
val zone = player.Zone
val events = zone.AvatarEvents
val playerChannel = player.Name
val terminal_guid = msg.terminal_guid
val implant_type = implant.Type
val message = s"wants to learn $implant_type"
val (interface, slotNumber) = player.VehicleSeated match {
case Some(mech_guid) =>
(
zone.map.TerminalToInterface.get(mech_guid.guid),
if (!player.Implants.exists({ case (implantType, _, _) => implantType == implant_type })) {
//no duplicates
player.InstallImplant(implant)
} else {
None
}
)
case _ =>
(None, None)
}
val result = if (interface.contains(terminal_guid.guid) && slotNumber.isDefined) {
val slot = slotNumber.get
log.info(s"$message - put in slot $slot")
events ! AvatarServiceMessage(
playerChannel,
AvatarAction.SendResponse(
Service.defaultPlayerGUID,
AvatarImplantMessage(player.GUID, ImplantAction.Add, slot, implant_type.id)
)
)
ImplantInitializationStart(slot)
true
} else {
if (interface.isEmpty) {
log.warn(s"$message - not interacting with a terminal")
} else if (!interface.contains(terminal_guid.guid)) {
log.warn(s"$message - interacting with the wrong terminal, ${interface.get}")
} else if (slotNumber.isEmpty) {
log.warn(s"$message - already knows that implant")
} else {
log.warn(s"$message - forgot to sit at a terminal")
}
false
}
events ! AvatarServiceMessage(
playerChannel,
AvatarAction.TerminalOrderResult(terminal_guid, msg.transaction_type, result)
)
case Terminal.SellImplant(implant) =>
val zone = player.Zone
val events = zone.AvatarEvents
val playerChannel = player.Name
val terminal_guid = msg.terminal_guid
val implant_type = implant.Type
val (interface, slotNumber) = player.VehicleSeated match {
case Some(mech_guid) =>
(
zone.map.TerminalToInterface.get(mech_guid.guid),
player.UninstallImplant(implant_type)
)
case None =>
(None, None)
}
val result = if (interface.contains(terminal_guid.guid) && slotNumber.isDefined) {
val slot = slotNumber.get
log.info(s"is uninstalling $implant_type - take from slot $slot")
UninitializeImplant(slot)
events ! AvatarServiceMessage(
playerChannel,
AvatarAction.SendResponse(
Service.defaultPlayerGUID,
AvatarImplantMessage(player.GUID, ImplantAction.Remove, slot, 0)
)
)
true
} else {
val message = s"${player.Name} can not sell $implant_type"
if (interface.isEmpty) {
log.warn(s"$message - not interacting with a terminal")
} else if (!interface.contains(terminal_guid.guid)) {
log.warn(s"$message - interacting with the wrong terminal, ${interface.get}")
} else if (slotNumber.isEmpty) {
log.warn(s"$message - does not know that implant")
} else {
log.warn(s"$message - forgot to sit at a terminal")
}
false
}
events ! AvatarServiceMessage(
playerChannel,
AvatarAction.TerminalOrderResult(terminal_guid, msg.transaction_type, result)
)
case _ => ; //terminal messages not handled here
case _ => assert(false, msg.toString)
}
case Zone.Ground.ItemOnGround(item, _, _) =>
@ -599,10 +442,10 @@ class PlayerControl(player: Player) extends Actor with JammableBehavior with Dam
case (Some(boomer: BoomerDeployable), Some(avatar)) =>
val guid = boomer.GUID
val factionChannel = boomer.Faction.toString
if (avatar.Deployables.Remove(boomer)) {
if (avatar.deployables.Remove(boomer)) {
boomer.Faction = PlanetSideEmpire.NEUTRAL
boomer.AssignOwnership(None)
avatar.Deployables.UpdateUIElement(boomer.Definition.Item).foreach {
avatar.deployables.UpdateUIElement(boomer.Definition.Item).foreach {
case (currElem, curr, maxElem, max) =>
avatarEvents ! AvatarServiceMessage(
name,
@ -648,12 +491,12 @@ class PlayerControl(player: Player) extends Actor with JammableBehavior with Dam
if (player.isAlive && !player.spectator) {
val originalHealth = player.Health
val originalArmor = player.Armor
val originalStamina = player.Stamina
val originalStamina = player.avatar.stamina
val originalCapacitor = player.Capacitor.toInt
val cause = applyDamageTo(player)
val health = player.Health
val armor = player.Armor
val stamina = player.Stamina
val stamina = player.avatar.stamina
val capacitor = player.Capacitor.toInt
val damageToHealth = originalHealth - health
val damageToArmor = originalArmor - armor
@ -682,7 +525,7 @@ class PlayerControl(player: Player) extends Actor with JammableBehavior with Dam
): Unit = {
val targetGUID = target.GUID
val zone = target.Zone
val zoneId = zone.Id
val zoneId = zone.id
val events = zone.AvatarEvents
val health = target.Health
if (damageToArmor > 0) {
@ -701,7 +544,7 @@ class PlayerControl(player: Player) extends Actor with JammableBehavior with Dam
events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(targetGUID, 0, health))
}
if (damageToStamina > 0) {
UpdateStamina()
avatarActor ! AvatarActor.ConsumeStamina(damageToStamina)
}
//activity on map
zone.Activity ! Zone.HotSpot.Activity(cause.target, cause.projectile.owner, cause.hit_pos)
@ -761,20 +604,13 @@ class PlayerControl(player: Player) extends Actor with JammableBehavior with Dam
val zone = target.Zone
val events = zone.AvatarEvents
val nameChannel = target.Name
val zoneChannel = zone.Id
val zoneChannel = zone.id
target.Die
//unjam
CancelJammeredSound(target)
CancelJammeredStatus(target)
//implants off
target.Stamina = 0
UpdateStamina() //turn off implants / OutOfStamina
//uninitialize implants
target.Implants.indices.foreach {
case slot if target.Implant(slot) != ImplantType.None =>
UninitializeImplant(slot)
}
target.ResetAllImplants() //anything else specific to the backend
avatarActor ! AvatarActor.DeinitializeImplants()
events ! AvatarServiceMessage(
nameChannel,
AvatarAction.Killed(player_guid, target.VehicleSeated)
@ -873,7 +709,7 @@ class PlayerControl(player: Player) extends Actor with JammableBehavior with Dam
target match {
case obj: Player if !jammedSound =>
obj.Zone.AvatarEvents ! AvatarServiceMessage(
obj.Zone.Id,
obj.Zone.id,
AvatarAction.PlanetsideAttributeToAll(obj.GUID, 27, 1)
)
super.StartJammeredSound(obj, 3000)
@ -890,25 +726,13 @@ class PlayerControl(player: Player) extends Actor with JammableBehavior with Dam
* @param dur the duration of the timer, in milliseconds
*/
override def StartJammeredStatus(target: Any, dur: Int): Unit = {
//TODO these features
val zone = player.Zone
player.Implants.indices.foreach { slot => // Deactivate & uninitialize all implants
zone.AvatarEvents ! AvatarServiceMessage(
zone.Id,
AvatarAction.PlanetsideAttribute(player.GUID, 28, player.Implant(slot).id * 2)
) // Deactivation sound / effect
ImplantActivation(slot, status = 0)
UninitializeImplant(slot)
}
player.skipStaminaRegenForTurns = math.max(player.skipStaminaRegenForTurns, 10)
avatarActor ! AvatarActor.DeinitializeImplants()
avatarActor ! AvatarActor.SuspendStaminaRegeneration(5 seconds)
super.StartJammeredStatus(target, dur)
}
override def CancelJammeredStatus(target: Any): Unit = {
player.Implants.indices.foreach { slot => // Start reinitializing all implants
player.ImplantSlot(slot).InitializeTime = 0 //setting time to 0 will restart implant initialization (eventually)
ImplantInitializationStart(slot)
}
avatarActor ! AvatarActor.InitializeImplants(instant = true)
super.CancelJammeredStatus(target)
}
@ -921,7 +745,7 @@ class PlayerControl(player: Player) extends Actor with JammableBehavior with Dam
target match {
case obj: Player if jammedSound =>
obj.Zone.AvatarEvents ! AvatarServiceMessage(
obj.Zone.Id,
obj.Zone.id,
AvatarAction.PlanetsideAttributeToAll(obj.GUID, 27, 0)
)
super.CancelJammeredSound(obj)
@ -956,7 +780,7 @@ class PlayerControl(player: Player) extends Actor with JammableBehavior with Dam
val obj = ContainerObject
val zone = obj.Zone
val events = zone.AvatarEvents
val toChannel = if (obj.VisibleSlots.contains(slot)) zone.Id else player.Name
val toChannel = if (obj.VisibleSlots.contains(slot)) zone.id else player.Name
item.Faction = PlanetSideEmpire.NEUTRAL
if (slot == obj.DrawnSlot) {
obj.DrawnSlot = Player.HandsDownSlot
@ -986,7 +810,7 @@ class PlayerControl(player: Player) extends Actor with JammableBehavior with Dam
)
)
if (obj.VisibleSlots.contains(slot)) {
events ! AvatarServiceMessage(zone.Id, AvatarAction.EquipmentInHand(guid, guid, slot, item))
events ! AvatarServiceMessage(zone.id, AvatarAction.EquipmentInHand(guid, guid, slot, item))
}
//handle specific types of items
item match {
@ -998,10 +822,10 @@ class PlayerControl(player: Player) extends Actor with JammableBehavior with Dam
val bguid = boomer.GUID
val faction = player.Faction
val factionChannel = faction.toString
if (avatar.Deployables.Add(boomer)) {
if (avatar.deployables.Add(boomer)) {
boomer.Faction = faction
boomer.AssignOwnership(player)
avatar.Deployables.UpdateUIElement(boomer.Definition.Item).foreach {
avatar.deployables.UpdateUIElement(boomer.Definition.Item).foreach {
case (currElem, curr, maxElem, max) =>
events ! AvatarServiceMessage(
name,
@ -1045,250 +869,4 @@ class PlayerControl(player: Player) extends Actor with JammableBehavior with Dam
AvatarAction.SendResponse(Service.defaultPlayerGUID, ObjectDetachMessage(obj.GUID, item.GUID, Vector3.Zero, 0f))
)
}
/**
* na
* @param changeInStamina na
*/
def StaminaChanged(changeInStamina: Int): Unit = {
val beforeStamina = player.Stamina
val afterStamina = player.Stamina += changeInStamina
if (beforeStamina != afterStamina) {
UpdateStamina()
}
}
/**
* Determine whether the current stamina value for this player requires a greater change in player states.
* Losing all stamina and not yet being fatigued deactivates implants.
* Having stamina of 20 points or greater and having previously been fatigued
* allows implants to operate once again.
* Initialization must be restarted manually for any implant that had not previously finished initializing.
*/
def UpdateStamina(): Unit = {
val currentStamina = player.Stamina
if (currentStamina == 0 && !player.Fatigued) { // Only be fatigued once even if loses all stamina again
player.Fatigued = true
player.skipStaminaRegenForTurns = math.max(player.skipStaminaRegenForTurns, 6)
player.Implants.indices.foreach { slot => // Disable all implants
ImplantActivation(slot, status = 0)
player.Zone.AvatarEvents ! AvatarServiceMessage(
player.Name,
AvatarAction.SendResponse(
Service.defaultPlayerGUID,
AvatarImplantMessage(player.GUID, ImplantAction.OutOfStamina, slot, 1)
)
)
}
} else if (currentStamina >= 20) {
val wasFatigued = player.Fatigued
player.Fatigued = false
if (wasFatigued) { //reactivate only if we were fatigued
player.Implants.indices.foreach { slot => // Re-enable all implants
player.Zone.AvatarEvents ! AvatarServiceMessage(
player.Name,
AvatarAction.SendResponse(
Service.defaultPlayerGUID,
AvatarImplantMessage(player.GUID, ImplantAction.OutOfStamina, slot, 0)
)
)
if (!player.ImplantSlot(slot).Initialized) {
ImplantInitializationStart(slot)
}
}
}
}
player.Zone.AvatarEvents ! AvatarServiceMessage(
player.Name,
AvatarAction.PlanetsideAttributeToAll(player.GUID, 2, currentStamina)
)
}
/**
* The process of starting an implant so that it can be activated is one that requires a matter of time.
* If the implant should already have been started, then just switch to the proper state.
* Always (check to) initialize implants when setting up an avatar or becoming fatigued or when revived.
* @param slot the slot in which this implant is found
*/
def ImplantInitializationStart(slot: Int): Unit = {
val implantSlot = player.ImplantSlot(slot)
if (implantSlot.Installed.isDefined) {
if (!implantSlot.Initialized) {
val time = System.currentTimeMillis
val initializationTime = if (implantSlot.InitializeTime == 0L) {
implantSlot.InitializeTime = time
time
} else {
implantSlot.InitializeTime
}
val maxInitializationTime = implantSlot.MaxTimer * 1000
if (time - initializationTime > maxInitializationTime) {
//this implant should have already been initialized
ImplantInitializationComplete(slot)
} else {
// Start client side initialization timer
// Check this along the bottom of the character information window
//progress accumulates according to the client's knowledge of the implant initialization time
//what is normally a 60s timer that is set to 120s on the server will still visually update as if 60s
val percent = (100 * (time - initializationTime) / maxInitializationTime.toFloat).toInt
player.Zone.AvatarEvents ! AvatarServiceMessage(
player.Name,
AvatarAction.SendResponse(Service.defaultPlayerGUID, ActionProgressMessage(slot + 6, percent))
)
// Callback after initialization timer to complete initialization
implantSlotTimers(slot).cancel
implantSlotTimers(slot) = context.system.scheduler.scheduleOnce(
(maxInitializationTime - (time - initializationTime)) milliseconds,
self,
Player.ImplantInitializationComplete(slot)
)
}
} else {
ImplantInitializationComplete(slot)
}
}
}
/**
* The implant is ready to be made available and active on selection.
* The end result of a timed process, occasionally an implant will become "already active".
* @param slot the slot in which this implant is found
*/
def ImplantInitializationComplete(slot: Int): Unit = {
val implantSlot = player.ImplantSlot(slot)
if (implantSlot.Installed.isDefined) {
implantSlot.Initialized = true
player.Zone.AvatarEvents ! AvatarServiceMessage(
player.Name,
AvatarAction.SendResponse(
Service.defaultPlayerGUID,
AvatarImplantMessage(player.GUID, ImplantAction.Initialization, slot, 1)
)
)
implantSlotTimers(slot).cancel
implantSlotTimers(slot) = Default.Cancellable
}
}
/**
* Whether or not the implant is being used by the player who installed it.
* If the implant has no business having its activation state changed yet, it (re)starts its initialization phase.
* @param slot the slot in which this implant is found
* @param status `1`, if the implant should become active;
* `0`, if it should be deactivated
*/
def ImplantActivation(slot: Int, status: Int): Unit = {
val implantSlot = player.ImplantSlot(slot)
if (!implantSlot.Initialized && !player.Fatigued) {
log.warn(s"implant in slot $slot is trying to (de)activate when not even initialized!")
//we should not be activating or deactivataing, but initializing
implantSlotTimers(slot).cancel
implantSlotTimers(slot) = Default.Cancellable
implantSlot.Active = false
//normal deactivation
player.Zone.AvatarEvents ! AvatarServiceMessage(
player.Name,
AvatarAction.DeactivateImplantSlot(player.GUID, slot)
)
//initialization process (from scratch)
implantSlot.InitializeTime = 0
ImplantInitializationStart(slot)
} else if (status == 0 && implantSlot.Active) {
implantSlotTimers(slot).cancel
implantSlotTimers(slot) = Default.Cancellable
implantSlot.Active = false
player.Zone.AvatarEvents ! AvatarServiceMessage(
player.Zone.Id,
AvatarAction.PlanetsideAttribute(player.GUID, 28, player.Implant(slot).id * 2)
) // Deactivation sound / effect
player.Zone.AvatarEvents ! AvatarServiceMessage(
player.Name,
AvatarAction.DeactivateImplantSlot(player.GUID, slot)
)
} else if (status == 1 && implantSlot.Initialized && !player.Fatigued) {
implantSlot.Installed match {
case Some(implant)
if (implant.Type == ImplantType.PersonalShield && player.ExoSuit == ExoSuitType.Infiltration) ||
(implant.Type == ImplantType.Surge && player.ExoSuit == ExoSuitType.MAX) =>
//TODO STILL NOT ALLOWED (but make it look normal)
case Some(implant) =>
if (implantSlot.Active) {
// Some events such as zoning will reset the implant on the client side without sending a deactivation packet
// But the implant will remain in an active state server side. For now, allow reactivation of the implant.
log.warn(s"implant $slot is already active, but activating again")
implantSlotTimers(slot).cancel
implantSlotTimers(slot) = Default.Cancellable
}
val activationStaminaCost = implant.ActivationStaminaCost
if (activationStaminaCost > 0) {
player.Stamina -= activationStaminaCost // Activation stamina drain
UpdateStamina()
}
if (!player.Fatigued) {
implantSlot.Active = true
val zone = player.Zone
val drainInterval = implant.GetCostIntervalByExoSuit(player.ExoSuit)
if (drainInterval > 0) { // Ongoing stamina drain, if applicable
implantSlotTimers(slot).cancel
implantSlotTimers(slot) = context.system.scheduler.scheduleWithFixedDelay(
initialDelay = 0 seconds,
drainInterval milliseconds,
self,
Player.StaminaChanged(-implant.StaminaCost)
)
}
zone.AvatarEvents ! AvatarServiceMessage(
zone.Id,
AvatarAction.PlanetsideAttribute(player.GUID, 28, player.Implant(slot).id * 2 + 1)
) // Activation sound / effect
zone.AvatarEvents ! AvatarServiceMessage(player.Name, AvatarAction.ActivateImplantSlot(player.GUID, slot))
}
case _ =>
//there should have been an implant here ...
implantSlot.Active = false
implantSlot.Initialized = false
implantSlot.InitializeTime = 0L
//todo: AvatarImplantMessage(tplayer.GUID, ImplantAction.Remove, slot, 0)?
}
}
}
/**
* The implant in this slot is no longer active and is no longer considered ready to activate.
* @param slot the slot in which an implant could be found
*/
def UninitializeImplant(slot: Int): Unit = {
implantSlotTimers(slot).cancel
implantSlotTimers(slot) = Default.Cancellable
val zone = player.Zone
val guid = player.GUID
val playerChannel = player.Name
val zoneChannel = zone.Id
val implantSlot = player.ImplantSlot(slot)
// if(implantSlot.Active) {
// zone.AvatarEvents ! AvatarServiceMessage(zoneChannel, AvatarAction.PlanetsideAttribute(guid, 28, player.Implant(slot).id * 2)) // Deactivation sound / effect
// zone.AvatarEvents ! AvatarServiceMessage(playerChannel, AvatarAction.DeactivateImplantSlot(guid, slot))
// }
implantSlot.Active = false
implantSlot.Initialized = false
implantSlot.InitializeTime = 0L
zone.AvatarEvents ! AvatarServiceMessage(
playerChannel,
AvatarAction.SendResponse(Service.defaultPlayerGUID, ActionProgressMessage(slot + 6, 100))
)
zone.AvatarEvents ! AvatarServiceMessage(
zoneChannel,
AvatarAction.SendResponse(
Service.defaultPlayerGUID,
AvatarImplantMessage(guid, ImplantAction.Initialization, slot, 0)
)
)
}
}
object PlayerControl {
/**
*/
private case class StaminaRegen()
}

View file

@ -73,12 +73,12 @@ final case class Projectile(
object Projectile {
/** the first projectile GUID used by all clients internally */
final val BaseUID: Int = 40100
final val baseUID: Int = 40100
/** all clients progress through 40100 to 40124 normally, skipping only for long-lived projectiles
* 40125 to 40149 are being reserved as a guard against undetected overflow
*/
final val RangeUID: Int = 40150
final val rangeUID: Int = 40150
/**
* Overloaded constructor for an `Unresolved` projectile.

View file

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

View file

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

View file

@ -14,11 +14,11 @@ import scala.collection.mutable
* Passive implants are always active and thus have no cost.
* After being activated, a non-passive implant consumes a specific amount of stamina at regular intervals
* Some implants will specify a different interval for consuming stamina based on the exo-suit the player is wearing
*
* @param implantType the type of implant that is defined
* @see `ImplantType`
*/
class ImplantDefinition(private val implantType: Int) extends BasicDefinition {
ImplantType(implantType)
class ImplantDefinition(val implantType: ImplantType) extends BasicDefinition {
/** how long it takes the implant to become ready for activation; is milliseconds */
private var initializationDuration: Long = 0L
@ -84,16 +84,4 @@ class ImplantDefinition(private val implantType: Int) extends BasicDefinition {
def GetCostIntervalByExoSuit(exosuit: ExoSuitType.Value): Int =
costIntervalByExoSuit.getOrElse(exosuit, CostIntervalDefault)
def CostIntervalByExoSuitHashMap: mutable.Map[ExoSuitType.Value, Int] = costIntervalByExoSuit
def Type: ImplantType.Value = ImplantType(implantType)
}
object ImplantDefinition {
def apply(implantType: Int): ImplantDefinition = {
new ImplantDefinition(implantType)
}
def apply(implantType: ImplantType.Value): ImplantDefinition = {
new ImplantDefinition(implantType.id)
}
}

View file

@ -4,7 +4,7 @@ package net.psforever.objects.definition.converter
import net.psforever.objects.Player
import net.psforever.objects.equipment.{Equipment, EquipmentSlot}
import net.psforever.packet.game.objectcreate._
import net.psforever.types.{ExoSuitType, GrenadeState, ImplantType, PlanetSideGUID}
import net.psforever.types.{ExoSuitType, GrenadeState, PlanetSideGUID}
import scala.annotation.tailrec
import scala.util.{Success, Try}
@ -96,13 +96,13 @@ object AvatarConverter {
false,
facingPitch = obj.Orientation.y,
facingYawUpper = obj.FacingYawUpper,
obj.LFS,
obj.avatar.lookingForSquad,
GrenadeState.None,
obj.Cloaked,
false,
false,
unk5 = false,
unk6 = false,
charging_pose = false,
false,
unk7 = false,
on_zipline = None
)
CharacterAppearanceData(aa, ab, RibbonBars())
@ -117,44 +117,43 @@ object AvatarConverter {
} else {
StatConverter.Health(obj.Armor, MaxArmor)
},
DressBattleRank(obj),
obj.avatar.br.uniformStyle,
0,
DressCommandRank(obj),
MakeImplantEffectList(obj.Implants.toIndexedSeq),
MakeCosmetics(obj)
obj.avatar.cr.value,
obj.avatar.implants.flatten.filter(_.active).flatMap(_.definition.implantType.effect).toList,
obj.avatar.cosmetics
)
}
def MakeDetailedCharacterData(obj: Player): Option[Int] => DetailedCharacterData = {
val bep: Long = obj.BEP
val maxOpt: Option[Long] = if (obj.ExoSuit == ExoSuitType.MAX) { Some(0L) }
else { None }
val ba: DetailedCharacterA = DetailedCharacterA(
bep,
obj.CEP,
obj.avatar.bep,
obj.avatar.cep,
0L,
0L,
0L,
obj.MaxHealth,
obj.Health,
false,
unk4 = false,
obj.Armor,
0L,
obj.MaxStamina,
obj.Stamina,
obj.avatar.maxStamina,
obj.avatar.stamina,
maxOpt,
0,
0,
0L,
List(0, 0, 0, 0, 0, 0),
obj.Certifications.toList.sortBy(_.id) //TODO is sorting necessary?
obj.avatar.certifications.toList.sortBy(_.value) //TODO is sorting necessary?
)
val bb: (Long, Option[Int]) => DetailedCharacterB = DetailedCharacterB(
None,
MakeImplantEntries(obj),
obj.avatar.implants.flatten.map(_.toEntry).toList,
Nil,
Nil,
obj.FirstTimeEvents,
obj.avatar.firstTimeEvents.toList,
tutorials = List.empty[String], //TODO tutorial list
0L,
0L,
@ -165,9 +164,9 @@ object AvatarConverter {
Nil,
Nil,
false,
MakeCosmetics(obj)
obj.avatar.cosmetics
)
pad_length: Option[Int] => DetailedCharacterData(ba, bb(bep, pad_length))(pad_length)
pad_length: Option[Int] => DetailedCharacterData(ba, bb(obj.avatar.bep, pad_length))(pad_length)
}
def MakeInventoryData(obj: Player): InventoryData = {
@ -180,98 +179,6 @@ object AvatarConverter {
)
}
/**
* Select the appropriate `UniformStyle` design for a player's accumulated battle experience points.
* At certain battle ranks, all exo-suits undergo some form of coloration change.
* @param obj the `Player` game object
* @return the resulting uniform upgrade level
*/
private def DressBattleRank(obj: Player): UniformStyle.Value = {
val bep: Long = obj.BEP
if (bep > 2583440) { //BR25+
UniformStyle.ThirdUpgrade
} else if (bep > 308989) { //BR14+
UniformStyle.SecondUpgrade
} else if (bep > 44999) { //BR7+
UniformStyle.FirstUpgrade
} else { //BR1+
UniformStyle.Normal
}
}
/**
* Select the appropriate design for a player's accumulated command experience points.
* Visual cues for command rank include armlets, anklets, and, finally, a backpack, awarded at different ranks.
* @param obj the `Player` game object
* @return the resulting uniform upgrade level
*/
private def DressCommandRank(obj: Player): Int = {
val cep = obj.CEP
if (cep > 599999) {
5
} else if (cep > 299999) {
4
} else if (cep > 149999) {
3
} else if (cep > 49999) {
2
} else if (cep > 9999) {
1
} else {
0
}
}
/**
* Transform an `Array` of `Implant` objects into a `List` of `ImplantEntry` objects suitable as packet data.
* @param obj the `Player` game object
* @return the resulting implant `List`
* @see `ImplantEntry` in `DetailedCharacterData`
*/
private def MakeImplantEntries(obj: Player): List[ImplantEntry] = {
//val numImplants : Int = DetailedCharacterData.numberOfImplantSlots(obj.BEP)
//val implants = obj.Implants
obj.Implants
.map({
case (implant, initialization, _) =>
if (initialization == 0) {
ImplantEntry(implant, None)
} else {
ImplantEntry(implant, Some(math.max(0, initialization).toInt))
}
})
.toList
}
/**
* Find and encode implants whose effect will be displayed on this player.
* @param implants a `Sequence` of `ImplantSlot` objects
* @return the effect of an active implant
*/
private def MakeImplantEffectList(implants: Seq[(ImplantType.Value, Long, Boolean)]): List[ImplantEffects.Value] = {
implants.collect {
case (ImplantType.AdvancedRegen, _, true) => ImplantEffects.RegenEffects
case (ImplantType.DarklightVision, _, true) => ImplantEffects.DarklightEffects
case (ImplantType.PersonalShield, _, true) => ImplantEffects.PersonalShieldEffects
case (ImplantType.Surge, _, true) => ImplantEffects.SurgeEffects
}.toList
}
/**
* Should this player be of battle rank 24 or higher, they will have a mandatory cosmetics object in their bitstream.
* Players that have not yet set any cosmetic personal effects will still have this field recorded as `None`
* but it must be represented nonetheless.
* @param obj the `Player` game object
* @see `Cosmetics`
* @return the `Cosmetics` options
*/
def MakeCosmetics(obj: Player): Option[Cosmetics] =
if (DetailedCharacterData.isBR24(obj.BEP)) {
obj.PersonalStyleFeatures.orElse(Some(Cosmetics()))
} else {
None
}
/**
* Given a player with an inventory, convert the contents of that inventory into converted-decoded packet data.
* The inventory is not represented in a `0x17` `Player`, so the conversion is only valid for `0x18` avatars.
@ -363,7 +270,7 @@ object AvatarConverter {
if (!iter.hasNext) {
list
} else {
val slot: EquipmentSlot = iter.next
val slot: EquipmentSlot = iter.next()
if (slot.Equipment.isDefined) {
val equip: Equipment = slot.Equipment.get
recursiveMakeHolsters(

View file

@ -1,6 +1,7 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.definition.converter
import net.psforever.objects.avatar.Certification
import net.psforever.objects.{Player, Tool}
import net.psforever.objects.equipment.EquipmentSlot
import net.psforever.packet.game.objectcreate._
@ -42,10 +43,10 @@ class CharacterSelectConverter extends AvatarConverter {
CommonFieldData(
obj.Faction,
bops = false,
false,
false,
alternate = false,
v1 = false,
None,
false,
jammered = false,
None,
v5 = None,
PlanetSideGUID(0)
@ -62,38 +63,37 @@ class CharacterSelectConverter extends AvatarConverter {
0L,
outfit_name = "",
outfit_logo = 0,
false,
unk1 = false,
backpack = false,
false,
false,
false,
unk2 = false,
unk3 = false,
unk4 = false,
facingPitch = 0,
facingYawUpper = 0,
lfs = false,
GrenadeState.None,
obj.Cloaked,
false,
false,
is_cloaking = obj.Cloaked,
unk5 = false,
unk6 = false,
charging_pose = false,
false,
unk7 = false,
on_zipline = None
)
CharacterAppearanceData(aa, ab, RibbonBars())
}
private def MakeDetailedCharacterData(obj: Player): Option[Int] => DetailedCharacterData = {
val bep: Long = obj.BEP
val maxOpt: Option[Long] = if (obj.ExoSuit == ExoSuitType.MAX) { Some(0L) }
else { None }
val ba: DetailedCharacterA = DetailedCharacterA(
bep,
obj.CEP,
obj.avatar.bep,
obj.avatar.cep,
0L,
0L,
0L,
1,
1,
false,
unk4 = false,
0,
0L,
1,
@ -103,11 +103,13 @@ class CharacterSelectConverter extends AvatarConverter {
0,
0L,
List(0, 0, 0, 0, 0, 0),
certs = List.empty[CertificationType.Value]
certs = List.empty[Certification]
)
val bb: (Long, Option[Int]) => DetailedCharacterB = DetailedCharacterB(
None,
MakeImplantEntries(obj), //necessary for correct stream length
// necessary for correct stream length
List.fill[ImplantEntry](obj.avatar.br.implantSlots)(ImplantEntry(ImplantType.None, None)),
Nil,
Nil,
firstTimeEvents = List.empty[String],
@ -120,20 +122,10 @@ class CharacterSelectConverter extends AvatarConverter {
Some(DCDExtra2(0, 0)),
Nil,
Nil,
false,
AvatarConverter.MakeCosmetics(obj)
unkC = false,
obj.avatar.cosmetics
)
pad_length: Option[Int] => DetailedCharacterData(ba, bb(bep, pad_length))(pad_length)
}
/**
* Transform an `Array` of `Implant` objects into a `List` of `ImplantEntry` objects suitable as packet data.
* @param obj the `Player` game object
* @return the resulting implant `List`
* @see `ImplantEntry` in `DetailedCharacterData`
*/
private def MakeImplantEntries(obj: Player): List[ImplantEntry] = {
List.fill[ImplantEntry](DetailedCharacterData.numberOfImplantSlots(obj.BEP))(ImplantEntry(ImplantType.None, None))
pad_length: Option[Int] => DetailedCharacterData(ba, bb(obj.avatar.bep, pad_length))(pad_length)
}
/**
@ -152,7 +144,7 @@ class CharacterSelectConverter extends AvatarConverter {
if (!iter.hasNext) {
list
} else {
val slot: EquipmentSlot = iter.next
val slot: EquipmentSlot = iter.next()
slot.Equipment match {
case Some(equip: Tool) =>
val jammed = equip.Jammed

View file

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

View file

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

View file

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

View file

@ -76,7 +76,7 @@ class TaskResolver() extends Actor {
TimeoutCleanup()
case msg =>
log.warn(s"$self received an unexpected message $msg from $sender")
log.warn(s"$self received an unexpected message $msg from ${sender()}")
}
/**
@ -408,7 +408,7 @@ object TaskResolver {
if (!iter.hasNext) {
None
} else {
val index: Int = iter.next
val index: Int = iter.next()
if (tasks(index).task.isComplete == resolution) {
Some(index)
} else {
@ -428,7 +428,7 @@ object TaskResolver {
if (!iter.hasNext) {
true
} else {
if (iter.next.isComplete == resolution) {
if (iter.next().isComplete == resolution) {
filterCompletionMatch(iter, resolution)
} else {
false
@ -452,7 +452,7 @@ object TaskResolver {
if (!iter.hasNext) {
indexList
} else {
val index: Int = iter.next
val index: Int = iter.next()
val taskEntry = tasks(index)
if (
taskEntry.Executing && taskEntry.task.isComplete == Task.Resolution.Incomplete && now - taskEntry.Start > taskEntry.task.Timeout
@ -476,7 +476,7 @@ object TaskResolver {
if (!iter.hasNext) {
None
} else {
if (iter.next.task == target) {
if (iter.next().task == target) {
Some(index)
} else {
findTask(iter, target, index + 1)
@ -496,7 +496,7 @@ object TaskResolver {
if (!iter.hasNext) {
None
} else {
val tEntry = iter.next
val tEntry = iter.next()
if (tEntry.subtasks.contains(target)) {
Some(index)
} else {

View file

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

View file

@ -126,11 +126,11 @@ class UniqueNumberSystem(private val guid: NumberPoolHub, private val poolActors
case Some(entry) =>
entry.replyTo ! Failure(new Exception(s"for ${entry.target} with number $number, ${ex.getMessage}"))
case None => ;
log.error(s"could not find original request $nid that caused error $ex, but pool was $sender")
log.error(s"could not find original request $nid that caused error $ex, but pool was ${sender()}")
//no callback is possible
}
case _ => ;
log.error(s"could not find original request $id that caused error $ex, but pool was $sender")
log.error(s"could not find original request $id that caused error $ex, but pool was ${sender()}")
//no callback is possible
}

View file

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

View file

@ -1,7 +1,8 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.loadouts
import net.psforever.types.{CertificationType, ExoSuitType}
import net.psforever.objects.avatar.Certification
import net.psforever.types.ExoSuitType
/**
* A blueprint of a player's uniform, their holster items, and their inventory items, saved in a specific state.
@ -12,6 +13,7 @@ import net.psforever.types.{CertificationType, ExoSuitType}
* The ten-long list is initialized with `FavoritesMessage` packets assigned to the "Infantry" list.
* Specific entries are added or removed using `FavoritesRequest` packets,
* re-established using other conventional game packets.
*
* @param label the name by which this inventory will be known when displayed in a Favorites list;
* field gets inherited
* @param visible_slots simplified representation of the `Equipment` that can see "seen" on the target;
@ -106,15 +108,16 @@ object InfantryLoadout {
* Assuming the exo-suit is a mechanized assault type,
* use the subtype to determine what certifications would be valid for permitted access to that specific exo-suit.
* The "C" does not stand for "certification."
*
* @see `CertificationType`
* @param subtype the numeric subtype
* @return a `Set` of all certifications that would grant access to the mechanized assault exo-suit subtype
*/
def DetermineSubtypeC(subtype: Int): Set[CertificationType.Value] =
def DetermineSubtypeC(subtype: Int): Set[Certification] =
subtype match {
case 1 => Set(CertificationType.AIMAX, CertificationType.UniMAX)
case 2 => Set(CertificationType.AVMAX, CertificationType.UniMAX)
case 3 => Set(CertificationType.AAMAX, CertificationType.UniMAX)
case _ => Set.empty[CertificationType.Value]
case 1 => Set(Certification.AIMAX, Certification.UniMAX)
case 2 => Set(Certification.AVMAX, Certification.UniMAX)
case 3 => Set(Certification.AAMAX, Certification.UniMAX)
case _ => Set.empty[Certification]
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -25,114 +25,133 @@ trait DeploymentBehavior {
val deployBehavior: Receive = {
case Deployment.TryDeploymentChange(state) =>
sender ! TryDeploymentStateChange(state)
sender() ! TryDeploymentStateChange(state)
case Deployment.TryDeploy(state) =>
sender ! TryDeployStateChange(state)
sender() ! TryDeployStateChange(state)
case Deployment.TryUndeploy(state) =>
sender ! TryUndeployStateChange(state)
sender() ! TryUndeployStateChange(state)
}
def TryDeploymentStateChange(state : DriveState.Value) : Any = {
val obj = DeploymentObject
def TryDeploymentStateChange(state: DriveState.Value): Any = {
val obj = DeploymentObject
val prevState = obj.DeploymentState
if(TryDeploymentChange(obj, state)) {
if(Deployment.CheckForDeployState(state)) {
if (TryDeploymentChange(obj, state)) {
if (Deployment.CheckForDeployState(state)) {
DeploymentAction(obj, state, prevState)
Deployment.CanDeploy(obj, state)
}
else {
} else {
UndeploymentAction(obj, state, prevState)
Deployment.CanUndeploy(obj, state)
}
}
else {
} else {
Deployment.CanNotChangeDeployment(obj, state, "incorrect transition state")
}
}
def TryDeployStateChange(state : DriveState.Value) : Any = {
val obj = DeploymentObject
def TryDeployStateChange(state: DriveState.Value): Any = {
val obj = DeploymentObject
val prevState = obj.DeploymentState
if(Deployment.CheckForDeployState(state) && TryDeploymentChange(obj, state)) {
if (Deployment.CheckForDeployState(state) && TryDeploymentChange(obj, state)) {
DeploymentAction(obj, state, prevState)
Deployment.CanDeploy(obj, state)
}
else {
} else {
Deployment.CanNotChangeDeployment(obj, state, "incorrect deploy transition state")
}
}
def TryUndeployStateChange(state : DriveState.Value) : Any = {
val obj = DeploymentObject
def TryUndeployStateChange(state: DriveState.Value): Any = {
val obj = DeploymentObject
val prevState = obj.DeploymentState
if(Deployment.CheckForUndeployState(state) && TryUndeploymentChange(obj, state)) {
if (Deployment.CheckForUndeployState(state) && TryUndeploymentChange(obj, state)) {
UndeploymentAction(obj, state, prevState)
Deployment.CanUndeploy(obj, state)
}
else {
} else {
Deployment.CanNotChangeDeployment(obj, state, "incorrect undeploy transition state")
}
}
def TryDeploymentChange(obj : Deployment.DeploymentObject, state : DriveState.Value) : Boolean = {
def TryDeploymentChange(obj: Deployment.DeploymentObject, state: DriveState.Value): Boolean = {
DeploymentBehavior.TryDeploymentChange(obj, state)
}
def TryUndeploymentChange(obj : Deployment.DeploymentObject, state : DriveState.Value) : Boolean = {
def TryUndeploymentChange(obj: Deployment.DeploymentObject, state: DriveState.Value): Boolean = {
DeploymentBehavior.TryDeploymentChange(obj, state)
}
def DeploymentAction(obj : Deployment.DeploymentObject, state : DriveState.Value, prevState : DriveState.Value) : DriveState.Value = {
val guid = obj.GUID
val zone = obj.Zone
val zoneChannel = zone.Id
val GUID0 = Service.defaultPlayerGUID
def DeploymentAction(
obj: Deployment.DeploymentObject,
state: DriveState.Value,
prevState: DriveState.Value
): DriveState.Value = {
val guid = obj.GUID
val zone = obj.Zone
val zoneChannel = zone.id
val GUID0 = Service.defaultPlayerGUID
//TODO remove this arbitrary allowance angle when no longer helpful
if(obj.Orientation.x > 30 && obj.Orientation.x < 330) {
if (obj.Orientation.x > 30 && obj.Orientation.x < 330) {
obj.DeploymentState = prevState
prevState
}
else if(state == DriveState.Deploying) {
} else if (state == DriveState.Deploying) {
obj.Velocity = Some(Vector3.Zero) //no velocity
zone.VehicleEvents ! VehicleServiceMessage(zoneChannel, VehicleAction.DeployRequest(GUID0, guid, state, 0, false, Vector3.Zero))
context.system.scheduler.scheduleOnce(obj.DeployTime milliseconds, obj.Actor, Deployment.TryDeploy(DriveState.Deployed))
zone.VehicleEvents ! VehicleServiceMessage(
zoneChannel,
VehicleAction.DeployRequest(GUID0, guid, state, 0, false, Vector3.Zero)
)
context.system.scheduler.scheduleOnce(
obj.DeployTime milliseconds,
obj.Actor,
Deployment.TryDeploy(DriveState.Deployed)
)
state
}
else if(state == DriveState.Deployed) {
} else if (state == DriveState.Deployed) {
obj.Velocity = Some(Vector3.Zero) //no velocity
zone.VehicleEvents ! VehicleServiceMessage(zoneChannel, VehicleAction.DeployRequest(GUID0, guid, state, 0, false, Vector3.Zero))
zone.VehicleEvents ! VehicleServiceMessage(
zoneChannel,
VehicleAction.DeployRequest(GUID0, guid, state, 0, false, Vector3.Zero)
)
state
}
else {
} else {
prevState
}
}
def UndeploymentAction(obj : Deployment.DeploymentObject, state : DriveState.Value, prevState : DriveState.Value) : DriveState.Value = {
val guid = obj.GUID
val zone = obj.Zone
val zoneChannel = zone.Id
val GUID0 = Service.defaultPlayerGUID
if(state == DriveState.Undeploying) {
zone.VehicleEvents ! VehicleServiceMessage(zoneChannel, VehicleAction.DeployRequest(GUID0, guid, state, 0, false, Vector3.Zero))
def UndeploymentAction(
obj: Deployment.DeploymentObject,
state: DriveState.Value,
prevState: DriveState.Value
): DriveState.Value = {
val guid = obj.GUID
val zone = obj.Zone
val zoneChannel = zone.id
val GUID0 = Service.defaultPlayerGUID
if (state == DriveState.Undeploying) {
zone.VehicleEvents ! VehicleServiceMessage(
zoneChannel,
VehicleAction.DeployRequest(GUID0, guid, state, 0, false, Vector3.Zero)
)
import scala.concurrent.ExecutionContext.Implicits.global
context.system.scheduler.scheduleOnce(obj.UndeployTime milliseconds, obj.Actor, Deployment.TryUndeploy(DriveState.Mobile))
context.system.scheduler.scheduleOnce(
obj.UndeployTime milliseconds,
obj.Actor,
Deployment.TryUndeploy(DriveState.Mobile)
)
state
}
else if(state == DriveState.Mobile) {
zone.VehicleEvents ! VehicleServiceMessage(zoneChannel, VehicleAction.DeployRequest(GUID0, guid, state, 0, false, Vector3.Zero))
} else if (state == DriveState.Mobile) {
zone.VehicleEvents ! VehicleServiceMessage(
zoneChannel,
VehicleAction.DeployRequest(GUID0, guid, state, 0, false, Vector3.Zero)
)
state
}
else {
} else {
prevState
}
}
}
object DeploymentBehavior {
def TryDeploymentChange(obj : Deployment.DeploymentObject, state : DriveState.Value) : Boolean = {
def TryDeploymentChange(obj: Deployment.DeploymentObject, state: DriveState.Value): Boolean = {
Deployment.NextState(obj.DeploymentState) == state && (obj.DeploymentState = state) == state
}
}

View file

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

View file

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

View file

@ -21,19 +21,19 @@ object GenericHackables {
* @return the percentage amount of progress per tick
*/
def GetHackSpeed(player: Player, obj: PlanetSideServerObject): Float = {
val playerHackLevel = Player.GetHackLevel(player)
val playerHackLevel = player.avatar.hackingSkillLevel()
val timeToHack = obj match {
case vehicle: Vehicle => vehicle.JackingDuration(playerHackLevel).toFloat
case hackable: Hackable => hackable.HackDuration(playerHackLevel).toFloat
case _ =>
log.warn(
s"${player.Name} tried to hack an object that has no hack time defined - ${obj.Definition.Name}#${obj.GUID} on ${obj.Zone.Id}"
s"${player.Name} tried to hack an object that has no hack time defined - ${obj.Definition.Name}#${obj.GUID} on ${obj.Zone.id}"
)
0f
}
if (timeToHack == 0) {
log.warn(
s"${player.Name} tried to hack an object that they don't have the correct hacking level for - ${obj.Definition.Name}#${obj.GUID} on ${obj.Zone.Id}"
s"${player.Name} tried to hack an object that they don't have the correct hacking level for - ${obj.Definition.Name}#${obj.GUID} on ${obj.Zone.id}"
)
0f
} else {
@ -110,7 +110,7 @@ object GenericHackables {
ask(target.Actor, CommonMessages.Hack(tplayer, target))(1 second).mapTo[Boolean].onComplete {
case Success(_) =>
val zone = target.Zone
val zoneId = zone.Id
val zoneId = zone.id
val pguid = tplayer.GUID
zone.LocalEvents ! LocalServiceMessage(
zoneId,
@ -118,7 +118,8 @@ object GenericHackables {
)
zone.LocalEvents ! LocalServiceMessage(
zoneId,
LocalAction.HackTemporarily(pguid, zone, target, unk, target.HackEffectDuration(Player.GetHackLevel(user)))
LocalAction
.HackTemporarily(pguid, zone, target, unk, target.HackEffectDuration(user.avatar.hackingSkillLevel()))
)
case Failure(_) => log.warn(s"Hack message failed on target guid: ${target.GUID}")
}

View file

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

View file

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

View file

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

View file

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

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