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