mirror of
https://github.com/2revoemag/PSF-BotServer.git
synced 2026-07-12 06:55:10 +00:00
Stamina / Implants (#485)
* removing stamina update business logic from the Player class * I really need to sort this out * implant changes: deactivate when changing armor or loadouts and when zoning; extra details for state management when being jammed or fatigued upon loading * merge with master * pull rebase on master; moved implant learning/forgetting to PlayerControl, but not yet completely tested * unhandled case of no implant in a slot during avatar setup * complete implant deactivation optional? * moved reference to player control agency for matters concerning stamina updates on damage taken * - > + * crouching makes nothing better * PlayerControl now handles stamina regeneration; handling a case where being fatigued because activation charge is too much leaves the drain timer running * no more stamina drain message; moving functionality into the class (out of the object) for expediency; handling implant uninitialization differently upon death * test repairs; redundant messages in player damage and player death logic * no jumping; riders get a free pass * making the code uglier
This commit is contained in:
parent
3ea51d404e
commit
181fdb9c84
10 changed files with 737 additions and 536 deletions
|
|
@ -202,7 +202,9 @@ class Avatar(private val char_id : Long, val name : String, val faction : Planet
|
||||||
implants.foreach(slot => {
|
implants.foreach(slot => {
|
||||||
slot.Installed match {
|
slot.Installed match {
|
||||||
case Some(_) =>
|
case Some(_) =>
|
||||||
|
slot.Active = false
|
||||||
slot.Initialized = false
|
slot.Initialized = false
|
||||||
|
slot.InitializeTime = 0L
|
||||||
case None => ;
|
case None => ;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
@ -302,7 +304,7 @@ class Avatar(private val char_id : Long, val name : String, val faction : Planet
|
||||||
|
|
||||||
def ObjectTypeNameReference(id : Long) : String = {
|
def ObjectTypeNameReference(id : Long) : String = {
|
||||||
objectTypeNameReference.get(id) match {
|
objectTypeNameReference.get(id) match {
|
||||||
case Some(name) => name
|
case Some(objectName) => objectName
|
||||||
case None => ""
|
case None => ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
// Copyright (c) 2017 PSForever
|
// Copyright (c) 2017 PSForever
|
||||||
package net.psforever.objects
|
package net.psforever.objects
|
||||||
|
|
||||||
import akka.actor.Cancellable
|
|
||||||
import net.psforever.objects.definition.ImplantDefinition
|
import net.psforever.objects.definition.ImplantDefinition
|
||||||
import net.psforever.types.{ExoSuitType, ImplantType}
|
import net.psforever.types.{ExoSuitType, ImplantType}
|
||||||
|
|
||||||
|
|
@ -19,19 +18,18 @@ class ImplantSlot {
|
||||||
private var unlocked : Boolean = false
|
private var unlocked : Boolean = false
|
||||||
/** whether this implant is ready for use */
|
/** whether this implant is ready for use */
|
||||||
private var initialized : Boolean = false
|
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 */
|
/** is this implant active */
|
||||||
private var active : Boolean = false
|
private var active : Boolean = false
|
||||||
/** what implant is currently installed in this slot; None if there is no implant currently installed */
|
/** what implant is currently installed in this slot; None if there is no implant currently installed */
|
||||||
private var implant : Option[ImplantDefinition] = None
|
private var implant : Option[ImplantDefinition] = None
|
||||||
|
|
||||||
def InitializeTimer : Cancellable = initializeTimer
|
def InitializeTime : Long = initializeTime
|
||||||
|
|
||||||
def InitializeTimer_=(timer : Cancellable) : Cancellable = {
|
def InitializeTime_=(time : Long) : Long = {
|
||||||
initializeTimer = timer
|
initializeTime = time
|
||||||
initializeTimer
|
InitializeTime
|
||||||
}
|
}
|
||||||
|
|
||||||
def Unlocked : Boolean = unlocked
|
def Unlocked : Boolean = unlocked
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
// Copyright (c) 2017 PSForever
|
// Copyright (c) 2017 PSForever
|
||||||
package net.psforever.objects
|
package net.psforever.objects
|
||||||
|
|
||||||
import akka.actor.ActorRef
|
|
||||||
import net.psforever.objects.avatar.LoadoutManager
|
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.equipment.{Equipment, EquipmentSize, EquipmentSlot, JammableUnit}
|
||||||
import net.psforever.objects.inventory.{Container, GridInventory, InventoryItem}
|
import net.psforever.objects.inventory.{Container, GridInventory, InventoryItem}
|
||||||
import net.psforever.objects.serverobject.PlanetSideServerObject
|
import net.psforever.objects.serverobject.PlanetSideServerObject
|
||||||
|
|
@ -124,11 +123,6 @@ class Player(private val core : Avatar) extends PlanetSideServerObject
|
||||||
|
|
||||||
def Stamina_=(assignStamina : Int) : Int = {
|
def Stamina_=(assignStamina : Int) : Int = {
|
||||||
stamina = if(isAlive) { math.min(math.max(0, assignStamina), MaxStamina) } else { 0 }
|
stamina = if(isAlive) { math.min(math.max(0, assignStamina), MaxStamina) } else { 0 }
|
||||||
|
|
||||||
if(Actor != Default.Actor) {
|
|
||||||
Actor ! Player.StaminaChanged(Stamina)
|
|
||||||
}
|
|
||||||
|
|
||||||
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) })
|
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 ResetAllImplants() : Unit = core.ResetAllImplants()
|
||||||
|
|
||||||
def FacingYawUpper : Float = facingYawUpper
|
def FacingYawUpper : Float = facingYawUpper
|
||||||
|
|
@ -674,8 +672,12 @@ object Player {
|
||||||
final case class ImplantInitializationStart(slot : Int)
|
final case class ImplantInitializationStart(slot : Int)
|
||||||
final case class UninitializeImplant(slot : Int)
|
final case class UninitializeImplant(slot : Int)
|
||||||
final case class ImplantInitializationComplete(slot : Int)
|
final case class ImplantInitializationComplete(slot : Int)
|
||||||
final case class DrainStamina(amount : Int)
|
final case class StaminaRegen()
|
||||||
final case class StaminaChanged(currentStamina : Int)
|
final case class StaminaChanged(currentStamina : Option[Int] = None)
|
||||||
|
|
||||||
|
object StaminaChanged {
|
||||||
|
def apply(amount : Int) : StaminaChanged = StaminaChanged(Some(amount))
|
||||||
|
}
|
||||||
|
|
||||||
def apply(core : Avatar) : Player = {
|
def apply(core : Avatar) : Player = {
|
||||||
new Player(core)
|
new Player(core)
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
// Copyright (c) 2020 PSForever
|
// Copyright (c) 2020 PSForever
|
||||||
package net.psforever.objects.avatar
|
package net.psforever.objects.avatar
|
||||||
|
|
||||||
import akka.actor.{Actor, ActorRef, Props}
|
import akka.actor.{Actor, ActorRef, Cancellable, Props}
|
||||||
import net.psforever.objects._
|
import net.psforever.objects.{Player, _}
|
||||||
import net.psforever.objects.ballistics.{PlayerSource, ResolvedProjectile}
|
import net.psforever.objects.ballistics.{PlayerSource, ResolvedProjectile}
|
||||||
import net.psforever.objects.definition.ImplantDefinition
|
|
||||||
import net.psforever.objects.equipment._
|
import net.psforever.objects.equipment._
|
||||||
import net.psforever.objects.inventory.{GridInventory, InventoryItem}
|
import net.psforever.objects.inventory.{GridInventory, InventoryItem}
|
||||||
import net.psforever.objects.loadouts.Loadout
|
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 log = org.log4s.getLogger(player.Name)
|
||||||
private [this] val damageLog = org.log4s.getLogger(Damageable.LogChannel)
|
private [this] val damageLog = org.log4s.getLogger(Damageable.LogChannel)
|
||||||
|
|
||||||
// A collection of timers for each slot to trigger stamina drain on an interval
|
/** Stamina will be used. Stamina will be restored. */
|
||||||
val implantSlotStaminaDrainTimers = mutable.HashMap(0 -> Default.Cancellable, 1 -> Default.Cancellable, 2 -> Default.Cancellable)
|
var staminaRegen : Cancellable = Default.Cancellable
|
||||||
// control agency for the player's locker container (dedicated inventory slot #5)
|
/**
|
||||||
|
* 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 lockerControlAgent : ActorRef = {
|
||||||
val locker = player.Locker
|
val locker = player.Locker
|
||||||
locker.Zone = player.Zone
|
locker.Zone = player.Zone
|
||||||
|
|
@ -51,106 +55,55 @@ class PlayerControl(player : Player) extends Actor
|
||||||
override def postStop() : Unit = {
|
override def postStop() : Unit = {
|
||||||
lockerControlAgent ! akka.actor.PoisonPill
|
lockerControlAgent ! akka.actor.PoisonPill
|
||||||
player.Locker.Actor = Default.Actor
|
player.Locker.Actor = Default.Actor
|
||||||
implantSlotStaminaDrainTimers.values.foreach { _.cancel }
|
staminaRegen.cancel
|
||||||
|
implantSlotTimers.values.foreach { _.cancel }
|
||||||
}
|
}
|
||||||
|
|
||||||
def receive : Receive = jammableBehavior
|
def receive : Receive = jammableBehavior
|
||||||
.orElse(takesDamage)
|
.orElse(takesDamage)
|
||||||
.orElse(containerBehavior)
|
.orElse(containerBehavior)
|
||||||
.orElse {
|
.orElse {
|
||||||
case Player.ImplantActivation(slot: Int, status : Int) =>
|
case Player.ImplantActivation(slot : Int, status : Int) =>
|
||||||
// todo: disable implants with stamina cost when changing armour type
|
ImplantActivation(slot, status)
|
||||||
val implantSlot = player.ImplantSlot(slot)
|
|
||||||
|
|
||||||
// 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)
|
case Player.UninitializeImplant(slot : Int) =>
|
||||||
if(status == 0 && (implantSlot.Active || !implantSlot.Initialized)) {
|
UninitializeImplant(slot)
|
||||||
// Cancel stamina drain timer
|
|
||||||
implantSlotStaminaDrainTimers(slot).cancel()
|
|
||||||
implantSlotStaminaDrainTimers(slot) = Default.Cancellable
|
|
||||||
|
|
||||||
implantSlot.Active = false
|
case Player.ImplantInitializationStart(slot : Int) =>
|
||||||
player.Zone.AvatarEvents ! AvatarServiceMessage(player.Zone.Id, AvatarAction.PlanetsideAttribute(player.GUID, 28, player.Implant(slot).id * 2)) // Deactivation sound / effect
|
ImplantInitializationStart(slot)
|
||||||
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
|
|
||||||
|
|
||||||
if (implant.ActivationStaminaCost >= 0) {
|
case Player.ImplantInitializationComplete(slot : Int) =>
|
||||||
player.Stamina -= implant.ActivationStaminaCost // Activation stamina drain
|
ImplantInitializationComplete(slot)
|
||||||
}
|
|
||||||
|
|
||||||
if(implant.StaminaCost > 0 && implant.GetCostIntervalByExoSuit(player.ExoSuit) > 0) { // Ongoing stamina drain, if applicable
|
case Player.StaminaRegen() =>
|
||||||
implantSlotStaminaDrainTimers(slot) = context.system.scheduler.scheduleWithFixedDelay(0 seconds, implant.GetCostIntervalByExoSuit(player.ExoSuit) milliseconds, self, Player.DrainStamina(implant.StaminaCost))
|
if(staminaRegen == Default.Cancellable) {
|
||||||
}
|
staminaRegen.cancel
|
||||||
|
staminaRegen = context.system.scheduler.scheduleOnce(delay = 500 milliseconds, self, PlayerControl.StaminaRegen())
|
||||||
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.UninitializeImplant(slot: Int) =>
|
case PlayerControl.StaminaRegen() =>
|
||||||
PlayerControl.UninitializeImplant(player, slot)
|
staminaRegen.cancel
|
||||||
|
if (player.isAlive) {
|
||||||
case Player.ImplantInitializationStart(slot: Int) =>
|
if (player.skipStaminaRegenForTurns > 0) {
|
||||||
val implantSlot = player.ImplantSlot(slot)
|
// Do not renew stamina for a while
|
||||||
if(implantSlot.Installed.isDefined) {
|
player.skipStaminaRegenForTurns -= 1
|
||||||
if(implantSlot.Initialized) {
|
|
||||||
PlayerControl.UninitializeImplant(player, slot)
|
|
||||||
}
|
}
|
||||||
|
else if ((player.VehicleSeated.nonEmpty || !player.isMoving && !player.Jumping) && player.Stamina < player.MaxStamina) {
|
||||||
// Start client side initialization timer
|
// Regen stamina roughly every 500ms
|
||||||
player.Zone.AvatarEvents ! AvatarServiceMessage(player.Name, AvatarAction.SendResponse(player.GUID, ActionProgressMessage(slot + 6, 0)))
|
StaminaChanged(changeInStamina = 1)
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
staminaRegen = context.system.scheduler.scheduleOnce(delay = 500 milliseconds, self, PlayerControl.StaminaRegen())
|
||||||
|
|
||||||
case Player.DrainStamina(amount : Int) =>
|
case Player.StaminaChanged(Some(changeInStamina)) =>
|
||||||
player.Stamina -= amount
|
StaminaChanged(changeInStamina)
|
||||||
|
|
||||||
case Player.StaminaChanged(currentStamina : Int) =>
|
case Player.StaminaChanged(None) =>
|
||||||
if(currentStamina == 0) {
|
UpdateStamina()
|
||||||
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.Die() =>
|
case Player.Die() =>
|
||||||
if(player.isAlive) {
|
if(player.isAlive) {
|
||||||
PlayerControl.DestructionAwareness(player, None)
|
DestructionAwareness(player, None)
|
||||||
}
|
}
|
||||||
|
|
||||||
case CommonMessages.Use(user, Some(item : Tool)) if item.Definition == GlobalDefinitions.medicalapplicator && player.isAlive =>
|
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 =>
|
stow.foreach { elem =>
|
||||||
player.Inventory.InsertQuickly(elem.start, elem.obj)
|
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,
|
player.Zone.AvatarEvents ! AvatarServiceMessage(player.Zone.Id,
|
||||||
AvatarAction.ChangeExosuit(player.GUID, exosuit, subtype, player.LastDrawnSlot, exosuit == ExoSuitType.MAX && requestToChangeArmor,
|
AvatarAction.ChangeExosuit(player.GUID, exosuit, subtype, player.LastDrawnSlot, exosuit == ExoSuitType.MAX && requestToChangeArmor,
|
||||||
beforeHolsters.map { case InventoryItem(obj, _) => (obj, obj.GUID) }, afterHolsters,
|
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 }
|
(afterHolsters ++ afterInventory).foreach { entry => entry.obj.Faction = player.Faction }
|
||||||
toDeleteOrDrop.foreach { entry => entry.obj.Faction = PlanetSideEmpire.NEUTRAL }
|
toDeleteOrDrop.foreach { entry => entry.obj.Faction = PlanetSideEmpire.NEUTRAL }
|
||||||
|
//deactivate non-passive implants
|
||||||
|
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,
|
player.Zone.AvatarEvents ! AvatarServiceMessage(player.Zone.Id,
|
||||||
AvatarAction.ChangeLoadout(player.GUID, nextSuit, nextSubtype, player.LastDrawnSlot, exosuit == ExoSuitType.MAX,
|
AvatarAction.ChangeLoadout(player.GUID, nextSuit, nextSubtype, player.LastDrawnSlot, exosuit == ExoSuitType.MAX,
|
||||||
oldHolsters.map { case InventoryItem(obj, _) => (obj, obj.GUID) }, afterHolsters,
|
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))
|
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
|
case _ => ; //terminal messages not handled here
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -490,21 +543,189 @@ class PlayerControl(player : Player) extends Actor
|
||||||
if(player.isAlive) {
|
if(player.isAlive) {
|
||||||
val originalHealth = player.Health
|
val originalHealth = player.Health
|
||||||
val originalArmor = player.Armor
|
val originalArmor = player.Armor
|
||||||
|
val originalStamina = player.Stamina
|
||||||
val originalCapacitor = player.Capacitor.toInt
|
val originalCapacitor = player.Capacitor.toInt
|
||||||
val cause = applyDamageTo(player)
|
val cause = applyDamageTo(player)
|
||||||
val health = player.Health
|
val health = player.Health
|
||||||
val armor = player.Armor
|
val armor = player.Armor
|
||||||
|
val stamina = player.Stamina
|
||||||
val capacitor = player.Capacitor.toInt
|
val capacitor = player.Capacitor.toInt
|
||||||
val damageToHealth = originalHealth - health
|
val damageToHealth = originalHealth - health
|
||||||
val damageToArmor = originalArmor - armor
|
val damageToArmor = originalArmor - armor
|
||||||
|
val damageToStamina = originalStamina - stamina
|
||||||
val damageToCapacitor = originalCapacitor - capacitor
|
val damageToCapacitor = originalCapacitor - capacitor
|
||||||
PlayerControl.HandleDamage(player, cause, damageToHealth, damageToArmor, damageToCapacitor)
|
HandleDamage(player, cause, damageToHealth, damageToArmor, damageToStamina, damageToCapacitor)
|
||||||
if(damageToHealth > 0 || damageToArmor > 0 || damageToCapacitor > 0) {
|
if(damageToHealth > 0 || damageToArmor > 0 || damageToStamina > 0 || damageToCapacitor > 0) {
|
||||||
damageLog.info(s"${player.Name}-infantry: BEFORE=$originalHealth/$originalArmor/$originalCapacitor, AFTER=$health/$armor/$capacitor, CHANGE=$damageToHealth/$damageToArmor/$damageToCapacitor")
|
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.<br>
|
||||||
|
* <br>
|
||||||
|
* 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.<br>
|
||||||
|
* <br>
|
||||||
|
* 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.
|
* Start the jammered buzzing.
|
||||||
* Although, as a rule, the jammering sound effect should last as long as the jammering status,
|
* 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 target an object that can be affected by the jammered status
|
||||||
* @param dur the duration of the timer, in milliseconds
|
* @param dur the duration of the timer, in milliseconds
|
||||||
*/
|
*/
|
||||||
override def StartJammeredStatus(target : Any, dur : Int) : Unit = target match {
|
override def StartJammeredStatus(target : Any, dur : Int) : Unit = {
|
||||||
case obj : Player =>
|
//TODO these features
|
||||||
//TODO these features
|
val zone = player.Zone
|
||||||
// val guid = obj.GUID
|
player.Implants.indices.foreach { slot => // Deactivate & uninitialize all implants
|
||||||
// val zone = obj.Zone
|
zone.AvatarEvents ! AvatarServiceMessage(zone.Id, AvatarAction.PlanetsideAttribute(player.GUID, 28, player.Implant(slot).id * 2)) // Deactivation sound / effect
|
||||||
// val zoneId = zone.Id
|
ImplantActivation(slot, status = 0)
|
||||||
// val events = zone.AvatarEvents
|
UninitializeImplant(slot)
|
||||||
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
|
player.skipStaminaRegenForTurns = math.max(player.skipStaminaRegenForTurns, 10)
|
||||||
self ! Player.ImplantActivation(slot, 0)
|
super.StartJammeredStatus(target, dur)
|
||||||
PlayerControl.UninitializeImplant(player, slot)
|
|
||||||
}
|
|
||||||
|
|
||||||
obj.skipStaminaRegenForTurns = math.max(obj.skipStaminaRegenForTurns, 10)
|
|
||||||
super.StartJammeredStatus(target, dur)
|
|
||||||
case _ => ;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override def CancelJammeredStatus(target: Any): Unit = {
|
override def CancelJammeredStatus(target: Any): Unit = {
|
||||||
player.Implants.indices.foreach { slot => // Start reinitializing all implants
|
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)
|
super.CancelJammeredStatus(target)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -664,175 +879,200 @@ class PlayerControl(player : Player) extends Actor
|
||||||
val zone = obj.Zone
|
val zone = obj.Zone
|
||||||
zone.AvatarEvents ! AvatarServiceMessage(player.Name, AvatarAction.SendResponse(Service.defaultPlayerGUID, ObjectDetachMessage(obj.GUID, item.GUID, Vector3.Zero, 0f)))
|
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 {
|
object PlayerControl {
|
||||||
/**
|
/** */
|
||||||
* na
|
private case class StaminaRegen()
|
||||||
* @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.<br>
|
|
||||||
* <br>
|
|
||||||
* 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.<br>
|
|
||||||
* <br>
|
|
||||||
* 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)))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -245,7 +245,7 @@ object OrderTerminalDefinition {
|
||||||
}
|
}
|
||||||
|
|
||||||
def Dispatch(sender : ActorRef, terminal : Terminal, msg : Terminal.TerminalMessage) : Unit = {
|
def Dispatch(sender : ActorRef, terminal : Terminal, msg : Terminal.TerminalMessage) : Unit = {
|
||||||
sender ! msg
|
msg.player.Actor ! msg
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 any health damage was applied also drain an amount of stamina equal to half the health damage
|
||||||
if(player.Health < originalHealth) {
|
if(player.Health < originalHealth) {
|
||||||
val delta = originalHealth - player.Health
|
val delta = originalHealth - player.Health
|
||||||
player.Stamina = player.Stamina - math.floor(delta / 2).toInt
|
player.Stamina -= math.floor(delta / 2).toInt
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case _ =>
|
case _ =>
|
||||||
|
|
|
||||||
|
|
@ -89,52 +89,41 @@ object Shortcut extends Marshallable[Shortcut] {
|
||||||
/**
|
/**
|
||||||
A map to convert between ImplantTypes and Implant Shortcuts
|
A map to convert between ImplantTypes and Implant Shortcuts
|
||||||
*/
|
*/
|
||||||
final lazy val ImplantsMap = Map(
|
final lazy val ImplantsMap : Map[ImplantType.Value, Option[Shortcut]] = Map(
|
||||||
ImplantType.AdvancedRegen->REGENERATION,
|
ImplantType.AdvancedRegen->Regeneration,
|
||||||
ImplantType.Targeting->ENHANCED_TARGETING,
|
ImplantType.Targeting->EnhancedTargeting,
|
||||||
ImplantType.AudioAmplifier->AUDIO_AMPLIFIER,
|
ImplantType.AudioAmplifier->AudioAmplifier,
|
||||||
ImplantType.DarklightVision->DARKLIGHT_VISION,
|
ImplantType.DarklightVision->DartklightVision,
|
||||||
ImplantType.MeleeBooster->MELEE_BOOSTER,
|
ImplantType.MeleeBooster->MeleeBooster,
|
||||||
ImplantType.PersonalShield->PERSONAL_SHIELD,
|
ImplantType.PersonalShield->PersonalShield,
|
||||||
ImplantType.RangeMagnifier->RANGE_MAGNIFIER,
|
ImplantType.RangeMagnifier->RangeMagnifier,
|
||||||
ImplantType.SecondWind->SECOND_WIND,
|
ImplantType.SecondWind->SecondWind,
|
||||||
ImplantType.SilentRun->SENSOR_SHIELD,
|
ImplantType.SilentRun->SensorShield,
|
||||||
ImplantType.Surge->SURGE
|
ImplantType.Surge->Surge
|
||||||
)
|
).withDefaultValue(None)
|
||||||
|
|
||||||
/**
|
/** Preset for the Audio Amplifier implant. */
|
||||||
* Preset for the Audio Amplifier implant. */
|
final val AudioAmplifier : Some[Shortcut] = Some(Shortcut(2, "audio_amplifier"))
|
||||||
final val AUDIO_AMPLIFIER : 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 Darklight Vision implant. */
|
/** Preset for the Enhanced Targeting implant. */
|
||||||
final val DARKLIGHT_VISION : Some[Shortcut] = Some(Shortcut(2, "darklight_vision"))
|
final val EnhancedTargeting : Some[Shortcut] = Some(Shortcut(2, "targeting"))
|
||||||
/**
|
/** Preset for the medkit quick-use option. */
|
||||||
* Preset for the Enhanced Targeting implant. */
|
final val Medkit : Some[Shortcut] = Some(Shortcut(0, "medkit"))
|
||||||
final val ENHANCED_TARGETING : Some[Shortcut] = Some(Shortcut(2, "targeting"))
|
/** Preset for the Melee Booster implant. */
|
||||||
/**
|
final val MeleeBooster : Some[Shortcut] = Some(Shortcut(2, "melee_booster"))
|
||||||
* Preset for the medkit quick-use option. */
|
/** Preset for the Personal Shield implant. */
|
||||||
final val MEDKIT : Some[Shortcut] = Some(Shortcut(0, "medkit"))
|
final val PersonalShield : Some[Shortcut] = Some(Shortcut(2, "personal_shield"))
|
||||||
/**
|
/** Preset for the Range Magnifier implant. */
|
||||||
* Preset for the Melee Booster implant. */
|
final val RangeMagnifier : Some[Shortcut] = Some(Shortcut(2, "range_magnifier"))
|
||||||
final val MELEE_BOOSTER : Some[Shortcut] = Some(Shortcut(2, "melee_booster"))
|
/** Preset for the Regeneration implant. */
|
||||||
/**
|
final val Regeneration : Some[Shortcut] = Some(Shortcut(2, "advanced_regen"))
|
||||||
* Preset for the Personal Shield implant. */
|
/** Preset for the Second Wind implant. */
|
||||||
final val PERSONAL_SHIELD : Some[Shortcut] = Some(Shortcut(2, "personal_shield"))
|
final val SecondWind : Some[Shortcut] = Some(Shortcut(2, "second_wind"))
|
||||||
/**
|
/** Preset for the Sensor Shield implant. */
|
||||||
* Preset for the Range Magnifier implant. */
|
final val SensorShield : Some[Shortcut] = Some(Shortcut(2, "silent_run"))
|
||||||
final val RANGE_MAGNIFIER : Some[Shortcut] = Some(Shortcut(2, "range_magnifier"))
|
/** Preset for the Surge implant. */
|
||||||
/**
|
final val Surge : Some[Shortcut] = Some(Shortcut(2, "surge"))
|
||||||
* 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"))
|
|
||||||
/**
|
/**
|
||||||
* Converter for text macro parameters that acts like a preset.
|
* Converter for text macro parameters that acts like a preset.
|
||||||
* @param effect1 a three letter acronym displayed in the hotbar
|
* @param effect1 a three letter acronym displayed in the hotbar
|
||||||
|
|
|
||||||
|
|
@ -89,27 +89,27 @@ class CreateShortcutMessageTest extends Specification {
|
||||||
}
|
}
|
||||||
|
|
||||||
"presets" in {
|
"presets" in {
|
||||||
Shortcut.AUDIO_AMPLIFIER.get.purpose mustEqual 2
|
Shortcut.AudioAmplifier.get.purpose mustEqual 2
|
||||||
Shortcut.AUDIO_AMPLIFIER.get.tile mustEqual "audio_amplifier"
|
Shortcut.AudioAmplifier.get.tile mustEqual "audio_amplifier"
|
||||||
Shortcut.DARKLIGHT_VISION.get.purpose mustEqual 2
|
Shortcut.DartklightVision.get.purpose mustEqual 2
|
||||||
Shortcut.DARKLIGHT_VISION.get.tile mustEqual "darklight_vision"
|
Shortcut.DartklightVision.get.tile mustEqual "darklight_vision"
|
||||||
Shortcut.ENHANCED_TARGETING.get.purpose mustEqual 2
|
Shortcut.EnhancedTargeting.get.purpose mustEqual 2
|
||||||
Shortcut.ENHANCED_TARGETING.get.tile mustEqual "targeting"
|
Shortcut.EnhancedTargeting.get.tile mustEqual "targeting"
|
||||||
Shortcut.MEDKIT.get.purpose mustEqual 0
|
Shortcut.Medkit.get.purpose mustEqual 0
|
||||||
Shortcut.MEDKIT.get.tile mustEqual "medkit"
|
Shortcut.Medkit.get.tile mustEqual "medkit"
|
||||||
Shortcut.MELEE_BOOSTER.get.purpose mustEqual 2
|
Shortcut.MeleeBooster.get.purpose mustEqual 2
|
||||||
Shortcut.MELEE_BOOSTER.get.tile mustEqual "melee_booster"
|
Shortcut.MeleeBooster.get.tile mustEqual "melee_booster"
|
||||||
Shortcut.PERSONAL_SHIELD.get.purpose mustEqual 2
|
Shortcut.PersonalShield.get.purpose mustEqual 2
|
||||||
Shortcut.PERSONAL_SHIELD.get.tile mustEqual "personal_shield"
|
Shortcut.PersonalShield.get.tile mustEqual "personal_shield"
|
||||||
Shortcut.RANGE_MAGNIFIER.get.purpose mustEqual 2
|
Shortcut.RangeMagnifier.get.purpose mustEqual 2
|
||||||
Shortcut.RANGE_MAGNIFIER.get.tile mustEqual "range_magnifier"
|
Shortcut.RangeMagnifier.get.tile mustEqual "range_magnifier"
|
||||||
Shortcut.REGENERATION.get.purpose mustEqual 2
|
Shortcut.Regeneration.get.purpose mustEqual 2
|
||||||
Shortcut.REGENERATION.get.tile mustEqual "advanced_regen"
|
Shortcut.Regeneration.get.tile mustEqual "advanced_regen"
|
||||||
Shortcut.SECOND_WIND.get.purpose mustEqual 2
|
Shortcut.SecondWind.get.purpose mustEqual 2
|
||||||
Shortcut.SECOND_WIND.get.tile mustEqual "second_wind"
|
Shortcut.SecondWind.get.tile mustEqual "second_wind"
|
||||||
Shortcut.SENSOR_SHIELD.get.purpose mustEqual 2
|
Shortcut.SensorShield.get.purpose mustEqual 2
|
||||||
Shortcut.SENSOR_SHIELD.get.tile mustEqual "silent_run"
|
Shortcut.SensorShield.get.tile mustEqual "silent_run"
|
||||||
Shortcut.SURGE.get.purpose mustEqual 2
|
Shortcut.Surge.get.purpose mustEqual 2
|
||||||
Shortcut.SURGE.get.tile mustEqual "surge"
|
Shortcut.Surge.get.tile mustEqual "surge"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -343,17 +343,23 @@ class PlayerControlDamageTest extends ActorTest {
|
||||||
assert(player2.Health == player2.Definition.DefaultHealth)
|
assert(player2.Health == player2.Definition.DefaultHealth)
|
||||||
assert(player2.Armor == player2.MaxArmor)
|
assert(player2.Armor == player2.MaxArmor)
|
||||||
player2.Actor ! Vitality.Damage(applyDamageTo)
|
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)
|
val msg_activity = activityProbe.receiveOne(200 milliseconds)
|
||||||
assert(
|
assert(
|
||||||
msg_avatar.head match {
|
msg_avatar.head match {
|
||||||
case AvatarServiceMessage("test", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 0, _)) => true
|
case AvatarServiceMessage("test", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 4, _)) => true
|
||||||
case _ => false
|
case _ => false
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
assert(
|
assert(
|
||||||
msg_avatar(1) match {
|
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
|
case _ => false
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
@ -367,7 +373,7 @@ class PlayerControlDamageTest extends ActorTest {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
assert(
|
assert(
|
||||||
msg_avatar(2) match {
|
msg_avatar(3) match {
|
||||||
case AvatarServiceMessage("TestCharacter2", AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(10, Vector3(2, 0, 0)))) => true
|
case AvatarServiceMessage("TestCharacter2", AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(10, Vector3(2, 0, 0)))) => true
|
||||||
case _ => false
|
case _ => false
|
||||||
}
|
}
|
||||||
|
|
@ -440,19 +446,19 @@ class PlayerControlDeathStandingTest extends ActorTest {
|
||||||
)
|
)
|
||||||
assert(
|
assert(
|
||||||
msg_avatar(1) match {
|
msg_avatar(1) match {
|
||||||
case AvatarServiceMessage("TestCharacter2", AvatarAction.Killed(PlanetSideGUID(2), None)) => true
|
case AvatarServiceMessage("TestCharacter2", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 2, _)) => true
|
||||||
case _ => false
|
case _ => false
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
assert(
|
assert(
|
||||||
msg_avatar(2) match {
|
msg_avatar(2) match {
|
||||||
case AvatarServiceMessage("test", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 0, _)) => true
|
case AvatarServiceMessage("TestCharacter2", AvatarAction.Killed(PlanetSideGUID(2), None)) => true
|
||||||
case _ => false
|
case _ => false
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
assert(
|
assert(
|
||||||
msg_avatar(3) match {
|
msg_avatar(3) match {
|
||||||
case AvatarServiceMessage("TestCharacter2", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 2, _)) => true
|
case AvatarServiceMessage("test", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 0, _)) => true
|
||||||
case _ => false
|
case _ => false
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
@ -546,12 +552,18 @@ class PlayerControlDeathSeatedTest extends ActorTest {
|
||||||
activityProbe.expectNoMessage(200 milliseconds)
|
activityProbe.expectNoMessage(200 milliseconds)
|
||||||
assert(
|
assert(
|
||||||
msg_avatar.head match {
|
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
|
case _ => false
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
assert(
|
assert(
|
||||||
msg_avatar(1) match {
|
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(_,
|
case AvatarServiceMessage("TestCharacter2", AvatarAction.SendResponse(_,
|
||||||
ObjectDetachMessage(PlanetSideGUID(5), PlanetSideGUID(2), _, _, _, _))
|
ObjectDetachMessage(PlanetSideGUID(5), PlanetSideGUID(2), _, _, _, _))
|
||||||
) => true
|
) => true
|
||||||
|
|
@ -559,26 +571,20 @@ class PlayerControlDeathSeatedTest extends ActorTest {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
assert(
|
assert(
|
||||||
msg_avatar(2) match {
|
msg_avatar(3) match {
|
||||||
case AvatarServiceMessage("TestCharacter2", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 29, 1)) => true
|
case AvatarServiceMessage("TestCharacter2", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 29, 1)) => true
|
||||||
case _ => false
|
case _ => false
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
assert(
|
assert(
|
||||||
msg_avatar(3) match {
|
msg_avatar(4) match {
|
||||||
case AvatarServiceMessage("test", AvatarAction.ObjectDelete(PlanetSideGUID(2), PlanetSideGUID(2), _)) => true
|
case AvatarServiceMessage("test", AvatarAction.ObjectDelete(PlanetSideGUID(2), PlanetSideGUID(2), _)) => true
|
||||||
case _ => false
|
case _ => false
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
assert(
|
|
||||||
msg_avatar(4) match {
|
|
||||||
case AvatarServiceMessage("test", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 0, _)) => true
|
|
||||||
case _ => false
|
|
||||||
}
|
|
||||||
)
|
|
||||||
assert(
|
assert(
|
||||||
msg_avatar(5) match {
|
msg_avatar(5) match {
|
||||||
case AvatarServiceMessage("TestCharacter2", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 2, _)) => true
|
case AvatarServiceMessage("test", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 0, _)) => true
|
||||||
case _ => false
|
case _ => false
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -2584,86 +2584,6 @@ class WorldSessionActor extends Actor
|
||||||
}
|
}
|
||||||
lastTerminalOrderFulfillment = true
|
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) =>
|
case Terminal.BuyVehicle(vehicle, weapons, trunk) =>
|
||||||
continent.Map.TerminalToSpawnPad.get(msg.terminal_guid.guid) match {
|
continent.Map.TerminalToSpawnPad.get(msg.terminal_guid.guid) match {
|
||||||
case Some(pad_guid) =>
|
case Some(pad_guid) =>
|
||||||
|
|
@ -2740,8 +2660,7 @@ class WorldSessionActor extends Actor
|
||||||
* @param reply na
|
* @param reply na
|
||||||
*/
|
*/
|
||||||
def HandleVehicleServiceResponse(toChannel : String, guid : PlanetSideGUID, reply : VehicleResponse.Response) : Unit = {
|
def HandleVehicleServiceResponse(toChannel : String, guid : PlanetSideGUID, reply : VehicleResponse.Response) : Unit = {
|
||||||
val tplayer_guid = if(player.HasGUID) player.GUID
|
val tplayer_guid = if(player.HasGUID) player.GUID else PlanetSideGUID(0)
|
||||||
else PlanetSideGUID(0)
|
|
||||||
reply match {
|
reply match {
|
||||||
case VehicleResponse.AttachToRails(vehicle_guid, pad_guid) =>
|
case VehicleResponse.AttachToRails(vehicle_guid, pad_guid) =>
|
||||||
sendResponse(ObjectAttachMessage(pad_guid, vehicle_guid, 3))
|
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))
|
sendResponse(VehicleStateMessage(vehicle_guid, unk1, pos, ang, vel, unk2, unk3, unk4, wheel_direction, unk5, unk6))
|
||||||
if(player.VehicleSeated.contains(vehicle_guid)) {
|
if(player.VehicleSeated.contains(vehicle_guid)) {
|
||||||
player.Position = pos
|
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) =>
|
case VehicleResponse.SendResponse(msg) =>
|
||||||
|
|
@ -3173,26 +3103,42 @@ class WorldSessionActor extends Actor
|
||||||
if(player.spectator) {
|
if(player.spectator) {
|
||||||
sendResponse(ChatMsg(ChatMessageType.CMT_TOGGLESPECTATORMODE, false, "", "on", None))
|
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)
|
val implantSlot = player.ImplantSlot(slot)
|
||||||
if(implantSlot.Initialized) {
|
implantSlot.Installed match {
|
||||||
sendResponse(AvatarImplantMessage(guid, ImplantAction.Initialization, slot, 1))
|
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))
|
sendResponse(PlanetsideAttributeMessage(PlanetSideGUID(0), 82, 0))
|
||||||
//TODO if Medkit does not have shortcut, add to a free slot or write over slot 64
|
//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))
|
sendResponse(ChangeShortcutBankMessage(guid, 0))
|
||||||
//Favorites lists
|
//Favorites lists
|
||||||
val (inf, veh) = avatar.EquipmentLoadouts.Loadouts.partition { case (index, _) => index < 10 }
|
val (inf, veh) = avatar.EquipmentLoadouts.Loadouts.partition { case (index, _) => index < 10 }
|
||||||
|
|
@ -3265,6 +3211,11 @@ class WorldSessionActor extends Actor
|
||||||
))
|
))
|
||||||
case (Some(vehicle), Some(0)) =>
|
case (Some(vehicle), Some(0)) =>
|
||||||
//summon any passengers and cargo vehicles left behind on previous continent
|
//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(
|
LoadZoneTransferPassengerMessages(
|
||||||
guid,
|
guid,
|
||||||
continent.Id,
|
continent.Id,
|
||||||
|
|
@ -3284,6 +3235,9 @@ class WorldSessionActor extends Actor
|
||||||
//killed during spawn setup or possibly a relog into a corpse (by accident?)
|
//killed during spawn setup or possibly a relog into a corpse (by accident?)
|
||||||
player.Actor ! Player.Die()
|
player.Actor ! Player.Die()
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
tplayer.Actor ! Player.StaminaRegen()
|
||||||
|
}
|
||||||
upstreamMessageCount = 0
|
upstreamMessageCount = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3870,27 +3824,13 @@ class WorldSessionActor extends Actor
|
||||||
zoneLoaded = Some(true)
|
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) =>
|
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) {
|
//log.info(s"$msg")
|
||||||
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"))
|
|
||||||
}
|
|
||||||
turnCounter(avatar_guid)
|
turnCounter(avatar_guid)
|
||||||
val isMoving = WorldEntity.isMoving(vel)
|
val isMoving = WorldEntity.isMoving(vel)
|
||||||
val isMovingPlus = isMoving || is_jumping || jump_thrust
|
val isMovingPlus = isMoving || is_jumping || jump_thrust
|
||||||
if(isMovingPlus) {
|
if(isMovingPlus) {
|
||||||
CancelZoningProcessWithDescriptiveReason("cancel_motion")
|
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.Position = pos
|
||||||
player.Velocity = vel
|
player.Velocity = vel
|
||||||
player.Orientation = Vector3(player.Orientation.x, pitch, yaw)
|
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))
|
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()
|
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) =>
|
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
|
//the majority of the following check retrieves information to determine if we are in control of the child
|
||||||
FindContainedWeapon match {
|
FindContainedWeapon match {
|
||||||
case (Some(o), Some(tool)) =>
|
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) =>
|
case msg@VehicleStateMessage(vehicle_guid, unk1, pos, ang, vel, flying, unk6, unk7, wheels, is_decelerating, is_cloaked) =>
|
||||||
if(deadState == DeadState.Alive) {
|
//log.info(s"$msg")
|
||||||
GetVehicleAndSeat() match {
|
GetVehicleAndSeat() match {
|
||||||
case (Some(obj), Some(0)) =>
|
case (Some(obj), Some(0)) =>
|
||||||
//we're driving the vehicle
|
//we're driving the vehicle
|
||||||
turnCounter(player.GUID)
|
turnCounter(player.GUID)
|
||||||
val seat = obj.Seats(0)
|
val seat = obj.Seats(0)
|
||||||
player.Position = pos //convenient
|
player.Position = pos //convenient
|
||||||
if(seat.ControlledWeapon.isEmpty) {
|
if(seat.ControlledWeapon.isEmpty) {
|
||||||
player.Orientation = Vector3.z(ang.z) //convenient
|
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
|
if(obj.Definition.CanFly) {
|
||||||
obj.Orientation = ang
|
obj.Flying = flying.nonEmpty //usually Some(7)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
else {
|
obj.Cloaked = obj.Definition.CanCloak && is_cloaked
|
||||||
obj.Velocity = None
|
}
|
||||||
obj.Flying = false
|
else {
|
||||||
}
|
obj.Velocity = None
|
||||||
continent.VehicleEvents ! VehicleServiceMessage(continent.Id, VehicleAction.VehicleState(player.GUID, vehicle_guid, unk1, obj.Position, ang, obj.Velocity, if(obj.Flying) {
|
obj.Flying = false
|
||||||
flying
|
}
|
||||||
}
|
continent.VehicleEvents ! VehicleServiceMessage(continent.Id, VehicleAction.VehicleState(player.GUID, vehicle_guid, unk1, obj.Position, ang, obj.Velocity, if(obj.Flying) {
|
||||||
else {
|
flying
|
||||||
None
|
}
|
||||||
}, unk6, unk7, wheels, is_decelerating, obj.Cloaked))
|
else {
|
||||||
updateSquad()
|
None
|
||||||
case (None, _) =>
|
}, unk6, unk7, wheels, is_decelerating, obj.Cloaked))
|
||||||
//log.error(s"VehicleState: no vehicle $vehicle_guid found in zone")
|
updateSquad()
|
||||||
//TODO placing a "not driving" warning here may trigger as we are disembarking the vehicle
|
case (None, _) =>
|
||||||
case (_, Some(index)) =>
|
//log.error(s"VehicleState: no vehicle $vehicle_guid found in zone")
|
||||||
log.error(s"VehicleState: player should not be dispatching this kind of packet from vehicle#$vehicle_guid when not the driver ($index)")
|
//TODO placing a "not driving" warning here may trigger as we are disembarking the vehicle
|
||||||
case _ => ;
|
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) {
|
if (player.death_by == -1) {
|
||||||
sendResponse(ChatMsg(ChatMessageType.UNK_71, true, "", "Your account has been logged out by a Customer Service Representative.", None))
|
sendResponse(ChatMsg(ChatMessageType.UNK_71, true, "", "Your account has been logged out by a Customer Service Representative.", None))
|
||||||
Thread.sleep(300)
|
Thread.sleep(300)
|
||||||
|
|
@ -4825,7 +4769,7 @@ class WorldSessionActor extends Actor
|
||||||
|
|
||||||
case msg@AvatarJumpMessage(state) =>
|
case msg@AvatarJumpMessage(state) =>
|
||||||
//log.info("AvatarJump: " + msg)
|
//log.info("AvatarJump: " + msg)
|
||||||
player.Stamina = player.Stamina - 10
|
player.Actor ! Player.StaminaChanged(-10)
|
||||||
player.skipStaminaRegenForTurns = math.max(player.skipStaminaRegenForTurns, 5)
|
player.skipStaminaRegenForTurns = math.max(player.skipStaminaRegenForTurns, 5)
|
||||||
|
|
||||||
case msg@ZipLineMessage(player_guid, forwards, action, path_id, pos) =>
|
case msg@ZipLineMessage(player_guid, forwards, action, path_id, pos) =>
|
||||||
|
|
@ -5123,7 +5067,7 @@ class WorldSessionActor extends Actor
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
player.Stamina = player.Stamina + 100
|
player.Actor ! Player.StaminaChanged(100)
|
||||||
sendResponse(PlanetsideAttributeMessage(avatar_guid, 2, player.Stamina))
|
sendResponse(PlanetsideAttributeMessage(avatar_guid, 2, player.Stamina))
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
@ -5614,7 +5558,8 @@ class WorldSessionActor extends Actor
|
||||||
}
|
}
|
||||||
else { //shooting
|
else { //shooting
|
||||||
if (tool.FireModeIndex == 1 && (tool.Definition.Name == "anniversary_guna" || tool.Definition.Name == "anniversary_gun" || tool.Definition.Name == "anniversary_gunb")) {
|
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))
|
prefire = shooting.orElse(Some(weapon_guid))
|
||||||
|
|
@ -8165,6 +8110,15 @@ class WorldSessionActor extends Actor
|
||||||
val player_guid : PlanetSideGUID = tplayer.GUID
|
val player_guid : PlanetSideGUID = tplayer.GUID
|
||||||
val obj_guid : PlanetSideGUID = obj.GUID
|
val obj_guid : PlanetSideGUID = obj.GUID
|
||||||
PlayerActionsToCancel()
|
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")
|
log.info(s"MountVehicleMsg: ${player.Name}_guid mounts $obj @ $seatNum")
|
||||||
sendResponse(ObjectAttachMessage(obj_guid, player_guid, seatNum))
|
sendResponse(ObjectAttachMessage(obj_guid, player_guid, seatNum))
|
||||||
continent.VehicleEvents ! VehicleServiceMessage(continent.Id, VehicleAction.MountVehicle(player_guid, obj_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)
|
LoadZoneAsPlayer(newPlayer, zone_id)
|
||||||
}
|
}
|
||||||
else {
|
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 {
|
interstellarFerry.orElse(continent.GUID(player.VehicleSeated)) match {
|
||||||
case Some(vehicle : Vehicle) => //driver or passenger in vehicle using a warp gate, or a droppod
|
case Some(vehicle : Vehicle) => //driver or passenger in vehicle using a warp gate, or a droppod
|
||||||
LoadZoneInVehicle(vehicle, pos, ori, zone_id)
|
LoadZoneInVehicle(vehicle, pos, ori, zone_id)
|
||||||
|
|
@ -10150,8 +10111,11 @@ class WorldSessionActor extends Actor
|
||||||
}
|
}
|
||||||
|
|
||||||
def DeactivateImplants() : Unit = {
|
def DeactivateImplants() : Unit = {
|
||||||
for(slot <- 0 to player.Implants.length - 1) {
|
//TODO 3 implant slots?
|
||||||
player.Actor ! Player.ImplantActivation(slot, 0)
|
player.Implants.indices.foreach { slot =>
|
||||||
|
if(player.ImplantSlot(slot).Active) {
|
||||||
|
player.Actor ! Player.ImplantActivation(slot, 0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue