diff --git a/common/src/main/scala/net/psforever/objects/Avatar.scala b/common/src/main/scala/net/psforever/objects/Avatar.scala
index 451d85ffd..95a43ae7b 100644
--- a/common/src/main/scala/net/psforever/objects/Avatar.scala
+++ b/common/src/main/scala/net/psforever/objects/Avatar.scala
@@ -202,7 +202,9 @@ class Avatar(private val char_id : Long, val name : String, val faction : Planet
implants.foreach(slot => {
slot.Installed match {
case Some(_) =>
+ slot.Active = false
slot.Initialized = false
+ slot.InitializeTime = 0L
case None => ;
}
})
@@ -302,7 +304,7 @@ class Avatar(private val char_id : Long, val name : String, val faction : Planet
def ObjectTypeNameReference(id : Long) : String = {
objectTypeNameReference.get(id) match {
- case Some(name) => name
+ case Some(objectName) => objectName
case None => ""
}
}
diff --git a/common/src/main/scala/net/psforever/objects/ImplantSlot.scala b/common/src/main/scala/net/psforever/objects/ImplantSlot.scala
index 5cc624ea1..c77cf2309 100644
--- a/common/src/main/scala/net/psforever/objects/ImplantSlot.scala
+++ b/common/src/main/scala/net/psforever/objects/ImplantSlot.scala
@@ -1,7 +1,6 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects
-import akka.actor.Cancellable
import net.psforever.objects.definition.ImplantDefinition
import net.psforever.types.{ExoSuitType, ImplantType}
@@ -19,19 +18,18 @@ class ImplantSlot {
private var unlocked : Boolean = false
/** whether this implant is ready for use */
private var initialized : Boolean = false
- /** a cancellable timer that can be used to set an implant as initialized once complete */
- private var initializeTimer: Cancellable = Default.Cancellable
-
+ /** */
+ 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 InitializeTimer : Cancellable = initializeTimer
+ def InitializeTime : Long = initializeTime
- def InitializeTimer_=(timer : Cancellable) : Cancellable = {
- initializeTimer = timer
- initializeTimer
+ def InitializeTime_=(time : Long) : Long = {
+ initializeTime = time
+ InitializeTime
}
def Unlocked : Boolean = unlocked
diff --git a/common/src/main/scala/net/psforever/objects/Player.scala b/common/src/main/scala/net/psforever/objects/Player.scala
index 7da2c79d7..b29b19257 100644
--- a/common/src/main/scala/net/psforever/objects/Player.scala
+++ b/common/src/main/scala/net/psforever/objects/Player.scala
@@ -1,9 +1,8 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects
-import akka.actor.ActorRef
import net.psforever.objects.avatar.LoadoutManager
-import net.psforever.objects.definition.{AvatarDefinition, ExoSuitDefinition, SpecialExoSuitDefinition}
+import net.psforever.objects.definition.{AvatarDefinition, ExoSuitDefinition, ImplantDefinition, SpecialExoSuitDefinition}
import net.psforever.objects.equipment.{Equipment, EquipmentSize, EquipmentSlot, JammableUnit}
import net.psforever.objects.inventory.{Container, GridInventory, InventoryItem}
import net.psforever.objects.serverobject.PlanetSideServerObject
@@ -124,11 +123,6 @@ class Player(private val core : Avatar) extends PlanetSideServerObject
def Stamina_=(assignStamina : Int) : Int = {
stamina = if(isAlive) { math.min(math.max(0, assignStamina), MaxStamina) } else { 0 }
-
- if(Actor != Default.Actor) {
- Actor ! Player.StaminaChanged(Stamina)
- }
-
Stamina
}
@@ -374,6 +368,10 @@ class Player(private val core : Avatar) extends PlanetSideServerObject
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
@@ -674,8 +672,12 @@ object Player {
final case class ImplantInitializationStart(slot : Int)
final case class UninitializeImplant(slot : Int)
final case class ImplantInitializationComplete(slot : Int)
- final case class DrainStamina(amount : Int)
- final case class StaminaChanged(currentStamina : Int)
+ final case class StaminaRegen()
+ final case class StaminaChanged(currentStamina : Option[Int] = None)
+
+ object StaminaChanged {
+ def apply(amount : Int) : StaminaChanged = StaminaChanged(Some(amount))
+ }
def apply(core : Avatar) : Player = {
new Player(core)
diff --git a/common/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala b/common/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala
index f864527b3..b1b397331 100644
--- a/common/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala
+++ b/common/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala
@@ -1,10 +1,9 @@
// Copyright (c) 2020 PSForever
package net.psforever.objects.avatar
-import akka.actor.{Actor, ActorRef, Props}
-import net.psforever.objects._
+import akka.actor.{Actor, ActorRef, Cancellable, Props}
+import net.psforever.objects.{Player, _}
import net.psforever.objects.ballistics.{PlayerSource, ResolvedProjectile}
-import net.psforever.objects.definition.ImplantDefinition
import net.psforever.objects.equipment._
import net.psforever.objects.inventory.{GridInventory, InventoryItem}
import net.psforever.objects.loadouts.Loadout
@@ -39,9 +38,14 @@ class PlayerControl(player : Player) extends Actor
private [this] val log = org.log4s.getLogger(player.Name)
private [this] val damageLog = org.log4s.getLogger(Damageable.LogChannel)
- // A collection of timers for each slot to trigger stamina drain on an interval
- val implantSlotStaminaDrainTimers = mutable.HashMap(0 -> Default.Cancellable, 1 -> Default.Cancellable, 2 -> Default.Cancellable)
- // control agency for the player's locker container (dedicated inventory slot #5)
+ /** Stamina will be used. Stamina will be restored. */
+ var staminaRegen : Cancellable = Default.Cancellable
+ /**
+ * A collection of timers indexed for the implant in each slot.
+ * Before an implant is ready, it serves as the initialization timer.
+ * After being initialized, it is used as the stamina drain interval when the implant is active. */
+ val implantSlotTimers = mutable.HashMap(0 -> Default.Cancellable, 1 -> Default.Cancellable, 2 -> Default.Cancellable)
+ /** control agency for the player's locker container (dedicated inventory slot #5) */
val lockerControlAgent : ActorRef = {
val locker = player.Locker
locker.Zone = player.Zone
@@ -51,106 +55,55 @@ class PlayerControl(player : Player) extends Actor
override def postStop() : Unit = {
lockerControlAgent ! akka.actor.PoisonPill
player.Locker.Actor = Default.Actor
- implantSlotStaminaDrainTimers.values.foreach { _.cancel }
+ staminaRegen.cancel
+ implantSlotTimers.values.foreach { _.cancel }
}
def receive : Receive = jammableBehavior
.orElse(takesDamage)
.orElse(containerBehavior)
.orElse {
- case Player.ImplantActivation(slot: Int, status : Int) =>
- // todo: disable implants with stamina cost when changing armour type
- val implantSlot = player.ImplantSlot(slot)
+ case Player.ImplantActivation(slot : Int, status : Int) =>
+ ImplantActivation(slot, status)
- // Allow uninitialized implants to be deactivated in case they're stuck in a state where they are no longer active or initialized but still draining stamina (e.g. by EMP)
- if(status == 0 && (implantSlot.Active || !implantSlot.Initialized)) {
- // Cancel stamina drain timer
- implantSlotStaminaDrainTimers(slot).cancel()
- implantSlotStaminaDrainTimers(slot) = Default.Cancellable
+ case Player.UninitializeImplant(slot : Int) =>
+ UninitializeImplant(slot)
- 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: ImplantDefinition) =>
- 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.
- // todo: Deactivate implants server side when actions like zoning happen. (Other actions?)
- log.warn(s"Implant $slot is already active, but activating again")
- implantSlotStaminaDrainTimers(slot).cancel()
- implantSlotStaminaDrainTimers(slot) = Default.Cancellable
- }
- implantSlot.Active = true
+ case Player.ImplantInitializationStart(slot : Int) =>
+ ImplantInitializationStart(slot)
- if (implant.ActivationStaminaCost >= 0) {
- player.Stamina -= implant.ActivationStaminaCost // Activation stamina drain
- }
+ case Player.ImplantInitializationComplete(slot : Int) =>
+ ImplantInitializationComplete(slot)
- if(implant.StaminaCost > 0 && implant.GetCostIntervalByExoSuit(player.ExoSuit) > 0) { // Ongoing stamina drain, if applicable
- implantSlotStaminaDrainTimers(slot) = context.system.scheduler.scheduleWithFixedDelay(0 seconds, implant.GetCostIntervalByExoSuit(player.ExoSuit) milliseconds, self, Player.DrainStamina(implant.StaminaCost))
- }
-
- player.Zone.AvatarEvents ! AvatarServiceMessage(player.Zone.Id, AvatarAction.PlanetsideAttribute(player.GUID, 28, player.Implant(slot).id * 2 + 1)) // Activation sound / effect
- player.Zone.AvatarEvents ! AvatarServiceMessage(player.Name, AvatarAction.ActivateImplantSlot(player.GUID, slot))
- case _ => ;
- }
- }
- else {
- log.warn(s"Can't handle ImplantActivation: Player GUID: ${player.GUID} Slot $slot Status: $status Initialized: ${implantSlot.Initialized} Active: ${implantSlot.Active} Fatigued: ${player.Fatigued}")
+ case Player.StaminaRegen() =>
+ if(staminaRegen == Default.Cancellable) {
+ staminaRegen.cancel
+ staminaRegen = context.system.scheduler.scheduleOnce(delay = 500 milliseconds, self, PlayerControl.StaminaRegen())
}
- case Player.UninitializeImplant(slot: Int) =>
- PlayerControl.UninitializeImplant(player, slot)
-
- case Player.ImplantInitializationStart(slot: Int) =>
- val implantSlot = player.ImplantSlot(slot)
- if(implantSlot.Installed.isDefined) {
- if(implantSlot.Initialized) {
- PlayerControl.UninitializeImplant(player, slot)
+ case PlayerControl.StaminaRegen() =>
+ staminaRegen.cancel
+ if (player.isAlive) {
+ if (player.skipStaminaRegenForTurns > 0) {
+ // Do not renew stamina for a while
+ player.skipStaminaRegenForTurns -= 1
}
-
- // Start client side initialization timer
- player.Zone.AvatarEvents ! AvatarServiceMessage(player.Name, AvatarAction.SendResponse(player.GUID, ActionProgressMessage(slot + 6, 0)))
-
- // Callback after initialization timer to complete initialization
- implantSlot.InitializeTimer = context.system.scheduler.scheduleOnce(implantSlot.MaxTimer seconds, self, Player.ImplantInitializationComplete(slot))
- }
-
- case Player.ImplantInitializationComplete(slot: Int) =>
- val implantSlot = player.ImplantSlot(slot)
- if(implantSlot.Installed.isDefined) {
- player.Zone.AvatarEvents ! AvatarServiceMessage(player.Name, AvatarAction.SendResponse(player.GUID, AvatarImplantMessage(player.GUID, ImplantAction.Initialization, slot, 1)))
- implantSlot.Initialized = true
- if(implantSlot.InitializeTimer != Default.Cancellable) {
- implantSlot.InitializeTimer.cancel()
- implantSlot.InitializeTimer = Default.Cancellable
+ 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.DrainStamina(amount : Int) =>
- player.Stamina -= amount
+ case Player.StaminaChanged(Some(changeInStamina)) =>
+ StaminaChanged(changeInStamina)
- case Player.StaminaChanged(currentStamina : Int) =>
- if(currentStamina == 0) {
- player.Fatigued = true
- player.skipStaminaRegenForTurns += 4
- player.Implants.indices.foreach { slot => // Disable all implants
- self ! Player.ImplantActivation(slot, 0)
- player.Zone.AvatarEvents ! AvatarServiceMessage(player.Zone.Id, AvatarAction.SendResponseTargeted(player.GUID, AvatarImplantMessage(player.GUID, ImplantAction.OutOfStamina, slot, 1)))
- }
- } else if (player.Fatigued && currentStamina >= 20) {
- player.Fatigued = false
- player.Implants.indices.foreach { slot => // Re-enable all implants
- player.Zone.AvatarEvents ! AvatarServiceMessage(player.Zone.Id, AvatarAction.SendResponseTargeted(player.GUID, AvatarImplantMessage(player.GUID, ImplantAction.OutOfStamina, slot, 0)))
- }
- }
- player.Zone.AvatarEvents ! AvatarServiceMessage(player.Zone.Id, AvatarAction.PlanetsideAttributeSelf(player.GUID, 2, player.Stamina))
+ case Player.StaminaChanged(None) =>
+ UpdateStamina()
case Player.Die() =>
if(player.isAlive) {
- PlayerControl.DestructionAwareness(player, None)
+ DestructionAwareness(player, None)
}
case CommonMessages.Use(user, Some(item : Tool)) if item.Definition == GlobalDefinitions.medicalapplicator && player.isAlive =>
@@ -308,6 +261,13 @@ class PlayerControl(player : Player) extends Actor
stow.foreach { elem =>
player.Inventory.InsertQuickly(elem.start, elem.obj)
}
+ //deactivate non-passive implants
+ implantSlotTimers.keys.foreach { index =>
+ val implantSlot = player.ImplantSlot(index)
+ if(implantSlot.Installed.nonEmpty && implantSlot.Active && (implantSlot.Charge(originalSuit) > 0 || implantSlot.Charge(exosuit) > 0)) {
+ ImplantActivation(index, status = 0)
+ }
+ }
player.Zone.AvatarEvents ! AvatarServiceMessage(player.Zone.Id,
AvatarAction.ChangeExosuit(player.GUID, exosuit, subtype, player.LastDrawnSlot, exosuit == ExoSuitType.MAX && requestToChangeArmor,
beforeHolsters.map { case InventoryItem(obj, _) => (obj, obj.GUID) }, afterHolsters,
@@ -431,6 +391,13 @@ class PlayerControl(player : Player) extends Actor
}
(afterHolsters ++ afterInventory).foreach { entry => entry.obj.Faction = player.Faction }
toDeleteOrDrop.foreach { entry => entry.obj.Faction = PlanetSideEmpire.NEUTRAL }
+ //deactivate non-passive implants
+ implantSlotTimers.keys.foreach { index =>
+ val implantSlot = player.ImplantSlot(index)
+ if(implantSlot.Installed.nonEmpty && implantSlot.Active && (implantSlot.Charge(originalSuit) > 0 || implantSlot.Charge(nextSuit) > 0)) {
+ ImplantActivation(index, status = 0)
+ }
+ }
player.Zone.AvatarEvents ! AvatarServiceMessage(player.Zone.Id,
AvatarAction.ChangeLoadout(player.GUID, nextSuit, nextSubtype, player.LastDrawnSlot, exosuit == ExoSuitType.MAX,
oldHolsters.map { case InventoryItem(obj, _) => (obj, obj.GUID) }, afterHolsters,
@@ -438,6 +405,92 @@ class PlayerControl(player : Player) extends Actor
)
player.Zone.AvatarEvents ! AvatarServiceMessage(player.Name, AvatarAction.TerminalOrderResult(msg.terminal_guid, msg.transaction_type, true))
+ case Terminal.LearnImplant(implant) =>
+ val zone = player.Zone
+ val events = zone.AvatarEvents
+ val playerChannel = player.Name
+ val terminal_guid = msg.terminal_guid
+ val implant_type = implant.Type
+ val message = s"wants to learn $implant_type"
+ val (interface, slotNumber) = player.VehicleSeated match {
+ case Some(mech_guid) =>
+ (
+ zone.Map.TerminalToInterface.get(mech_guid.guid),
+ if(!player.Implants.exists({ case (implantType, _, _) => implantType == implant_type })) {
+ //no duplicates
+ player.InstallImplant(implant)
+ }
+ else {
+ None
+ }
+ )
+ case _ =>
+ (None, None)
+ }
+ val result = if(interface.contains(terminal_guid.guid) && slotNumber.isDefined) {
+ val slot = slotNumber.get
+ log.info(s"$message - put in slot $slot")
+ events ! AvatarServiceMessage(playerChannel, AvatarAction.SendResponse(Service.defaultPlayerGUID, AvatarImplantMessage(player.GUID, ImplantAction.Add, slot, implant_type.id)))
+ ImplantInitializationStart(slot)
+ true
+ }
+ else {
+ if(interface.isEmpty) {
+ log.warn(s"$message - not interacting with a terminal")
+ }
+ else if(!interface.contains(terminal_guid.guid)) {
+ log.warn(s"$message - interacting with the wrong terminal, ${interface.get}")
+ }
+ else if(slotNumber.isEmpty) {
+ log.warn(s"$message - already knows that implant")
+ }
+ else {
+ log.warn(s"$message - forgot to sit at a terminal")
+ }
+ false
+ }
+ events ! AvatarServiceMessage(playerChannel, AvatarAction.TerminalOrderResult(terminal_guid, msg.transaction_type, result))
+
+ case Terminal.SellImplant(implant) =>
+ val zone = player.Zone
+ val events = zone.AvatarEvents
+ val playerChannel = player.Name
+ val terminal_guid = msg.terminal_guid
+ val implant_type = implant.Type
+ val (interface, slotNumber) = player.VehicleSeated match {
+ case Some(mech_guid) =>
+ (
+ zone.Map.TerminalToInterface.get(mech_guid.guid),
+ player.UninstallImplant(implant_type)
+ )
+ case None =>
+ (None, None)
+ }
+ val result = if(interface.contains(terminal_guid.guid) && slotNumber.isDefined) {
+ val slot = slotNumber.get
+ log.info(s"is uninstalling $implant_type - take from slot $slot")
+ UninitializeImplant(slot)
+ events ! AvatarServiceMessage(playerChannel, AvatarAction.SendResponse(Service.defaultPlayerGUID, AvatarImplantMessage(player.GUID, ImplantAction.Remove, slot, 0)))
+ true
+ }
+ else {
+ val message = s"${player.Name} can not sell $implant_type"
+ if(interface.isEmpty) {
+ log.warn(s"$message - not interacting with a terminal")
+ }
+ else if(!interface.contains(terminal_guid.guid)) {
+ log.warn(s"$message - interacting with the wrong terminal, ${interface.get}")
+ }
+ else if(slotNumber.isEmpty) {
+ log.warn(s"$message - does not know that implant")
+ }
+ else {
+ log.warn(s"$message - forgot to sit at a terminal")
+ }
+ false
+ }
+ events ! AvatarServiceMessage(playerChannel, AvatarAction.TerminalOrderResult(terminal_guid, msg.transaction_type, result))
+
case _ => ; //terminal messages not handled here
}
@@ -490,21 +543,189 @@ class PlayerControl(player : Player) extends Actor
if(player.isAlive) {
val originalHealth = player.Health
val originalArmor = player.Armor
+ val originalStamina = player.Stamina
val originalCapacitor = player.Capacitor.toInt
val cause = applyDamageTo(player)
val health = player.Health
val armor = player.Armor
+ val stamina = player.Stamina
val capacitor = player.Capacitor.toInt
val damageToHealth = originalHealth - health
val damageToArmor = originalArmor - armor
+ val damageToStamina = originalStamina - stamina
val damageToCapacitor = originalCapacitor - capacitor
- PlayerControl.HandleDamage(player, cause, damageToHealth, damageToArmor, damageToCapacitor)
- if(damageToHealth > 0 || damageToArmor > 0 || damageToCapacitor > 0) {
- damageLog.info(s"${player.Name}-infantry: BEFORE=$originalHealth/$originalArmor/$originalCapacitor, AFTER=$health/$armor/$capacitor, CHANGE=$damageToHealth/$damageToArmor/$damageToCapacitor")
+ HandleDamage(player, cause, damageToHealth, damageToArmor, damageToStamina, damageToCapacitor)
+ if(damageToHealth > 0 || damageToArmor > 0 || damageToStamina > 0 || damageToCapacitor > 0) {
+ damageLog.info(s"${player.Name}-infantry: BEFORE=$originalHealth/$originalArmor/$originalStamina/$originalCapacitor, AFTER=$health/$armor/$stamina/$capacitor, CHANGE=$damageToHealth/$damageToArmor/$damageToStamina/$damageToCapacitor")
}
}
}
+ /**
+ * na
+ * @param target na
+ */
+ def HandleDamage(target : Player, cause : ResolvedProjectile, damageToHealth : Int, damageToArmor : Int, damageToStamina : Int, damageToCapacitor : Int) : Unit = {
+ val targetGUID = target.GUID
+ val zone = target.Zone
+ val zoneId = zone.Id
+ val events = zone.AvatarEvents
+ val health = target.Health
+ if(damageToArmor > 0) {
+ events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(targetGUID, 4, target.Armor))
+ }
+ if(health > 0) {
+ if(damageToCapacitor > 0) {
+ events ! AvatarServiceMessage(target.Name, AvatarAction.PlanetsideAttributeSelf(targetGUID, 7, target.Capacitor.toLong))
+ }
+ if(damageToHealth > 0 || damageToStamina > 0) {
+ target.History(cause)
+ if(damageToHealth > 0) {
+ events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(targetGUID, 0, health))
+ }
+ if(damageToStamina > 0) {
+ UpdateStamina()
+ }
+ //activity on map
+ zone.Activity ! Zone.HotSpot.Activity(cause.target, cause.projectile.owner, cause.hit_pos)
+ //alert damage source
+ DamageAwareness(target, cause)
+ }
+ if(Damageable.CanJammer(target, cause)) {
+ target.Actor ! JammableUnit.Jammered(cause)
+ }
+ }
+ else {
+ DestructionAwareness(target, Some(cause))
+ }
+ }
+
+ /**
+ * na
+ * @param target na
+ * @param cause na
+ */
+ def DamageAwareness(target : Player, cause : ResolvedProjectile) : Unit = {
+ val zone = target.Zone
+ zone.AvatarEvents ! AvatarServiceMessage(
+ target.Name,
+ cause.projectile.owner match {
+ case pSource : PlayerSource => //player damage
+ val name = pSource.Name
+ zone.LivePlayers.find(_.Name == name).orElse(zone.Corpses.find(_.Name == name)) match {
+ case Some(tplayer) => AvatarAction.HitHint(tplayer.GUID, target.GUID)
+ case None => AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(10, pSource.Position))
+ }
+ case source => AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(10, source.Position))
+ }
+ )
+ }
+
+ /**
+ * The player has lost all his vitality and must be killed.
+ *
+ * Shift directly into a state of being dead on the client by setting health to zero points,
+ * whereupon the player will perform a dramatic death animation.
+ * Stamina is also set to zero points.
+ * If the player was in a vehicle at the time of demise, special conditions apply and
+ * the model must be manipulated so it behaves correctly.
+ * Do not move or completely destroy the `Player` object as its coordinates of death will be important.
+ *
+ * A maximum revive waiting timer is started.
+ * When this timer reaches zero, the avatar will attempt to spawn back on its faction-specific sanctuary continent.
+ * @param target na
+ * @param cause na
+ */
+ def DestructionAwareness(target : Player, cause : Option[ResolvedProjectile]) : Unit = {
+ val player_guid = target.GUID
+ val pos = target.Position
+ val respawnTimer = 300000 //milliseconds
+ val zone = target.Zone
+ val events = zone.AvatarEvents
+ val nameChannel = target.Name
+ val zoneChannel = zone.Id
+ target.Die
+ //unjam
+ CancelJammeredSound(target)
+ CancelJammeredStatus(target)
+ //implants off
+ target.Stamina = 0
+ UpdateStamina() //turn off implants / OutOfStamina
+ //uninitialize implants
+ target.Implants.indices.foreach { case slot if target.Implant(slot) != ImplantType.None =>
+ UninitializeImplant(slot)
+ }
+ target.ResetAllImplants() //anything else specific to the backend
+ events ! AvatarServiceMessage(nameChannel, AvatarAction.Killed(player_guid, target.VehicleSeated)) //align client interface fields with state
+ zone.GUID(target.VehicleSeated) match {
+ case Some(obj : Mountable) =>
+ //boot cadaver from seat internally (vehicle perspective)
+ obj.PassengerInSeat(target) match {
+ case Some(index) =>
+ obj.Seats(index).Occupant = None
+ case _ => ;
+ }
+ //boot cadaver from seat on client
+ events ! AvatarServiceMessage(nameChannel, AvatarAction.SendResponse(Service.defaultPlayerGUID,
+ ObjectDetachMessage(obj.GUID, player_guid, target.Position, Vector3.Zero))
+ )
+ //make player invisible on client
+ events ! AvatarServiceMessage(nameChannel, AvatarAction.PlanetsideAttributeToAll(player_guid, 29, 1))
+ //only the dead player should "see" their own body, so that the death camera has something to focus on
+ events ! AvatarServiceMessage(zoneChannel, AvatarAction.ObjectDelete(player_guid, player_guid))
+ case _ => ;
+ }
+ events ! AvatarServiceMessage(zoneChannel, AvatarAction.PlanetsideAttributeToAll(player_guid, 0, 0)) //health
+ if(target.Capacitor > 0) {
+ target.Capacitor = 0
+ events ! AvatarServiceMessage(nameChannel, AvatarAction.PlanetsideAttributeToAll(player_guid, 7, 0)) // capacitor
+ }
+ val attribute = cause match {
+ case Some(resolved) =>
+ resolved.projectile.owner match {
+ case pSource : PlayerSource =>
+ val name = pSource.Name
+ zone.LivePlayers.find(_.Name == name).orElse(zone.Corpses.find(_.Name == name)) match {
+ case Some(tplayer) => tplayer.GUID
+ case None => player_guid
+ }
+ case _ => player_guid
+ }
+ case _ => player_guid
+ }
+ events ! AvatarServiceMessage(
+ nameChannel,
+ AvatarAction.SendResponse(Service.defaultPlayerGUID, DestroyMessage(player_guid, attribute, Service.defaultPlayerGUID, pos)) //how many players get this message?
+ )
+ events ! AvatarServiceMessage(
+ nameChannel,
+ AvatarAction.SendResponse(Service.defaultPlayerGUID, AvatarDeadStateMessage(DeadState.Dead, respawnTimer, respawnTimer, pos, target.Faction, true))
+ )
+ //TODO other methods of death?
+ val pentry = PlayerSource(target)
+ (target.History.find({p => p.isInstanceOf[PlayerSuicide]}) match {
+ case Some(PlayerSuicide(_)) =>
+ None
+ case _ =>
+ cause.orElse { target.LastShot } match {
+ case out @ Some(shot) =>
+ if(System.nanoTime - shot.hit_time < (10 seconds).toNanos) {
+ out
+ }
+ else {
+ None //suicide
+ }
+ case None =>
+ None //suicide
+ }
+ }) match {
+ case Some(shot) =>
+ events ! AvatarServiceMessage(zoneChannel, AvatarAction.DestroyDisplay(shot.projectile.owner, pentry, shot.projectile.attribute_to))
+ case None =>
+ events ! AvatarServiceMessage(zoneChannel, AvatarAction.DestroyDisplay(pentry, pentry, 0))
+ }
+ }
+
/**
* Start the jammered buzzing.
* Although, as a rule, the jammering sound effect should last as long as the jammering status,
@@ -530,29 +751,23 @@ class PlayerControl(player : Player) extends Actor
* @param target an object that can be affected by the jammered status
* @param dur the duration of the timer, in milliseconds
*/
- override def StartJammeredStatus(target : Any, dur : Int) : Unit = target match {
- case obj : Player =>
- //TODO these features
-// val guid = obj.GUID
-// val zone = obj.Zone
-// val zoneId = zone.Id
-// val events = zone.AvatarEvents
- player.Implants.indices.foreach { slot => // Deactivate & uninitialize all implants
- player.Zone.AvatarEvents ! AvatarServiceMessage(player.Zone.Id, AvatarAction.PlanetsideAttribute(player.GUID, 28, player.Implant(slot).id * 2)) // Deactivation sound / effect
- self ! Player.ImplantActivation(slot, 0)
- PlayerControl.UninitializeImplant(player, slot)
- }
-
- obj.skipStaminaRegenForTurns = math.max(obj.skipStaminaRegenForTurns, 10)
- super.StartJammeredStatus(target, dur)
- case _ => ;
+ override def StartJammeredStatus(target : Any, dur : Int) : Unit = {
+ //TODO these features
+ val zone = player.Zone
+ player.Implants.indices.foreach { slot => // Deactivate & uninitialize all implants
+ zone.AvatarEvents ! AvatarServiceMessage(zone.Id, AvatarAction.PlanetsideAttribute(player.GUID, 28, player.Implant(slot).id * 2)) // Deactivation sound / effect
+ ImplantActivation(slot, status = 0)
+ UninitializeImplant(slot)
+ }
+ player.skipStaminaRegenForTurns = math.max(player.skipStaminaRegenForTurns, 10)
+ super.StartJammeredStatus(target, dur)
}
override def CancelJammeredStatus(target: Any): Unit = {
player.Implants.indices.foreach { slot => // Start reinitializing all implants
- self ! Player.ImplantInitializationStart(slot)
+ player.ImplantSlot(slot).InitializeTime = 0 //setting time to 0 will restart implant initialization (eventually)
+ ImplantInitializationStart(slot)
}
-
super.CancelJammeredStatus(target)
}
@@ -664,175 +879,200 @@ class PlayerControl(player : Player) extends Actor
val zone = obj.Zone
zone.AvatarEvents ! AvatarServiceMessage(player.Name, 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 {
- /**
- * na
- * @param target na
- */
- def HandleDamage(target : Player, cause : ResolvedProjectile, damageToHealth : Int, damageToArmor : Int, damageToCapacitor : Int) : Unit = {
- val targetGUID = target.GUID
- val zone = target.Zone
- val zoneId = zone.Id
- val events = zone.AvatarEvents
- val health = target.Health
- if(health > 0) {
- if(damageToCapacitor > 0) {
- events ! AvatarServiceMessage(target.Name, AvatarAction.PlanetsideAttributeSelf(targetGUID, 7, target.Capacitor.toLong))
- }
- if(damageToHealth > 0 || damageToArmor > 0) {
- target.History(cause)
- if(damageToHealth > 0) {
- events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(targetGUID, 0, health))
- }
- if(damageToArmor > 0) {
- events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(targetGUID, 4, target.Armor))
- }
- //activity on map
- zone.Activity ! Zone.HotSpot.Activity(cause.target, cause.projectile.owner, cause.hit_pos)
- //alert damage source
- DamageAwareness(target, cause)
- }
- if(Damageable.CanJammer(target, cause)) {
- target.Actor ! JammableUnit.Jammered(cause)
- }
- }
- else {
- if(damageToArmor > 0) {
- events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(targetGUID, 4, target.Armor))
- }
- DestructionAwareness(target, Some(cause))
- }
- }
-
- /**
- * na
- * @param target na
- * @param cause na
- */
- def DamageAwareness(target : Player, cause : ResolvedProjectile) : Unit = {
- val zone = target.Zone
- zone.AvatarEvents ! AvatarServiceMessage(
- target.Name,
- cause.projectile.owner match {
- case pSource : PlayerSource => //player damage
- val name = pSource.Name
- zone.LivePlayers.find(_.Name == name).orElse(zone.Corpses.find(_.Name == name)) match {
- case Some(player) => AvatarAction.HitHint(player.GUID, target.GUID)
- case None => AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(10, pSource.Position))
- }
- case source => AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(10, source.Position))
- }
- )
- }
-
- /**
- * The player has lost all his vitality and must be killed.
- *
- * Shift directly into a state of being dead on the client by setting health to zero points,
- * whereupon the player will perform a dramatic death animation.
- * Stamina is also set to zero points.
- * If the player was in a vehicle at the time of demise, special conditions apply and
- * the model must be manipulated so it behaves correctly.
- * Do not move or completely destroy the `Player` object as its coordinates of death will be important.
- *
- * A maximum revive waiting timer is started.
- * When this timer reaches zero, the avatar will attempt to spawn back on its faction-specific sanctuary continent.
- * @param target na
- * @param cause na
- */
- def DestructionAwareness(target : Player, cause : Option[ResolvedProjectile]) : Unit = {
- val player_guid = target.GUID
- val pos = target.Position
- val respawnTimer = 300000 //milliseconds
- val zone = target.Zone
- val events = zone.AvatarEvents
- val nameChannel = target.Name
- val zoneChannel = zone.Id
- target.Die
- //unjam
- target.Actor ! JammableUnit.ClearJammeredSound()
- target.Actor ! JammableUnit.ClearJammeredStatus()
- events ! AvatarServiceMessage(nameChannel, AvatarAction.Killed(player_guid, target.VehicleSeated)) //align client interface fields with state
- zone.GUID(target.VehicleSeated) match {
- case Some(obj : Mountable) =>
- //boot cadaver from seat internally (vehicle perspective)
- obj.PassengerInSeat(target) match {
- case Some(index) =>
- obj.Seats(index).Occupant = None
- case _ => ;
- }
- //boot cadaver from seat on client
- events ! AvatarServiceMessage(nameChannel, AvatarAction.SendResponse(Service.defaultPlayerGUID,
- ObjectDetachMessage(obj.GUID, player_guid, target.Position, Vector3.Zero))
- )
- //make player invisible on client
- events ! AvatarServiceMessage(nameChannel, AvatarAction.PlanetsideAttributeToAll(player_guid, 29, 1))
- //only the dead player should "see" their own body, so that the death camera has something to focus on
- events ! AvatarServiceMessage(zoneChannel, AvatarAction.ObjectDelete(player_guid, player_guid))
- case _ => ;
- }
- events ! AvatarServiceMessage(zoneChannel, AvatarAction.PlanetsideAttributeToAll(player_guid, 0, 0)) //health
- events ! AvatarServiceMessage(nameChannel, AvatarAction.PlanetsideAttributeToAll(player_guid, 2, 0)) //stamina
- if(target.Capacitor > 0) {
- target.Capacitor = 0
- events ! AvatarServiceMessage(nameChannel, AvatarAction.PlanetsideAttributeToAll(player_guid, 7, 0)) // capacitor
- }
- val attribute = cause match {
- case Some(resolved) =>
- resolved.projectile.owner match {
- case pSource : PlayerSource =>
- val name = pSource.Name
- zone.LivePlayers.find(_.Name == name).orElse(zone.Corpses.find(_.Name == name)) match {
- case Some(player) => player.GUID
- case None => player_guid
- }
- case _ => player_guid
- }
- case _ => player_guid
- }
- events ! AvatarServiceMessage(
- nameChannel,
- AvatarAction.SendResponse(Service.defaultPlayerGUID, DestroyMessage(player_guid, attribute, Service.defaultPlayerGUID, pos)) //how many players get this message?
- )
- events ! AvatarServiceMessage(
- nameChannel,
- AvatarAction.SendResponse(Service.defaultPlayerGUID, AvatarDeadStateMessage(DeadState.Dead, respawnTimer, respawnTimer, pos, target.Faction, true))
- )
- //TODO other methods of death?
- val pentry = PlayerSource(target)
- (target.History.find({p => p.isInstanceOf[PlayerSuicide]}) match {
- case Some(PlayerSuicide(_)) =>
- None
- case _ =>
- cause.orElse { target.LastShot } match {
- case out @ Some(shot) =>
- if(System.nanoTime - shot.hit_time < (10 seconds).toNanos) {
- out
- }
- else {
- None //suicide
- }
- case None =>
- None //suicide
- }
- }) match {
- case Some(shot) =>
- events ! AvatarServiceMessage(zoneChannel, AvatarAction.DestroyDisplay(shot.projectile.owner, pentry, shot.projectile.attribute_to))
- case None =>
- events ! AvatarServiceMessage(zoneChannel, AvatarAction.DestroyDisplay(pentry, pentry, 0))
- }
- }
-
- def UninitializeImplant(player: Player, slot: Int): Unit = {
- val implantSlot = player.ImplantSlot(slot)
-
- implantSlot.Initialized = false
- if(implantSlot.InitializeTimer != Default.Cancellable) {
- implantSlot.InitializeTimer.cancel()
- implantSlot.InitializeTimer = Default.Cancellable
- }
- player.Zone.AvatarEvents ! AvatarServiceMessage(player.Zone.Id, AvatarAction.SendResponse(player.GUID, AvatarImplantMessage(player.GUID, ImplantAction.Initialization, slot, 0)))
- }
+ /** */
+ private case class StaminaRegen()
}
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/OrderTerminalDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/OrderTerminalDefinition.scala
index dac5366f2..ff19e49a2 100644
--- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/OrderTerminalDefinition.scala
+++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/OrderTerminalDefinition.scala
@@ -245,7 +245,7 @@ object OrderTerminalDefinition {
}
def Dispatch(sender : ActorRef, terminal : Terminal, msg : Terminal.TerminalMessage) : Unit = {
- sender ! msg
+ msg.player.Actor ! msg
}
}
diff --git a/common/src/main/scala/net/psforever/objects/vital/resolution/ResolutionCalculations.scala b/common/src/main/scala/net/psforever/objects/vital/resolution/ResolutionCalculations.scala
index 518de2b3e..a0b566b73 100644
--- a/common/src/main/scala/net/psforever/objects/vital/resolution/ResolutionCalculations.scala
+++ b/common/src/main/scala/net/psforever/objects/vital/resolution/ResolutionCalculations.scala
@@ -184,7 +184,7 @@ object ResolutionCalculations {
// If any health damage was applied also drain an amount of stamina equal to half the health damage
if(player.Health < originalHealth) {
val delta = originalHealth - player.Health
- player.Stamina = player.Stamina - math.floor(delta / 2).toInt
+ player.Stamina -= math.floor(delta / 2).toInt
}
}
case _ =>
diff --git a/common/src/main/scala/net/psforever/packet/game/CreateShortcutMessage.scala b/common/src/main/scala/net/psforever/packet/game/CreateShortcutMessage.scala
index 9c1491a4f..94904bc5f 100644
--- a/common/src/main/scala/net/psforever/packet/game/CreateShortcutMessage.scala
+++ b/common/src/main/scala/net/psforever/packet/game/CreateShortcutMessage.scala
@@ -89,52 +89,41 @@ object Shortcut extends Marshallable[Shortcut] {
/**
A map to convert between ImplantTypes and Implant Shortcuts
*/
- final lazy val ImplantsMap = Map(
- ImplantType.AdvancedRegen->REGENERATION,
- ImplantType.Targeting->ENHANCED_TARGETING,
- ImplantType.AudioAmplifier->AUDIO_AMPLIFIER,
- ImplantType.DarklightVision->DARKLIGHT_VISION,
- ImplantType.MeleeBooster->MELEE_BOOSTER,
- ImplantType.PersonalShield->PERSONAL_SHIELD,
- ImplantType.RangeMagnifier->RANGE_MAGNIFIER,
- ImplantType.SecondWind->SECOND_WIND,
- ImplantType.SilentRun->SENSOR_SHIELD,
- ImplantType.Surge->SURGE
- )
+ final lazy val ImplantsMap : Map[ImplantType.Value, Option[Shortcut]] = Map(
+ ImplantType.AdvancedRegen->Regeneration,
+ ImplantType.Targeting->EnhancedTargeting,
+ ImplantType.AudioAmplifier->AudioAmplifier,
+ ImplantType.DarklightVision->DartklightVision,
+ ImplantType.MeleeBooster->MeleeBooster,
+ ImplantType.PersonalShield->PersonalShield,
+ ImplantType.RangeMagnifier->RangeMagnifier,
+ ImplantType.SecondWind->SecondWind,
+ ImplantType.SilentRun->SensorShield,
+ ImplantType.Surge->Surge
+ ).withDefaultValue(None)
- /**
- * Preset for the Audio Amplifier implant. */
- final val AUDIO_AMPLIFIER : Some[Shortcut] = Some(Shortcut(2, "audio_amplifier"))
- /**
- * Preset for the Darklight Vision implant. */
- final val DARKLIGHT_VISION : Some[Shortcut] = Some(Shortcut(2, "darklight_vision"))
- /**
- * Preset for the Enhanced Targeting implant. */
- final val ENHANCED_TARGETING : Some[Shortcut] = Some(Shortcut(2, "targeting"))
- /**
- * Preset for the medkit quick-use option. */
- final val MEDKIT : Some[Shortcut] = Some(Shortcut(0, "medkit"))
- /**
- * Preset for the Melee Booster implant. */
- final val MELEE_BOOSTER : Some[Shortcut] = Some(Shortcut(2, "melee_booster"))
- /**
- * Preset for the Personal Shield implant. */
- final val PERSONAL_SHIELD : Some[Shortcut] = Some(Shortcut(2, "personal_shield"))
- /**
- * Preset for the Range Magnifier implant. */
- final val RANGE_MAGNIFIER : Some[Shortcut] = Some(Shortcut(2, "range_magnifier"))
- /**
- * Preset for the Regeneration implant. */
- final val REGENERATION : Some[Shortcut] = Some(Shortcut(2, "advanced_regen"))
- /**
- * Preset for the Second Wind implant. */
- final val SECOND_WIND : Some[Shortcut] = Some(Shortcut(2, "second_wind"))
- /**
- * Preset for the Sensor Shield implant. */
- final val SENSOR_SHIELD : Some[Shortcut] = Some(Shortcut(2, "silent_run"))
- /**
- * Preset for the Surge implant. */
- final val SURGE : Some[Shortcut] = Some(Shortcut(2, "surge"))
+ /** Preset for the Audio Amplifier implant. */
+ final val AudioAmplifier : Some[Shortcut] = Some(Shortcut(2, "audio_amplifier"))
+ /** Preset for the Darklight Vision implant. */
+ final val DartklightVision : Some[Shortcut] = Some(Shortcut(2, "darklight_vision"))
+ /** Preset for the Enhanced Targeting implant. */
+ final val EnhancedTargeting : Some[Shortcut] = Some(Shortcut(2, "targeting"))
+ /** Preset for the medkit quick-use option. */
+ final val Medkit : Some[Shortcut] = Some(Shortcut(0, "medkit"))
+ /** Preset for the Melee Booster implant. */
+ final val MeleeBooster : Some[Shortcut] = Some(Shortcut(2, "melee_booster"))
+ /** Preset for the Personal Shield implant. */
+ final val PersonalShield : Some[Shortcut] = Some(Shortcut(2, "personal_shield"))
+ /** Preset for the Range Magnifier implant. */
+ final val RangeMagnifier : Some[Shortcut] = Some(Shortcut(2, "range_magnifier"))
+ /** Preset for the Regeneration implant. */
+ final val Regeneration : Some[Shortcut] = Some(Shortcut(2, "advanced_regen"))
+ /** Preset for the Second Wind implant. */
+ final val SecondWind : Some[Shortcut] = Some(Shortcut(2, "second_wind"))
+ /** Preset for the Sensor Shield implant. */
+ final val SensorShield : Some[Shortcut] = Some(Shortcut(2, "silent_run"))
+ /** Preset for the Surge implant. */
+ final val Surge : Some[Shortcut] = Some(Shortcut(2, "surge"))
/**
* Converter for text macro parameters that acts like a preset.
* @param effect1 a three letter acronym displayed in the hotbar
diff --git a/common/src/test/scala/game/CreateShortcutMessageTest.scala b/common/src/test/scala/game/CreateShortcutMessageTest.scala
index f5a662bf3..6a4b3bde1 100644
--- a/common/src/test/scala/game/CreateShortcutMessageTest.scala
+++ b/common/src/test/scala/game/CreateShortcutMessageTest.scala
@@ -89,27 +89,27 @@ class CreateShortcutMessageTest extends Specification {
}
"presets" in {
- Shortcut.AUDIO_AMPLIFIER.get.purpose mustEqual 2
- Shortcut.AUDIO_AMPLIFIER.get.tile mustEqual "audio_amplifier"
- Shortcut.DARKLIGHT_VISION.get.purpose mustEqual 2
- Shortcut.DARKLIGHT_VISION.get.tile mustEqual "darklight_vision"
- Shortcut.ENHANCED_TARGETING.get.purpose mustEqual 2
- Shortcut.ENHANCED_TARGETING.get.tile mustEqual "targeting"
- Shortcut.MEDKIT.get.purpose mustEqual 0
- Shortcut.MEDKIT.get.tile mustEqual "medkit"
- Shortcut.MELEE_BOOSTER.get.purpose mustEqual 2
- Shortcut.MELEE_BOOSTER.get.tile mustEqual "melee_booster"
- Shortcut.PERSONAL_SHIELD.get.purpose mustEqual 2
- Shortcut.PERSONAL_SHIELD.get.tile mustEqual "personal_shield"
- Shortcut.RANGE_MAGNIFIER.get.purpose mustEqual 2
- Shortcut.RANGE_MAGNIFIER.get.tile mustEqual "range_magnifier"
- Shortcut.REGENERATION.get.purpose mustEqual 2
- Shortcut.REGENERATION.get.tile mustEqual "advanced_regen"
- Shortcut.SECOND_WIND.get.purpose mustEqual 2
- Shortcut.SECOND_WIND.get.tile mustEqual "second_wind"
- Shortcut.SENSOR_SHIELD.get.purpose mustEqual 2
- Shortcut.SENSOR_SHIELD.get.tile mustEqual "silent_run"
- Shortcut.SURGE.get.purpose mustEqual 2
- Shortcut.SURGE.get.tile mustEqual "surge"
+ Shortcut.AudioAmplifier.get.purpose mustEqual 2
+ Shortcut.AudioAmplifier.get.tile mustEqual "audio_amplifier"
+ Shortcut.DartklightVision.get.purpose mustEqual 2
+ Shortcut.DartklightVision.get.tile mustEqual "darklight_vision"
+ Shortcut.EnhancedTargeting.get.purpose mustEqual 2
+ Shortcut.EnhancedTargeting.get.tile mustEqual "targeting"
+ Shortcut.Medkit.get.purpose mustEqual 0
+ Shortcut.Medkit.get.tile mustEqual "medkit"
+ Shortcut.MeleeBooster.get.purpose mustEqual 2
+ Shortcut.MeleeBooster.get.tile mustEqual "melee_booster"
+ Shortcut.PersonalShield.get.purpose mustEqual 2
+ Shortcut.PersonalShield.get.tile mustEqual "personal_shield"
+ Shortcut.RangeMagnifier.get.purpose mustEqual 2
+ Shortcut.RangeMagnifier.get.tile mustEqual "range_magnifier"
+ Shortcut.Regeneration.get.purpose mustEqual 2
+ Shortcut.Regeneration.get.tile mustEqual "advanced_regen"
+ Shortcut.SecondWind.get.purpose mustEqual 2
+ Shortcut.SecondWind.get.tile mustEqual "second_wind"
+ Shortcut.SensorShield.get.purpose mustEqual 2
+ Shortcut.SensorShield.get.tile mustEqual "silent_run"
+ Shortcut.Surge.get.purpose mustEqual 2
+ Shortcut.Surge.get.tile mustEqual "surge"
}
}
diff --git a/common/src/test/scala/objects/PlayerControlTest.scala b/common/src/test/scala/objects/PlayerControlTest.scala
index a977c7c8b..7c4da5c48 100644
--- a/common/src/test/scala/objects/PlayerControlTest.scala
+++ b/common/src/test/scala/objects/PlayerControlTest.scala
@@ -343,17 +343,23 @@ class PlayerControlDamageTest extends ActorTest {
assert(player2.Health == player2.Definition.DefaultHealth)
assert(player2.Armor == player2.MaxArmor)
player2.Actor ! Vitality.Damage(applyDamageTo)
- val msg_avatar = avatarProbe.receiveN(3, 500 milliseconds)
+ val msg_avatar = avatarProbe.receiveN(4, 500 milliseconds)
val msg_activity = activityProbe.receiveOne(200 milliseconds)
assert(
msg_avatar.head match {
- case AvatarServiceMessage("test", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 0, _)) => true
+ case AvatarServiceMessage("test", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 4, _)) => true
case _ => false
}
)
assert(
msg_avatar(1) match {
- case AvatarServiceMessage("test", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 4, _)) => true
+ case AvatarServiceMessage("test", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 0, _)) => true
+ case _ => false
+ }
+ )
+ assert(
+ msg_avatar(2) match {
+ case AvatarServiceMessage("TestCharacter2", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 2, _)) => true
case _ => false
}
)
@@ -367,7 +373,7 @@ class PlayerControlDamageTest extends ActorTest {
}
)
assert(
- msg_avatar(2) match {
+ msg_avatar(3) match {
case AvatarServiceMessage("TestCharacter2", AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(10, Vector3(2, 0, 0)))) => true
case _ => false
}
@@ -440,19 +446,19 @@ class PlayerControlDeathStandingTest extends ActorTest {
)
assert(
msg_avatar(1) match {
- case AvatarServiceMessage("TestCharacter2", AvatarAction.Killed(PlanetSideGUID(2), None)) => true
+ case AvatarServiceMessage("TestCharacter2", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 2, _)) => true
case _ => false
}
)
assert(
msg_avatar(2) match {
- case AvatarServiceMessage("test", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 0, _)) => true
+ case AvatarServiceMessage("TestCharacter2", AvatarAction.Killed(PlanetSideGUID(2), None)) => true
case _ => false
}
)
assert(
msg_avatar(3) match {
- case AvatarServiceMessage("TestCharacter2", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 2, _)) => true
+ case AvatarServiceMessage("test", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 0, _)) => true
case _ => false
}
)
@@ -546,12 +552,18 @@ class PlayerControlDeathSeatedTest extends ActorTest {
activityProbe.expectNoMessage(200 milliseconds)
assert(
msg_avatar.head match {
- case AvatarServiceMessage("TestCharacter2", AvatarAction.Killed(PlanetSideGUID(2), Some(PlanetSideGUID(5)))) => true
+ case AvatarServiceMessage("TestCharacter2", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 2, _)) => true
case _ => false
}
)
assert(
msg_avatar(1) match {
+ case AvatarServiceMessage("TestCharacter2", AvatarAction.Killed(PlanetSideGUID(2), Some(PlanetSideGUID(5)))) => true
+ case _ => false
+ }
+ )
+ assert(
+ msg_avatar(2) match {
case AvatarServiceMessage("TestCharacter2", AvatarAction.SendResponse(_,
ObjectDetachMessage(PlanetSideGUID(5), PlanetSideGUID(2), _, _, _, _))
) => true
@@ -559,26 +571,20 @@ class PlayerControlDeathSeatedTest extends ActorTest {
}
)
assert(
- msg_avatar(2) match {
+ msg_avatar(3) match {
case AvatarServiceMessage("TestCharacter2", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 29, 1)) => true
case _ => false
}
)
assert(
- msg_avatar(3) match {
+ msg_avatar(4) match {
case AvatarServiceMessage("test", AvatarAction.ObjectDelete(PlanetSideGUID(2), PlanetSideGUID(2), _)) => true
case _ => false
}
)
- assert(
- msg_avatar(4) match {
- case AvatarServiceMessage("test", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 0, _)) => true
- case _ => false
- }
- )
assert(
msg_avatar(5) match {
- case AvatarServiceMessage("TestCharacter2", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 2, _)) => true
+ case AvatarServiceMessage("test", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 0, _)) => true
case _ => false
}
)
diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala
index 7392b05fc..1861be013 100644
--- a/pslogin/src/main/scala/WorldSessionActor.scala
+++ b/pslogin/src/main/scala/WorldSessionActor.scala
@@ -2584,86 +2584,6 @@ class WorldSessionActor extends Actor
}
lastTerminalOrderFulfillment = true
- case Terminal.LearnImplant(implant) =>
- val terminal_guid = msg.terminal_guid
- val implant_type = implant.Type
- val message = s"Implants: ${tplayer.Name} wants to learn $implant_type"
- val (interface, slotNumber) = tplayer.VehicleSeated match {
- case Some(mech_guid) =>
- (
- continent.Map.TerminalToInterface.get(mech_guid.guid),
- if(!avatar.Implants.exists({ slot => slot.Implant == implant_type })) {
- //no duplicates
- avatar.InstallImplant(implant)
- }
- else {
- None
- }
- )
- case _ =>
- (None, None)
- }
- if(interface.contains(terminal_guid.guid) && slotNumber.isDefined) {
- val slot = slotNumber.get
- log.info(s"$message - put in slot $slot")
- sendResponse(AvatarImplantMessage(tplayer.GUID, ImplantAction.Add, slot, implant_type.id))
- sendResponse(ItemTransactionResultMessage(terminal_guid, TransactionType.Learn, true))
- player.Actor ! Player.ImplantInitializationStart(slot)
- }
- 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")
- }
- sendResponse(ItemTransactionResultMessage(terminal_guid, TransactionType.Learn, false))
- }
- lastTerminalOrderFulfillment = true
-
- case Terminal.SellImplant(implant) =>
- val terminal_guid = msg.terminal_guid
- val implant_type = implant.Type
- val (interface, slotNumber) = tplayer.VehicleSeated match {
- case Some(mech_guid) =>
- (
- continent.Map.TerminalToInterface.get(mech_guid.guid),
- avatar.UninstallImplant(implant_type)
- )
- case None =>
- (None, None)
- }
- if(interface.contains(terminal_guid.guid) && slotNumber.isDefined) {
- val slot = slotNumber.get
- log.info(s"${tplayer.Name} is selling $implant_type - take from slot $slot")
- player.Actor ! Player.UninitializeImplant(slot)
- sendResponse(AvatarImplantMessage(tplayer.GUID, ImplantAction.Remove, slot, 0))
- sendResponse(ItemTransactionResultMessage(terminal_guid, TransactionType.Sell, true))
- }
- else {
- val message = s"${tplayer.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")
- }
- sendResponse(ItemTransactionResultMessage(terminal_guid, TransactionType.Sell, false))
- }
- lastTerminalOrderFulfillment = true
-
case Terminal.BuyVehicle(vehicle, weapons, trunk) =>
continent.Map.TerminalToSpawnPad.get(msg.terminal_guid.guid) match {
case Some(pad_guid) =>
@@ -2740,8 +2660,7 @@ class WorldSessionActor extends Actor
* @param reply na
*/
def HandleVehicleServiceResponse(toChannel : String, guid : PlanetSideGUID, reply : VehicleResponse.Response) : Unit = {
- val tplayer_guid = if(player.HasGUID) player.GUID
- else PlanetSideGUID(0)
+ val tplayer_guid = if(player.HasGUID) player.GUID else PlanetSideGUID(0)
reply match {
case VehicleResponse.AttachToRails(vehicle_guid, pad_guid) =>
sendResponse(ObjectAttachMessage(pad_guid, vehicle_guid, 3))
@@ -2868,6 +2787,17 @@ class WorldSessionActor extends Actor
sendResponse(VehicleStateMessage(vehicle_guid, unk1, pos, ang, vel, unk2, unk3, unk4, wheel_direction, unk5, unk6))
if(player.VehicleSeated.contains(vehicle_guid)) {
player.Position = pos
+ GetVehicleAndSeat() match {
+ case (Some(_), Some(0)) => ;
+ case (Some(_), Some(_)) =>
+ turnCounter(guid)
+ if (player.death_by == -1) {
+ sendResponse(ChatMsg(ChatMessageType.UNK_71, true, "", "Your account has been logged out by a Customer Service Representative.", None))
+ Thread.sleep(300)
+ sendResponse(DropSession(sessionId, "kick by GM"))
+ }
+ case _ => ;
+ }
}
}
case VehicleResponse.SendResponse(msg) =>
@@ -3173,26 +3103,42 @@ class WorldSessionActor extends Actor
if(player.spectator) {
sendResponse(ChatMsg(ChatMessageType.CMT_TOGGLESPECTATORMODE, false, "", "on", None))
}
- (0 until DetailedCharacterData.numberOfImplantSlots(tplayer.BEP)).foreach(slot => {
+ if(player.Jammed) {
+ //TODO something better than just canceling?
+ player.Actor ! JammableUnit.ClearJammeredStatus()
+ player.Actor ! JammableUnit.ClearJammeredSound()
+ }
+ val fatigued = player.Fatigued
+ (0 until DetailedCharacterData.numberOfImplantSlots(tplayer.BEP)).foreach {slot =>
val implantSlot = player.ImplantSlot(slot)
- if(implantSlot.Initialized) {
- sendResponse(AvatarImplantMessage(guid, ImplantAction.Initialization, slot, 1))
+ implantSlot.Installed match {
+ case Some(_) =>
+ if (implantSlot.Initialized) {
+ sendResponse(AvatarImplantMessage(guid, ImplantAction.Initialization, slot, 1))
+ if (fatigued) {
+ sendResponse(AvatarImplantMessage(guid, ImplantAction.OutOfStamina, slot, 1))
+ }
+ }
+ else if (!fatigued) {
+ player.Actor ! Player.ImplantInitializationStart(slot)
+ }
+ //TODO if this implant is Installed but does not have shortcut, add to a free slot or write over slot 61/62/63
+ // for now, just write into slots 2, 3 and 4
+ val implant = implantSlot.Implant
+ Shortcut.ImplantsMap(implant) match {
+ case shortcut @ Some(_) =>
+ sendResponse(CreateShortcutMessage(guid, slot + 2, 0, addShortcut = true, shortcut))
+ case None if implant != ImplantType.None =>
+ log.warn(s"could not find shortcut for implant $implant")
+ case _ => ;
+ }
+ case _ => ;
}
- else {
- player.Actor ! Player.ImplantInitializationStart(slot)
- }
- //TODO if this implant is Installed but does not have shortcut, add to a free slot or write over slot 61/62/63
- // for now, just write into slots 2, 3 and 4
- Shortcut.ImplantsMap(implantSlot.Implant) match {
- case Some(shortcut : Shortcut) =>
- sendResponse(CreateShortcutMessage(guid, slot + 2, 0, addShortcut = true, Some(shortcut)))
- case _ => log.warn(s"Could not find shortcut for implant ${implantSlot.Implant.toString()}")
- }
- })
+ }
sendResponse(PlanetsideAttributeMessage(PlanetSideGUID(0), 82, 0))
//TODO if Medkit does not have shortcut, add to a free slot or write over slot 64
- sendResponse(CreateShortcutMessage(guid, 1, 0, true, Shortcut.MEDKIT))
+ sendResponse(CreateShortcutMessage(guid, 1, 0, true, Shortcut.Medkit))
sendResponse(ChangeShortcutBankMessage(guid, 0))
//Favorites lists
val (inf, veh) = avatar.EquipmentLoadouts.Loadouts.partition { case (index, _) => index < 10 }
@@ -3265,6 +3211,11 @@ class WorldSessionActor extends Actor
))
case (Some(vehicle), Some(0)) =>
//summon any passengers and cargo vehicles left behind on previous continent
+ if(vehicle.Jammed) {
+ //TODO something better than just canceling?
+ vehicle.Actor ! JammableUnit.ClearJammeredStatus()
+ vehicle.Actor ! JammableUnit.ClearJammeredSound()
+ }
LoadZoneTransferPassengerMessages(
guid,
continent.Id,
@@ -3284,6 +3235,9 @@ class WorldSessionActor extends Actor
//killed during spawn setup or possibly a relog into a corpse (by accident?)
player.Actor ! Player.Die()
}
+ else {
+ tplayer.Actor ! Player.StaminaRegen()
+ }
upstreamMessageCount = 0
}
@@ -3870,27 +3824,13 @@ class WorldSessionActor extends Actor
zoneLoaded = Some(true)
case msg @ PlayerStateMessageUpstream(avatar_guid, pos, vel, yaw, pitch, yaw_upper, seq_time, unk3, is_crouching, is_jumping, jump_thrust, is_cloaking, unk5, unk6) =>
- if (player.death_by == -1) {
- sendResponse(ChatMsg(ChatMessageType.UNK_71, true, "", "Your account has been logged out by a Customer Service Representative.", None))
- Thread.sleep(300)
- sendResponse(DropSession(sessionId, "kick by GM"))
- }
+ //log.info(s"$msg")
turnCounter(avatar_guid)
val isMoving = WorldEntity.isMoving(vel)
val isMovingPlus = isMoving || is_jumping || jump_thrust
if(isMovingPlus) {
CancelZoningProcessWithDescriptiveReason("cancel_motion")
}
-
- if(deadState == DeadState.Alive && upstreamMessageCount % 2 == 0) { // Regen stamina roughly every 500ms
- if(player.skipStaminaRegenForTurns > 0) {
- //do not renew stamina for a while
- player.skipStaminaRegenForTurns -= 1
- }
- else if(!isMovingPlus && player.Stamina != player.MaxStamina) {
- player.Stamina += 1
- }
- }
player.Position = pos
player.Velocity = vel
player.Orientation = Vector3(player.Orientation.x, pitch, yaw)
@@ -3937,8 +3877,14 @@ class WorldSessionActor extends Actor
}
continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.PlayerState(avatar_guid, player.Position, player.Velocity, yaw, pitch, yaw_upper, seq_time, is_crouching, is_jumping, jump_thrust, is_cloaking, player.spectator, wepInHand))
updateSquad()
+ if(player.death_by == -1) {
+ sendResponse(ChatMsg(ChatMessageType.UNK_71, true, "", "Your account has been logged out by a Customer Service Representative.", None))
+ Thread.sleep(300)
+ sendResponse(DropSession(sessionId, "kick by GM"))
+ }
case msg@ChildObjectStateMessage(object_guid, pitch, yaw) =>
+ //log.info(s"$msg")
//the majority of the following check retrieves information to determine if we are in control of the child
FindContainedWeapon match {
case (Some(o), Some(tool)) =>
@@ -3971,49 +3917,47 @@ class WorldSessionActor extends Actor
}
case msg@VehicleStateMessage(vehicle_guid, unk1, pos, ang, vel, flying, unk6, unk7, wheels, is_decelerating, is_cloaked) =>
- if(deadState == DeadState.Alive) {
- GetVehicleAndSeat() match {
- case (Some(obj), Some(0)) =>
- //we're driving the vehicle
- turnCounter(player.GUID)
- val seat = obj.Seats(0)
- player.Position = pos //convenient
- if(seat.ControlledWeapon.isEmpty) {
- player.Orientation = Vector3.z(ang.z) //convenient
+ //log.info(s"$msg")
+ GetVehicleAndSeat() match {
+ case (Some(obj), Some(0)) =>
+ //we're driving the vehicle
+ turnCounter(player.GUID)
+ val seat = obj.Seats(0)
+ player.Position = pos //convenient
+ if(seat.ControlledWeapon.isEmpty) {
+ player.Orientation = Vector3.z(ang.z) //convenient
+ }
+ obj.Position = pos
+ obj.Orientation = ang
+ if(obj.MountedIn.isEmpty) {
+ if(obj.DeploymentState != DriveState.Deployed) {
+ obj.Velocity = vel
+ } else {
+ obj.Velocity = Some(Vector3.Zero)
}
- obj.Position = pos
- obj.Orientation = ang
- if(obj.MountedIn.isEmpty) {
- if(obj.DeploymentState != DriveState.Deployed) {
- obj.Velocity = vel
- } else {
- obj.Velocity = Some(Vector3.Zero)
- }
- if(obj.Definition.CanFly) {
- obj.Flying = flying.nonEmpty //usually Some(7)
- }
- obj.Cloaked = obj.Definition.CanCloak && is_cloaked
+ if(obj.Definition.CanFly) {
+ obj.Flying = flying.nonEmpty //usually Some(7)
}
- else {
- obj.Velocity = None
- obj.Flying = false
- }
- continent.VehicleEvents ! VehicleServiceMessage(continent.Id, VehicleAction.VehicleState(player.GUID, vehicle_guid, unk1, obj.Position, ang, obj.Velocity, if(obj.Flying) {
- flying
- }
- else {
- None
- }, unk6, unk7, wheels, is_decelerating, obj.Cloaked))
- updateSquad()
- case (None, _) =>
- //log.error(s"VehicleState: no vehicle $vehicle_guid found in zone")
- //TODO placing a "not driving" warning here may trigger as we are disembarking the vehicle
- case (_, Some(index)) =>
- log.error(s"VehicleState: player should not be dispatching this kind of packet from vehicle#$vehicle_guid when not the driver ($index)")
- case _ => ;
- }
+ obj.Cloaked = obj.Definition.CanCloak && is_cloaked
+ }
+ else {
+ obj.Velocity = None
+ obj.Flying = false
+ }
+ continent.VehicleEvents ! VehicleServiceMessage(continent.Id, VehicleAction.VehicleState(player.GUID, vehicle_guid, unk1, obj.Position, ang, obj.Velocity, if(obj.Flying) {
+ flying
+ }
+ else {
+ None
+ }, unk6, unk7, wheels, is_decelerating, obj.Cloaked))
+ updateSquad()
+ case (None, _) =>
+ //log.error(s"VehicleState: no vehicle $vehicle_guid found in zone")
+ //TODO placing a "not driving" warning here may trigger as we are disembarking the vehicle
+ case (_, Some(index)) =>
+ log.error(s"VehicleState: player should not be dispatching this kind of packet from vehicle#$vehicle_guid when not the driver ($index)")
+ case _ => ;
}
- //log.info(s"VehicleState: $msg")
if (player.death_by == -1) {
sendResponse(ChatMsg(ChatMessageType.UNK_71, true, "", "Your account has been logged out by a Customer Service Representative.", None))
Thread.sleep(300)
@@ -4825,7 +4769,7 @@ class WorldSessionActor extends Actor
case msg@AvatarJumpMessage(state) =>
//log.info("AvatarJump: " + msg)
- player.Stamina = player.Stamina - 10
+ player.Actor ! Player.StaminaChanged(-10)
player.skipStaminaRegenForTurns = math.max(player.skipStaminaRegenForTurns, 5)
case msg@ZipLineMessage(player_guid, forwards, action, path_id, pos) =>
@@ -5123,7 +5067,7 @@ class WorldSessionActor extends Actor
false
}
else {
- player.Stamina = player.Stamina + 100
+ player.Actor ! Player.StaminaChanged(100)
sendResponse(PlanetsideAttributeMessage(avatar_guid, 2, player.Stamina))
true
}
@@ -5614,7 +5558,8 @@ class WorldSessionActor extends Actor
}
else { //shooting
if (tool.FireModeIndex == 1 && (tool.Definition.Name == "anniversary_guna" || tool.Definition.Name == "anniversary_gun" || tool.Definition.Name == "anniversary_gunb")) {
- player.Stamina = 0
+ player.Actor ! Player.StaminaChanged(-player.Stamina)
+ player.skipStaminaRegenForTurns = math.max(player.skipStaminaRegenForTurns, 3)
}
prefire = shooting.orElse(Some(weapon_guid))
@@ -8165,6 +8110,15 @@ class WorldSessionActor extends Actor
val player_guid : PlanetSideGUID = tplayer.GUID
val obj_guid : PlanetSideGUID = obj.GUID
PlayerActionsToCancel()
+ //deactivate non-passive implants
+ tplayer.Implants.indices.foreach { index =>
+ val implantSlot = tplayer.ImplantSlot(index)
+ if(implantSlot.Active && implantSlot.Charge(tplayer.ExoSuit) > 0) {
+ tplayer.Actor ! Player.ImplantActivation(index, 0)
+ }
+ }
+ //delay regen
+ player.skipStaminaRegenForTurns = math.max(player.skipStaminaRegenForTurns, 6)
log.info(s"MountVehicleMsg: ${player.Name}_guid mounts $obj @ $seatNum")
sendResponse(ObjectAttachMessage(obj_guid, player_guid, seatNum))
continent.VehicleEvents ! VehicleServiceMessage(continent.Id, VehicleAction.MountVehicle(player_guid, obj_guid, seatNum))
@@ -8765,6 +8719,13 @@ class WorldSessionActor extends Actor
LoadZoneAsPlayer(newPlayer, zone_id)
}
else {
+ //deactivate non-passive implants
+ player.Implants.indices.foreach { index =>
+ val implantSlot = player.ImplantSlot(index)
+ if(implantSlot.Active && implantSlot.Charge(player.ExoSuit) > 0) {
+ player.Actor ! Player.ImplantActivation(index, 0)
+ }
+ }
interstellarFerry.orElse(continent.GUID(player.VehicleSeated)) match {
case Some(vehicle : Vehicle) => //driver or passenger in vehicle using a warp gate, or a droppod
LoadZoneInVehicle(vehicle, pos, ori, zone_id)
@@ -10150,8 +10111,11 @@ class WorldSessionActor extends Actor
}
def DeactivateImplants() : Unit = {
- for(slot <- 0 to player.Implants.length - 1) {
- player.Actor ! Player.ImplantActivation(slot, 0)
+ //TODO 3 implant slots?
+ player.Implants.indices.foreach { slot =>
+ if(player.ImplantSlot(slot).Active) {
+ player.Actor ! Player.ImplantActivation(slot, 0)
+ }
}
}