mirror of
https://github.com/psforever/PSF-LoginServer.git
synced 2026-01-19 18:44:45 +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
|
|
@ -202,7 +202,9 @@ class Avatar(private val char_id : Long, val name : String, val faction : Planet
|
|||
implants.foreach(slot => {
|
||||
slot.Installed match {
|
||||
case Some(_) =>
|
||||
slot.Active = false
|
||||
slot.Initialized = false
|
||||
slot.InitializeTime = 0L
|
||||
case None => ;
|
||||
}
|
||||
})
|
||||
|
|
@ -302,7 +304,7 @@ class Avatar(private val char_id : Long, val name : String, val faction : Planet
|
|||
|
||||
def ObjectTypeNameReference(id : Long) : String = {
|
||||
objectTypeNameReference.get(id) match {
|
||||
case Some(name) => name
|
||||
case Some(objectName) => objectName
|
||||
case None => ""
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package net.psforever.objects
|
||||
|
||||
import akka.actor.Cancellable
|
||||
import net.psforever.objects.definition.ImplantDefinition
|
||||
import net.psforever.types.{ExoSuitType, ImplantType}
|
||||
|
||||
|
|
@ -19,19 +18,18 @@ class ImplantSlot {
|
|||
private var unlocked : Boolean = false
|
||||
/** whether this implant is ready for use */
|
||||
private var initialized : Boolean = false
|
||||
/** a cancellable timer that can be used to set an implant as initialized once complete */
|
||||
private var initializeTimer: Cancellable = Default.Cancellable
|
||||
|
||||
/** */
|
||||
private var initializeTime : Long = 0L
|
||||
/** is this implant active */
|
||||
private var active : Boolean = false
|
||||
/** what implant is currently installed in this slot; None if there is no implant currently installed */
|
||||
private var implant : Option[ImplantDefinition] = None
|
||||
|
||||
def InitializeTimer : Cancellable = initializeTimer
|
||||
def InitializeTime : Long = initializeTime
|
||||
|
||||
def InitializeTimer_=(timer : Cancellable) : Cancellable = {
|
||||
initializeTimer = timer
|
||||
initializeTimer
|
||||
def InitializeTime_=(time : Long) : Long = {
|
||||
initializeTime = time
|
||||
InitializeTime
|
||||
}
|
||||
|
||||
def Unlocked : Boolean = unlocked
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package net.psforever.objects
|
||||
|
||||
import akka.actor.ActorRef
|
||||
import net.psforever.objects.avatar.LoadoutManager
|
||||
import net.psforever.objects.definition.{AvatarDefinition, ExoSuitDefinition, SpecialExoSuitDefinition}
|
||||
import net.psforever.objects.definition.{AvatarDefinition, ExoSuitDefinition, ImplantDefinition, SpecialExoSuitDefinition}
|
||||
import net.psforever.objects.equipment.{Equipment, EquipmentSize, EquipmentSlot, JammableUnit}
|
||||
import net.psforever.objects.inventory.{Container, GridInventory, InventoryItem}
|
||||
import net.psforever.objects.serverobject.PlanetSideServerObject
|
||||
|
|
@ -124,11 +123,6 @@ class Player(private val core : Avatar) extends PlanetSideServerObject
|
|||
|
||||
def Stamina_=(assignStamina : Int) : Int = {
|
||||
stamina = if(isAlive) { math.min(math.max(0, assignStamina), MaxStamina) } else { 0 }
|
||||
|
||||
if(Actor != Default.Actor) {
|
||||
Actor ! Player.StaminaChanged(Stamina)
|
||||
}
|
||||
|
||||
Stamina
|
||||
}
|
||||
|
||||
|
|
@ -374,6 +368,10 @@ class Player(private val core : Avatar) extends PlanetSideServerObject
|
|||
core.Implants.takeWhile(_.Unlocked).map( implant => { (implant.Implant, implant.MaxTimer, implant.Active) })
|
||||
}
|
||||
|
||||
def InstallImplant(implant : ImplantDefinition) : Option[Int] = core.InstallImplant(implant)
|
||||
|
||||
def UninstallImplant(implant : ImplantType.Value) : Option[Int] = core.UninstallImplant(implant)
|
||||
|
||||
def ResetAllImplants() : Unit = core.ResetAllImplants()
|
||||
|
||||
def FacingYawUpper : Float = facingYawUpper
|
||||
|
|
@ -674,8 +672,12 @@ object Player {
|
|||
final case class ImplantInitializationStart(slot : Int)
|
||||
final case class UninitializeImplant(slot : Int)
|
||||
final case class ImplantInitializationComplete(slot : Int)
|
||||
final case class DrainStamina(amount : Int)
|
||||
final case class StaminaChanged(currentStamina : Int)
|
||||
final case class StaminaRegen()
|
||||
final case class StaminaChanged(currentStamina : Option[Int] = None)
|
||||
|
||||
object StaminaChanged {
|
||||
def apply(amount : Int) : StaminaChanged = StaminaChanged(Some(amount))
|
||||
}
|
||||
|
||||
def apply(core : Avatar) : Player = {
|
||||
new Player(core)
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
// Copyright (c) 2020 PSForever
|
||||
package net.psforever.objects.avatar
|
||||
|
||||
import akka.actor.{Actor, ActorRef, Props}
|
||||
import net.psforever.objects._
|
||||
import akka.actor.{Actor, ActorRef, Cancellable, Props}
|
||||
import net.psforever.objects.{Player, _}
|
||||
import net.psforever.objects.ballistics.{PlayerSource, ResolvedProjectile}
|
||||
import net.psforever.objects.definition.ImplantDefinition
|
||||
import net.psforever.objects.equipment._
|
||||
import net.psforever.objects.inventory.{GridInventory, InventoryItem}
|
||||
import net.psforever.objects.loadouts.Loadout
|
||||
|
|
@ -39,9 +38,14 @@ class PlayerControl(player : Player) extends Actor
|
|||
private [this] val log = org.log4s.getLogger(player.Name)
|
||||
private [this] val damageLog = org.log4s.getLogger(Damageable.LogChannel)
|
||||
|
||||
// A collection of timers for each slot to trigger stamina drain on an interval
|
||||
val implantSlotStaminaDrainTimers = mutable.HashMap(0 -> Default.Cancellable, 1 -> Default.Cancellable, 2 -> Default.Cancellable)
|
||||
// control agency for the player's locker container (dedicated inventory slot #5)
|
||||
/** Stamina will be used. Stamina will be restored. */
|
||||
var staminaRegen : Cancellable = Default.Cancellable
|
||||
/**
|
||||
* A collection of timers indexed for the implant in each slot.
|
||||
* Before an implant is ready, it serves as the initialization timer.
|
||||
* After being initialized, it is used as the stamina drain interval when the implant is active. */
|
||||
val implantSlotTimers = mutable.HashMap(0 -> Default.Cancellable, 1 -> Default.Cancellable, 2 -> Default.Cancellable)
|
||||
/** control agency for the player's locker container (dedicated inventory slot #5) */
|
||||
val lockerControlAgent : ActorRef = {
|
||||
val locker = player.Locker
|
||||
locker.Zone = player.Zone
|
||||
|
|
@ -51,106 +55,55 @@ class PlayerControl(player : Player) extends Actor
|
|||
override def postStop() : Unit = {
|
||||
lockerControlAgent ! akka.actor.PoisonPill
|
||||
player.Locker.Actor = Default.Actor
|
||||
implantSlotStaminaDrainTimers.values.foreach { _.cancel }
|
||||
staminaRegen.cancel
|
||||
implantSlotTimers.values.foreach { _.cancel }
|
||||
}
|
||||
|
||||
def receive : Receive = jammableBehavior
|
||||
.orElse(takesDamage)
|
||||
.orElse(containerBehavior)
|
||||
.orElse {
|
||||
case Player.ImplantActivation(slot: Int, status : Int) =>
|
||||
// todo: disable implants with stamina cost when changing armour type
|
||||
val implantSlot = player.ImplantSlot(slot)
|
||||
case Player.ImplantActivation(slot : Int, status : Int) =>
|
||||
ImplantActivation(slot, status)
|
||||
|
||||
// Allow uninitialized implants to be deactivated in case they're stuck in a state where they are no longer active or initialized but still draining stamina (e.g. by EMP)
|
||||
if(status == 0 && (implantSlot.Active || !implantSlot.Initialized)) {
|
||||
// Cancel stamina drain timer
|
||||
implantSlotStaminaDrainTimers(slot).cancel()
|
||||
implantSlotStaminaDrainTimers(slot) = Default.Cancellable
|
||||
case Player.UninitializeImplant(slot : Int) =>
|
||||
UninitializeImplant(slot)
|
||||
|
||||
implantSlot.Active = false
|
||||
player.Zone.AvatarEvents ! AvatarServiceMessage(player.Zone.Id, AvatarAction.PlanetsideAttribute(player.GUID, 28, player.Implant(slot).id * 2)) // Deactivation sound / effect
|
||||
player.Zone.AvatarEvents ! AvatarServiceMessage(player.Name, AvatarAction.DeactivateImplantSlot(player.GUID, slot))
|
||||
} else if (status == 1 && implantSlot.Initialized && !player.Fatigued) {
|
||||
implantSlot.Installed match {
|
||||
case Some(implant: ImplantDefinition) =>
|
||||
if(implantSlot.Active) {
|
||||
// Some events such as zoning will reset the implant on the client side without sending a deactivation packet
|
||||
// But the implant will remain in an active state server side. For now, allow reactivation of the implant.
|
||||
// todo: Deactivate implants server side when actions like zoning happen. (Other actions?)
|
||||
log.warn(s"Implant $slot is already active, but activating again")
|
||||
implantSlotStaminaDrainTimers(slot).cancel()
|
||||
implantSlotStaminaDrainTimers(slot) = Default.Cancellable
|
||||
}
|
||||
implantSlot.Active = true
|
||||
case Player.ImplantInitializationStart(slot : Int) =>
|
||||
ImplantInitializationStart(slot)
|
||||
|
||||
if (implant.ActivationStaminaCost >= 0) {
|
||||
player.Stamina -= implant.ActivationStaminaCost // Activation stamina drain
|
||||
}
|
||||
case Player.ImplantInitializationComplete(slot : Int) =>
|
||||
ImplantInitializationComplete(slot)
|
||||
|
||||
if(implant.StaminaCost > 0 && implant.GetCostIntervalByExoSuit(player.ExoSuit) > 0) { // Ongoing stamina drain, if applicable
|
||||
implantSlotStaminaDrainTimers(slot) = context.system.scheduler.scheduleWithFixedDelay(0 seconds, implant.GetCostIntervalByExoSuit(player.ExoSuit) milliseconds, self, Player.DrainStamina(implant.StaminaCost))
|
||||
}
|
||||
|
||||
player.Zone.AvatarEvents ! AvatarServiceMessage(player.Zone.Id, AvatarAction.PlanetsideAttribute(player.GUID, 28, player.Implant(slot).id * 2 + 1)) // Activation sound / effect
|
||||
player.Zone.AvatarEvents ! AvatarServiceMessage(player.Name, AvatarAction.ActivateImplantSlot(player.GUID, slot))
|
||||
case _ => ;
|
||||
}
|
||||
}
|
||||
else {
|
||||
log.warn(s"Can't handle ImplantActivation: Player GUID: ${player.GUID} Slot $slot Status: $status Initialized: ${implantSlot.Initialized} Active: ${implantSlot.Active} Fatigued: ${player.Fatigued}")
|
||||
case Player.StaminaRegen() =>
|
||||
if(staminaRegen == Default.Cancellable) {
|
||||
staminaRegen.cancel
|
||||
staminaRegen = context.system.scheduler.scheduleOnce(delay = 500 milliseconds, self, PlayerControl.StaminaRegen())
|
||||
}
|
||||
|
||||
case Player.UninitializeImplant(slot: Int) =>
|
||||
PlayerControl.UninitializeImplant(player, slot)
|
||||
|
||||
case Player.ImplantInitializationStart(slot: Int) =>
|
||||
val implantSlot = player.ImplantSlot(slot)
|
||||
if(implantSlot.Installed.isDefined) {
|
||||
if(implantSlot.Initialized) {
|
||||
PlayerControl.UninitializeImplant(player, slot)
|
||||
case PlayerControl.StaminaRegen() =>
|
||||
staminaRegen.cancel
|
||||
if (player.isAlive) {
|
||||
if (player.skipStaminaRegenForTurns > 0) {
|
||||
// Do not renew stamina for a while
|
||||
player.skipStaminaRegenForTurns -= 1
|
||||
}
|
||||
|
||||
// Start client side initialization timer
|
||||
player.Zone.AvatarEvents ! AvatarServiceMessage(player.Name, AvatarAction.SendResponse(player.GUID, ActionProgressMessage(slot + 6, 0)))
|
||||
|
||||
// Callback after initialization timer to complete initialization
|
||||
implantSlot.InitializeTimer = context.system.scheduler.scheduleOnce(implantSlot.MaxTimer seconds, self, Player.ImplantInitializationComplete(slot))
|
||||
}
|
||||
|
||||
case Player.ImplantInitializationComplete(slot: Int) =>
|
||||
val implantSlot = player.ImplantSlot(slot)
|
||||
if(implantSlot.Installed.isDefined) {
|
||||
player.Zone.AvatarEvents ! AvatarServiceMessage(player.Name, AvatarAction.SendResponse(player.GUID, AvatarImplantMessage(player.GUID, ImplantAction.Initialization, slot, 1)))
|
||||
implantSlot.Initialized = true
|
||||
if(implantSlot.InitializeTimer != Default.Cancellable) {
|
||||
implantSlot.InitializeTimer.cancel()
|
||||
implantSlot.InitializeTimer = Default.Cancellable
|
||||
else if ((player.VehicleSeated.nonEmpty || !player.isMoving && !player.Jumping) && player.Stamina < player.MaxStamina) {
|
||||
// Regen stamina roughly every 500ms
|
||||
StaminaChanged(changeInStamina = 1)
|
||||
}
|
||||
}
|
||||
staminaRegen = context.system.scheduler.scheduleOnce(delay = 500 milliseconds, self, PlayerControl.StaminaRegen())
|
||||
|
||||
case Player.DrainStamina(amount : Int) =>
|
||||
player.Stamina -= amount
|
||||
case Player.StaminaChanged(Some(changeInStamina)) =>
|
||||
StaminaChanged(changeInStamina)
|
||||
|
||||
case Player.StaminaChanged(currentStamina : Int) =>
|
||||
if(currentStamina == 0) {
|
||||
player.Fatigued = true
|
||||
player.skipStaminaRegenForTurns += 4
|
||||
player.Implants.indices.foreach { slot => // Disable all implants
|
||||
self ! Player.ImplantActivation(slot, 0)
|
||||
player.Zone.AvatarEvents ! AvatarServiceMessage(player.Zone.Id, AvatarAction.SendResponseTargeted(player.GUID, AvatarImplantMessage(player.GUID, ImplantAction.OutOfStamina, slot, 1)))
|
||||
}
|
||||
} else if (player.Fatigued && currentStamina >= 20) {
|
||||
player.Fatigued = false
|
||||
player.Implants.indices.foreach { slot => // Re-enable all implants
|
||||
player.Zone.AvatarEvents ! AvatarServiceMessage(player.Zone.Id, AvatarAction.SendResponseTargeted(player.GUID, AvatarImplantMessage(player.GUID, ImplantAction.OutOfStamina, slot, 0)))
|
||||
}
|
||||
}
|
||||
player.Zone.AvatarEvents ! AvatarServiceMessage(player.Zone.Id, AvatarAction.PlanetsideAttributeSelf(player.GUID, 2, player.Stamina))
|
||||
case Player.StaminaChanged(None) =>
|
||||
UpdateStamina()
|
||||
|
||||
case Player.Die() =>
|
||||
if(player.isAlive) {
|
||||
PlayerControl.DestructionAwareness(player, None)
|
||||
DestructionAwareness(player, None)
|
||||
}
|
||||
|
||||
case CommonMessages.Use(user, Some(item : Tool)) if item.Definition == GlobalDefinitions.medicalapplicator && player.isAlive =>
|
||||
|
|
@ -308,6 +261,13 @@ class PlayerControl(player : Player) extends Actor
|
|||
stow.foreach { elem =>
|
||||
player.Inventory.InsertQuickly(elem.start, elem.obj)
|
||||
}
|
||||
//deactivate non-passive implants
|
||||
implantSlotTimers.keys.foreach { index =>
|
||||
val implantSlot = player.ImplantSlot(index)
|
||||
if(implantSlot.Installed.nonEmpty && implantSlot.Active && (implantSlot.Charge(originalSuit) > 0 || implantSlot.Charge(exosuit) > 0)) {
|
||||
ImplantActivation(index, status = 0)
|
||||
}
|
||||
}
|
||||
player.Zone.AvatarEvents ! AvatarServiceMessage(player.Zone.Id,
|
||||
AvatarAction.ChangeExosuit(player.GUID, exosuit, subtype, player.LastDrawnSlot, exosuit == ExoSuitType.MAX && requestToChangeArmor,
|
||||
beforeHolsters.map { case InventoryItem(obj, _) => (obj, obj.GUID) }, afterHolsters,
|
||||
|
|
@ -431,6 +391,13 @@ class PlayerControl(player : Player) extends Actor
|
|||
}
|
||||
(afterHolsters ++ afterInventory).foreach { entry => entry.obj.Faction = player.Faction }
|
||||
toDeleteOrDrop.foreach { entry => entry.obj.Faction = PlanetSideEmpire.NEUTRAL }
|
||||
//deactivate non-passive implants
|
||||
implantSlotTimers.keys.foreach { index =>
|
||||
val implantSlot = player.ImplantSlot(index)
|
||||
if(implantSlot.Installed.nonEmpty && implantSlot.Active && (implantSlot.Charge(originalSuit) > 0 || implantSlot.Charge(nextSuit) > 0)) {
|
||||
ImplantActivation(index, status = 0)
|
||||
}
|
||||
}
|
||||
player.Zone.AvatarEvents ! AvatarServiceMessage(player.Zone.Id,
|
||||
AvatarAction.ChangeLoadout(player.GUID, nextSuit, nextSubtype, player.LastDrawnSlot, exosuit == ExoSuitType.MAX,
|
||||
oldHolsters.map { case InventoryItem(obj, _) => (obj, obj.GUID) }, afterHolsters,
|
||||
|
|
@ -438,6 +405,92 @@ class PlayerControl(player : Player) extends Actor
|
|||
)
|
||||
player.Zone.AvatarEvents ! AvatarServiceMessage(player.Name, AvatarAction.TerminalOrderResult(msg.terminal_guid, msg.transaction_type, true))
|
||||
|
||||
case Terminal.LearnImplant(implant) =>
|
||||
val zone = player.Zone
|
||||
val events = zone.AvatarEvents
|
||||
val playerChannel = player.Name
|
||||
val terminal_guid = msg.terminal_guid
|
||||
val implant_type = implant.Type
|
||||
val message = s"wants to learn $implant_type"
|
||||
val (interface, slotNumber) = player.VehicleSeated match {
|
||||
case Some(mech_guid) =>
|
||||
(
|
||||
zone.Map.TerminalToInterface.get(mech_guid.guid),
|
||||
if(!player.Implants.exists({ case (implantType, _, _) => implantType == implant_type })) {
|
||||
//no duplicates
|
||||
player.InstallImplant(implant)
|
||||
}
|
||||
else {
|
||||
None
|
||||
}
|
||||
)
|
||||
case _ =>
|
||||
(None, None)
|
||||
}
|
||||
val result = if(interface.contains(terminal_guid.guid) && slotNumber.isDefined) {
|
||||
val slot = slotNumber.get
|
||||
log.info(s"$message - put in slot $slot")
|
||||
events ! AvatarServiceMessage(playerChannel, AvatarAction.SendResponse(Service.defaultPlayerGUID, AvatarImplantMessage(player.GUID, ImplantAction.Add, slot, implant_type.id)))
|
||||
ImplantInitializationStart(slot)
|
||||
true
|
||||
}
|
||||
else {
|
||||
if(interface.isEmpty) {
|
||||
log.warn(s"$message - not interacting with a terminal")
|
||||
}
|
||||
else if(!interface.contains(terminal_guid.guid)) {
|
||||
log.warn(s"$message - interacting with the wrong terminal, ${interface.get}")
|
||||
}
|
||||
else if(slotNumber.isEmpty) {
|
||||
log.warn(s"$message - already knows that implant")
|
||||
}
|
||||
else {
|
||||
log.warn(s"$message - forgot to sit at a terminal")
|
||||
}
|
||||
false
|
||||
}
|
||||
events ! AvatarServiceMessage(playerChannel, AvatarAction.TerminalOrderResult(terminal_guid, msg.transaction_type, result))
|
||||
|
||||
case Terminal.SellImplant(implant) =>
|
||||
val zone = player.Zone
|
||||
val events = zone.AvatarEvents
|
||||
val playerChannel = player.Name
|
||||
val terminal_guid = msg.terminal_guid
|
||||
val implant_type = implant.Type
|
||||
val (interface, slotNumber) = player.VehicleSeated match {
|
||||
case Some(mech_guid) =>
|
||||
(
|
||||
zone.Map.TerminalToInterface.get(mech_guid.guid),
|
||||
player.UninstallImplant(implant_type)
|
||||
)
|
||||
case None =>
|
||||
(None, None)
|
||||
}
|
||||
val result = if(interface.contains(terminal_guid.guid) && slotNumber.isDefined) {
|
||||
val slot = slotNumber.get
|
||||
log.info(s"is uninstalling $implant_type - take from slot $slot")
|
||||
UninitializeImplant(slot)
|
||||
events ! AvatarServiceMessage(playerChannel, AvatarAction.SendResponse(Service.defaultPlayerGUID, AvatarImplantMessage(player.GUID, ImplantAction.Remove, slot, 0)))
|
||||
true
|
||||
}
|
||||
else {
|
||||
val message = s"${player.Name} can not sell $implant_type"
|
||||
if(interface.isEmpty) {
|
||||
log.warn(s"$message - not interacting with a terminal")
|
||||
}
|
||||
else if(!interface.contains(terminal_guid.guid)) {
|
||||
log.warn(s"$message - interacting with the wrong terminal, ${interface.get}")
|
||||
}
|
||||
else if(slotNumber.isEmpty) {
|
||||
log.warn(s"$message - does not know that implant")
|
||||
}
|
||||
else {
|
||||
log.warn(s"$message - forgot to sit at a terminal")
|
||||
}
|
||||
false
|
||||
}
|
||||
events ! AvatarServiceMessage(playerChannel, AvatarAction.TerminalOrderResult(terminal_guid, msg.transaction_type, result))
|
||||
|
||||
case _ => ; //terminal messages not handled here
|
||||
}
|
||||
|
||||
|
|
@ -490,21 +543,189 @@ class PlayerControl(player : Player) extends Actor
|
|||
if(player.isAlive) {
|
||||
val originalHealth = player.Health
|
||||
val originalArmor = player.Armor
|
||||
val originalStamina = player.Stamina
|
||||
val originalCapacitor = player.Capacitor.toInt
|
||||
val cause = applyDamageTo(player)
|
||||
val health = player.Health
|
||||
val armor = player.Armor
|
||||
val stamina = player.Stamina
|
||||
val capacitor = player.Capacitor.toInt
|
||||
val damageToHealth = originalHealth - health
|
||||
val damageToArmor = originalArmor - armor
|
||||
val damageToStamina = originalStamina - stamina
|
||||
val damageToCapacitor = originalCapacitor - capacitor
|
||||
PlayerControl.HandleDamage(player, cause, damageToHealth, damageToArmor, damageToCapacitor)
|
||||
if(damageToHealth > 0 || damageToArmor > 0 || damageToCapacitor > 0) {
|
||||
damageLog.info(s"${player.Name}-infantry: BEFORE=$originalHealth/$originalArmor/$originalCapacitor, AFTER=$health/$armor/$capacitor, CHANGE=$damageToHealth/$damageToArmor/$damageToCapacitor")
|
||||
HandleDamage(player, cause, damageToHealth, damageToArmor, damageToStamina, damageToCapacitor)
|
||||
if(damageToHealth > 0 || damageToArmor > 0 || damageToStamina > 0 || damageToCapacitor > 0) {
|
||||
damageLog.info(s"${player.Name}-infantry: BEFORE=$originalHealth/$originalArmor/$originalStamina/$originalCapacitor, AFTER=$health/$armor/$stamina/$capacitor, CHANGE=$damageToHealth/$damageToArmor/$damageToStamina/$damageToCapacitor")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* na
|
||||
* @param target na
|
||||
*/
|
||||
def HandleDamage(target : Player, cause : ResolvedProjectile, damageToHealth : Int, damageToArmor : Int, damageToStamina : Int, damageToCapacitor : Int) : Unit = {
|
||||
val targetGUID = target.GUID
|
||||
val zone = target.Zone
|
||||
val zoneId = zone.Id
|
||||
val events = zone.AvatarEvents
|
||||
val health = target.Health
|
||||
if(damageToArmor > 0) {
|
||||
events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(targetGUID, 4, target.Armor))
|
||||
}
|
||||
if(health > 0) {
|
||||
if(damageToCapacitor > 0) {
|
||||
events ! AvatarServiceMessage(target.Name, AvatarAction.PlanetsideAttributeSelf(targetGUID, 7, target.Capacitor.toLong))
|
||||
}
|
||||
if(damageToHealth > 0 || damageToStamina > 0) {
|
||||
target.History(cause)
|
||||
if(damageToHealth > 0) {
|
||||
events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(targetGUID, 0, health))
|
||||
}
|
||||
if(damageToStamina > 0) {
|
||||
UpdateStamina()
|
||||
}
|
||||
//activity on map
|
||||
zone.Activity ! Zone.HotSpot.Activity(cause.target, cause.projectile.owner, cause.hit_pos)
|
||||
//alert damage source
|
||||
DamageAwareness(target, cause)
|
||||
}
|
||||
if(Damageable.CanJammer(target, cause)) {
|
||||
target.Actor ! JammableUnit.Jammered(cause)
|
||||
}
|
||||
}
|
||||
else {
|
||||
DestructionAwareness(target, Some(cause))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* na
|
||||
* @param target na
|
||||
* @param cause na
|
||||
*/
|
||||
def DamageAwareness(target : Player, cause : ResolvedProjectile) : Unit = {
|
||||
val zone = target.Zone
|
||||
zone.AvatarEvents ! AvatarServiceMessage(
|
||||
target.Name,
|
||||
cause.projectile.owner match {
|
||||
case pSource : PlayerSource => //player damage
|
||||
val name = pSource.Name
|
||||
zone.LivePlayers.find(_.Name == name).orElse(zone.Corpses.find(_.Name == name)) match {
|
||||
case Some(tplayer) => AvatarAction.HitHint(tplayer.GUID, target.GUID)
|
||||
case None => AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(10, pSource.Position))
|
||||
}
|
||||
case source => AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(10, source.Position))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* The player has lost all his vitality and must be killed.<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.
|
||||
* Although, as a rule, the jammering sound effect should last as long as the jammering status,
|
||||
|
|
@ -530,29 +751,23 @@ class PlayerControl(player : Player) extends Actor
|
|||
* @param target an object that can be affected by the jammered status
|
||||
* @param dur the duration of the timer, in milliseconds
|
||||
*/
|
||||
override def StartJammeredStatus(target : Any, dur : Int) : Unit = target match {
|
||||
case obj : Player =>
|
||||
//TODO these features
|
||||
// val guid = obj.GUID
|
||||
// val zone = obj.Zone
|
||||
// val zoneId = zone.Id
|
||||
// val events = zone.AvatarEvents
|
||||
player.Implants.indices.foreach { slot => // Deactivate & uninitialize all implants
|
||||
player.Zone.AvatarEvents ! AvatarServiceMessage(player.Zone.Id, AvatarAction.PlanetsideAttribute(player.GUID, 28, player.Implant(slot).id * 2)) // Deactivation sound / effect
|
||||
self ! Player.ImplantActivation(slot, 0)
|
||||
PlayerControl.UninitializeImplant(player, slot)
|
||||
}
|
||||
|
||||
obj.skipStaminaRegenForTurns = math.max(obj.skipStaminaRegenForTurns, 10)
|
||||
super.StartJammeredStatus(target, dur)
|
||||
case _ => ;
|
||||
override def StartJammeredStatus(target : Any, dur : Int) : Unit = {
|
||||
//TODO these features
|
||||
val zone = player.Zone
|
||||
player.Implants.indices.foreach { slot => // Deactivate & uninitialize all implants
|
||||
zone.AvatarEvents ! AvatarServiceMessage(zone.Id, AvatarAction.PlanetsideAttribute(player.GUID, 28, player.Implant(slot).id * 2)) // Deactivation sound / effect
|
||||
ImplantActivation(slot, status = 0)
|
||||
UninitializeImplant(slot)
|
||||
}
|
||||
player.skipStaminaRegenForTurns = math.max(player.skipStaminaRegenForTurns, 10)
|
||||
super.StartJammeredStatus(target, dur)
|
||||
}
|
||||
|
||||
override def CancelJammeredStatus(target: Any): Unit = {
|
||||
player.Implants.indices.foreach { slot => // Start reinitializing all implants
|
||||
self ! Player.ImplantInitializationStart(slot)
|
||||
player.ImplantSlot(slot).InitializeTime = 0 //setting time to 0 will restart implant initialization (eventually)
|
||||
ImplantInitializationStart(slot)
|
||||
}
|
||||
|
||||
super.CancelJammeredStatus(target)
|
||||
}
|
||||
|
||||
|
|
@ -664,175 +879,200 @@ class PlayerControl(player : Player) extends Actor
|
|||
val zone = obj.Zone
|
||||
zone.AvatarEvents ! AvatarServiceMessage(player.Name, AvatarAction.SendResponse(Service.defaultPlayerGUID, ObjectDetachMessage(obj.GUID, item.GUID, Vector3.Zero, 0f)))
|
||||
}
|
||||
|
||||
/**
|
||||
* na
|
||||
* @param changeInStamina na
|
||||
*/
|
||||
def StaminaChanged(changeInStamina : Int) : Unit = {
|
||||
val beforeStamina = player.Stamina
|
||||
val afterStamina = player.Stamina += changeInStamina
|
||||
if(beforeStamina != afterStamina) {
|
||||
UpdateStamina()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the current stamina value for this player requires a greater change in player states.
|
||||
* Losing all stamina and not yet being fatigued deactivates implants.
|
||||
* Having stamina of 20 points or greater and having previously been fatigued
|
||||
* allows implants to operate once again.
|
||||
* Initialization must be restarted manually for any implant that had not previously finished initializing.
|
||||
*/
|
||||
def UpdateStamina() : Unit = {
|
||||
val currentStamina = player.Stamina
|
||||
if(currentStamina == 0 && !player.Fatigued) { // Only be fatigued once even if loses all stamina again
|
||||
player.Fatigued = true
|
||||
player.skipStaminaRegenForTurns = math.max(player.skipStaminaRegenForTurns, 6)
|
||||
player.Implants.indices.foreach { slot => // Disable all implants
|
||||
ImplantActivation(slot, status = 0)
|
||||
player.Zone.AvatarEvents ! AvatarServiceMessage(player.Name, AvatarAction.SendResponse(Service.defaultPlayerGUID, AvatarImplantMessage(player.GUID, ImplantAction.OutOfStamina, slot, 1)))
|
||||
}
|
||||
}
|
||||
else if(currentStamina >= 20) {
|
||||
val wasFatigued = player.Fatigued
|
||||
player.Fatigued = false
|
||||
if(wasFatigued) { //reactivate only if we were fatigued
|
||||
player.Implants.indices.foreach { slot => // Re-enable all implants
|
||||
player.Zone.AvatarEvents ! AvatarServiceMessage(player.Name, AvatarAction.SendResponse(Service.defaultPlayerGUID, AvatarImplantMessage(player.GUID, ImplantAction.OutOfStamina, slot, 0)))
|
||||
if(!player.ImplantSlot(slot).Initialized) {
|
||||
ImplantInitializationStart(slot)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
player.Zone.AvatarEvents ! AvatarServiceMessage(player.Name, AvatarAction.PlanetsideAttributeToAll(player.GUID, 2, currentStamina))
|
||||
}
|
||||
|
||||
/**
|
||||
* The process of starting an implant so that it can be activated is one that requires a matter of time.
|
||||
* If the implant should already have been started, then just switch to the proper state.
|
||||
* Always (check to) initialize implants when setting up an avatar or becoming fatigued or when revived.
|
||||
* @param slot the slot in which this implant is found
|
||||
*/
|
||||
def ImplantInitializationStart(slot : Int) : Unit = {
|
||||
val implantSlot = player.ImplantSlot(slot)
|
||||
if(implantSlot.Installed.isDefined) {
|
||||
if(!implantSlot.Initialized) {
|
||||
val time = System.currentTimeMillis
|
||||
val initializationTime = if(implantSlot.InitializeTime == 0L) {
|
||||
implantSlot.InitializeTime = time
|
||||
time
|
||||
}
|
||||
else {
|
||||
implantSlot.InitializeTime
|
||||
}
|
||||
val maxInitializationTime = implantSlot.MaxTimer * 1000
|
||||
if (time - initializationTime > maxInitializationTime) {
|
||||
//this implant should have already been initialized
|
||||
ImplantInitializationComplete(slot)
|
||||
}
|
||||
else {
|
||||
// Start client side initialization timer
|
||||
// Check this along the bottom of the character information window
|
||||
//progress accumulates according to the client's knowledge of the implant initialization time
|
||||
//what is normally a 60s timer that is set to 120s on the server will still visually update as if 60s
|
||||
val percent = (100 * (time - initializationTime) / maxInitializationTime.toFloat ).toInt
|
||||
player.Zone.AvatarEvents ! AvatarServiceMessage(player.Name, AvatarAction.SendResponse(Service.defaultPlayerGUID, ActionProgressMessage(slot + 6, percent)))
|
||||
// Callback after initialization timer to complete initialization
|
||||
implantSlotTimers(slot).cancel
|
||||
implantSlotTimers(slot) = context.system.scheduler.scheduleOnce((maxInitializationTime - (time - initializationTime)) milliseconds, self, Player.ImplantInitializationComplete(slot))
|
||||
}
|
||||
}
|
||||
else {
|
||||
ImplantInitializationComplete(slot)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The implant is ready to be made available and active on selection.
|
||||
* The end result of a timed process, occasionally an implant will become "already active".
|
||||
* @param slot the slot in which this implant is found
|
||||
*/
|
||||
def ImplantInitializationComplete(slot : Int) : Unit = {
|
||||
val implantSlot = player.ImplantSlot(slot)
|
||||
if(implantSlot.Installed.isDefined) {
|
||||
implantSlot.Initialized = true
|
||||
player.Zone.AvatarEvents ! AvatarServiceMessage(player.Name, AvatarAction.SendResponse(Service.defaultPlayerGUID, AvatarImplantMessage(player.GUID, ImplantAction.Initialization, slot, 1)))
|
||||
implantSlotTimers(slot).cancel
|
||||
implantSlotTimers(slot) = Default.Cancellable
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not the implant is being used by the player who installed it.
|
||||
* If the implant has no business having its activation state changed yet, it (re)starts its initialization phase.
|
||||
* @param slot the slot in which this implant is found
|
||||
* @param status `1`, if the implant should become active;
|
||||
* `0`, if it should be deactivated
|
||||
*/
|
||||
def ImplantActivation(slot : Int, status : Int) : Unit = {
|
||||
val implantSlot = player.ImplantSlot(slot)
|
||||
if(!implantSlot.Initialized && !player.Fatigued) {
|
||||
log.warn(s"implant in slot $slot is trying to (de)activate when not even initialized!")
|
||||
//we should not be activating or deactivataing, but initializing
|
||||
implantSlotTimers(slot).cancel
|
||||
implantSlotTimers(slot) = Default.Cancellable
|
||||
implantSlot.Active = false
|
||||
//normal deactivation
|
||||
player.Zone.AvatarEvents ! AvatarServiceMessage(player.Name, AvatarAction.DeactivateImplantSlot(player.GUID, slot))
|
||||
//initialization process (from scratch)
|
||||
implantSlot.InitializeTime = 0
|
||||
ImplantInitializationStart(slot)
|
||||
}
|
||||
else if(status == 0 && implantSlot.Active) {
|
||||
implantSlotTimers(slot).cancel
|
||||
implantSlotTimers(slot) = Default.Cancellable
|
||||
implantSlot.Active = false
|
||||
player.Zone.AvatarEvents ! AvatarServiceMessage(player.Zone.Id, AvatarAction.PlanetsideAttribute(player.GUID, 28, player.Implant(slot).id * 2)) // Deactivation sound / effect
|
||||
player.Zone.AvatarEvents ! AvatarServiceMessage(player.Name, AvatarAction.DeactivateImplantSlot(player.GUID, slot))
|
||||
}
|
||||
else if(status == 1 && implantSlot.Initialized && !player.Fatigued) {
|
||||
implantSlot.Installed match {
|
||||
case Some(implant)
|
||||
if(implant.Type == ImplantType.PersonalShield && player.ExoSuit == ExoSuitType.Infiltration) ||
|
||||
(implant.Type == ImplantType.Surge && player.ExoSuit == ExoSuitType.MAX) =>
|
||||
//TODO STILL NOT ALLOWED (but make it look normal)
|
||||
case Some(implant) =>
|
||||
if (implantSlot.Active) {
|
||||
// Some events such as zoning will reset the implant on the client side without sending a deactivation packet
|
||||
// But the implant will remain in an active state server side. For now, allow reactivation of the implant.
|
||||
log.warn(s"implant $slot is already active, but activating again")
|
||||
implantSlotTimers(slot).cancel
|
||||
implantSlotTimers(slot) = Default.Cancellable
|
||||
}
|
||||
val activationStaminaCost = implant.ActivationStaminaCost
|
||||
if (activationStaminaCost > 0) {
|
||||
player.Stamina -= activationStaminaCost // Activation stamina drain
|
||||
UpdateStamina()
|
||||
}
|
||||
if (!player.Fatigued) {
|
||||
implantSlot.Active = true
|
||||
val zone = player.Zone
|
||||
val drainInterval = implant.GetCostIntervalByExoSuit(player.ExoSuit)
|
||||
if (drainInterval > 0) { // Ongoing stamina drain, if applicable
|
||||
implantSlotTimers(slot).cancel
|
||||
implantSlotTimers(slot) = context.system.scheduler.scheduleWithFixedDelay(initialDelay = 0 seconds, drainInterval milliseconds, self, Player.StaminaChanged(-implant.StaminaCost))
|
||||
}
|
||||
zone.AvatarEvents ! AvatarServiceMessage(zone.Id, AvatarAction.PlanetsideAttribute(player.GUID, 28, player.Implant(slot).id * 2 + 1)) // Activation sound / effect
|
||||
zone.AvatarEvents ! AvatarServiceMessage(player.Name, AvatarAction.ActivateImplantSlot(player.GUID, slot))
|
||||
}
|
||||
case _ =>
|
||||
//there should have been an implant here ...
|
||||
implantSlot.Active = false
|
||||
implantSlot.Initialized = false
|
||||
implantSlot.InitializeTime = 0L
|
||||
//todo: AvatarImplantMessage(tplayer.GUID, ImplantAction.Remove, slot, 0)?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The implant in this slot is no longer active and is no longer considered ready to activate.
|
||||
* @param slot the slot in which an implant could be found
|
||||
*/
|
||||
def UninitializeImplant(slot: Int): Unit = {
|
||||
implantSlotTimers(slot).cancel
|
||||
implantSlotTimers(slot) = Default.Cancellable
|
||||
val zone = player.Zone
|
||||
val guid = player.GUID
|
||||
val playerChannel = player.Name
|
||||
val zoneChannel = zone.Id
|
||||
val implantSlot = player.ImplantSlot(slot)
|
||||
// if(implantSlot.Active) {
|
||||
// zone.AvatarEvents ! AvatarServiceMessage(zoneChannel, AvatarAction.PlanetsideAttribute(guid, 28, player.Implant(slot).id * 2)) // Deactivation sound / effect
|
||||
// zone.AvatarEvents ! AvatarServiceMessage(playerChannel, AvatarAction.DeactivateImplantSlot(guid, slot))
|
||||
// }
|
||||
implantSlot.Active = false
|
||||
implantSlot.Initialized = false
|
||||
implantSlot.InitializeTime = 0L
|
||||
zone.AvatarEvents ! AvatarServiceMessage(playerChannel, AvatarAction.SendResponse(Service.defaultPlayerGUID, ActionProgressMessage(slot + 6, 100)))
|
||||
zone.AvatarEvents ! AvatarServiceMessage(zoneChannel, AvatarAction.SendResponse(Service.defaultPlayerGUID, AvatarImplantMessage(guid, ImplantAction.Initialization, slot, 0)))
|
||||
}
|
||||
}
|
||||
|
||||
object PlayerControl {
|
||||
/**
|
||||
* na
|
||||
* @param target na
|
||||
*/
|
||||
def HandleDamage(target : Player, cause : ResolvedProjectile, damageToHealth : Int, damageToArmor : Int, damageToCapacitor : Int) : Unit = {
|
||||
val targetGUID = target.GUID
|
||||
val zone = target.Zone
|
||||
val zoneId = zone.Id
|
||||
val events = zone.AvatarEvents
|
||||
val health = target.Health
|
||||
if(health > 0) {
|
||||
if(damageToCapacitor > 0) {
|
||||
events ! AvatarServiceMessage(target.Name, AvatarAction.PlanetsideAttributeSelf(targetGUID, 7, target.Capacitor.toLong))
|
||||
}
|
||||
if(damageToHealth > 0 || damageToArmor > 0) {
|
||||
target.History(cause)
|
||||
if(damageToHealth > 0) {
|
||||
events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(targetGUID, 0, health))
|
||||
}
|
||||
if(damageToArmor > 0) {
|
||||
events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(targetGUID, 4, target.Armor))
|
||||
}
|
||||
//activity on map
|
||||
zone.Activity ! Zone.HotSpot.Activity(cause.target, cause.projectile.owner, cause.hit_pos)
|
||||
//alert damage source
|
||||
DamageAwareness(target, cause)
|
||||
}
|
||||
if(Damageable.CanJammer(target, cause)) {
|
||||
target.Actor ! JammableUnit.Jammered(cause)
|
||||
}
|
||||
}
|
||||
else {
|
||||
if(damageToArmor > 0) {
|
||||
events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(targetGUID, 4, target.Armor))
|
||||
}
|
||||
DestructionAwareness(target, Some(cause))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* na
|
||||
* @param target na
|
||||
* @param cause na
|
||||
*/
|
||||
def DamageAwareness(target : Player, cause : ResolvedProjectile) : Unit = {
|
||||
val zone = target.Zone
|
||||
zone.AvatarEvents ! AvatarServiceMessage(
|
||||
target.Name,
|
||||
cause.projectile.owner match {
|
||||
case pSource : PlayerSource => //player damage
|
||||
val name = pSource.Name
|
||||
zone.LivePlayers.find(_.Name == name).orElse(zone.Corpses.find(_.Name == name)) match {
|
||||
case Some(player) => AvatarAction.HitHint(player.GUID, target.GUID)
|
||||
case None => AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(10, pSource.Position))
|
||||
}
|
||||
case source => AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(10, source.Position))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* The player has lost all his vitality and must be killed.<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)))
|
||||
}
|
||||
/** */
|
||||
private case class StaminaRegen()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -245,7 +245,7 @@ object OrderTerminalDefinition {
|
|||
}
|
||||
|
||||
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(player.Health < originalHealth) {
|
||||
val delta = originalHealth - player.Health
|
||||
player.Stamina = player.Stamina - math.floor(delta / 2).toInt
|
||||
player.Stamina -= math.floor(delta / 2).toInt
|
||||
}
|
||||
}
|
||||
case _ =>
|
||||
|
|
|
|||
|
|
@ -89,52 +89,41 @@ object Shortcut extends Marshallable[Shortcut] {
|
|||
/**
|
||||
A map to convert between ImplantTypes and Implant Shortcuts
|
||||
*/
|
||||
final lazy val ImplantsMap = Map(
|
||||
ImplantType.AdvancedRegen->REGENERATION,
|
||||
ImplantType.Targeting->ENHANCED_TARGETING,
|
||||
ImplantType.AudioAmplifier->AUDIO_AMPLIFIER,
|
||||
ImplantType.DarklightVision->DARKLIGHT_VISION,
|
||||
ImplantType.MeleeBooster->MELEE_BOOSTER,
|
||||
ImplantType.PersonalShield->PERSONAL_SHIELD,
|
||||
ImplantType.RangeMagnifier->RANGE_MAGNIFIER,
|
||||
ImplantType.SecondWind->SECOND_WIND,
|
||||
ImplantType.SilentRun->SENSOR_SHIELD,
|
||||
ImplantType.Surge->SURGE
|
||||
)
|
||||
final lazy val ImplantsMap : Map[ImplantType.Value, Option[Shortcut]] = Map(
|
||||
ImplantType.AdvancedRegen->Regeneration,
|
||||
ImplantType.Targeting->EnhancedTargeting,
|
||||
ImplantType.AudioAmplifier->AudioAmplifier,
|
||||
ImplantType.DarklightVision->DartklightVision,
|
||||
ImplantType.MeleeBooster->MeleeBooster,
|
||||
ImplantType.PersonalShield->PersonalShield,
|
||||
ImplantType.RangeMagnifier->RangeMagnifier,
|
||||
ImplantType.SecondWind->SecondWind,
|
||||
ImplantType.SilentRun->SensorShield,
|
||||
ImplantType.Surge->Surge
|
||||
).withDefaultValue(None)
|
||||
|
||||
/**
|
||||
* Preset for the Audio Amplifier implant. */
|
||||
final val AUDIO_AMPLIFIER : Some[Shortcut] = Some(Shortcut(2, "audio_amplifier"))
|
||||
/**
|
||||
* Preset for the Darklight Vision implant. */
|
||||
final val DARKLIGHT_VISION : Some[Shortcut] = Some(Shortcut(2, "darklight_vision"))
|
||||
/**
|
||||
* Preset for the Enhanced Targeting implant. */
|
||||
final val ENHANCED_TARGETING : Some[Shortcut] = Some(Shortcut(2, "targeting"))
|
||||
/**
|
||||
* Preset for the medkit quick-use option. */
|
||||
final val MEDKIT : Some[Shortcut] = Some(Shortcut(0, "medkit"))
|
||||
/**
|
||||
* Preset for the Melee Booster implant. */
|
||||
final val MELEE_BOOSTER : Some[Shortcut] = Some(Shortcut(2, "melee_booster"))
|
||||
/**
|
||||
* Preset for the Personal Shield implant. */
|
||||
final val PERSONAL_SHIELD : Some[Shortcut] = Some(Shortcut(2, "personal_shield"))
|
||||
/**
|
||||
* Preset for the Range Magnifier implant. */
|
||||
final val RANGE_MAGNIFIER : Some[Shortcut] = Some(Shortcut(2, "range_magnifier"))
|
||||
/**
|
||||
* Preset for the Regeneration implant. */
|
||||
final val REGENERATION : Some[Shortcut] = Some(Shortcut(2, "advanced_regen"))
|
||||
/**
|
||||
* Preset for the Second Wind implant. */
|
||||
final val SECOND_WIND : Some[Shortcut] = Some(Shortcut(2, "second_wind"))
|
||||
/**
|
||||
* Preset for the Sensor Shield implant. */
|
||||
final val SENSOR_SHIELD : Some[Shortcut] = Some(Shortcut(2, "silent_run"))
|
||||
/**
|
||||
* Preset for the Surge implant. */
|
||||
final val SURGE : Some[Shortcut] = Some(Shortcut(2, "surge"))
|
||||
/** Preset for the Audio Amplifier implant. */
|
||||
final val AudioAmplifier : Some[Shortcut] = Some(Shortcut(2, "audio_amplifier"))
|
||||
/** Preset for the Darklight Vision implant. */
|
||||
final val DartklightVision : Some[Shortcut] = Some(Shortcut(2, "darklight_vision"))
|
||||
/** Preset for the Enhanced Targeting implant. */
|
||||
final val EnhancedTargeting : Some[Shortcut] = Some(Shortcut(2, "targeting"))
|
||||
/** Preset for the medkit quick-use option. */
|
||||
final val Medkit : Some[Shortcut] = Some(Shortcut(0, "medkit"))
|
||||
/** Preset for the Melee Booster implant. */
|
||||
final val MeleeBooster : Some[Shortcut] = Some(Shortcut(2, "melee_booster"))
|
||||
/** Preset for the Personal Shield implant. */
|
||||
final val PersonalShield : Some[Shortcut] = Some(Shortcut(2, "personal_shield"))
|
||||
/** Preset for the Range Magnifier implant. */
|
||||
final val RangeMagnifier : Some[Shortcut] = Some(Shortcut(2, "range_magnifier"))
|
||||
/** Preset for the Regeneration implant. */
|
||||
final val Regeneration : Some[Shortcut] = Some(Shortcut(2, "advanced_regen"))
|
||||
/** Preset for the Second Wind implant. */
|
||||
final val SecondWind : Some[Shortcut] = Some(Shortcut(2, "second_wind"))
|
||||
/** Preset for the Sensor Shield implant. */
|
||||
final val SensorShield : Some[Shortcut] = Some(Shortcut(2, "silent_run"))
|
||||
/** Preset for the Surge implant. */
|
||||
final val Surge : Some[Shortcut] = Some(Shortcut(2, "surge"))
|
||||
/**
|
||||
* Converter for text macro parameters that acts like a preset.
|
||||
* @param effect1 a three letter acronym displayed in the hotbar
|
||||
|
|
|
|||
|
|
@ -89,27 +89,27 @@ class CreateShortcutMessageTest extends Specification {
|
|||
}
|
||||
|
||||
"presets" in {
|
||||
Shortcut.AUDIO_AMPLIFIER.get.purpose mustEqual 2
|
||||
Shortcut.AUDIO_AMPLIFIER.get.tile mustEqual "audio_amplifier"
|
||||
Shortcut.DARKLIGHT_VISION.get.purpose mustEqual 2
|
||||
Shortcut.DARKLIGHT_VISION.get.tile mustEqual "darklight_vision"
|
||||
Shortcut.ENHANCED_TARGETING.get.purpose mustEqual 2
|
||||
Shortcut.ENHANCED_TARGETING.get.tile mustEqual "targeting"
|
||||
Shortcut.MEDKIT.get.purpose mustEqual 0
|
||||
Shortcut.MEDKIT.get.tile mustEqual "medkit"
|
||||
Shortcut.MELEE_BOOSTER.get.purpose mustEqual 2
|
||||
Shortcut.MELEE_BOOSTER.get.tile mustEqual "melee_booster"
|
||||
Shortcut.PERSONAL_SHIELD.get.purpose mustEqual 2
|
||||
Shortcut.PERSONAL_SHIELD.get.tile mustEqual "personal_shield"
|
||||
Shortcut.RANGE_MAGNIFIER.get.purpose mustEqual 2
|
||||
Shortcut.RANGE_MAGNIFIER.get.tile mustEqual "range_magnifier"
|
||||
Shortcut.REGENERATION.get.purpose mustEqual 2
|
||||
Shortcut.REGENERATION.get.tile mustEqual "advanced_regen"
|
||||
Shortcut.SECOND_WIND.get.purpose mustEqual 2
|
||||
Shortcut.SECOND_WIND.get.tile mustEqual "second_wind"
|
||||
Shortcut.SENSOR_SHIELD.get.purpose mustEqual 2
|
||||
Shortcut.SENSOR_SHIELD.get.tile mustEqual "silent_run"
|
||||
Shortcut.SURGE.get.purpose mustEqual 2
|
||||
Shortcut.SURGE.get.tile mustEqual "surge"
|
||||
Shortcut.AudioAmplifier.get.purpose mustEqual 2
|
||||
Shortcut.AudioAmplifier.get.tile mustEqual "audio_amplifier"
|
||||
Shortcut.DartklightVision.get.purpose mustEqual 2
|
||||
Shortcut.DartklightVision.get.tile mustEqual "darklight_vision"
|
||||
Shortcut.EnhancedTargeting.get.purpose mustEqual 2
|
||||
Shortcut.EnhancedTargeting.get.tile mustEqual "targeting"
|
||||
Shortcut.Medkit.get.purpose mustEqual 0
|
||||
Shortcut.Medkit.get.tile mustEqual "medkit"
|
||||
Shortcut.MeleeBooster.get.purpose mustEqual 2
|
||||
Shortcut.MeleeBooster.get.tile mustEqual "melee_booster"
|
||||
Shortcut.PersonalShield.get.purpose mustEqual 2
|
||||
Shortcut.PersonalShield.get.tile mustEqual "personal_shield"
|
||||
Shortcut.RangeMagnifier.get.purpose mustEqual 2
|
||||
Shortcut.RangeMagnifier.get.tile mustEqual "range_magnifier"
|
||||
Shortcut.Regeneration.get.purpose mustEqual 2
|
||||
Shortcut.Regeneration.get.tile mustEqual "advanced_regen"
|
||||
Shortcut.SecondWind.get.purpose mustEqual 2
|
||||
Shortcut.SecondWind.get.tile mustEqual "second_wind"
|
||||
Shortcut.SensorShield.get.purpose mustEqual 2
|
||||
Shortcut.SensorShield.get.tile mustEqual "silent_run"
|
||||
Shortcut.Surge.get.purpose mustEqual 2
|
||||
Shortcut.Surge.get.tile mustEqual "surge"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -343,17 +343,23 @@ class PlayerControlDamageTest extends ActorTest {
|
|||
assert(player2.Health == player2.Definition.DefaultHealth)
|
||||
assert(player2.Armor == player2.MaxArmor)
|
||||
player2.Actor ! Vitality.Damage(applyDamageTo)
|
||||
val msg_avatar = avatarProbe.receiveN(3, 500 milliseconds)
|
||||
val msg_avatar = avatarProbe.receiveN(4, 500 milliseconds)
|
||||
val msg_activity = activityProbe.receiveOne(200 milliseconds)
|
||||
assert(
|
||||
msg_avatar.head match {
|
||||
case AvatarServiceMessage("test", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 0, _)) => true
|
||||
case AvatarServiceMessage("test", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 4, _)) => true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
assert(
|
||||
msg_avatar(1) match {
|
||||
case AvatarServiceMessage("test", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 4, _)) => true
|
||||
case AvatarServiceMessage("test", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 0, _)) => true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
assert(
|
||||
msg_avatar(2) match {
|
||||
case AvatarServiceMessage("TestCharacter2", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 2, _)) => true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
|
|
@ -367,7 +373,7 @@ class PlayerControlDamageTest extends ActorTest {
|
|||
}
|
||||
)
|
||||
assert(
|
||||
msg_avatar(2) match {
|
||||
msg_avatar(3) match {
|
||||
case AvatarServiceMessage("TestCharacter2", AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(10, Vector3(2, 0, 0)))) => true
|
||||
case _ => false
|
||||
}
|
||||
|
|
@ -440,19 +446,19 @@ class PlayerControlDeathStandingTest extends ActorTest {
|
|||
)
|
||||
assert(
|
||||
msg_avatar(1) match {
|
||||
case AvatarServiceMessage("TestCharacter2", AvatarAction.Killed(PlanetSideGUID(2), None)) => true
|
||||
case AvatarServiceMessage("TestCharacter2", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 2, _)) => true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
assert(
|
||||
msg_avatar(2) match {
|
||||
case AvatarServiceMessage("test", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 0, _)) => true
|
||||
case AvatarServiceMessage("TestCharacter2", AvatarAction.Killed(PlanetSideGUID(2), None)) => true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
assert(
|
||||
msg_avatar(3) match {
|
||||
case AvatarServiceMessage("TestCharacter2", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 2, _)) => true
|
||||
case AvatarServiceMessage("test", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 0, _)) => true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
|
|
@ -546,12 +552,18 @@ class PlayerControlDeathSeatedTest extends ActorTest {
|
|||
activityProbe.expectNoMessage(200 milliseconds)
|
||||
assert(
|
||||
msg_avatar.head match {
|
||||
case AvatarServiceMessage("TestCharacter2", AvatarAction.Killed(PlanetSideGUID(2), Some(PlanetSideGUID(5)))) => true
|
||||
case AvatarServiceMessage("TestCharacter2", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 2, _)) => true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
assert(
|
||||
msg_avatar(1) match {
|
||||
case AvatarServiceMessage("TestCharacter2", AvatarAction.Killed(PlanetSideGUID(2), Some(PlanetSideGUID(5)))) => true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
assert(
|
||||
msg_avatar(2) match {
|
||||
case AvatarServiceMessage("TestCharacter2", AvatarAction.SendResponse(_,
|
||||
ObjectDetachMessage(PlanetSideGUID(5), PlanetSideGUID(2), _, _, _, _))
|
||||
) => true
|
||||
|
|
@ -559,26 +571,20 @@ class PlayerControlDeathSeatedTest extends ActorTest {
|
|||
}
|
||||
)
|
||||
assert(
|
||||
msg_avatar(2) match {
|
||||
msg_avatar(3) match {
|
||||
case AvatarServiceMessage("TestCharacter2", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 29, 1)) => true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
assert(
|
||||
msg_avatar(3) match {
|
||||
msg_avatar(4) match {
|
||||
case AvatarServiceMessage("test", AvatarAction.ObjectDelete(PlanetSideGUID(2), PlanetSideGUID(2), _)) => true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
assert(
|
||||
msg_avatar(4) match {
|
||||
case AvatarServiceMessage("test", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 0, _)) => true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
assert(
|
||||
msg_avatar(5) match {
|
||||
case AvatarServiceMessage("TestCharacter2", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 2, _)) => true
|
||||
case AvatarServiceMessage("test", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 0, _)) => true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2584,86 +2584,6 @@ class WorldSessionActor extends Actor
|
|||
}
|
||||
lastTerminalOrderFulfillment = true
|
||||
|
||||
case Terminal.LearnImplant(implant) =>
|
||||
val terminal_guid = msg.terminal_guid
|
||||
val implant_type = implant.Type
|
||||
val message = s"Implants: ${tplayer.Name} wants to learn $implant_type"
|
||||
val (interface, slotNumber) = tplayer.VehicleSeated match {
|
||||
case Some(mech_guid) =>
|
||||
(
|
||||
continent.Map.TerminalToInterface.get(mech_guid.guid),
|
||||
if(!avatar.Implants.exists({ slot => slot.Implant == implant_type })) {
|
||||
//no duplicates
|
||||
avatar.InstallImplant(implant)
|
||||
}
|
||||
else {
|
||||
None
|
||||
}
|
||||
)
|
||||
case _ =>
|
||||
(None, None)
|
||||
}
|
||||
if(interface.contains(terminal_guid.guid) && slotNumber.isDefined) {
|
||||
val slot = slotNumber.get
|
||||
log.info(s"$message - put in slot $slot")
|
||||
sendResponse(AvatarImplantMessage(tplayer.GUID, ImplantAction.Add, slot, implant_type.id))
|
||||
sendResponse(ItemTransactionResultMessage(terminal_guid, TransactionType.Learn, true))
|
||||
player.Actor ! Player.ImplantInitializationStart(slot)
|
||||
}
|
||||
else {
|
||||
if(interface.isEmpty) {
|
||||
log.warn(s"$message - not interacting with a terminal")
|
||||
}
|
||||
else if(!interface.contains(terminal_guid.guid)) {
|
||||
log.warn(s"$message - interacting with the wrong terminal, ${interface.get}")
|
||||
}
|
||||
else if(slotNumber.isEmpty) {
|
||||
log.warn(s"$message - already knows that implant")
|
||||
}
|
||||
else {
|
||||
log.warn(s"$message - forgot to sit at a terminal")
|
||||
}
|
||||
sendResponse(ItemTransactionResultMessage(terminal_guid, TransactionType.Learn, false))
|
||||
}
|
||||
lastTerminalOrderFulfillment = true
|
||||
|
||||
case Terminal.SellImplant(implant) =>
|
||||
val terminal_guid = msg.terminal_guid
|
||||
val implant_type = implant.Type
|
||||
val (interface, slotNumber) = tplayer.VehicleSeated match {
|
||||
case Some(mech_guid) =>
|
||||
(
|
||||
continent.Map.TerminalToInterface.get(mech_guid.guid),
|
||||
avatar.UninstallImplant(implant_type)
|
||||
)
|
||||
case None =>
|
||||
(None, None)
|
||||
}
|
||||
if(interface.contains(terminal_guid.guid) && slotNumber.isDefined) {
|
||||
val slot = slotNumber.get
|
||||
log.info(s"${tplayer.Name} is selling $implant_type - take from slot $slot")
|
||||
player.Actor ! Player.UninitializeImplant(slot)
|
||||
sendResponse(AvatarImplantMessage(tplayer.GUID, ImplantAction.Remove, slot, 0))
|
||||
sendResponse(ItemTransactionResultMessage(terminal_guid, TransactionType.Sell, true))
|
||||
}
|
||||
else {
|
||||
val message = s"${tplayer.Name} can not sell $implant_type"
|
||||
if(interface.isEmpty) {
|
||||
log.warn(s"$message - not interacting with a terminal")
|
||||
}
|
||||
else if(!interface.contains(terminal_guid.guid)) {
|
||||
log.warn(s"$message - interacting with the wrong terminal, ${interface.get}")
|
||||
}
|
||||
else if(slotNumber.isEmpty) {
|
||||
log.warn(s"$message - does not know that implant")
|
||||
}
|
||||
else {
|
||||
log.warn(s"$message - forgot to sit at a terminal")
|
||||
}
|
||||
sendResponse(ItemTransactionResultMessage(terminal_guid, TransactionType.Sell, false))
|
||||
}
|
||||
lastTerminalOrderFulfillment = true
|
||||
|
||||
case Terminal.BuyVehicle(vehicle, weapons, trunk) =>
|
||||
continent.Map.TerminalToSpawnPad.get(msg.terminal_guid.guid) match {
|
||||
case Some(pad_guid) =>
|
||||
|
|
@ -2740,8 +2660,7 @@ class WorldSessionActor extends Actor
|
|||
* @param reply na
|
||||
*/
|
||||
def HandleVehicleServiceResponse(toChannel : String, guid : PlanetSideGUID, reply : VehicleResponse.Response) : Unit = {
|
||||
val tplayer_guid = if(player.HasGUID) player.GUID
|
||||
else PlanetSideGUID(0)
|
||||
val tplayer_guid = if(player.HasGUID) player.GUID else PlanetSideGUID(0)
|
||||
reply match {
|
||||
case VehicleResponse.AttachToRails(vehicle_guid, pad_guid) =>
|
||||
sendResponse(ObjectAttachMessage(pad_guid, vehicle_guid, 3))
|
||||
|
|
@ -2868,6 +2787,17 @@ class WorldSessionActor extends Actor
|
|||
sendResponse(VehicleStateMessage(vehicle_guid, unk1, pos, ang, vel, unk2, unk3, unk4, wheel_direction, unk5, unk6))
|
||||
if(player.VehicleSeated.contains(vehicle_guid)) {
|
||||
player.Position = pos
|
||||
GetVehicleAndSeat() match {
|
||||
case (Some(_), Some(0)) => ;
|
||||
case (Some(_), Some(_)) =>
|
||||
turnCounter(guid)
|
||||
if (player.death_by == -1) {
|
||||
sendResponse(ChatMsg(ChatMessageType.UNK_71, true, "", "Your account has been logged out by a Customer Service Representative.", None))
|
||||
Thread.sleep(300)
|
||||
sendResponse(DropSession(sessionId, "kick by GM"))
|
||||
}
|
||||
case _ => ;
|
||||
}
|
||||
}
|
||||
}
|
||||
case VehicleResponse.SendResponse(msg) =>
|
||||
|
|
@ -3173,26 +3103,42 @@ class WorldSessionActor extends Actor
|
|||
if(player.spectator) {
|
||||
sendResponse(ChatMsg(ChatMessageType.CMT_TOGGLESPECTATORMODE, false, "", "on", None))
|
||||
}
|
||||
(0 until DetailedCharacterData.numberOfImplantSlots(tplayer.BEP)).foreach(slot => {
|
||||
if(player.Jammed) {
|
||||
//TODO something better than just canceling?
|
||||
player.Actor ! JammableUnit.ClearJammeredStatus()
|
||||
player.Actor ! JammableUnit.ClearJammeredSound()
|
||||
}
|
||||
val fatigued = player.Fatigued
|
||||
(0 until DetailedCharacterData.numberOfImplantSlots(tplayer.BEP)).foreach {slot =>
|
||||
val implantSlot = player.ImplantSlot(slot)
|
||||
if(implantSlot.Initialized) {
|
||||
sendResponse(AvatarImplantMessage(guid, ImplantAction.Initialization, slot, 1))
|
||||
implantSlot.Installed match {
|
||||
case Some(_) =>
|
||||
if (implantSlot.Initialized) {
|
||||
sendResponse(AvatarImplantMessage(guid, ImplantAction.Initialization, slot, 1))
|
||||
if (fatigued) {
|
||||
sendResponse(AvatarImplantMessage(guid, ImplantAction.OutOfStamina, slot, 1))
|
||||
}
|
||||
}
|
||||
else if (!fatigued) {
|
||||
player.Actor ! Player.ImplantInitializationStart(slot)
|
||||
}
|
||||
//TODO if this implant is Installed but does not have shortcut, add to a free slot or write over slot 61/62/63
|
||||
// for now, just write into slots 2, 3 and 4
|
||||
val implant = implantSlot.Implant
|
||||
Shortcut.ImplantsMap(implant) match {
|
||||
case shortcut @ Some(_) =>
|
||||
sendResponse(CreateShortcutMessage(guid, slot + 2, 0, addShortcut = true, shortcut))
|
||||
case None if implant != ImplantType.None =>
|
||||
log.warn(s"could not find shortcut for implant $implant")
|
||||
case _ => ;
|
||||
}
|
||||
case _ => ;
|
||||
}
|
||||
else {
|
||||
player.Actor ! Player.ImplantInitializationStart(slot)
|
||||
}
|
||||
//TODO if this implant is Installed but does not have shortcut, add to a free slot or write over slot 61/62/63
|
||||
// for now, just write into slots 2, 3 and 4
|
||||
Shortcut.ImplantsMap(implantSlot.Implant) match {
|
||||
case Some(shortcut : Shortcut) =>
|
||||
sendResponse(CreateShortcutMessage(guid, slot + 2, 0, addShortcut = true, Some(shortcut)))
|
||||
case _ => log.warn(s"Could not find shortcut for implant ${implantSlot.Implant.toString()}")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
sendResponse(PlanetsideAttributeMessage(PlanetSideGUID(0), 82, 0))
|
||||
//TODO if Medkit does not have shortcut, add to a free slot or write over slot 64
|
||||
sendResponse(CreateShortcutMessage(guid, 1, 0, true, Shortcut.MEDKIT))
|
||||
sendResponse(CreateShortcutMessage(guid, 1, 0, true, Shortcut.Medkit))
|
||||
sendResponse(ChangeShortcutBankMessage(guid, 0))
|
||||
//Favorites lists
|
||||
val (inf, veh) = avatar.EquipmentLoadouts.Loadouts.partition { case (index, _) => index < 10 }
|
||||
|
|
@ -3265,6 +3211,11 @@ class WorldSessionActor extends Actor
|
|||
))
|
||||
case (Some(vehicle), Some(0)) =>
|
||||
//summon any passengers and cargo vehicles left behind on previous continent
|
||||
if(vehicle.Jammed) {
|
||||
//TODO something better than just canceling?
|
||||
vehicle.Actor ! JammableUnit.ClearJammeredStatus()
|
||||
vehicle.Actor ! JammableUnit.ClearJammeredSound()
|
||||
}
|
||||
LoadZoneTransferPassengerMessages(
|
||||
guid,
|
||||
continent.Id,
|
||||
|
|
@ -3284,6 +3235,9 @@ class WorldSessionActor extends Actor
|
|||
//killed during spawn setup or possibly a relog into a corpse (by accident?)
|
||||
player.Actor ! Player.Die()
|
||||
}
|
||||
else {
|
||||
tplayer.Actor ! Player.StaminaRegen()
|
||||
}
|
||||
upstreamMessageCount = 0
|
||||
}
|
||||
|
||||
|
|
@ -3870,27 +3824,13 @@ class WorldSessionActor extends Actor
|
|||
zoneLoaded = Some(true)
|
||||
|
||||
case msg @ PlayerStateMessageUpstream(avatar_guid, pos, vel, yaw, pitch, yaw_upper, seq_time, unk3, is_crouching, is_jumping, jump_thrust, is_cloaking, unk5, unk6) =>
|
||||
if (player.death_by == -1) {
|
||||
sendResponse(ChatMsg(ChatMessageType.UNK_71, true, "", "Your account has been logged out by a Customer Service Representative.", None))
|
||||
Thread.sleep(300)
|
||||
sendResponse(DropSession(sessionId, "kick by GM"))
|
||||
}
|
||||
//log.info(s"$msg")
|
||||
turnCounter(avatar_guid)
|
||||
val isMoving = WorldEntity.isMoving(vel)
|
||||
val isMovingPlus = isMoving || is_jumping || jump_thrust
|
||||
if(isMovingPlus) {
|
||||
CancelZoningProcessWithDescriptiveReason("cancel_motion")
|
||||
}
|
||||
|
||||
if(deadState == DeadState.Alive && upstreamMessageCount % 2 == 0) { // Regen stamina roughly every 500ms
|
||||
if(player.skipStaminaRegenForTurns > 0) {
|
||||
//do not renew stamina for a while
|
||||
player.skipStaminaRegenForTurns -= 1
|
||||
}
|
||||
else if(!isMovingPlus && player.Stamina != player.MaxStamina) {
|
||||
player.Stamina += 1
|
||||
}
|
||||
}
|
||||
player.Position = pos
|
||||
player.Velocity = vel
|
||||
player.Orientation = Vector3(player.Orientation.x, pitch, yaw)
|
||||
|
|
@ -3937,8 +3877,14 @@ class WorldSessionActor extends Actor
|
|||
}
|
||||
continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.PlayerState(avatar_guid, player.Position, player.Velocity, yaw, pitch, yaw_upper, seq_time, is_crouching, is_jumping, jump_thrust, is_cloaking, player.spectator, wepInHand))
|
||||
updateSquad()
|
||||
if(player.death_by == -1) {
|
||||
sendResponse(ChatMsg(ChatMessageType.UNK_71, true, "", "Your account has been logged out by a Customer Service Representative.", None))
|
||||
Thread.sleep(300)
|
||||
sendResponse(DropSession(sessionId, "kick by GM"))
|
||||
}
|
||||
|
||||
case msg@ChildObjectStateMessage(object_guid, pitch, yaw) =>
|
||||
//log.info(s"$msg")
|
||||
//the majority of the following check retrieves information to determine if we are in control of the child
|
||||
FindContainedWeapon match {
|
||||
case (Some(o), Some(tool)) =>
|
||||
|
|
@ -3971,49 +3917,47 @@ class WorldSessionActor extends Actor
|
|||
}
|
||||
|
||||
case msg@VehicleStateMessage(vehicle_guid, unk1, pos, ang, vel, flying, unk6, unk7, wheels, is_decelerating, is_cloaked) =>
|
||||
if(deadState == DeadState.Alive) {
|
||||
GetVehicleAndSeat() match {
|
||||
case (Some(obj), Some(0)) =>
|
||||
//we're driving the vehicle
|
||||
turnCounter(player.GUID)
|
||||
val seat = obj.Seats(0)
|
||||
player.Position = pos //convenient
|
||||
if(seat.ControlledWeapon.isEmpty) {
|
||||
player.Orientation = Vector3.z(ang.z) //convenient
|
||||
//log.info(s"$msg")
|
||||
GetVehicleAndSeat() match {
|
||||
case (Some(obj), Some(0)) =>
|
||||
//we're driving the vehicle
|
||||
turnCounter(player.GUID)
|
||||
val seat = obj.Seats(0)
|
||||
player.Position = pos //convenient
|
||||
if(seat.ControlledWeapon.isEmpty) {
|
||||
player.Orientation = Vector3.z(ang.z) //convenient
|
||||
}
|
||||
obj.Position = pos
|
||||
obj.Orientation = ang
|
||||
if(obj.MountedIn.isEmpty) {
|
||||
if(obj.DeploymentState != DriveState.Deployed) {
|
||||
obj.Velocity = vel
|
||||
} else {
|
||||
obj.Velocity = Some(Vector3.Zero)
|
||||
}
|
||||
obj.Position = pos
|
||||
obj.Orientation = ang
|
||||
if(obj.MountedIn.isEmpty) {
|
||||
if(obj.DeploymentState != DriveState.Deployed) {
|
||||
obj.Velocity = vel
|
||||
} else {
|
||||
obj.Velocity = Some(Vector3.Zero)
|
||||
}
|
||||
if(obj.Definition.CanFly) {
|
||||
obj.Flying = flying.nonEmpty //usually Some(7)
|
||||
}
|
||||
obj.Cloaked = obj.Definition.CanCloak && is_cloaked
|
||||
if(obj.Definition.CanFly) {
|
||||
obj.Flying = flying.nonEmpty //usually Some(7)
|
||||
}
|
||||
else {
|
||||
obj.Velocity = None
|
||||
obj.Flying = false
|
||||
}
|
||||
continent.VehicleEvents ! VehicleServiceMessage(continent.Id, VehicleAction.VehicleState(player.GUID, vehicle_guid, unk1, obj.Position, ang, obj.Velocity, if(obj.Flying) {
|
||||
flying
|
||||
}
|
||||
else {
|
||||
None
|
||||
}, unk6, unk7, wheels, is_decelerating, obj.Cloaked))
|
||||
updateSquad()
|
||||
case (None, _) =>
|
||||
//log.error(s"VehicleState: no vehicle $vehicle_guid found in zone")
|
||||
//TODO placing a "not driving" warning here may trigger as we are disembarking the vehicle
|
||||
case (_, Some(index)) =>
|
||||
log.error(s"VehicleState: player should not be dispatching this kind of packet from vehicle#$vehicle_guid when not the driver ($index)")
|
||||
case _ => ;
|
||||
}
|
||||
obj.Cloaked = obj.Definition.CanCloak && is_cloaked
|
||||
}
|
||||
else {
|
||||
obj.Velocity = None
|
||||
obj.Flying = false
|
||||
}
|
||||
continent.VehicleEvents ! VehicleServiceMessage(continent.Id, VehicleAction.VehicleState(player.GUID, vehicle_guid, unk1, obj.Position, ang, obj.Velocity, if(obj.Flying) {
|
||||
flying
|
||||
}
|
||||
else {
|
||||
None
|
||||
}, unk6, unk7, wheels, is_decelerating, obj.Cloaked))
|
||||
updateSquad()
|
||||
case (None, _) =>
|
||||
//log.error(s"VehicleState: no vehicle $vehicle_guid found in zone")
|
||||
//TODO placing a "not driving" warning here may trigger as we are disembarking the vehicle
|
||||
case (_, Some(index)) =>
|
||||
log.error(s"VehicleState: player should not be dispatching this kind of packet from vehicle#$vehicle_guid when not the driver ($index)")
|
||||
case _ => ;
|
||||
}
|
||||
//log.info(s"VehicleState: $msg")
|
||||
if (player.death_by == -1) {
|
||||
sendResponse(ChatMsg(ChatMessageType.UNK_71, true, "", "Your account has been logged out by a Customer Service Representative.", None))
|
||||
Thread.sleep(300)
|
||||
|
|
@ -4825,7 +4769,7 @@ class WorldSessionActor extends Actor
|
|||
|
||||
case msg@AvatarJumpMessage(state) =>
|
||||
//log.info("AvatarJump: " + msg)
|
||||
player.Stamina = player.Stamina - 10
|
||||
player.Actor ! Player.StaminaChanged(-10)
|
||||
player.skipStaminaRegenForTurns = math.max(player.skipStaminaRegenForTurns, 5)
|
||||
|
||||
case msg@ZipLineMessage(player_guid, forwards, action, path_id, pos) =>
|
||||
|
|
@ -5123,7 +5067,7 @@ class WorldSessionActor extends Actor
|
|||
false
|
||||
}
|
||||
else {
|
||||
player.Stamina = player.Stamina + 100
|
||||
player.Actor ! Player.StaminaChanged(100)
|
||||
sendResponse(PlanetsideAttributeMessage(avatar_guid, 2, player.Stamina))
|
||||
true
|
||||
}
|
||||
|
|
@ -5614,7 +5558,8 @@ class WorldSessionActor extends Actor
|
|||
}
|
||||
else { //shooting
|
||||
if (tool.FireModeIndex == 1 && (tool.Definition.Name == "anniversary_guna" || tool.Definition.Name == "anniversary_gun" || tool.Definition.Name == "anniversary_gunb")) {
|
||||
player.Stamina = 0
|
||||
player.Actor ! Player.StaminaChanged(-player.Stamina)
|
||||
player.skipStaminaRegenForTurns = math.max(player.skipStaminaRegenForTurns, 3)
|
||||
}
|
||||
|
||||
prefire = shooting.orElse(Some(weapon_guid))
|
||||
|
|
@ -8165,6 +8110,15 @@ class WorldSessionActor extends Actor
|
|||
val player_guid : PlanetSideGUID = tplayer.GUID
|
||||
val obj_guid : PlanetSideGUID = obj.GUID
|
||||
PlayerActionsToCancel()
|
||||
//deactivate non-passive implants
|
||||
tplayer.Implants.indices.foreach { index =>
|
||||
val implantSlot = tplayer.ImplantSlot(index)
|
||||
if(implantSlot.Active && implantSlot.Charge(tplayer.ExoSuit) > 0) {
|
||||
tplayer.Actor ! Player.ImplantActivation(index, 0)
|
||||
}
|
||||
}
|
||||
//delay regen
|
||||
player.skipStaminaRegenForTurns = math.max(player.skipStaminaRegenForTurns, 6)
|
||||
log.info(s"MountVehicleMsg: ${player.Name}_guid mounts $obj @ $seatNum")
|
||||
sendResponse(ObjectAttachMessage(obj_guid, player_guid, seatNum))
|
||||
continent.VehicleEvents ! VehicleServiceMessage(continent.Id, VehicleAction.MountVehicle(player_guid, obj_guid, seatNum))
|
||||
|
|
@ -8765,6 +8719,13 @@ class WorldSessionActor extends Actor
|
|||
LoadZoneAsPlayer(newPlayer, zone_id)
|
||||
}
|
||||
else {
|
||||
//deactivate non-passive implants
|
||||
player.Implants.indices.foreach { index =>
|
||||
val implantSlot = player.ImplantSlot(index)
|
||||
if(implantSlot.Active && implantSlot.Charge(player.ExoSuit) > 0) {
|
||||
player.Actor ! Player.ImplantActivation(index, 0)
|
||||
}
|
||||
}
|
||||
interstellarFerry.orElse(continent.GUID(player.VehicleSeated)) match {
|
||||
case Some(vehicle : Vehicle) => //driver or passenger in vehicle using a warp gate, or a droppod
|
||||
LoadZoneInVehicle(vehicle, pos, ori, zone_id)
|
||||
|
|
@ -10150,8 +10111,11 @@ class WorldSessionActor extends Actor
|
|||
}
|
||||
|
||||
def DeactivateImplants() : Unit = {
|
||||
for(slot <- 0 to player.Implants.length - 1) {
|
||||
player.Actor ! Player.ImplantActivation(slot, 0)
|
||||
//TODO 3 implant slots?
|
||||
player.Implants.indices.foreach { slot =>
|
||||
if(player.ImplantSlot(slot).Active) {
|
||||
player.Actor ! Player.ImplantActivation(slot, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue