mirror of
https://github.com/2revoemag/PSF-BotServer.git
synced 2026-01-19 18:14:44 +00:00
Misc Fixes (#1076)
* new paradigm for character creation detection of old characters by name; vehicle channel when seated in vehicle (ant); second wind activates as long as non-fatal damage n>=25; coordinated sequence of deployables whose UI is being updated * the max timer will assert itself through death and respawn * in theory, the tests are fixed; that may change from execution to execution, as is usual * adjusted how the mechanized exo-suit timer asserts itself when in conjunction with prior exo-suit purchase orders * players in seats have their mounted information shortened in a more straightforward, less fault-prone way; stamina recharge command shortened * fixed vehicles not loading when player has no GUID; deactivated squad features (may cause trouble for the Router, but we'll manage); removed lingering, unnecessary radiation tick * even if the player seems to be standing completely still, send an update packet once in a while (1500ms) * removing an active router will always clean up an active router telepad with which it is paired * better timing for refresh of the character select screen; potential to stop moving vehicles from anothers's perspectives * block mounting while vehicle in motion, or in control; if ejected early, end control early * block mounting while vehicle in motion, or in control (2)
This commit is contained in:
parent
66f45edcd3
commit
70c4393e9b
|
|
@ -90,6 +90,9 @@ game {
|
|||
# Purchases timers for the mechanized assault exo-suits all update at the same time when any of them would update
|
||||
shared-max-cooldown = no
|
||||
|
||||
# Purchases timers for the battleframe robotics vehicles all update at the same time when either of them would update
|
||||
shared-bfr-cooldown = yes
|
||||
|
||||
# HART system, shuttles and facilities
|
||||
hart {
|
||||
# How long the shuttle is not boarding passengers (going through the motions)
|
||||
|
|
@ -208,7 +211,7 @@ game {
|
|||
# Use the index of that sample distance from this sequence in the sequence `delays` below.
|
||||
ranges = [150, 300, 400]
|
||||
# The absolute time delay before a successful packet must be dispatched regardless of distance. (s)
|
||||
delay-max = 1000
|
||||
delay-max = 1500
|
||||
# The time delays for each distance range before a successful packet must be dispatched. [s]
|
||||
# The index for an entry in this sequence is expected to be discovered using the `ranges` sequence above.
|
||||
# Delays between packets may not be as precise as desired
|
||||
|
|
|
|||
|
|
@ -1,24 +1,17 @@
|
|||
// Copyright (c) 2019 PSForever
|
||||
package net.psforever.actors.session
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import akka.actor.Cancellable
|
||||
import akka.actor.typed.scaladsl.{ActorContext, Behaviors, StashBuffer}
|
||||
import akka.actor.typed.{ActorRef, Behavior, PostStop, SupervisorStrategy}
|
||||
import net.psforever.objects.avatar.{ProgressDecoration, SpecialCarry}
|
||||
import net.psforever.objects.avatar.scoring.{Death, EquipmentStat, KDAStat, Kill}
|
||||
import net.psforever.objects.sourcing.SourceWithHealthEntry
|
||||
import net.psforever.objects.vital.projectile.ProjectileReason
|
||||
import net.psforever.objects.vital.{DamagingActivity, HealingActivity, SpawningActivity, Vitality}
|
||||
import net.psforever.packet.game.objectcreate.BasicCharacterData
|
||||
import net.psforever.types.ExperienceType
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import org.joda.time.{LocalDateTime, Seconds}
|
||||
|
||||
import scala.collection.mutable
|
||||
import scala.concurrent.{ExecutionContextExecutor, Future, Promise}
|
||||
import scala.util.{Failure, Success}
|
||||
import scala.concurrent.duration._
|
||||
//
|
||||
import net.psforever.objects._
|
||||
import net.psforever.objects.avatar.{
|
||||
Avatar,
|
||||
BattleRank,
|
||||
|
|
@ -29,25 +22,31 @@ import net.psforever.objects.avatar.{
|
|||
Implant,
|
||||
MemberLists,
|
||||
PlayerControl,
|
||||
Shortcut => AvatarShortcut
|
||||
ProgressDecoration,
|
||||
Shortcut => AvatarShortcut,
|
||||
SpecialCarry
|
||||
}
|
||||
import net.psforever.objects.avatar.scoring.{Death, EquipmentStat, KDAStat, Kill}
|
||||
import net.psforever.objects.definition._
|
||||
import net.psforever.objects.definition.converter.CharacterSelectConverter
|
||||
import net.psforever.objects.inventory.Container
|
||||
import net.psforever.objects.equipment.{Equipment, EquipmentSlot}
|
||||
import net.psforever.objects.inventory.InventoryItem
|
||||
import net.psforever.objects.inventory.{Container, InventoryItem}
|
||||
import net.psforever.objects.loadouts.{InfantryLoadout, Loadout, VehicleLoadout}
|
||||
import net.psforever.objects._
|
||||
import net.psforever.objects.locker.LockerContainer
|
||||
import net.psforever.objects.sourcing.PlayerSource
|
||||
import net.psforever.objects.vital.HealFromImplant
|
||||
import net.psforever.packet.game.objectcreate.{ObjectClass, RibbonBars}
|
||||
import net.psforever.objects.sourcing.{PlayerSource,SourceWithHealthEntry}
|
||||
import net.psforever.objects.vital.projectile.ProjectileReason
|
||||
import net.psforever.objects.vital.{DamagingActivity, HealFromImplant, HealingActivity, SpawningActivity, Vitality}
|
||||
import net.psforever.packet.game.objectcreate.{BasicCharacterData, ObjectClass, RibbonBars}
|
||||
import net.psforever.packet.game.{Friend => GameFriend, _}
|
||||
import net.psforever.persistence
|
||||
import net.psforever.services.Service
|
||||
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||
import net.psforever.types.{
|
||||
CharacterSex,
|
||||
CharacterVoice,
|
||||
Cosmetic,
|
||||
ExoSuitType,
|
||||
ExperienceType,
|
||||
ImplantType,
|
||||
LoadoutType,
|
||||
MemberAction,
|
||||
|
|
@ -57,11 +56,7 @@ import net.psforever.types.{
|
|||
TransactionType
|
||||
}
|
||||
import net.psforever.util.Database._
|
||||
import net.psforever.persistence
|
||||
import net.psforever.util.{Config, Database, DefinitionUtil}
|
||||
import net.psforever.services.Service
|
||||
//import org.log4s.Logger
|
||||
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||
|
||||
object AvatarActor {
|
||||
def apply(sessionActor: ActorRef[SessionActor.Command]): Behavior[Command] =
|
||||
|
|
@ -400,28 +395,33 @@ object AvatarActor {
|
|||
def resolveSharedPurchaseTimeNames(pair: (BasicDefinition, String)): Seq[(BasicDefinition, String)] = {
|
||||
val (definition, name) = pair
|
||||
if (name.matches("(tr|nc|vs)hev_.+") && Config.app.game.sharedMaxCooldown) {
|
||||
//all mechanized exo-suits
|
||||
val faction = name.take(2)
|
||||
(if (faction.equals("nc")) {
|
||||
Seq(GlobalDefinitions.nchev_scattercannon, GlobalDefinitions.nchev_falcon, GlobalDefinitions.nchev_sparrow)
|
||||
} else if (faction.equals("vs")) {
|
||||
Seq(GlobalDefinitions.vshev_quasar, GlobalDefinitions.vshev_comet, GlobalDefinitions.vshev_starfire)
|
||||
} else {
|
||||
Seq(GlobalDefinitions.trhev_dualcycler, GlobalDefinitions.trhev_pounder, GlobalDefinitions.trhev_burster)
|
||||
}).zip(
|
||||
Seq(s"${faction}hev_antipersonnel", s"${faction}hev_antivehicular", s"${faction}hev_antiaircraft")
|
||||
)
|
||||
} else {
|
||||
Seq(GlobalDefinitions.nchev_scattercannon, GlobalDefinitions.nchev_falcon, GlobalDefinitions.nchev_sparrow)
|
||||
} else if (faction.equals("vs")) {
|
||||
Seq(GlobalDefinitions.vshev_quasar, GlobalDefinitions.vshev_comet, GlobalDefinitions.vshev_starfire)
|
||||
} else {
|
||||
Seq(GlobalDefinitions.trhev_dualcycler, GlobalDefinitions.trhev_pounder, GlobalDefinitions.trhev_burster)
|
||||
}).map { tdef => (tdef, tdef.Descriptor) }
|
||||
} else if ((name.matches(".+_gunner") || name.matches(".+_flight")) && Config.app.game.sharedBfrCooldown) {
|
||||
//all battleframe robotics vehicles
|
||||
definition match {
|
||||
case vdef: VehicleDefinition if GlobalDefinitions.isBattleFrameFlightVehicle(vdef) =>
|
||||
val bframe = name.substring(0, name.indexOf('_'))
|
||||
val gunner = bframe + "_gunner"
|
||||
Seq((DefinitionUtil.fromString(gunner), gunner), (vdef, name))
|
||||
|
||||
Seq((DefinitionUtil.fromString(gunner), gunner), pair)
|
||||
case vdef: VehicleDefinition if GlobalDefinitions.isBattleFrameGunnerVehicle(vdef) =>
|
||||
val bframe = name.substring(0, name.indexOf('_'))
|
||||
val flight = bframe + "_flight"
|
||||
Seq((vdef, name), (DefinitionUtil.fromString(flight), flight))
|
||||
|
||||
Seq(pair, (DefinitionUtil.fromString(flight), flight))
|
||||
case _ =>
|
||||
Seq.empty
|
||||
}
|
||||
} else {
|
||||
definition match {
|
||||
case tdef: ToolDefinition if GlobalDefinitions.isMaxArms(tdef) =>
|
||||
Seq((tdef, tdef.Descriptor))
|
||||
case _ =>
|
||||
Seq(pair)
|
||||
}
|
||||
|
|
@ -913,43 +913,8 @@ class AvatarActor(
|
|||
AvatarActor.displayLookingForSquad(session.get, if (lfs) 1 else 0)
|
||||
Behaviors.same
|
||||
|
||||
case CreateAvatar(name, head, voice, gender, empire) =>
|
||||
import ctx._
|
||||
ctx.run(query[persistence.Avatar].filter(_.name ilike lift(name)).filter(!_.deleted)).onComplete {
|
||||
case Success(characters) =>
|
||||
characters.headOption match {
|
||||
case None =>
|
||||
val result = for {
|
||||
_ <- ctx.run(
|
||||
query[persistence.Avatar]
|
||||
.insert(
|
||||
_.name -> lift(name),
|
||||
_.accountId -> lift(account.id),
|
||||
_.factionId -> lift(empire.id),
|
||||
_.headId -> lift(head),
|
||||
_.voiceId -> lift(voice.id),
|
||||
_.genderId -> lift(gender.value),
|
||||
_.bep -> lift(Config.app.game.newAvatar.br.experience),
|
||||
_.cep -> lift(Config.app.game.newAvatar.cr.experience)
|
||||
)
|
||||
)
|
||||
} yield ()
|
||||
result.onComplete {
|
||||
case Success(_) =>
|
||||
log.debug(s"AvatarActor: created character $name for account ${account.name}")
|
||||
sessionActor ! SessionActor.SendResponse(ActionResultMessage.Pass)
|
||||
sendAvatars(account)
|
||||
case Failure(e) => log.error(e)("db failure")
|
||||
}
|
||||
case Some(_) =>
|
||||
// send "char already exists"
|
||||
sessionActor ! SessionActor.SendResponse(ActionResultMessage.Fail(1))
|
||||
}
|
||||
case Failure(e) =>
|
||||
log.error(e)("db failure")
|
||||
sessionActor ! SessionActor.SendResponse(ActionResultMessage.Fail(3))
|
||||
sendAvatars(account)
|
||||
}
|
||||
case CreateAvatar(name, head, voice, sex, empire) =>
|
||||
createAvatar(account, name, sex, empire, head, voice)
|
||||
Behaviors.same
|
||||
|
||||
case DeleteAvatar(id) =>
|
||||
|
|
@ -1482,7 +1447,8 @@ class AvatarActor(
|
|||
Behaviors.same
|
||||
|
||||
case UpdatePurchaseTime(definition, time) =>
|
||||
var newTimes = avatar.cooldowns.purchase
|
||||
var theTimes = avatar.cooldowns.purchase
|
||||
var updateTheTimes: Boolean = false
|
||||
AvatarActor
|
||||
.resolveSharedPurchaseTimeNames(AvatarActor.resolvePurchaseTimeName(avatar.faction, definition))
|
||||
.foreach {
|
||||
|
|
@ -1490,7 +1456,8 @@ class AvatarActor(
|
|||
Avatar.purchaseCooldowns.get(item) match {
|
||||
case Some(cooldown) =>
|
||||
//only send for items with cooldowns
|
||||
newTimes = newTimes.updated(name, time)
|
||||
updateTheTimes = true
|
||||
theTimes = theTimes.updated(name, time)
|
||||
updatePurchaseTimer(
|
||||
name,
|
||||
cooldown.toSeconds,
|
||||
|
|
@ -1502,7 +1469,9 @@ class AvatarActor(
|
|||
case _ => ;
|
||||
}
|
||||
}
|
||||
avatarCopy(avatar.copy(cooldowns = avatar.cooldowns.copy(purchase = newTimes)))
|
||||
if (updateTheTimes) {
|
||||
avatarCopy(avatar.copy(cooldowns = avatar.cooldowns.copy(purchase = theTimes)))
|
||||
}
|
||||
Behaviors.same
|
||||
|
||||
case UpdateUseTime(definition, time) =>
|
||||
|
|
@ -1632,11 +1601,7 @@ class AvatarActor(
|
|||
Behaviors.same
|
||||
|
||||
case RestoreStamina(stamina) =>
|
||||
tryRestoreStaminaForSession(stamina) match {
|
||||
case Some(sess) =>
|
||||
actuallyRestoreStamina(stamina, sess)
|
||||
case _ => ;
|
||||
}
|
||||
tryRestoreStaminaForSession(stamina).collect { actuallyRestoreStamina(stamina, _) }
|
||||
Behaviors.same
|
||||
|
||||
case RestoreStaminaPeriodically(stamina) =>
|
||||
|
|
@ -1897,11 +1862,7 @@ class AvatarActor(
|
|||
)
|
||||
)
|
||||
// if we need to start stamina regeneration
|
||||
tryRestoreStaminaForSession(stamina = 1) match {
|
||||
case Some(_) =>
|
||||
defaultStaminaRegen(initialDelay = 0.5f seconds)
|
||||
case _ => ;
|
||||
}
|
||||
tryRestoreStaminaForSession(stamina = 1).collect { _ => defaultStaminaRegen(initialDelay = 0.5f seconds) }
|
||||
replyTo ! AvatarLoginResponse(avatar)
|
||||
case Failure(e) =>
|
||||
log.error(e)("db failure")
|
||||
|
|
@ -1991,11 +1952,7 @@ class AvatarActor(
|
|||
}
|
||||
|
||||
def restoreStaminaPeriodically(stamina: Int): Unit = {
|
||||
tryRestoreStaminaForSession(stamina) match {
|
||||
case Some(sess) =>
|
||||
actuallyRestoreStaminaIfStationary(stamina, sess)
|
||||
case _ => ;
|
||||
}
|
||||
tryRestoreStaminaForSession(stamina).collect { actuallyRestoreStaminaIfStationary(stamina, _) }
|
||||
startIfStoppedStaminaRegen(initialDelay = 0.5f seconds)
|
||||
}
|
||||
|
||||
|
|
@ -2633,27 +2590,40 @@ class AvatarActor(
|
|||
|
||||
def refreshPurchaseTimes(keys: Set[String]): Unit = {
|
||||
var keysToDrop: Seq[String] = Nil
|
||||
val faction = avatar.faction
|
||||
keys.foreach { key =>
|
||||
avatar.cooldowns.purchase.find { case (name, _) => name.equals(key) } match {
|
||||
case Some((name, purchaseTime)) =>
|
||||
avatar
|
||||
.cooldowns
|
||||
.purchase
|
||||
.find { case (name, _) => name.equals(key) }
|
||||
.flatMap { case (name, purchaseTime) =>
|
||||
val secondsSincePurchase = Seconds.secondsBetween(purchaseTime, LocalDateTime.now()).getSeconds
|
||||
Avatar.purchaseCooldowns.find(_._1.Name.equals(name)) match {
|
||||
case Some((obj, cooldown)) if cooldown.toSeconds - secondsSincePurchase > 0 =>
|
||||
val (_, name) = AvatarActor.resolvePurchaseTimeName(avatar.faction, obj)
|
||||
updatePurchaseTimer(
|
||||
name,
|
||||
cooldown.toSeconds - secondsSincePurchase,
|
||||
obj match {
|
||||
case _: KitDefinition => false
|
||||
case _ => true
|
||||
}
|
||||
)
|
||||
|
||||
case _ =>
|
||||
keysToDrop = keysToDrop :+ key //key has timed-out
|
||||
Avatar
|
||||
.purchaseCooldowns
|
||||
.find(_._1.Descriptor.equals(name))
|
||||
.collect {
|
||||
case (obj, cooldown) =>
|
||||
(obj, cooldown.toSeconds - secondsSincePurchase)
|
||||
}
|
||||
.orElse {
|
||||
keysToDrop = keysToDrop :+ key //key indicates cooldown, but no cooldown delay
|
||||
None
|
||||
}
|
||||
case _ => ;
|
||||
}
|
||||
}
|
||||
.collect {
|
||||
case (obj, remainingTime) if remainingTime > 0 =>
|
||||
val (_, resolvedName) = AvatarActor.resolvePurchaseTimeName(faction, obj)
|
||||
updatePurchaseTimer(
|
||||
resolvedName,
|
||||
remainingTime,
|
||||
obj match {
|
||||
case _: KitDefinition => false
|
||||
case _ => true
|
||||
}
|
||||
)
|
||||
case (_, _) =>
|
||||
keysToDrop = keysToDrop :+ key //key has timed-out
|
||||
}
|
||||
}
|
||||
if (keysToDrop.nonEmpty) {
|
||||
val cdown = avatar.cooldowns
|
||||
|
|
@ -2999,4 +2969,131 @@ class AvatarActor(
|
|||
def updateToolDischarge(stats: EquipmentStat): Unit = {
|
||||
avatar.scorecard.rate(stats)
|
||||
}
|
||||
|
||||
def createAvatar(
|
||||
account: Account,
|
||||
name: String,
|
||||
sex: CharacterSex,
|
||||
empire: PlanetSideEmpire.Value,
|
||||
head: Int,
|
||||
voice: CharacterVoice.Value
|
||||
): Unit = {
|
||||
import ctx._
|
||||
ctx.run(
|
||||
query[persistence.Avatar]
|
||||
.filter(_.name ilike lift(name))
|
||||
.map(a => (a.accountId, a.deleted, a.created))
|
||||
)
|
||||
.onComplete {
|
||||
case Success(foundCharacters) if foundCharacters.size == 1 =>
|
||||
testFoundCharacters(
|
||||
account,
|
||||
name,
|
||||
foundCharacters
|
||||
.collect { case (a, b, c) => (a, b, c.toDate.getTime) }
|
||||
)
|
||||
case Success(foundCharacters) if foundCharacters.nonEmpty =>
|
||||
testFoundCharacters(
|
||||
account,
|
||||
name,
|
||||
foundCharacters
|
||||
.map { case (a, b, created) => (a, b, created.toDate.getTime) }
|
||||
.sortBy { case (_, _, created) => created }
|
||||
.toList
|
||||
)
|
||||
case Success(_) =>
|
||||
//create new character
|
||||
actuallyCreateNewCharacter(account.id, account.name, name, sex, empire, head, voice)
|
||||
.onComplete(_ => sendAvatars(account))
|
||||
case Failure(e) =>
|
||||
log.error(e)("db failure")
|
||||
sessionActor ! SessionActor.SendResponse(ActionResultMessage.Fail(error = 3))
|
||||
sendAvatars(account)
|
||||
}
|
||||
}
|
||||
|
||||
private def testFoundCharacters(
|
||||
account: Account,
|
||||
name: String,
|
||||
foundCharacters: IterableOnce[(Int, Boolean, Long)]): Unit = {
|
||||
val foundCharactersIterator = foundCharacters.iterator
|
||||
if (foundCharactersIterator.exists { case (_, deleted, _ ) => !deleted } ||
|
||||
foundCharactersIterator.exists { case (accountId, _, _) => accountId != account.id }) {
|
||||
//send "char already exists"
|
||||
sessionActor ! SessionActor.SendResponse(ActionResultMessage.Fail(error = 1))
|
||||
} else {
|
||||
//reactivate character
|
||||
reactivateCharacter(account.id, account.name, name)
|
||||
.onComplete(_ => sendAvatars(account))
|
||||
}
|
||||
}
|
||||
|
||||
private def actuallyCreateNewCharacter(
|
||||
accountId: Int,
|
||||
accountName: String,
|
||||
name: String,
|
||||
sex: CharacterSex,
|
||||
empire: PlanetSideEmpire.Value,
|
||||
head: Int,
|
||||
voice: CharacterVoice.Value,
|
||||
bep: Long = Config.app.game.newAvatar.br.experience,
|
||||
cep: Long = Config.app.game.newAvatar.cr.experience
|
||||
): Future[Boolean] = {
|
||||
val output: Promise[Boolean] = Promise[Boolean]()
|
||||
import ctx._
|
||||
val result = for {
|
||||
_ <- ctx.run(
|
||||
query[persistence.Avatar]
|
||||
.insert(
|
||||
_.name -> lift(name),
|
||||
_.accountId -> lift(accountId),
|
||||
_.factionId -> lift(empire.id),
|
||||
_.headId -> lift(head),
|
||||
_.voiceId -> lift(voice.id),
|
||||
_.genderId -> lift(sex.value),
|
||||
_.bep -> lift(bep),
|
||||
_.cep -> lift(cep)
|
||||
)
|
||||
)
|
||||
} yield ()
|
||||
result.onComplete {
|
||||
case Success(_) =>
|
||||
log.debug(s"AvatarActor: created character $name for account $accountName")
|
||||
sessionActor ! SessionActor.SendResponse(ActionResultMessage.Pass)
|
||||
output.success(true)
|
||||
case Failure(e) =>
|
||||
log.error(e)("db failure")
|
||||
sessionActor ! SessionActor.SendResponse(ActionResultMessage.Fail(error = 3))
|
||||
output.success(false)
|
||||
}
|
||||
output.future
|
||||
}
|
||||
|
||||
private def reactivateCharacter(
|
||||
accountId: Int,
|
||||
accountName: String,
|
||||
characterName: String
|
||||
): Future[Boolean] = {
|
||||
val output: Promise[Boolean] = Promise[Boolean]()
|
||||
import ctx._
|
||||
val result = for {
|
||||
out <- ctx.run(
|
||||
query[persistence.Avatar]
|
||||
.filter(a => a.accountId == lift(accountId))
|
||||
.filter(a => a.name ilike lift(characterName))
|
||||
.update(_.deleted -> lift(false))
|
||||
)
|
||||
} yield out
|
||||
result.onComplete {
|
||||
case Success(_) =>
|
||||
log.debug(s"AvatarActor: character belonging to $accountName with name $characterName reactivated")
|
||||
sessionActor ! SessionActor.SendResponse(ActionResultMessage.Pass)
|
||||
output.success(true)
|
||||
case Failure(e) =>
|
||||
log.error(e)("db failure")
|
||||
sessionActor ! SessionActor.SendResponse(ActionResultMessage.Fail(error = 3))
|
||||
output.success(false)
|
||||
}
|
||||
output.future
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -104,10 +104,10 @@ class SessionAvatarHandlers(
|
|||
} //ms
|
||||
if (!wasVisible ||
|
||||
!previouslyInDrawableRange ||
|
||||
durationSince > drawConfig.delayMax ||
|
||||
(!lastMsg.contains(pstateToSave) &&
|
||||
(canSeeReallyFar ||
|
||||
currentDistance < drawConfig.rangeMin * drawConfig.rangeMin ||
|
||||
durationSince > drawConfig.delayMax ||
|
||||
sessionData.canSeeReallyFar ||
|
||||
durationSince > targetDelay
|
||||
)
|
||||
|
|
@ -425,8 +425,7 @@ class SessionAvatarHandlers(
|
|||
sessionData.zoning.zoningStatus = Zoning.Status.None
|
||||
sessionData.zoning.spawn.deadState = DeadState.Dead
|
||||
continent.GUID(mount).collect { case obj: Vehicle =>
|
||||
sessionData.vehicles.ConditionalDriverVehicleControl(obj)
|
||||
sessionData.vehicles.serverVehicleControlVelocity = None
|
||||
sessionData.vehicles.DriverVehicleControl(obj)
|
||||
sessionData.unaccessContainer(obj)
|
||||
}
|
||||
sessionData.playerActionsToCancel()
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import net.psforever.packet.game.{ChatMsg, DelayedPathMountMsg, DismountVehicleM
|
|||
import net.psforever.services.Service
|
||||
import net.psforever.services.local.{LocalAction, LocalServiceMessage}
|
||||
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
|
||||
import net.psforever.types.{BailType, ChatMessageType, PlanetSideGUID}
|
||||
import net.psforever.types.{BailType, ChatMessageType, PlanetSideGUID, Vector3}
|
||||
|
||||
class SessionMountHandlers(
|
||||
val sessionData: SessionData,
|
||||
|
|
@ -56,6 +56,7 @@ class SessionMountHandlers(
|
|||
sendResponse(PlanetsideAttributeMessage(obj_guid, obj.Definition.shieldUiAttribute, obj.Shields))
|
||||
sendResponse(PlanetsideAttributeMessage(obj_guid, attribute_type=45, obj.NtuCapacitorScaled))
|
||||
sendResponse(GenericObjectActionMessage(obj_guid, code=11))
|
||||
sessionData.accessContainer(obj)
|
||||
MountingAction(tplayer, obj, seatNumber)
|
||||
|
||||
case Mountable.CanMount(obj: Vehicle, seatNumber, _)
|
||||
|
|
@ -237,7 +238,7 @@ class SessionMountHandlers(
|
|||
}")
|
||||
sessionData.vehicles.ConditionalDriverVehicleControl(obj)
|
||||
sessionData.unaccessContainer(obj)
|
||||
DismountAction(tplayer, obj, seatNum)
|
||||
DismountVehicleAction(tplayer, obj, seatNum)
|
||||
|
||||
case Mountable.CanDismount(obj: Vehicle, seat_num, _) =>
|
||||
continent.VehicleEvents ! VehicleServiceMessage(
|
||||
|
|
@ -288,6 +289,43 @@ class SessionMountHandlers(
|
|||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Common activities/procedure when a player dismounts a valid mountable object.
|
||||
* @param tplayer the player
|
||||
* @param obj the mountable object
|
||||
* @param seatNum the mount out of which which the player is disembarking
|
||||
*/
|
||||
def DismountVehicleAction(tplayer: Player, obj: PlanetSideGameObject with Mountable, seatNum: Int): Unit = {
|
||||
DismountAction(tplayer, obj, seatNum)
|
||||
//until vehicles maintain synchronized momentum without a driver
|
||||
obj match {
|
||||
case v: Vehicle
|
||||
if seatNum == 0 && Vector3.MagnitudeSquared(v.Velocity.getOrElse(Vector3.Zero)) > 0f =>
|
||||
sessionData.vehicles.serverVehicleControlVelocity.collect { _ =>
|
||||
sessionData.vehicles.ServerVehicleOverrideStop(v)
|
||||
}
|
||||
v.Velocity = Vector3.Zero
|
||||
continent.VehicleEvents ! VehicleServiceMessage(
|
||||
continent.id,
|
||||
VehicleAction.VehicleState(
|
||||
tplayer.GUID,
|
||||
v.GUID,
|
||||
unk1 = 0,
|
||||
v.Position,
|
||||
v.Orientation,
|
||||
vel = None,
|
||||
v.Flying,
|
||||
unk3 = 0,
|
||||
unk4 = 0,
|
||||
wheel_direction = 15,
|
||||
unk5 = false,
|
||||
unk6 = v.Cloaked
|
||||
)
|
||||
)
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Common activities/procedure when a player dismounts a valid mountable object.
|
||||
* @param tplayer the player
|
||||
|
|
@ -305,7 +343,7 @@ class SessionMountHandlers(
|
|||
sendResponse(DismountVehicleMsg(playerGuid, bailType, wasKickedByDriver = false))
|
||||
continent.VehicleEvents ! VehicleServiceMessage(
|
||||
continent.id,
|
||||
VehicleAction.DismountVehicle(playerGuid, bailType, unk2=false)
|
||||
VehicleAction.DismountVehicle(playerGuid, bailType, unk2 = false)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,17 +55,17 @@ class SessionSquadHandlers(
|
|||
/* packet */
|
||||
|
||||
def handleSquadDefinitionAction(pkt: SquadDefinitionActionMessage): Unit = {
|
||||
val SquadDefinitionActionMessage(u1, u2, action) = pkt
|
||||
squadService ! SquadServiceMessage(player, continent, SquadServiceAction.Definition(u1, u2, action))
|
||||
// val SquadDefinitionActionMessage(u1, u2, action) = pkt
|
||||
// squadService ! SquadServiceMessage(player, continent, SquadServiceAction.Definition(u1, u2, action))
|
||||
}
|
||||
|
||||
def handleSquadMemberRequest(pkt: SquadMembershipRequest): Unit = {
|
||||
val SquadMembershipRequest(request_type, char_id, unk3, player_name, unk5) = pkt
|
||||
squadService ! SquadServiceMessage(
|
||||
player,
|
||||
continent,
|
||||
SquadServiceAction.Membership(request_type, char_id, unk3, player_name, unk5)
|
||||
)
|
||||
// val SquadMembershipRequest(request_type, char_id, unk3, player_name, unk5) = pkt
|
||||
// squadService ! SquadServiceMessage(
|
||||
// player,
|
||||
// continent,
|
||||
// SquadServiceAction.Membership(request_type, char_id, unk3, player_name, unk5)
|
||||
// )
|
||||
}
|
||||
|
||||
def handleSquadWaypointRequest(pkt: SquadWaypointRequest): Unit = {
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ class SessionVehicleHandlers(
|
|||
val resolvedPlayerGuid = if (player.HasGUID) {
|
||||
player.GUID
|
||||
} else {
|
||||
Service.defaultPlayerGUID
|
||||
PlanetSideGUID(-1)
|
||||
}
|
||||
val isNotSameTarget = resolvedPlayerGuid != guid
|
||||
reply match {
|
||||
|
|
@ -202,17 +202,19 @@ class SessionVehicleHandlers(
|
|||
val strafe = 1 + Vehicles.CargoOrientation(vehicle)
|
||||
val reverseSpeed = if (strafe > 1) { 0 } else { speed }
|
||||
//strafe or reverse, not both
|
||||
sessionData.vehicles.serverVehicleControlVelocity = Some(reverseSpeed)
|
||||
sendResponse(ServerVehicleOverrideMsg(
|
||||
lock_accelerator=true,
|
||||
lock_wheel=true,
|
||||
reverse=true,
|
||||
unk4=false,
|
||||
lock_vthrust=0,
|
||||
strafe,
|
||||
reverseSpeed,
|
||||
unk8=Some(0)
|
||||
))
|
||||
sessionData.vehicles.ServerVehicleOverrideWithPacket(
|
||||
vehicle,
|
||||
ServerVehicleOverrideMsg(
|
||||
lock_accelerator=true,
|
||||
lock_wheel=true,
|
||||
reverse=true,
|
||||
unk4=false,
|
||||
lock_vthrust=0,
|
||||
strafe,
|
||||
reverseSpeed,
|
||||
unk8=Some(0)
|
||||
)
|
||||
)
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
context.system.scheduler.scheduleOnce(
|
||||
delay milliseconds,
|
||||
|
|
@ -220,25 +222,27 @@ class SessionVehicleHandlers(
|
|||
VehicleServiceResponse(toChannel, PlanetSideGUID(0), VehicleResponse.KickCargo(vehicle, speed=0, delay))
|
||||
)
|
||||
|
||||
case VehicleResponse.KickCargo(_, _, _)
|
||||
case VehicleResponse.KickCargo(cargo, _, _)
|
||||
if player.VehicleSeated.nonEmpty && sessionData.zoning.spawn.deadState == DeadState.Alive =>
|
||||
sessionData.vehicles.serverVehicleControlVelocity = None
|
||||
sendResponse(ServerVehicleOverrideMsg(
|
||||
lock_accelerator=false,
|
||||
lock_wheel=false,
|
||||
reverse=false,
|
||||
unk4=false,
|
||||
lock_vthrust=0,
|
||||
lock_strafe=0,
|
||||
movement_speed=0,
|
||||
unk8=None
|
||||
))
|
||||
sessionData.vehicles.TotalDriverVehicleControl(cargo)
|
||||
|
||||
case VehicleResponse.StartPlayerSeatedInVehicle(vehicle, _)
|
||||
if player.VisibleSlots.contains(player.DrawnSlot) =>
|
||||
val vehicle_guid = vehicle.GUID
|
||||
sessionData.playerActionsToCancel()
|
||||
sessionData.vehicles.serverVehicleControlVelocity = Some(0)
|
||||
sessionData.vehicles.ServerVehicleOverrideWithPacket(
|
||||
vehicle,
|
||||
ServerVehicleOverrideMsg(
|
||||
lock_accelerator=false,
|
||||
lock_wheel=false,
|
||||
reverse=false,
|
||||
unk4=false,
|
||||
lock_vthrust=0,
|
||||
lock_strafe=0,
|
||||
movement_speed=0,
|
||||
unk8=None
|
||||
)
|
||||
)
|
||||
sessionData.terminals.CancelAllProximityUnits()
|
||||
player.DrawnSlot = Player.HandsDownSlot
|
||||
sendResponse(ObjectHeldMessage(player.GUID, Player.HandsDownSlot, unk1=true))
|
||||
|
|
@ -253,33 +257,51 @@ class SessionVehicleHandlers(
|
|||
}
|
||||
|
||||
case VehicleResponse.StartPlayerSeatedInVehicle(vehicle, _) =>
|
||||
val vehicle_guid = vehicle.GUID
|
||||
val vehicleGuid = vehicle.GUID
|
||||
val playerGuid = player.GUID
|
||||
sessionData.playerActionsToCancel()
|
||||
sessionData.vehicles.serverVehicleControlVelocity = Some(0)
|
||||
sessionData.terminals.CancelAllProximityUnits()
|
||||
sendResponse(PlanetsideAttributeMessage(vehicle_guid, attribute_type=22, attribute_value=1L)) //mount points off
|
||||
sendResponse(PlanetsideAttributeMessage(player.GUID, attribute_type=21, vehicle_guid)) //ownership
|
||||
sendResponse(PlanetsideAttributeMessage(vehicleGuid, attribute_type=22, attribute_value=1L)) //mount points off
|
||||
sendResponse(PlanetsideAttributeMessage(playerGuid, attribute_type=21, vehicleGuid)) //ownership
|
||||
vehicle.MountPoints.find { case (_, mp) => mp.seatIndex == 0 }.collect {
|
||||
case (mountPoint, _) => vehicle.Actor ! Mountable.TryMount(player, mountPoint)
|
||||
}
|
||||
|
||||
case VehicleResponse.PlayerSeatedInVehicle(vehicle, _) =>
|
||||
val vehicle_guid = vehicle.GUID
|
||||
sendResponse(PlanetsideAttributeMessage(vehicle_guid, attribute_type=22, attribute_value=0L)) //mount points on
|
||||
Vehicles.ReloadAccessPermissions(vehicle, player.Name)
|
||||
sessionData.vehicles.ServerVehicleLock(vehicle)
|
||||
sessionData.vehicles.ServerVehicleOverrideWithPacket(
|
||||
vehicle,
|
||||
ServerVehicleOverrideMsg(
|
||||
lock_accelerator=true,
|
||||
lock_wheel=true,
|
||||
reverse=false,
|
||||
unk4=false,
|
||||
lock_vthrust=0,
|
||||
lock_strafe=0,
|
||||
movement_speed=0,
|
||||
unk8=Some(0)
|
||||
)
|
||||
)
|
||||
sessionData.vehicles.serverVehicleControlVelocity = Some(-1)
|
||||
|
||||
case VehicleResponse.ServerVehicleOverrideStart(vehicle, _) =>
|
||||
val vdef = vehicle.Definition
|
||||
sessionData.vehicles.ServerVehicleOverride(
|
||||
sessionData.vehicles.ServerVehicleOverrideWithPacket(
|
||||
vehicle,
|
||||
vdef.AutoPilotSpeed1,
|
||||
flight= if (GlobalDefinitions.isFlightVehicle(vdef)) { 1 } else { 0 }
|
||||
ServerVehicleOverrideMsg(
|
||||
lock_accelerator=true,
|
||||
lock_wheel=true,
|
||||
reverse=false,
|
||||
unk4=false,
|
||||
lock_vthrust=if (GlobalDefinitions.isFlightVehicle(vdef)) { 1 } else { 0 },
|
||||
lock_strafe=0,
|
||||
movement_speed=vdef.AutoPilotSpeed1,
|
||||
unk8=Some(0)
|
||||
)
|
||||
)
|
||||
|
||||
case VehicleResponse.ServerVehicleOverrideEnd(vehicle, _) =>
|
||||
session = session.copy(avatar = avatar.copy(vehicle = Some(vehicle.GUID)))
|
||||
sessionData.vehicles.DriverVehicleControl(vehicle, vehicle.Definition.AutoPilotSpeed2)
|
||||
sessionData.vehicles.ServerVehicleOverrideStop(vehicle)
|
||||
|
||||
case VehicleResponse.PeriodicReminder(VehicleSpawnPad.Reminders.Blocked, data) =>
|
||||
sendResponse(ChatMsg(
|
||||
|
|
|
|||
|
|
@ -268,22 +268,23 @@ class VehicleOperations(
|
|||
|
||||
def handleMountVehicle(pkt: MountVehicleMsg): Unit = {
|
||||
val MountVehicleMsg(_, mountable_guid, entry_point) = pkt
|
||||
sessionData.validObject(mountable_guid, decorator = "MountVehicle") match {
|
||||
case Some(obj: Mountable) =>
|
||||
sessionData.validObject(mountable_guid, decorator = "MountVehicle").collect {
|
||||
case obj: Mountable =>
|
||||
obj.Actor ! Mountable.TryMount(player, entry_point)
|
||||
case Some(_) =>
|
||||
case _: Mountable =>
|
||||
log.warn(
|
||||
s"DismountVehicleMsg: ${player.Name} can not mount while server has asserted control; please wait"
|
||||
)
|
||||
case _ =>
|
||||
log.error(s"MountVehicleMsg: object ${mountable_guid.guid} not a mountable thing, ${player.Name}")
|
||||
case None => ;
|
||||
}
|
||||
}
|
||||
|
||||
def handleDismountVehicle(pkt: DismountVehicleMsg): Unit = {
|
||||
val DismountVehicleMsg(player_guid, bailType, wasKickedByDriver) = pkt
|
||||
val dError: (String, Player)=> Unit = dismountError(bailType, wasKickedByDriver)
|
||||
//TODO optimize this later
|
||||
//common warning for this section
|
||||
def dismountWarning(note: String): Unit = {
|
||||
log.error(s"$note; some vehicle might not know that ${player.Name} is no longer sitting in it")
|
||||
}
|
||||
if (player.GUID == player_guid) {
|
||||
//normally disembarking from a mount
|
||||
(sessionData.zoning.interstellarFerry.orElse(continent.GUID(player.VehicleSeated)) match {
|
||||
|
|
@ -295,16 +296,9 @@ class VehicleOperations(
|
|||
case out @ Some(_: Mountable) =>
|
||||
out
|
||||
case _ =>
|
||||
dismountWarning(
|
||||
s"DismountVehicleMsg: player ${player.Name}_guid not considered seated in a mountable entity"
|
||||
)
|
||||
sendResponse(DismountVehicleMsg(player_guid, bailType, wasKickedByDriver))
|
||||
dError(s"DismountVehicleMsg: player ${player.Name} not considered seated in a mountable entity", player)
|
||||
None
|
||||
}) match {
|
||||
case Some(_) if serverVehicleControlVelocity.nonEmpty =>
|
||||
log.debug(
|
||||
s"DismountVehicleMsg: ${player.Name} can not dismount from vehicle while server has asserted control; please wait"
|
||||
)
|
||||
case Some(obj: Mountable) =>
|
||||
obj.PassengerInSeat(player) match {
|
||||
case Some(seat_num) =>
|
||||
|
|
@ -325,15 +319,14 @@ class VehicleOperations(
|
|||
}
|
||||
|
||||
case None =>
|
||||
dismountWarning(
|
||||
s"DismountVehicleMsg: can not find where player ${player.Name}_guid is seated in mountable ${player.VehicleSeated}"
|
||||
)
|
||||
dError(s"DismountVehicleMsg: can not find where player ${player.Name}_guid is seated in mountable ${player.VehicleSeated}", player)
|
||||
}
|
||||
case _ =>
|
||||
dismountWarning(s"DismountVehicleMsg: can not find mountable entity ${player.VehicleSeated}")
|
||||
dError(s"DismountVehicleMsg: can not find mountable entity ${player.VehicleSeated}", player)
|
||||
}
|
||||
} else {
|
||||
//kicking someone else out of a mount; need to own that mount/mountable
|
||||
val dWarn: (String, Player)=> Unit = dismountWarning(bailType, wasKickedByDriver)
|
||||
player.avatar.vehicle match {
|
||||
case Some(obj_guid) =>
|
||||
(
|
||||
|
|
@ -353,23 +346,47 @@ class VehicleOperations(
|
|||
case Some(seat_num) =>
|
||||
obj.Actor ! Mountable.TryDismount(tplayer, seat_num, bailType)
|
||||
case None =>
|
||||
dismountWarning(
|
||||
s"DismountVehicleMsg: can not find where other player ${player.Name}_guid is seated in mountable $obj_guid"
|
||||
)
|
||||
dError(s"DismountVehicleMsg: can not find where other player ${tplayer.Name} is seated in mountable $obj_guid", tplayer)
|
||||
}
|
||||
case (None, _) => ;
|
||||
log.warn(s"DismountVehicleMsg: ${player.Name} can not find his vehicle")
|
||||
case (_, None) => ;
|
||||
log.warn(s"DismountVehicleMsg: player $player_guid could not be found to kick, ${player.Name}")
|
||||
case (None, _) =>
|
||||
dWarn(s"DismountVehicleMsg: ${player.Name} can not find his vehicle", player)
|
||||
case (_, None) =>
|
||||
dWarn(s"DismountVehicleMsg: player $player_guid could not be found to kick, ${player.Name}", player)
|
||||
case _ =>
|
||||
log.warn(s"DismountVehicleMsg: object is either not a Mountable or not a Player")
|
||||
dWarn(s"DismountVehicleMsg: object is either not a Mountable or not a Player", player)
|
||||
}
|
||||
case None =>
|
||||
log.warn(s"DismountVehicleMsg: ${player.Name} does not own a vehicle")
|
||||
dWarn(s"DismountVehicleMsg: ${player.Name} does not own a vehicle", player)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def dismountWarning(
|
||||
bailAs: BailType.Value,
|
||||
kickedByDriver: Boolean
|
||||
)
|
||||
(
|
||||
note: String,
|
||||
player: Player
|
||||
): Unit = {
|
||||
log.warn(note)
|
||||
player.VehicleSeated = None
|
||||
sendResponse(DismountVehicleMsg(player.GUID, bailAs, kickedByDriver))
|
||||
}
|
||||
|
||||
private def dismountError(
|
||||
bailAs: BailType.Value,
|
||||
kickedByDriver: Boolean
|
||||
)
|
||||
(
|
||||
note: String,
|
||||
player: Player
|
||||
): Unit = {
|
||||
log.error(s"$note; some vehicle might not know that ${player.Name} is no longer sitting in it")
|
||||
player.VehicleSeated = None
|
||||
sendResponse(DismountVehicleMsg(player.GUID, bailAs, kickedByDriver))
|
||||
}
|
||||
|
||||
def handleMountVehicleCargo(pkt: MountVehicleCargoMsg): Unit = {
|
||||
val MountVehicleCargoMsg(_, cargo_guid, carrier_guid, _) = pkt
|
||||
(continent.GUID(cargo_guid), continent.GUID(carrier_guid)) match {
|
||||
|
|
@ -514,87 +531,27 @@ class VehicleOperations(
|
|||
}
|
||||
|
||||
/**
|
||||
* This function is applied to vehicles that are leaving a cargo vehicle's cargo hold to auto reverse them out
|
||||
* Lock all applicable controls of the current vehicle
|
||||
* Set the vehicle to move in reverse
|
||||
* Place the current vehicle under the control of the driver's commands,
|
||||
* but leave it in a cancellable auto-drive.
|
||||
* @param vehicle the vehicle
|
||||
*/
|
||||
def ServerVehicleLockReverse(): Unit = {
|
||||
serverVehicleControlVelocity = Some(-1)
|
||||
sendResponse(
|
||||
ServerVehicleOverrideMsg(
|
||||
lock_accelerator = true,
|
||||
lock_wheel = true,
|
||||
reverse = true,
|
||||
unk4 = true,
|
||||
lock_vthrust = 0,
|
||||
lock_strafe = 1,
|
||||
movement_speed = 2,
|
||||
unk8 = Some(0)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is applied to vehicles that are leaving a cargo vehicle's cargo hold to strafe right out of the cargo hold for vehicles that are mounted sideways e.g. router/BFR
|
||||
* Lock all applicable controls of the current vehicle
|
||||
* Set the vehicle to strafe right
|
||||
*/
|
||||
def ServerVehicleLockStrafeRight(): Unit = {
|
||||
serverVehicleControlVelocity = Some(-1)
|
||||
sendResponse(
|
||||
ServerVehicleOverrideMsg(
|
||||
lock_accelerator = true,
|
||||
lock_wheel = true,
|
||||
reverse = false,
|
||||
unk4 = true,
|
||||
lock_vthrust = 0,
|
||||
lock_strafe = 3,
|
||||
movement_speed = 0,
|
||||
unk8 = Some(0)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is applied to vehicles that are leaving a cargo vehicle's cargo hold to strafe left out of the cargo hold for vehicles that are mounted sideways e.g. router/BFR
|
||||
* Lock all applicable controls of the current vehicle
|
||||
* Set the vehicle to strafe left
|
||||
*/
|
||||
def ServerVehicleLockStrafeLeft(): Unit = {
|
||||
serverVehicleControlVelocity = Some(-1)
|
||||
sendResponse(
|
||||
ServerVehicleOverrideMsg(
|
||||
lock_accelerator = true,
|
||||
lock_wheel = true,
|
||||
reverse = false,
|
||||
unk4 = true,
|
||||
lock_vthrust = 0,
|
||||
lock_strafe = 2,
|
||||
movement_speed = 0,
|
||||
unk8 = Some(0)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Lock all applicable controls of the current vehicle.
|
||||
* This includes forward motion, turning, and, if applicable, strafing.
|
||||
* @param vehicle the vehicle being controlled
|
||||
*/
|
||||
def ServerVehicleLock(vehicle: Vehicle): Unit = {
|
||||
serverVehicleControlVelocity = Some(-1)
|
||||
sendResponse(ServerVehicleOverrideMsg(lock_accelerator=true, lock_wheel=true, reverse=false, unk4=false, 0, 1, 0, Some(0)))
|
||||
def ServerVehicleOverrideStop(vehicle: Vehicle): Unit = {
|
||||
val vehicleGuid = vehicle.GUID
|
||||
session = session.copy(avatar = avatar.copy(vehicle = Some(vehicleGuid)))
|
||||
sessionData.vehicles.DriverVehicleControl(vehicle, vehicle.Definition.AutoPilotSpeed2)
|
||||
sendResponse(PlanetsideAttributeMessage(vehicleGuid, attribute_type=22, attribute_value=0L)) //mount points on
|
||||
}
|
||||
|
||||
/**
|
||||
* Place the current vehicle under the control of the server's commands.
|
||||
* @param vehicle the vehicle
|
||||
* @param speed how fast the vehicle is moving forward
|
||||
* @param flight whether the vehicle is ascending or not, if the vehicle is an applicable type
|
||||
* @param vehicle vehicle to be controlled;
|
||||
* the client's player who is receiving this packet should be mounted as its driver, but this is not explicitly tested
|
||||
* @param pkt packet to instigate server control
|
||||
*/
|
||||
def ServerVehicleOverride(vehicle: Vehicle, speed: Int = 0, flight: Int = 0): Unit = {
|
||||
serverVehicleControlVelocity = Some(speed)
|
||||
sendResponse(ServerVehicleOverrideMsg(lock_accelerator=true, lock_wheel=true, reverse=false, unk4=false, flight, 0, speed, Some(0)))
|
||||
def ServerVehicleOverrideWithPacket(vehicle: Vehicle, pkt: ServerVehicleOverrideMsg): Unit = {
|
||||
serverVehicleControlVelocity = Some(pkt.movement_speed)
|
||||
vehicle.DeploymentState = DriveState.AutoPilot
|
||||
sendResponse(pkt)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -605,9 +562,20 @@ class VehicleOperations(
|
|||
* @param flight whether the vehicle is ascending or not, if the vehicle is an applicable type
|
||||
*/
|
||||
def DriverVehicleControl(vehicle: Vehicle, speed: Int = 0, flight: Int = 0): Unit = {
|
||||
if (serverVehicleControlVelocity.nonEmpty) {
|
||||
serverVehicleControlVelocity = None
|
||||
sendResponse(ServerVehicleOverrideMsg(lock_accelerator=false, lock_wheel=false, reverse=false, unk4=true, flight, 0, speed, None))
|
||||
if (vehicle.DeploymentState == DriveState.AutoPilot) {
|
||||
TotalDriverVehicleControlWithPacket(
|
||||
vehicle,
|
||||
ServerVehicleOverrideMsg(
|
||||
lock_accelerator=false,
|
||||
lock_wheel=false,
|
||||
reverse=false,
|
||||
unk4=true,
|
||||
lock_vthrust=flight,
|
||||
lock_strafe=0,
|
||||
movement_speed=speed,
|
||||
unk8=None
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -618,15 +586,45 @@ class VehicleOperations(
|
|||
* @param vehicle the vehicle
|
||||
*/
|
||||
def ConditionalDriverVehicleControl(vehicle: Vehicle): Unit = {
|
||||
if (serverVehicleControlVelocity.nonEmpty && !serverVehicleControlVelocity.contains(0)) {
|
||||
if (vehicle.DeploymentState == DriveState.AutoPilot) {
|
||||
TotalDriverVehicleControl(vehicle)
|
||||
}
|
||||
}
|
||||
|
||||
//noinspection ScalaUnusedSymbol
|
||||
/**
|
||||
* Place the current vehicle under the control of the driver's commands,
|
||||
* but leave it in a cancellable auto-drive.
|
||||
* Stop all movement entirely.
|
||||
* @param vehicle the vehicle
|
||||
*/
|
||||
def TotalDriverVehicleControl(vehicle: Vehicle): Unit = {
|
||||
TotalDriverVehicleControlWithPacket(
|
||||
vehicle,
|
||||
ServerVehicleOverrideMsg(
|
||||
lock_accelerator=false,
|
||||
lock_wheel=false,
|
||||
reverse=false,
|
||||
unk4=false,
|
||||
lock_vthrust=0,
|
||||
lock_strafe=0,
|
||||
movement_speed=0,
|
||||
unk8=None
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Place the current vehicle under the control of the driver's commands,
|
||||
* but leave it in a cancellable auto-drive.
|
||||
* Stop all movement entirely.
|
||||
* @param vehicle the vehicle;
|
||||
* the client's player who is receiving this packet should be mounted as its driver, but this is not explicitly tested
|
||||
* @param pkt packet to instigate cancellable control
|
||||
*/
|
||||
def TotalDriverVehicleControlWithPacket(vehicle: Vehicle, pkt: ServerVehicleOverrideMsg): Unit = {
|
||||
serverVehicleControlVelocity = None
|
||||
sendResponse(ServerVehicleOverrideMsg(lock_accelerator=false, lock_wheel=false, reverse=false, unk4=false, 0, 0, 0, None))
|
||||
vehicle.DeploymentState = DriveState.Mobile
|
||||
sendResponse(pkt)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -82,19 +82,16 @@ object Deployables {
|
|||
val zone = target.Zone
|
||||
val events = zone.LocalEvents
|
||||
val item = target.Definition.Item
|
||||
target.OwnerName match {
|
||||
case Some(owner) =>
|
||||
zone.Players.find { p => owner.equals(p.name) } match {
|
||||
case Some(p) =>
|
||||
if (p.deployables.Remove(target)) {
|
||||
events ! LocalServiceMessage(owner, LocalAction.DeployableUIFor(item))
|
||||
}
|
||||
case None => ;
|
||||
}
|
||||
target.OwnerName
|
||||
.foreach { owner =>
|
||||
zone.LivePlayers.find { p => owner.equals(p.Name) }
|
||||
.orElse(zone.Corpses.find { c => owner.equals(c.Name) })
|
||||
.foreach { p =>
|
||||
p.Actor ! Player.LoseDeployable(target)
|
||||
}
|
||||
target.Owner = None
|
||||
target.OwnerName = None
|
||||
case None => ;
|
||||
}
|
||||
}
|
||||
events ! LocalServiceMessage(
|
||||
s"${target.Faction}",
|
||||
LocalAction.DeployableMapIcon(
|
||||
|
|
@ -114,6 +111,7 @@ object Deployables {
|
|||
* @return all previously-owned deployables after they have been processed;
|
||||
* boomers are listed before all other deployable types
|
||||
*/
|
||||
//noinspection ScalaUnusedSymbol
|
||||
def Disown(zone: Zone, avatar: Avatar, replyTo: ActorRef): List[Deployable] = {
|
||||
avatar.deployables
|
||||
.Clear()
|
||||
|
|
|
|||
|
|
@ -5411,6 +5411,7 @@ object GlobalDefinitions {
|
|||
dynomite.Tile = InventoryTile.Tile22
|
||||
|
||||
trhev_dualcycler.Name = "trhev_dualcycler"
|
||||
trhev_dualcycler.Descriptor = "trhev_antipersonnel"
|
||||
trhev_dualcycler.Size = EquipmentSize.Max
|
||||
trhev_dualcycler.AmmoTypes += dualcycler_ammo
|
||||
trhev_dualcycler.ProjectileTypes += dualcycler_projectile
|
||||
|
|
@ -5428,6 +5429,7 @@ object GlobalDefinitions {
|
|||
trhev_dualcycler.FireModes(2).Magazine = 200
|
||||
|
||||
trhev_pounder.Name = "trhev_pounder"
|
||||
trhev_pounder.Descriptor = "trhev_antivehicular"
|
||||
trhev_pounder.Size = EquipmentSize.Max
|
||||
trhev_pounder.AmmoTypes += pounder_ammo
|
||||
trhev_pounder.ProjectileTypes += pounder_projectile
|
||||
|
|
@ -5461,6 +5463,7 @@ object GlobalDefinitions {
|
|||
trhev_pounder.FireModes(5).Magazine = 30
|
||||
|
||||
trhev_burster.Name = "trhev_burster"
|
||||
trhev_burster.Descriptor = "trhev_antiaircraft"
|
||||
trhev_burster.Size = EquipmentSize.Max
|
||||
trhev_burster.AmmoTypes += burster_ammo
|
||||
trhev_burster.ProjectileTypes += burster_projectile
|
||||
|
|
@ -5470,6 +5473,7 @@ object GlobalDefinitions {
|
|||
trhev_burster.FireModes.head.Magazine = 40
|
||||
|
||||
nchev_scattercannon.Name = "nchev_scattercannon"
|
||||
nchev_scattercannon.Descriptor = "nchev_antipersonnel"
|
||||
nchev_scattercannon.Size = EquipmentSize.Max
|
||||
nchev_scattercannon.AmmoTypes += scattercannon_ammo
|
||||
nchev_scattercannon.ProjectileTypes += scattercannon_projectile
|
||||
|
|
@ -5490,6 +5494,7 @@ object GlobalDefinitions {
|
|||
nchev_scattercannon.FireModes(2).Chamber = 10 //40 shells * 10 pellets = 400
|
||||
|
||||
nchev_falcon.Name = "nchev_falcon"
|
||||
nchev_falcon.Descriptor = "nchev_antivehicular"
|
||||
nchev_falcon.Size = EquipmentSize.Max
|
||||
nchev_falcon.AmmoTypes += falcon_ammo
|
||||
nchev_falcon.ProjectileTypes += falcon_projectile
|
||||
|
|
@ -5499,6 +5504,7 @@ object GlobalDefinitions {
|
|||
nchev_falcon.FireModes.head.Magazine = 20
|
||||
|
||||
nchev_sparrow.Name = "nchev_sparrow"
|
||||
nchev_sparrow.Descriptor = "nchev_antiaircraft"
|
||||
nchev_sparrow.Size = EquipmentSize.Max
|
||||
nchev_sparrow.AmmoTypes += sparrow_ammo
|
||||
nchev_sparrow.ProjectileTypes += sparrow_projectile
|
||||
|
|
@ -5508,6 +5514,7 @@ object GlobalDefinitions {
|
|||
nchev_sparrow.FireModes.head.Magazine = 12
|
||||
|
||||
vshev_quasar.Name = "vshev_quasar"
|
||||
vshev_quasar.Descriptor = "vshev_antipersonnel"
|
||||
vshev_quasar.Size = EquipmentSize.Max
|
||||
vshev_quasar.AmmoTypes += quasar_ammo
|
||||
vshev_quasar.ProjectileTypes += quasar_projectile
|
||||
|
|
@ -5523,6 +5530,7 @@ object GlobalDefinitions {
|
|||
vshev_quasar.FireModes(1).Magazine = 120
|
||||
|
||||
vshev_comet.Name = "vshev_comet"
|
||||
vshev_comet.Descriptor = "vshev_antivehicular"
|
||||
vshev_comet.Size = EquipmentSize.Max
|
||||
vshev_comet.AmmoTypes += comet_ammo
|
||||
vshev_comet.ProjectileTypes += comet_projectile
|
||||
|
|
@ -5532,6 +5540,7 @@ object GlobalDefinitions {
|
|||
vshev_comet.FireModes.head.Magazine = 10
|
||||
|
||||
vshev_starfire.Name = "vshev_starfire"
|
||||
vshev_starfire.Descriptor = "vshev_antiaircraft"
|
||||
vshev_starfire.Size = EquipmentSize.Max
|
||||
vshev_starfire.AmmoTypes += starfire_ammo
|
||||
vshev_starfire.ProjectileTypes += starfire_projectile
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ import net.psforever.objects.sourcing.PlayerSource
|
|||
import net.psforever.objects.vital.collision.CollisionReason
|
||||
import net.psforever.objects.vital.environment.EnvironmentReason
|
||||
import net.psforever.objects.vital.etc.{PainboxReason, SuicideReason}
|
||||
import net.psforever.objects.vital.interaction.{DamageInteraction, DamageResult}
|
||||
import net.psforever.objects.vital.interaction.{Adversarial, DamageInteraction, DamageResult}
|
||||
import net.psforever.services.hart.ShuttleState
|
||||
import net.psforever.packet.PlanetSideGamePacket
|
||||
|
||||
|
|
@ -368,8 +368,6 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
|
|||
|
||||
case Terminal.InfantryLoadout(exosuit, subtype, holsters, inventory) =>
|
||||
log.info(s"${player.Name} wants to change equipment loadout to their option #${msg.unk1 + 1}")
|
||||
val fallbackSubtype = 0
|
||||
val fallbackSuit = ExoSuitType.Standard
|
||||
val originalSuit = player.ExoSuit
|
||||
val originalSubtype = Loadout.DetermineSubtype(player)
|
||||
//sanitize exo-suit for change
|
||||
|
|
@ -392,29 +390,39 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
|
|||
}) ++ dropHolsters ++ dropInventory
|
||||
//a loadout with a prohibited exo-suit type will result in the fallback exo-suit type
|
||||
//imposed 5min delay on mechanized exo-suit switches
|
||||
val (nextSuit, nextSubtype) =
|
||||
if (
|
||||
Players.CertificationToUseExoSuit(player, exosuit, subtype) &&
|
||||
(if (exosuit == ExoSuitType.MAX) {
|
||||
player.ResistArmMotion(PlayerControl.maxRestriction)
|
||||
val weapon = GlobalDefinitions.MAXArms(subtype, player.Faction)
|
||||
player.avatar.purchaseCooldown(weapon) match {
|
||||
case Some(_) => false
|
||||
case None =>
|
||||
val (nextSuit, nextSubtype) = {
|
||||
lazy val fallbackSuit = if (Players.CertificationToUseExoSuit(player, originalSuit, originalSubtype)) {
|
||||
//TODO will we ever need to check for the cooldown status of an original non-MAX exo-suit?
|
||||
(originalSuit, originalSubtype)
|
||||
} else {
|
||||
(ExoSuitType.Standard, 0)
|
||||
}
|
||||
if (Players.CertificationToUseExoSuit(player, exosuit, subtype)) {
|
||||
if (exosuit == ExoSuitType.MAX) {
|
||||
val weapon = GlobalDefinitions.MAXArms(subtype, player.Faction)
|
||||
val cooldown = player.avatar.purchaseCooldown(weapon)
|
||||
if (originalSubtype == subtype) {
|
||||
(exosuit, subtype) //same MAX subtype is free
|
||||
} else if (cooldown.nonEmpty) {
|
||||
fallbackSuit //different MAX subtype can not have cooldown
|
||||
} else {
|
||||
avatarActor ! AvatarActor.UpdatePurchaseTime(weapon)
|
||||
true
|
||||
(exosuit, subtype) //switching for first time causes cooldown
|
||||
}
|
||||
} else {
|
||||
(exosuit, subtype)
|
||||
}
|
||||
} else {
|
||||
player.ResistArmMotion(Player.neverRestrict)
|
||||
true
|
||||
})
|
||||
) {
|
||||
(exosuit, subtype)
|
||||
log.warn(
|
||||
s"${player.Name} no longer has permission to wear the exo-suit type $exosuit; will wear ${fallbackSuit._1} instead"
|
||||
)
|
||||
fallbackSuit
|
||||
}
|
||||
}
|
||||
if (nextSuit == ExoSuitType.MAX) {
|
||||
player.ResistArmMotion(PlayerControl.maxRestriction)
|
||||
} else {
|
||||
log.warn(
|
||||
s"${player.Name} no longer has permission to wear the exo-suit type $exosuit; will wear $fallbackSuit instead"
|
||||
)
|
||||
(fallbackSuit, fallbackSubtype)
|
||||
player.ResistArmMotion(Player.neverRestrict)
|
||||
}
|
||||
//sanitize (incoming) inventory
|
||||
//TODO equipment permissions; these loops may be expanded upon in future
|
||||
|
|
@ -802,7 +810,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
|
|||
//choose
|
||||
if (target.Health > 0) {
|
||||
//alive
|
||||
if (target.Health <= 25 && target.Health + damageToHealth > 25 &&
|
||||
if (target.Health <= 25 &&
|
||||
(player.avatar.implants.flatten.find { _.definition.implantType == ImplantType.SecondWind } match {
|
||||
case Some(wind) => wind.initialized
|
||||
case _ => false
|
||||
|
|
@ -810,8 +818,8 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
|
|||
//activate second wind
|
||||
player.Health += 25
|
||||
player.LogActivity(HealFromImplant(ImplantType.SecondWind, 25))
|
||||
avatarActor ! AvatarActor.ResetImplant(ImplantType.SecondWind)
|
||||
avatarActor ! AvatarActor.RestoreStamina(25)
|
||||
avatarActor ! AvatarActor.ResetImplant(ImplantType.SecondWind)
|
||||
}
|
||||
//take damage/update
|
||||
DamageAwareness(target, cause, damageToHealth, damageToArmor, damageToStamina, damageToCapacitor)
|
||||
|
|
@ -1025,21 +1033,23 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
|
|||
)
|
||||
//TODO other methods of death?
|
||||
val pentry = PlayerSource(target)
|
||||
(cause.adversarial match {
|
||||
case out @ Some(_) =>
|
||||
out
|
||||
case _ =>
|
||||
cause
|
||||
.adversarial
|
||||
.collect { case out @ Adversarial(attacker, _, _) if attacker != PlayerSource.Nobody => out }
|
||||
.orElse {
|
||||
target.LastDamage.collect {
|
||||
case attack if System.currentTimeMillis() - attack.interaction.hitTime < (10 seconds).toMillis =>
|
||||
attack.adversarial
|
||||
attack
|
||||
.adversarial
|
||||
.collect { case out @ Adversarial(attacker, _, _) if attacker != PlayerSource.Nobody => out }
|
||||
}.flatten
|
||||
}) match {
|
||||
} match {
|
||||
case Some(adversarial) =>
|
||||
events ! AvatarServiceMessage(
|
||||
zoneChannel,
|
||||
AvatarAction.DestroyDisplay(adversarial.attacker, pentry, adversarial.implement)
|
||||
)
|
||||
case None =>
|
||||
case _ =>
|
||||
events ! AvatarServiceMessage(zoneChannel, AvatarAction.DestroyDisplay(pentry, pentry, 0))
|
||||
}
|
||||
}
|
||||
|
|
@ -1288,6 +1298,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
|
|||
suicide()
|
||||
}
|
||||
|
||||
//noinspection ScalaUnusedSymbol
|
||||
def doInteractingWithGantryField(
|
||||
obj: PlanetSideServerObject,
|
||||
body: PieceOfEnvironment,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) 2018 PSForever
|
||||
package net.psforever.objects.ce
|
||||
|
||||
import akka.actor.{ActorContext, Cancellable}
|
||||
import akka.actor.ActorContext
|
||||
import net.psforever.objects.{Default, TelepadDeployable, Vehicle}
|
||||
import net.psforever.objects.serverobject.PlanetSideServerObject
|
||||
import net.psforever.objects.serverobject.structures.Amenity
|
||||
|
|
@ -147,6 +147,12 @@ object TelepadLike {
|
|||
LocalAction.SendResponse(GenericObjectActionMessage(telepadGUID, 28))
|
||||
)
|
||||
}
|
||||
|
||||
def InitializeTelepadDeployable(zone: Zone, internal: InternalTelepad, pad: TelepadDeployable): Unit = {
|
||||
internal.Telepad = pad.GUID
|
||||
TelepadLike.StartRouterInternalTelepad(zone, internal.Owner.GUID, internal)
|
||||
pad.Actor ! TelepadLike.Activate(internal)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -157,8 +163,6 @@ object TelepadLike {
|
|||
* @param obj an entity that extends `TelepadLike`
|
||||
*/
|
||||
class TelepadControl(obj: InternalTelepad) extends akka.actor.Actor {
|
||||
var setup: Cancellable = Default.Cancellable
|
||||
|
||||
def receive: akka.actor.Actor.Receive = {
|
||||
case TelepadLike.Activate(o: InternalTelepad) if obj eq o =>
|
||||
obj.Active = true
|
||||
|
|
@ -166,11 +170,9 @@ class TelepadControl(obj: InternalTelepad) extends akka.actor.Actor {
|
|||
case TelepadLike.Deactivate(o: InternalTelepad) if obj eq o =>
|
||||
obj.Active = false
|
||||
val zone = obj.Zone
|
||||
zone.GUID(obj.Telepad) match {
|
||||
case Some(oldTpad: TelepadDeployable)
|
||||
if !obj.Active && !setup.isCancelled =>
|
||||
zone.GUID(obj.Telepad).collect {
|
||||
case oldTpad: TelepadDeployable if oldTpad.Active =>
|
||||
oldTpad.Actor ! TelepadLike.SeverLink(obj)
|
||||
case _ => ;
|
||||
}
|
||||
obj.Telepad = None
|
||||
zone.LocalEvents ! LocalServiceMessage(zone.id, LocalAction.SendResponse(ObjectDeleteMessage(obj.GUID, 0)))
|
||||
|
|
@ -178,15 +180,20 @@ class TelepadControl(obj: InternalTelepad) extends akka.actor.Actor {
|
|||
case TelepadLike.RequestLink(tpad: TelepadDeployable) =>
|
||||
val zone = obj.Zone
|
||||
if (obj.Active) {
|
||||
zone.GUID(obj.Telepad) match {
|
||||
case Some(oldTpad: TelepadDeployable) if !obj.Active && !setup.isCancelled =>
|
||||
oldTpad.Actor ! TelepadLike.SeverLink(obj)
|
||||
case _ => ;
|
||||
}
|
||||
obj.Telepad = tpad.GUID
|
||||
//zone.LocalEvents ! LocalServiceMessage(zone.id, LocalAction.StartRouterInternalTelepad(obj.Owner.GUID, obj.GUID, obj))
|
||||
TelepadLike.StartRouterInternalTelepad(zone, obj.Owner.GUID, obj)
|
||||
tpad.Actor ! TelepadLike.Activate(obj)
|
||||
zone
|
||||
.GUID(obj.Telepad)
|
||||
.collect {
|
||||
case oldTpad: TelepadDeployable if oldTpad eq tpad =>
|
||||
Some(oldTpad)
|
||||
case oldTpad: TelepadDeployable if oldTpad.Active =>
|
||||
oldTpad.Actor ! TelepadLike.SeverLink(obj)
|
||||
TelepadLike.InitializeTelepadDeployable(zone, obj, tpad)
|
||||
Some(oldTpad)
|
||||
}
|
||||
.orElse {
|
||||
TelepadLike.InitializeTelepadDeployable(zone, obj, tpad)
|
||||
None
|
||||
}
|
||||
} else {
|
||||
val channel = obj.Owner.asInstanceOf[Vehicle].OwnerName.getOrElse("")
|
||||
zone.LocalEvents ! LocalServiceMessage(channel, LocalAction.RouterTelepadMessage("@Teleport_NotDeployed"))
|
||||
|
|
@ -200,6 +207,6 @@ class TelepadControl(obj: InternalTelepad) extends akka.actor.Actor {
|
|||
zone.LocalEvents ! LocalServiceMessage(zone.id, LocalAction.SendResponse(ObjectDeleteMessage(obj.GUID, 0)))
|
||||
}
|
||||
|
||||
case _ => ;
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ class VehicleConverter extends ObjectCreateConverter[Vehicle]() {
|
|||
health,
|
||||
unk4 = false,
|
||||
no_mount_points = false,
|
||||
obj.DeploymentState,
|
||||
SterilizedDeploymentState(obj),
|
||||
unk5 = false,
|
||||
unk6 = false,
|
||||
obj.Cloaked,
|
||||
|
|
@ -105,6 +105,13 @@ class VehicleConverter extends ObjectCreateConverter[Vehicle]() {
|
|||
.toList
|
||||
}
|
||||
|
||||
private def SterilizedDeploymentState(obj: Vehicle): DriveState.Value = {
|
||||
obj.DeploymentState match {
|
||||
case state if state.id < 0 => DriveState.Mobile
|
||||
case state => state
|
||||
}
|
||||
}
|
||||
|
||||
protected def SpecificFormatModifier: VehicleFormat.Value = VehicleFormat.Normal
|
||||
|
||||
protected def SpecificFormatData(obj: Vehicle): Option[SpecificVehicleData] = None
|
||||
|
|
|
|||
|
|
@ -59,7 +59,9 @@ object AmenitySource {
|
|||
)
|
||||
amenity.copy(occupants = obj match {
|
||||
case o: Mountable =>
|
||||
o.Seats.values.flatMap { _.occupants }.map { p => PlayerSource.inSeat(p, o, amenity) }.toList
|
||||
o.Seats
|
||||
.collect { case (num, seat) if seat.isOccupied => (num, seat.occupants.head) }
|
||||
.map { case (num, p) => PlayerSource.inSeat(p, amenity, num) }.toList
|
||||
case _ =>
|
||||
Nil
|
||||
})
|
||||
|
|
|
|||
|
|
@ -109,17 +109,17 @@ object PlayerSource {
|
|||
* even if this function is entirely for the purpose of establishing that the player is an occupant of the mountable entity.<br>
|
||||
* Don't think too much about it.
|
||||
* @param player player
|
||||
* @param mount mountable entity in which the player should be seated
|
||||
* @param source a `SourceEntry` for the aforementioned mountable entity
|
||||
* @param seatNumber the attributed seating index in which the player is mounted in `source`
|
||||
* @return a `PlayerSource` entity
|
||||
*/
|
||||
def inSeat(player: Player, mount: Mountable, source: SourceEntry): PlayerSource = {
|
||||
def inSeat(player: Player, source: SourceEntry, seatNumber: Int): PlayerSource = {
|
||||
val exosuit = player.ExoSuit
|
||||
val faction = player.Faction
|
||||
PlayerSource(
|
||||
player.Definition,
|
||||
exosuit,
|
||||
Some((source, mount.PassengerInSeat(player).get)),
|
||||
Some((source, seatNumber)),
|
||||
player.Health,
|
||||
player.Armor,
|
||||
player.Position,
|
||||
|
|
|
|||
|
|
@ -57,7 +57,9 @@ object TurretSource {
|
|||
)
|
||||
turret.copy(occupants = obj match {
|
||||
case o: Mountable =>
|
||||
o.Seats.values.flatMap { _.occupants }.map { p => PlayerSource.inSeat(p, o, turret) }.toList
|
||||
o.Seats
|
||||
.collect { case (num, seat) if seat.isOccupied => (num, seat.occupants.head) }
|
||||
.map { case (num, p) => PlayerSource.inSeat(p, turret, num) }.toList
|
||||
case _ =>
|
||||
Nil
|
||||
})
|
||||
|
|
|
|||
|
|
@ -53,9 +53,9 @@ object VehicleSource {
|
|||
)
|
||||
)
|
||||
vehicle.copy(occupants = {
|
||||
obj.Seats.values.map { seat =>
|
||||
obj.Seats.map { case (seatNumber, seat) =>
|
||||
seat.occupant match {
|
||||
case Some(p) => PlayerSource.inSeat(p, obj, vehicle) //shallow
|
||||
case Some(p) => PlayerSource.inSeat(p, vehicle, seatNumber) //shallow
|
||||
case _ => PlayerSource.Nobody
|
||||
}
|
||||
}.toList
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ trait AntTransferBehavior extends TransferBehavior with NtuStorageBehavior {
|
|||
|
||||
def UpdateNtuUI(vehicle: Vehicle with NtuContainer): Unit = {
|
||||
if (vehicle.Seats.values.exists(_.isOccupied)) {
|
||||
val display = scala.math.ceil(vehicle.NtuCapacitorScaled).toLong
|
||||
val display = vehicle.NtuCapacitorScaled.toLong
|
||||
vehicle.Zone.VehicleEvents ! VehicleServiceMessage(
|
||||
vehicle.Actor.toString,
|
||||
VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, vehicle.GUID, 45, display)
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import scala.concurrent.duration._
|
|||
class AntControl(vehicle: Vehicle)
|
||||
extends DeployingVehicleControl(vehicle)
|
||||
with AntTransferBehavior {
|
||||
def ChargeTransferObject = vehicle
|
||||
def ChargeTransferObject: Vehicle = vehicle
|
||||
|
||||
override def commonEnabledBehavior: Receive = super.commonEnabledBehavior.orElse(antBehavior)
|
||||
|
||||
|
|
@ -38,7 +38,7 @@ class AntControl(vehicle: Vehicle)
|
|||
vehicle.Actor,
|
||||
TransferBehavior.Charging(Ntu.Nanites)
|
||||
)
|
||||
case _ => ;
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -51,7 +51,7 @@ class AntControl(vehicle: Vehicle)
|
|||
state match {
|
||||
case DriveState.Undeploying =>
|
||||
TryStopChargingEvent(vehicle)
|
||||
case _ => ;
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import net.psforever.types._
|
|||
class DeployingVehicleControl(vehicle: Vehicle)
|
||||
extends VehicleControl(vehicle)
|
||||
with DeploymentBehavior {
|
||||
def DeploymentObject = vehicle
|
||||
def DeploymentObject: Vehicle = vehicle
|
||||
|
||||
override def commonEnabledBehavior : Receive = super.commonEnabledBehavior.orElse(deployBehavior)
|
||||
|
||||
|
|
@ -45,7 +45,7 @@ class DeployingVehicleControl(vehicle: Vehicle)
|
|||
override def commonDeleteBehavior : Receive =
|
||||
super.commonDeleteBehavior
|
||||
.orElse {
|
||||
case msg : Deployment.TryUndeploy =>
|
||||
case msg: Deployment.TryUndeploy =>
|
||||
deployBehavior.apply(msg)
|
||||
}
|
||||
|
||||
|
|
@ -53,7 +53,7 @@ class DeployingVehicleControl(vehicle: Vehicle)
|
|||
* Even when disabled, the vehicle can be made to undeploy.
|
||||
*/
|
||||
override def PrepareForDisabled(kickPassengers: Boolean) : Unit = {
|
||||
vehicle.Actor ! Deployment.TryUndeploy(DriveState.Undeploying)
|
||||
TryUndeployStateChange(DriveState.Undeploying)
|
||||
super.PrepareForDisabled(kickPassengers)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,11 +24,10 @@ class RouterControl(vehicle: Vehicle)
|
|||
override def specificResponseToDeployment(state: DriveState.Value): Unit = {
|
||||
state match {
|
||||
case DriveState.Deployed =>
|
||||
vehicle.Utility(UtilityType.internal_router_telepad_deployable) match {
|
||||
case Some(util: Utility.InternalTelepad) => util.Actor ! TelepadLike.Activate(util)
|
||||
case _ => ;
|
||||
vehicle.Utility(UtilityType.internal_router_telepad_deployable).collect {
|
||||
case util: Utility.InternalTelepad => util.Actor ! TelepadLike.Activate(util)
|
||||
}
|
||||
case _ => ;
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -40,11 +39,10 @@ class RouterControl(vehicle: Vehicle)
|
|||
override def specificResponseToUndeployment(state: DriveState.Value): Unit = {
|
||||
state match {
|
||||
case DriveState.Undeploying =>
|
||||
vehicle.Utility(UtilityType.internal_router_telepad_deployable) match {
|
||||
case Some(util: Utility.InternalTelepad) => util.Actor ! TelepadLike.Deactivate(util)
|
||||
case _ => ;
|
||||
vehicle.Utility(UtilityType.internal_router_telepad_deployable).collect {
|
||||
case util: Utility.InternalTelepad => util.Actor ! TelepadLike.Deactivate(util)
|
||||
}
|
||||
case _ => ;
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import net.psforever.objects.inventory.{GridInventory, InventoryItem}
|
|||
import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject, ServerObjectControl}
|
||||
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
|
||||
import net.psforever.objects.serverobject.containable.{Containable, ContainableBehavior}
|
||||
import net.psforever.objects.serverobject.damage.Damageable.Target
|
||||
import net.psforever.objects.serverobject.damage.{AggravatedBehavior, DamageableVehicle}
|
||||
import net.psforever.objects.serverobject.environment._
|
||||
import net.psforever.objects.serverobject.hackable.GenericHackables
|
||||
|
|
@ -120,6 +121,9 @@ class VehicleControl(vehicle: Vehicle)
|
|||
case Vehicle.Ownership(Some(player)) =>
|
||||
GainOwnership(player)
|
||||
|
||||
case Mountable.TryMount(user, mountPoint) if vehicle.DeploymentState == DriveState.AutoPilot =>
|
||||
sender() ! Mountable.MountMessages(user, Mountable.CanNotMount(vehicle, mountPoint))
|
||||
|
||||
case msg @ Mountable.TryMount(player, mount_point) =>
|
||||
mountBehavior.apply(msg)
|
||||
mountCleanup(mount_point, player)
|
||||
|
|
@ -864,10 +868,15 @@ class VehicleControl(vehicle: Vehicle)
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
override protected def DestructionAwareness(target: Target, cause: DamageResult): Unit = {
|
||||
passengerRadiationCloudTimer.cancel()
|
||||
super.DestructionAwareness(target, cause)
|
||||
}
|
||||
}
|
||||
|
||||
object VehicleControl {
|
||||
import net.psforever.objects.vital.{ShieldCharge}
|
||||
import net.psforever.objects.vital.ShieldCharge
|
||||
|
||||
private case class PrepareForDeletion()
|
||||
|
||||
|
|
|
|||
|
|
@ -31,7 +31,8 @@ import scodec.codecs._
|
|||
* For flight vehicles, for `n`, the forward air speed for the value in this packet will be at least `1.18 * n`.
|
||||
* This approximation is not always going to be accurate but serves as a good rule of thumb.
|
||||
* @param lock_accelerator driver has no control over vehicle acceleration
|
||||
* @param lock_wheel driver has no control over vehicle turning
|
||||
* @param lock_wheel driver has no control over vehicle turning;
|
||||
* generally, the driver never has turning control
|
||||
* @param reverse move in reverse
|
||||
* 0 = forward
|
||||
* 1 = reverse
|
||||
|
|
|
|||
|
|
@ -21,5 +21,7 @@ object DriveState extends Enumeration {
|
|||
val State7 = Value(7) //unknown; not encountered on a vehicle that can deploy; functions like Mobile
|
||||
val State127 = Value(127) //unknown
|
||||
|
||||
val Kneeling = Value(-1) //flag bfr kneeling state; should not not encode
|
||||
//the following values should never be encoded
|
||||
val Kneeling = Value(-1) //flag bfr kneeling state
|
||||
val AutoPilot = Value(-2) //when emerging from spawn pad, or being kicked from a ferry, during server guidance
|
||||
}
|
||||
|
|
|
|||
|
|
@ -154,6 +154,7 @@ case class GameConfig(
|
|||
newAvatar: NewAvatar,
|
||||
hart: HartConfig,
|
||||
sharedMaxCooldown: Boolean,
|
||||
sharedBfrCooldown: Boolean,
|
||||
baseCertifications: Seq[Certification],
|
||||
warpGates: WarpGateConfig,
|
||||
cavernRotation: CavernRotationConfig,
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ import org.specs2.mutable.Specification
|
|||
import scala.concurrent.duration._
|
||||
import net.psforever.objects.avatar.Avatar
|
||||
import net.psforever.objects.serverobject.terminals.implant.{ImplantTerminalMech, ImplantTerminalMechControl}
|
||||
import net.psforever.objects.sourcing.{PlayerSource, SourceEntry, VehicleSource}
|
||||
import net.psforever.objects.sourcing.{PlayerSource, SourceEntry}
|
||||
import net.psforever.objects.vital.interaction.DamageInteraction
|
||||
import net.psforever.objects.vital.base.DamageResolution
|
||||
import net.psforever.objects.vital.projectile.ProjectileReason
|
||||
|
|
@ -327,21 +327,17 @@ class DamageableEntityDamageTest extends ActorTest {
|
|||
gen.Actor ! Vitality.Damage(applyDamageTo)
|
||||
val msg1 = avatarProbe.receiveOne(500 milliseconds)
|
||||
val msg2 = activityProbe.receiveOne(500 milliseconds)
|
||||
assert(
|
||||
msg1 match {
|
||||
case AvatarServiceMessage("test", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 0, _)) => true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
assert(
|
||||
msg2 match {
|
||||
case activity: Zone.HotSpot.Activity =>
|
||||
activity.attacker == PlayerSource(player1) &&
|
||||
activity.defender == SourceEntry(gen) &&
|
||||
activity.location == Vector3(1, 0, 0)
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
msg1 match {
|
||||
case AvatarServiceMessage("test", AvatarAction.PlanetsideAttributeToAll(ValidPlanetSideGUID(2), 0, 3600)) => ()
|
||||
case _ => assert(false, "DamageableEntity:handle taking damage - player not messaged")
|
||||
}
|
||||
msg2 match {
|
||||
case activity: Zone.HotSpot.Activity
|
||||
if activity.attacker == PlayerSource(player1) &&
|
||||
activity.defender == SourceEntry(gen) &&
|
||||
activity.location == Vector3(1, 0, 0) => ()
|
||||
case _ => assert(false, "DamageableEntity:handle taking damage - activity not messaged")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -320,15 +320,12 @@ class DeployableBehaviorDeconstructOwnedTest extends FreedContextActorTest {
|
|||
jmine.Actor ! Deployable.Deconstruct()
|
||||
val eventsMsgs = eventsProbe.receiveN(3, 10.seconds)
|
||||
eventsMsgs.head match {
|
||||
case LocalServiceMessage("test", LocalAction.EliminateDeployable(`jmine`, PlanetSideGUID(1), Vector3(1,2,3), 2)) => ;
|
||||
case LocalServiceMessage("test",LocalAction.EliminateDeployable(mine, ValidPlanetSideGUID(1), Vector3(1.0,2.0,3.0),2))
|
||||
if mine eq jmine => ;
|
||||
case _ => assert(false, "owned deconstruct test - not eliminating deployable")
|
||||
}
|
||||
eventsMsgs(1) match {
|
||||
case LocalServiceMessage("TestCharacter1", LocalAction.DeployableUIFor(DeployedItem.jammer_mine)) => ;
|
||||
case _ => assert(false, "")
|
||||
}
|
||||
eventsMsgs(2) match {
|
||||
case LocalServiceMessage(
|
||||
case LocalServiceMessage(
|
||||
"TR",
|
||||
LocalAction.DeployableMapIcon(
|
||||
PlanetSideGUID(0),
|
||||
|
|
@ -338,6 +335,10 @@ class DeployableBehaviorDeconstructOwnedTest extends FreedContextActorTest {
|
|||
) => ;
|
||||
case _ => assert(false, "owned deconstruct test - not removing icon")
|
||||
}
|
||||
eventsMsgs(2) match {
|
||||
case LocalServiceMessage("TestCharacter1", LocalAction.DeployableUIFor(DeployedItem.jammer_mine)) => ;
|
||||
case _ => assert(false, "")
|
||||
}
|
||||
|
||||
assert(deployableList.isEmpty, "owned deconstruct test - deployable still in list")
|
||||
assert(!avatar.deployables.Contains(jmine), "owned deconstruct test - avatar still owns deployable")
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import net.psforever.objects.vital.interaction.DamageInteraction
|
|||
import net.psforever.objects.vital.projectile.ProjectileReason
|
||||
import akka.actor.typed.scaladsl.adapter._
|
||||
import net.psforever.objects.sourcing.{PlayerSource, SourceEntry}
|
||||
import net.psforever.services.local.LocalAction.DeployableMapIcon
|
||||
|
||||
import scala.collection.mutable
|
||||
import scala.concurrent.duration._
|
||||
|
|
@ -447,9 +448,9 @@ class ExplosiveDeployableJammerExplodeTest extends ActorTest {
|
|||
assert(!h_mine.Destroyed)
|
||||
|
||||
h_mine.Actor ! Vitality.Damage(applyDamageToH)
|
||||
val eventMsgs = eventsProbe.receiveN(5, 200 milliseconds)
|
||||
val eventMsgs = eventsProbe.receiveN(4, 200 milliseconds)
|
||||
val p1Msgs = player1Probe.receiveN(1, 200 milliseconds)
|
||||
player2Probe.expectNoMessage(200 milliseconds)
|
||||
val p2Msgs = player2Probe.receiveN(1, 200 milliseconds)
|
||||
eventMsgs.head match {
|
||||
case Zone.HotSpot.Conflict(target, attacker, _)
|
||||
if (target.Definition eq h_mine.Definition) && (attacker eq pSource) => ;
|
||||
|
|
@ -461,10 +462,6 @@ class ExplosiveDeployableJammerExplodeTest extends ActorTest {
|
|||
case _ => assert(false, "")
|
||||
}
|
||||
eventMsgs(2) match {
|
||||
case LocalServiceMessage("TestCharacter2", LocalAction.DeployableUIFor(DeployedItem.he_mine)) => ;
|
||||
case _ => assert(false, "")
|
||||
}
|
||||
eventMsgs(3) match {
|
||||
case LocalServiceMessage(
|
||||
"NC",
|
||||
LocalAction.DeployableMapIcon(
|
||||
|
|
@ -475,7 +472,7 @@ class ExplosiveDeployableJammerExplodeTest extends ActorTest {
|
|||
) => ;
|
||||
case _ => assert(false, "")
|
||||
}
|
||||
eventMsgs(4) match {
|
||||
eventMsgs(3) match {
|
||||
case AvatarServiceMessage(
|
||||
"test",
|
||||
AvatarAction.Destroy(PlanetSideGUID(2), PlanetSideGUID(3), Service.defaultPlayerGUID, Vector3.Zero)
|
||||
|
|
@ -486,7 +483,10 @@ class ExplosiveDeployableJammerExplodeTest extends ActorTest {
|
|||
case Vitality.Damage(_) => ;
|
||||
case _ => assert(false, "")
|
||||
}
|
||||
assert(!avatar2.deployables.Contains(h_mine))
|
||||
p2Msgs.head match {
|
||||
case Player.LoseDeployable(_) => ;
|
||||
case _ => assert(false, "")
|
||||
}
|
||||
assert(h_mine.Destroyed)
|
||||
}
|
||||
}
|
||||
|
|
@ -558,14 +558,10 @@ class ExplosiveDeployableDestructionTest extends ActorTest {
|
|||
assert(!h_mine.Destroyed)
|
||||
|
||||
h_mine.Actor ! Vitality.Damage(applyDamageTo)
|
||||
val eventMsgs = eventsProbe.receiveN(4, 200 milliseconds)
|
||||
val eventMsgs = eventsProbe.receiveN(3, 200 milliseconds)
|
||||
player1Probe.expectNoMessage(200 milliseconds)
|
||||
player2Probe.expectNoMessage(200 milliseconds)
|
||||
val p2Msgs = player2Probe.receiveN(1, 200 milliseconds)
|
||||
eventMsgs.head match {
|
||||
case LocalServiceMessage("TestCharacter2", LocalAction.DeployableUIFor(DeployedItem.he_mine)) => ;
|
||||
case _ => assert(false, "")
|
||||
}
|
||||
eventMsgs(1) match {
|
||||
case LocalServiceMessage(
|
||||
"NC",
|
||||
LocalAction.DeployableMapIcon(
|
||||
|
|
@ -576,17 +572,21 @@ class ExplosiveDeployableDestructionTest extends ActorTest {
|
|||
) => ;
|
||||
case _ => assert(false, "")
|
||||
}
|
||||
eventMsgs(2) match {
|
||||
eventMsgs(1) match {
|
||||
case AvatarServiceMessage(
|
||||
"test",
|
||||
AvatarAction.Destroy(PlanetSideGUID(2), PlanetSideGUID(3), Service.defaultPlayerGUID, Vector3.Zero)
|
||||
"test",
|
||||
AvatarAction.Destroy(PlanetSideGUID(2), PlanetSideGUID(3), Service.defaultPlayerGUID, Vector3.Zero)
|
||||
) => ;
|
||||
case _ => assert(false, "")
|
||||
}
|
||||
eventMsgs(3) match {
|
||||
eventMsgs(2) match {
|
||||
case LocalServiceMessage("test", LocalAction.TriggerEffect(_, "detonate_damaged_mine", PlanetSideGUID(2))) => ;
|
||||
case _ => assert(false, "")
|
||||
}
|
||||
p2Msgs.head match {
|
||||
case Player.LoseDeployable(_) => ;
|
||||
case _ => assert(false, "")
|
||||
}
|
||||
assert(h_mine.Health <= h_mine.Definition.DamageDestroysAt)
|
||||
assert(h_mine.Destroyed)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue