mirror of
https://github.com/psforever/PSF-LoginServer.git
synced 2026-01-20 02:54:46 +00:00
Implant definitions, stamina audit and initialization
This commit is contained in:
parent
28960ad24f
commit
404e2579ef
|
|
@ -51,24 +51,51 @@ object GlobalDefinitions {
|
|||
Implants
|
||||
*/
|
||||
val advanced_regen = ImplantDefinition(0)
|
||||
advanced_regen.InitializationDuration = 120000
|
||||
advanced_regen.StaminaCost = 2
|
||||
advanced_regen.CostIntervalDefault = 500
|
||||
|
||||
val targeting = ImplantDefinition(1)
|
||||
targeting.InitializationDuration = 60000
|
||||
|
||||
val audio_amplifier = ImplantDefinition(2)
|
||||
audio_amplifier.InitializationDuration = 60000
|
||||
audio_amplifier.StaminaCost = 1
|
||||
audio_amplifier.CostIntervalDefault = 1000
|
||||
|
||||
val darklight_vision = ImplantDefinition(3)
|
||||
darklight_vision.InitializationDuration = 60000
|
||||
darklight_vision.ActivationStaminaCost = 3
|
||||
darklight_vision.StaminaCost = 1
|
||||
darklight_vision.CostIntervalDefault = 500
|
||||
|
||||
val melee_booster = ImplantDefinition(4)
|
||||
melee_booster.InitializationDuration = 120000
|
||||
melee_booster.StaminaCost = 10
|
||||
|
||||
val personal_shield = ImplantDefinition(5)
|
||||
personal_shield.InitializationDuration = 120000
|
||||
personal_shield.StaminaCost = 1
|
||||
personal_shield.CostIntervalDefault = 600
|
||||
|
||||
val range_magnifier = ImplantDefinition(6)
|
||||
range_magnifier.InitializationDuration = 60000
|
||||
|
||||
val second_wind = ImplantDefinition(7)
|
||||
second_wind.InitializationDuration = 180000
|
||||
|
||||
val silent_run = ImplantDefinition(8)
|
||||
silent_run.InitializationDuration = 90000
|
||||
silent_run.StaminaCost = 1
|
||||
silent_run.CostIntervalDefault = 333
|
||||
silent_run.CostIntervalByExoSuitHashMap(ExoSuitType.Agile) = 1000
|
||||
|
||||
val surge = ImplantDefinition(9)
|
||||
surge.InitializationDuration = 90000
|
||||
surge.StaminaCost = 1
|
||||
surge.CostIntervalDefault = 1000
|
||||
surge.CostIntervalByExoSuitHashMap(ExoSuitType.Agile) = 500
|
||||
surge.CostIntervalByExoSuitHashMap(ExoSuitType.Reinforced) = 333
|
||||
|
||||
/*
|
||||
Projectiles
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package net.psforever.objects
|
||||
|
||||
import net.psforever.objects.definition.{ImplantDefinition, Stance}
|
||||
import akka.actor.Cancellable
|
||||
import net.psforever.objects.definition.ImplantDefinition
|
||||
import net.psforever.types.{ExoSuitType, ImplantType}
|
||||
|
||||
/**
|
||||
|
|
@ -18,11 +19,21 @@ 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 = DefaultCancellable.obj
|
||||
|
||||
/** 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 InitializeTimer_=(timer : Cancellable) : Cancellable = {
|
||||
initializeTimer = timer
|
||||
initializeTimer
|
||||
}
|
||||
|
||||
def Unlocked : Boolean = unlocked
|
||||
|
||||
def Unlocked_=(lock : Boolean) : Boolean = {
|
||||
|
|
@ -78,12 +89,12 @@ class ImplantSlot {
|
|||
case ImplantType.None =>
|
||||
-1L
|
||||
case _ =>
|
||||
Installed.get.Initialization
|
||||
Installed.get.InitializationDuration
|
||||
}
|
||||
|
||||
def ActivationCharge : Int = {
|
||||
if(Active) {
|
||||
Installed.get.ActivationCharge
|
||||
Installed.get.ActivationStaminaCost
|
||||
}
|
||||
else {
|
||||
0
|
||||
|
|
@ -92,15 +103,13 @@ class ImplantSlot {
|
|||
|
||||
/**
|
||||
* Calculate the stamina consumption of the implant for any given moment of being active after its activation.
|
||||
* As implant energy use can be influenced by both exo-suit worn and general stance held, both are considered.
|
||||
* @param suit the exo-suit being worn
|
||||
* @param stance the player's stance
|
||||
* @return the amount of stamina (energy) that is consumed
|
||||
*/
|
||||
def Charge(suit : ExoSuitType.Value, stance : Stance.Value) : Int = {
|
||||
def Charge(suit : ExoSuitType.Value) : Int = {
|
||||
if(Active) {
|
||||
val inst = Installed.get
|
||||
inst.DurationChargeBase + inst.DurationChargeByExoSuit(suit) + inst.DurationChargeByStance(stance)
|
||||
inst.StaminaCost
|
||||
}
|
||||
else {
|
||||
0
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// 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.equipment.{Equipment, EquipmentSize, EquipmentSlot, JammableUnit}
|
||||
|
|
@ -49,6 +50,7 @@ class Player(private val core : Avatar) extends PlanetSideServerObject
|
|||
private var crouching : Boolean = false
|
||||
private var jumping : Boolean = false
|
||||
private var cloaked : Boolean = false
|
||||
private var fatigued : Boolean = false // If stamina drops to 0, player is fatigued until regenerating at least 20 stamina
|
||||
|
||||
private var vehicleSeated : Option[PlanetSideGUID] = None
|
||||
|
||||
|
|
@ -138,8 +140,13 @@ class Player(private val core : Avatar) extends PlanetSideServerObject
|
|||
|
||||
def Stamina : Int = stamina
|
||||
|
||||
def Stamina_=(assignEnergy : Int) : Int = {
|
||||
stamina = if(isAlive) { math.min(math.max(0, assignEnergy), MaxStamina) } else { 0 }
|
||||
def Stamina_=(assignStamina : Int) : Int = {
|
||||
stamina = if(isAlive) { math.min(math.max(0, assignStamina), MaxStamina) } else { 0 }
|
||||
|
||||
if(Actor != ActorRef.noSender) {
|
||||
Actor ! Player.StaminaChanged(Stamina)
|
||||
}
|
||||
|
||||
Stamina
|
||||
}
|
||||
|
||||
|
|
@ -373,6 +380,8 @@ class Player(private val core : Avatar) extends PlanetSideServerObject
|
|||
*/
|
||||
def Implant(slot : Int) : ImplantType.Value = core.Implant(slot)
|
||||
|
||||
def ImplantSlot(slot: Int) : ImplantSlot = core.Implants(slot)
|
||||
|
||||
/**
|
||||
* A read-only `Array` of tuples representing important information about all unlocked implant slots.
|
||||
* @return a maximum of three implant types, initialization times, and active flags
|
||||
|
|
@ -411,6 +420,12 @@ class Player(private val core : Avatar) extends PlanetSideServerObject
|
|||
Cloaked
|
||||
}
|
||||
|
||||
def Fatigued : Boolean = fatigued
|
||||
def Fatigued_=(isFatigued : Boolean) : Boolean = {
|
||||
fatigued = isFatigued
|
||||
Fatigued
|
||||
}
|
||||
|
||||
def PersonalStyleFeatures : Option[Cosmetics] = core.PersonalStyleFeatures
|
||||
|
||||
def AddToPersonalStyle(value : PersonalStyle.Value) : (Option[Cosmetics], Option[Cosmetics]) = {
|
||||
|
|
@ -637,6 +652,12 @@ object Player {
|
|||
final val HandsDownSlot : Int = 255
|
||||
|
||||
final case class Die()
|
||||
final case class ImplantActivation(slot : Int, status : Int)
|
||||
final case class ImplantInitializationStart(slot : Int)
|
||||
final case class UninitializeImplant(slot : Int)
|
||||
final case class ImplantInitializationComplete(slot : Int)
|
||||
final case class DrainStamina(amount : Int)
|
||||
final case class StaminaChanged(currentStamina : Int)
|
||||
|
||||
def apply(core : Avatar) : Player = {
|
||||
new Player(core)
|
||||
|
|
|
|||
|
|
@ -2,17 +2,20 @@
|
|||
package net.psforever.objects.avatar
|
||||
|
||||
import akka.actor.Actor
|
||||
import net.psforever.objects.Player
|
||||
import net.psforever.objects.{DefaultCancellable, ImplantSlot, Player}
|
||||
import net.psforever.objects.ballistics.{PlayerSource, ResolvedProjectile, SourceEntry}
|
||||
import net.psforever.objects.definition.ImplantDefinition
|
||||
import net.psforever.objects.equipment.{JammableBehavior, JammableUnit}
|
||||
import net.psforever.objects.vital.{PlayerSuicide, Vitality}
|
||||
import net.psforever.objects.zones.Zone
|
||||
import net.psforever.packet.game._
|
||||
import net.psforever.types.{ExoSuitType, PlanetSideGUID}
|
||||
import net.psforever.types.{ExoSuitType, ImplantType, PlanetSideGUID}
|
||||
import services.Service
|
||||
import services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||
|
||||
import scala.concurrent.duration._
|
||||
import scala.collection.mutable
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
|
||||
/**
|
||||
* na;
|
||||
|
|
@ -25,7 +28,86 @@ class PlayerControl(player : Player) extends Actor
|
|||
private [this] val log = org.log4s.getLogger(player.Name)
|
||||
private [this] val damageLog = org.log4s.getLogger("DamageResolution")
|
||||
|
||||
// A collection of timers for each slot to trigger stamina drain on an interval
|
||||
val implantSlotStaminaDrainTimers = mutable.HashMap(0 -> DefaultCancellable.obj, 1 -> DefaultCancellable.obj, 2 -> DefaultCancellable.obj)
|
||||
|
||||
def receive : Receive = jammableBehavior.orElse {
|
||||
case Player.ImplantActivation(slot: Int, status : Int) =>
|
||||
// todo: disable implants with stamina cost when changing armour type
|
||||
val implantSlot = player.ImplantSlot(slot)
|
||||
|
||||
if(status == 0 && implantSlot.Active) {
|
||||
// Cancel stamina drain timer
|
||||
implantSlotStaminaDrainTimers(slot).cancel()
|
||||
implantSlotStaminaDrainTimers(slot) = DefaultCancellable.obj
|
||||
|
||||
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.Zone.Id, AvatarAction.DeactivateImplantSlot(player.GUID, slot))
|
||||
} else if (status == 1 && implantSlot.Initialized && !implantSlot.Active && !player.Fatigued) {
|
||||
implantSlot.Installed match {
|
||||
case Some(implant: ImplantDefinition) =>
|
||||
implantSlot.Active = true
|
||||
|
||||
if (implant.ActivationStaminaCost >= 0) {
|
||||
player.Stamina -= implant.ActivationStaminaCost // Activation stamina drain
|
||||
}
|
||||
|
||||
if(implant.StaminaCost > 0 && implant.GetCostIntervalByExoSuit(player.ExoSuit) > 0) { // Ongoing stamina drain, if applicable
|
||||
implantSlotStaminaDrainTimers(slot) = context.system.scheduler.schedule(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.Zone.Id, AvatarAction.ActivateImplantSlot(player.GUID, slot))
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
// Start client side initialization timer
|
||||
player.Zone.AvatarEvents ! AvatarServiceMessage(player.Zone.Id, AvatarAction.SendResponse(player.GUID, ActionProgressMessage(slot + 6, 0)))
|
||||
|
||||
// Callback after initialization timer to complete initialization
|
||||
implantSlot.InitializeTimer = context.system.scheduler.scheduleOnce(implantSlot.MaxTimer milliseconds, self, Player.ImplantInitializationComplete(slot))
|
||||
}
|
||||
|
||||
case Player.ImplantInitializationComplete(slot: Int) =>
|
||||
val implantSlot = player.ImplantSlot(slot)
|
||||
if(implantSlot.Installed.isDefined) {
|
||||
player.Zone.AvatarEvents ! AvatarServiceMessage(player.Zone.Id, AvatarAction.SendResponse(player.GUID, AvatarImplantMessage(player.GUID, ImplantAction.Initialization, slot, 1)))
|
||||
implantSlot.Initialized = true
|
||||
implantSlot.InitializeTimer = DefaultCancellable.obj
|
||||
}
|
||||
|
||||
case Player.DrainStamina(amount : Int) =>
|
||||
player.Stamina -= amount
|
||||
|
||||
case Player.StaminaChanged(currentStamina : Int) =>
|
||||
if(currentStamina == 0) {
|
||||
player.Fatigued = true
|
||||
player.skipStaminaRegenForTurns += 4
|
||||
for(slot <- 0 to player.Implants.length - 1) { // 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
|
||||
for(slot <- 0 to player.Implants.length - 1) { // 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() =>
|
||||
PlayerControl.HandleDestructionAwareness(player, player.GUID, None)
|
||||
|
||||
|
|
@ -233,4 +315,11 @@ object PlayerControl {
|
|||
events ! AvatarServiceMessage(zoneChannel, AvatarAction.DestroyDisplay(pentry, pentry, 0))
|
||||
}
|
||||
}
|
||||
|
||||
def UninitializeImplant(player: Player, slot: Int): Unit = {
|
||||
val implantSlot = player.ImplantSlot(slot)
|
||||
|
||||
implantSlot.Initialized = false
|
||||
player.Zone.AvatarEvents ! AvatarServiceMessage(player.Zone.Id, AvatarAction.SendResponse(player.GUID, AvatarImplantMessage(player.GUID, ImplantAction.Initialization, slot, 0)))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,19 +5,6 @@ import net.psforever.types.{ExoSuitType, ImplantType}
|
|||
|
||||
import scala.collection.mutable
|
||||
|
||||
/**
|
||||
* An `Enumeration` of a variety of poses or generalized movement.
|
||||
*/
|
||||
object Stance extends Enumeration {
|
||||
val
|
||||
Crouching,
|
||||
CrouchWalking, //not used, but should still be defined
|
||||
Standing,
|
||||
Walking, //not used, but should still be defined
|
||||
Running
|
||||
= Value
|
||||
}
|
||||
|
||||
/**
|
||||
* The definition for an installable player utility that grants a perk, usually in exchange for stamina (energy).<br>
|
||||
* <br>
|
||||
|
|
@ -25,36 +12,40 @@ object Stance extends Enumeration {
|
|||
* When activated by the user, an `activationCharge` may be deducted form that user's stamina reserves.
|
||||
* This does not necessarily have to be a non-zero value.
|
||||
* Passive implants are always active and thus have no cost.
|
||||
* After being activated, a non-passive implant consumes a specific amount of stamina each second.
|
||||
* This cost is modified by how the user is standing and what type of exo-suit they are wearing.
|
||||
* The `durationChargeBase` is the lowest cost for an implant.
|
||||
* Modifiers for exo-suit type and stance type are then added onto this base cost.
|
||||
* For example: wearing `Reinforced` costs 2 stamina but costs only 1 stamina in all other cases.
|
||||
* Assuming that is the only cost, the definition would have a base charge of 1 and a `Reinforced` modifier of 1.
|
||||
* After being activated, a non-passive implant consumes a specific amount of stamina at regular intervals
|
||||
* Some implants will specify a different interval for consuming stamina based on the exo-suit the player is wearing
|
||||
* @param implantType the type of implant that is defined
|
||||
* @see `ImplantType`
|
||||
*/
|
||||
class ImplantDefinition(private val implantType : Int) extends BasicDefinition {
|
||||
ImplantType(implantType)
|
||||
/** how long it takes the implant to spin-up; is milliseconds */
|
||||
private var initialization : Long = 0L
|
||||
/** how long it takes the implant to become ready for activation; is milliseconds */
|
||||
private var initializationDuration : Long = 0L
|
||||
/** a passive certification is activated as soon as it is ready (or other condition) */
|
||||
private var passive : Boolean = false
|
||||
/** how much turning on the implant costs */
|
||||
private var activationCharge : Int = 0
|
||||
/** how much energy does this implant cost to remain active per second*/
|
||||
private var durationChargeBase : Int = 0
|
||||
/** how much more energy does the implant cost for this exo-suit */
|
||||
private val durationChargeByExoSuit = mutable.HashMap[ExoSuitType.Value, Int]().withDefaultValue(0)
|
||||
/** how much more energy does the implant cost for this stance */
|
||||
private val durationChargeByStance = mutable.HashMap[Stance.Value, Int]().withDefaultValue(0)
|
||||
private var activationStaminaCost : Int = 0
|
||||
/** how much energy does this implant cost to remain activate per interval tick */
|
||||
private var staminaCost : Int = 0
|
||||
|
||||
/**
|
||||
* How often in milliseconds the stamina cost will be applied, per exo-suit type
|
||||
* in game_objects.adb.lst each armour type is listed as a numeric identifier
|
||||
* stamina_consumption_interval = Standard
|
||||
* stamina_consumption_interval1 = Infil
|
||||
* stamina_consumption_interval2 = Agile
|
||||
* stamina_consumption_interval3 = Rexo
|
||||
* stamina_consumption_interval4 = MAX?
|
||||
*/
|
||||
private var costIntervalDefault : Int = 0
|
||||
private val costIntervalByExoSuit = mutable.HashMap[ExoSuitType.Value, Int]().withDefaultValue(CostIntervalDefault)
|
||||
Name = "implant"
|
||||
|
||||
def Initialization : Long = initialization
|
||||
def InitializationDuration : Long = initializationDuration
|
||||
|
||||
def Initialization_=(time : Long) : Long = {
|
||||
initialization = math.max(0, time)
|
||||
Initialization
|
||||
def InitializationDuration_=(time : Long) : Long = {
|
||||
initializationDuration = math.max(0, time)
|
||||
InitializationDuration
|
||||
}
|
||||
|
||||
def Passive : Boolean = passive
|
||||
|
|
@ -64,23 +55,31 @@ class ImplantDefinition(private val implantType : Int) extends BasicDefinition {
|
|||
Passive
|
||||
}
|
||||
|
||||
def ActivationCharge : Int = activationCharge
|
||||
def ActivationStaminaCost : Int = activationStaminaCost
|
||||
|
||||
def ActivationCharge_=(charge : Int) : Int = {
|
||||
activationCharge = math.max(0, charge)
|
||||
ActivationCharge
|
||||
def ActivationStaminaCost_=(charge : Int) : Int = {
|
||||
activationStaminaCost = math.max(0, charge)
|
||||
ActivationStaminaCost
|
||||
}
|
||||
|
||||
def DurationChargeBase : Int = durationChargeBase
|
||||
def StaminaCost : Int = staminaCost
|
||||
|
||||
def DurationChargeBase_=(charge : Int) : Int = {
|
||||
durationChargeBase = math.max(0, charge)
|
||||
DurationChargeBase
|
||||
def StaminaCost_=(charge : Int) : Int = {
|
||||
staminaCost = math.max(0, charge)
|
||||
StaminaCost
|
||||
}
|
||||
|
||||
def DurationChargeByExoSuit : mutable.Map[ExoSuitType.Value, Int] = durationChargeByExoSuit
|
||||
|
||||
def DurationChargeByStance : mutable.Map[Stance.Value, Int] = durationChargeByStance
|
||||
def CostIntervalDefault : Int = {
|
||||
costIntervalDefault
|
||||
}
|
||||
def CostIntervalDefault_=(interval : Int) : Int = {
|
||||
costIntervalDefault = interval
|
||||
CostIntervalDefault
|
||||
}
|
||||
|
||||
def GetCostIntervalByExoSuit(exosuit : ExoSuitType.Value) : Int = costIntervalByExoSuit.getOrElse(exosuit, CostIntervalDefault)
|
||||
def CostIntervalByExoSuitHashMap : mutable.Map[ExoSuitType.Value, Int] = costIntervalByExoSuit
|
||||
|
||||
def Type : ImplantType.Value = ImplantType(implantType)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,9 +6,9 @@ import scodec.Codec
|
|||
import scodec.codecs._
|
||||
|
||||
/**
|
||||
*
|
||||
* 6,7,8 - Start implant initialization timer for slots 0,1,2 respectively. Allowed values: 0-100 (50 will start timer at 50% complete)
|
||||
*/
|
||||
final case class ActionProgressMessage(unk1 : Int,
|
||||
final case class ActionProgressMessage(action : Int,
|
||||
unk2 : Long)
|
||||
extends PlanetSideGamePacket {
|
||||
type Packet = ActionProgressMessage
|
||||
|
|
@ -18,7 +18,7 @@ final case class ActionProgressMessage(unk1 : Int,
|
|||
|
||||
object ActionProgressMessage extends Marshallable[ActionProgressMessage] {
|
||||
implicit val codec : Codec[ActionProgressMessage] = (
|
||||
("unk1" | uint4L) ::
|
||||
("action" | uint4L) ::
|
||||
("unk2" | uint32L)
|
||||
).as[ActionProgressMessage]
|
||||
}
|
||||
|
|
@ -74,6 +74,10 @@ class AvatarService(zone : Zone) extends Actor {
|
|||
AvatarEvents.publish(
|
||||
AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.DeactivateImplantSlot(slot))
|
||||
)
|
||||
case AvatarAction.ActivateImplantSlot(player_guid, slot) =>
|
||||
AvatarEvents.publish(
|
||||
AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.ActivateImplantSlot(slot))
|
||||
)
|
||||
case AvatarAction.DeployItem(player_guid, item) =>
|
||||
val definition = item.Definition
|
||||
val objectData = definition.Packet.ConstructorData(item).get
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import net.psforever.objects.equipment.Equipment
|
|||
import net.psforever.objects.inventory.Container
|
||||
import net.psforever.objects.zones.Zone
|
||||
import net.psforever.packet.PlanetSideGamePacket
|
||||
import net.psforever.packet.game.ImplantAction
|
||||
import net.psforever.packet.game.objectcreate.{ConstructorData, ObjectCreateMessageParent}
|
||||
import net.psforever.types.{ExoSuitType, PlanetSideEmpire, PlanetSideGUID, Vector3}
|
||||
|
||||
|
|
@ -32,6 +33,7 @@ object AvatarAction {
|
|||
final case class EnvironmentalDamage(player_guid : PlanetSideGUID, source_guid : PlanetSideGUID, amount: Int) extends Action
|
||||
final case class DeployItem(player_guid : PlanetSideGUID, item : PlanetSideGameObject with Deployable) extends Action
|
||||
final case class DeactivateImplantSlot(player_guid : PlanetSideGUID, slot : Int) extends Action
|
||||
final case class ActivateImplantSlot(player_guid : PlanetSideGUID, slot : Int) extends Action
|
||||
final case class Destroy(victim : PlanetSideGUID, killer : PlanetSideGUID, weapon : PlanetSideGUID, pos : Vector3) extends Action
|
||||
final case class DestroyDisplay(killer : SourceEntry, victim : SourceEntry, method : Int, unk : Int = 121) extends Action
|
||||
final case class DropItem(player_guid : PlanetSideGUID, item : Equipment, zone : Zone) extends Action
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import net.psforever.objects.ballistics.{Projectile, SourceEntry}
|
|||
import net.psforever.objects.equipment.Equipment
|
||||
import net.psforever.packet.PlanetSideGamePacket
|
||||
import net.psforever.packet.game.objectcreate.ConstructorData
|
||||
import net.psforever.packet.game.ObjectCreateMessage
|
||||
import net.psforever.packet.game.{ImplantAction, ObjectCreateMessage}
|
||||
import net.psforever.types.{ExoSuitType, PlanetSideEmpire, PlanetSideGUID, Vector3}
|
||||
import services.GenericEventBusMsg
|
||||
|
||||
|
|
@ -26,6 +26,7 @@ object AvatarResponse {
|
|||
final case class ConcealPlayer() extends Response
|
||||
final case class EnvironmentalDamage(target : PlanetSideGUID, source_guid : PlanetSideGUID, amount : Int) extends Response
|
||||
final case class DeactivateImplantSlot(slot : Int) extends Response
|
||||
final case class ActivateImplantSlot(slot : Int) extends Response
|
||||
final case class Destroy(victim : PlanetSideGUID, killer : PlanetSideGUID, weapon : PlanetSideGUID, pos : Vector3) extends Response
|
||||
final case class DestroyDisplay(killer : SourceEntry, victim : SourceEntry, method : Int, unk : Int) extends Response
|
||||
final case class DropItem(pkt : ObjectCreateMessage) extends Response
|
||||
|
|
|
|||
|
|
@ -2,31 +2,25 @@
|
|||
package objects
|
||||
|
||||
import net.psforever.objects.ImplantSlot
|
||||
import net.psforever.objects.definition.{ImplantDefinition, Stance}
|
||||
import net.psforever.objects.definition.ImplantDefinition
|
||||
import net.psforever.types.{ExoSuitType, ImplantType}
|
||||
import org.specs2.mutable._
|
||||
|
||||
class ImplantTest extends Specification {
|
||||
val sample = new ImplantDefinition(8) //variant of sensor shield/silent run
|
||||
sample.Initialization = 90000 //1:30
|
||||
sample.ActivationCharge = 3
|
||||
sample.DurationChargeBase = 1
|
||||
sample.DurationChargeByExoSuit += ExoSuitType.Agile -> 2
|
||||
sample.DurationChargeByExoSuit += ExoSuitType.Reinforced -> 2
|
||||
sample.DurationChargeByExoSuit += ExoSuitType.Standard -> 1
|
||||
sample.DurationChargeByStance += Stance.Running -> 1
|
||||
sample.InitializationDuration = 90000 //1:30
|
||||
sample.ActivationStaminaCost = 3
|
||||
sample.StaminaCost = 1
|
||||
sample.CostIntervalDefault = 1000
|
||||
sample.CostIntervalByExoSuitHashMap += ExoSuitType.Agile -> 500
|
||||
|
||||
"ImplantDefinition" should {
|
||||
"define" in {
|
||||
sample.Initialization mustEqual 90000
|
||||
sample.ActivationCharge mustEqual 3
|
||||
sample.DurationChargeBase mustEqual 1
|
||||
sample.DurationChargeByExoSuit(ExoSuitType.Agile) mustEqual 2
|
||||
sample.DurationChargeByExoSuit(ExoSuitType.Reinforced) mustEqual 2
|
||||
sample.DurationChargeByExoSuit(ExoSuitType.Standard) mustEqual 1
|
||||
sample.DurationChargeByExoSuit(ExoSuitType.Infiltration) mustEqual 0 //default value
|
||||
sample.DurationChargeByStance(Stance.Running) mustEqual 1
|
||||
sample.DurationChargeByStance(Stance.Crouching) mustEqual 0 //default value
|
||||
sample.InitializationDuration mustEqual 90000
|
||||
sample.ActivationStaminaCost mustEqual 3
|
||||
sample.StaminaCost mustEqual 1
|
||||
sample.GetCostIntervalByExoSuit(ExoSuitType.Reinforced) mustEqual 1000 // Default value
|
||||
sample.GetCostIntervalByExoSuit(ExoSuitType.Agile) mustEqual 500 // Overridden value
|
||||
sample.Type mustEqual ImplantType.SilentRun
|
||||
}
|
||||
}
|
||||
|
|
@ -73,14 +67,14 @@ class ImplantTest extends Specification {
|
|||
obj.Unlocked mustEqual true
|
||||
}
|
||||
|
||||
"initialize without an implant" in {
|
||||
"can not initialize without an implant" in {
|
||||
val obj = new ImplantSlot
|
||||
obj.Initialized mustEqual false
|
||||
obj.Initialized = true
|
||||
obj.Initialized mustEqual false
|
||||
}
|
||||
|
||||
"initialize an implant" in {
|
||||
"can initialize an implant" in {
|
||||
val obj = new ImplantSlot
|
||||
obj.Initialized mustEqual false
|
||||
|
||||
|
|
@ -90,7 +84,7 @@ class ImplantTest extends Specification {
|
|||
obj.Initialized mustEqual true
|
||||
}
|
||||
|
||||
"activate an uninitialized implant" in {
|
||||
"can not activate an uninitialized implant" in {
|
||||
val obj = new ImplantSlot
|
||||
obj.Unlocked = true
|
||||
obj.Implant = sample
|
||||
|
|
@ -101,7 +95,7 @@ class ImplantTest extends Specification {
|
|||
obj.Active mustEqual false
|
||||
}
|
||||
|
||||
"activate an initialized implant" in {
|
||||
"can activate an initialized implant" in {
|
||||
val obj = new ImplantSlot
|
||||
obj.Unlocked = true
|
||||
obj.Implant = sample
|
||||
|
|
@ -120,7 +114,7 @@ class ImplantTest extends Specification {
|
|||
obj.Initialized = true
|
||||
obj.Active mustEqual false
|
||||
obj.ActivationCharge mustEqual 0
|
||||
obj.Charge(ExoSuitType.Reinforced, Stance.Running) mustEqual 0
|
||||
obj.Charge(ExoSuitType.Reinforced) mustEqual 0
|
||||
}
|
||||
|
||||
"cost energy while active" in {
|
||||
|
|
@ -131,7 +125,7 @@ class ImplantTest extends Specification {
|
|||
obj.Active = true
|
||||
obj.Active mustEqual true
|
||||
obj.ActivationCharge mustEqual 3
|
||||
obj.Charge(ExoSuitType.Reinforced, Stance.Running) mustEqual 4
|
||||
obj.Charge(ExoSuitType.Reinforced) mustEqual 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -162,11 +162,13 @@ class WorldSessionActor extends Actor
|
|||
var squadUpdateCounter : Int = 0
|
||||
val queuedSquadActions : Seq[() => Unit] = Seq(SquadUpdates, NoSquadUpdates, NoSquadUpdates, NoSquadUpdates)
|
||||
|
||||
var timeDL : Long = 0
|
||||
var timeSurge : Long = 0
|
||||
lazy val unsignedIntMaxValue : Long = Int.MaxValue.toLong * 2L + 1L
|
||||
var serverTime : Long = 0
|
||||
|
||||
/** Keeps track of the number of PlayerStateMessageUpstream messages received by the client
|
||||
* As they should arrive roughly every 250 milliseconds this allows for a very crude method of scheduling tasks up to four times per second */
|
||||
private var playerStateMessageUpstreamCount = 0
|
||||
|
||||
var amsSpawnPoints : List[SpawnPoint] = Nil
|
||||
var clientKeepAlive : Cancellable = DefaultCancellable.obj
|
||||
var progressBarUpdate : Cancellable = DefaultCancellable.obj
|
||||
|
|
@ -1301,8 +1303,26 @@ class WorldSessionActor extends Actor
|
|||
InitializeDeployableQuantities(avatar) //set deployables ui elements
|
||||
AwardBattleExperiencePoints(avatar, 20000000L)
|
||||
avatar.CEP = 600000
|
||||
avatar.Implants(0).Unlocked = true
|
||||
avatar.Implants(0).Implant = GlobalDefinitions.darklight_vision
|
||||
avatar.Implants(1).Unlocked = true
|
||||
avatar.Implants(1).Implant = GlobalDefinitions.surge
|
||||
avatar.Implants(2).Unlocked = true
|
||||
avatar.Implants(2).Implant = GlobalDefinitions.targeting
|
||||
|
||||
player = new Player(avatar)
|
||||
|
||||
(0 until DetailedCharacterData.numberOfImplantSlots(player.BEP)).foreach(slot => {
|
||||
val implantSlot = player.ImplantSlot(slot)
|
||||
if(implantSlot.Initialized) {
|
||||
sendResponse(AvatarImplantMessage(player.GUID, ImplantAction.Initialization, slot, 1))
|
||||
}
|
||||
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
|
||||
})
|
||||
|
||||
//xy-coordinates indicate sanctuary spawn bias:
|
||||
player.Position = math.abs(scala.util.Random.nextInt() % avatar.name.hashCode % 4) match {
|
||||
case 0 => Vector3(8192, 8192, 0) //NE
|
||||
|
|
@ -1534,12 +1554,10 @@ class WorldSessionActor extends Actor
|
|||
}
|
||||
|
||||
case AvatarResponse.DeactivateImplantSlot(slot) =>
|
||||
//temporary solution until implants are finalized
|
||||
slot match {
|
||||
case 1 => DeactivateImplantDarkLight()
|
||||
case 2 => DeactivateImplantSurge()
|
||||
case _ => ;
|
||||
}
|
||||
sendResponse(AvatarImplantMessage(PlanetSideGUID(player.GUID.guid), ImplantAction.Activation, slot, 0))
|
||||
|
||||
case AvatarResponse.ActivateImplantSlot(slot) =>
|
||||
sendResponse(AvatarImplantMessage(PlanetSideGUID(player.GUID.guid), ImplantAction.Activation, slot, 1))
|
||||
|
||||
case AvatarResponse.Destroy(victim, killer, weapon, pos) =>
|
||||
// guid = victim // killer = killer ;)
|
||||
|
|
@ -1572,8 +1590,6 @@ class WorldSessionActor extends Actor
|
|||
val respawnTimer = 300000 //milliseconds
|
||||
ToggleMaxSpecialState(enable = false)
|
||||
deadState = DeadState.Dead
|
||||
timeDL = 0
|
||||
timeSurge = 0
|
||||
continent.GUID(player.VehicleSeated) match {
|
||||
case Some(obj : Vehicle) =>
|
||||
TotalDriverVehicleControl(obj)
|
||||
|
|
@ -2587,6 +2603,7 @@ class WorldSessionActor extends Actor
|
|||
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) {
|
||||
|
|
@ -2620,6 +2637,7 @@ class WorldSessionActor extends Actor
|
|||
if(interface.contains(terminal_guid.guid) && slotNumber.isDefined) {
|
||||
val slot = slotNumber.get
|
||||
log.info(s"$tplayer 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))
|
||||
}
|
||||
|
|
@ -3358,8 +3376,13 @@ class WorldSessionActor extends Actor
|
|||
sendResponse(ChatMsg(ChatMessageType.CMT_TOGGLESPECTATORMODE, false, "", "on", None))
|
||||
}
|
||||
(0 until DetailedCharacterData.numberOfImplantSlots(tplayer.BEP)).foreach(slot => {
|
||||
sendResponse(AvatarImplantMessage(guid, ImplantAction.Initialization, slot, 1)) //init implant slot
|
||||
sendResponse(AvatarImplantMessage(guid, ImplantAction.Activation, slot, 0)) //deactivate implant
|
||||
val implantSlot = player.ImplantSlot(slot)
|
||||
if(implantSlot.Initialized) {
|
||||
sendResponse(AvatarImplantMessage(guid, ImplantAction.Initialization, slot, 1))
|
||||
}
|
||||
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
|
||||
})
|
||||
sendResponse(PlanetsideAttributeMessage(PlanetSideGUID(0), 82, 0))
|
||||
|
|
@ -4011,70 +4034,20 @@ class WorldSessionActor extends Actor
|
|||
beginZoningSetCurrentAvatarFunc(player)
|
||||
|
||||
case msg @ PlayerStateMessageUpstream(avatar_guid, pos, vel, yaw, pitch, yaw_upper, seq_time, unk3, is_crouching, is_jumping, jump_thrust, is_cloaking, unk5, unk6) =>
|
||||
playerStateMessageUpstreamCount += 1
|
||||
val isMoving = WorldEntity.isMoving(vel)
|
||||
val isMovingPlus = isMoving || is_jumping || jump_thrust
|
||||
//implants and stamina management start
|
||||
val implantsAreActive = avatar.Implants(0).Active || avatar.Implants(1).Active
|
||||
val staminaBefore = player.Stamina
|
||||
val hadStaminaBefore = staminaBefore > 0
|
||||
val hasStaminaAfter = if(deadState == DeadState.Alive) {
|
||||
if(implantsAreActive && hadStaminaBefore) {
|
||||
val time = System.currentTimeMillis()
|
||||
if(timeDL != 0) {
|
||||
val duration = time - timeDL
|
||||
if(duration > 500) {
|
||||
val units = (duration / 500).toInt
|
||||
player.Stamina = player.Stamina - units
|
||||
timeDL += units * 500
|
||||
}
|
||||
}
|
||||
if(timeSurge != 0) {
|
||||
val duration = time - timeSurge
|
||||
val period = player.ExoSuit match {
|
||||
case ExoSuitType.Agile => 500
|
||||
case ExoSuitType.Reinforced => 333
|
||||
case ExoSuitType.Infiltration => 1000
|
||||
case ExoSuitType.Standard => 1000
|
||||
case _ => 1
|
||||
}
|
||||
if(duration > period) {
|
||||
val units = (duration / period).toInt
|
||||
player.Stamina = player.Stamina - units
|
||||
timeSurge += period * units
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(deadState == DeadState.Alive && playerStateMessageUpstreamCount % 2 == 0) { // Regen stamina roughly every 500ms
|
||||
if(player.skipStaminaRegenForTurns > 0) {
|
||||
//do not renew stamina for a while
|
||||
player.skipStaminaRegenForTurns -= 1
|
||||
player.Stamina > 0
|
||||
}
|
||||
else if(player.Stamina == 0 && hadStaminaBefore) {
|
||||
//if the player lost all stamina this turn (had stamina at the start), do not renew stamina for a while
|
||||
player.skipStaminaRegenForTurns = 4
|
||||
player.Stamina > 0
|
||||
}
|
||||
else if(isMovingPlus || player.Stamina == player.MaxStamina) {
|
||||
//ineligible for stamina regen
|
||||
player.Stamina > 0
|
||||
}
|
||||
else {
|
||||
player.Stamina = player.Stamina + 1
|
||||
true
|
||||
else if(!isMovingPlus && player.Stamina != player.MaxStamina) {
|
||||
player.Stamina += 1
|
||||
}
|
||||
}
|
||||
else {
|
||||
timeDL = 0
|
||||
timeSurge = 0
|
||||
false
|
||||
}
|
||||
if(staminaBefore != player.Stamina) { //stamina changed
|
||||
sendResponse(PlanetsideAttributeMessage(player.GUID, 2, player.Stamina))
|
||||
}
|
||||
if(implantsAreActive && !hasStaminaAfter) { //implants deactivated at 0 stamina
|
||||
DeactivateImplants()
|
||||
}
|
||||
//implants and stamina management finish
|
||||
|
||||
player.Position = pos
|
||||
player.Velocity = vel
|
||||
player.Orientation = Vector3(player.Orientation.x, pitch, yaw)
|
||||
|
|
@ -4667,7 +4640,6 @@ class WorldSessionActor extends Actor
|
|||
//log.info("AvatarJump: " + msg)
|
||||
player.Stamina = player.Stamina - 10
|
||||
player.skipStaminaRegenForTurns = math.max(player.skipStaminaRegenForTurns, 5)
|
||||
sendResponse(PlanetsideAttributeMessage(player.GUID, 2, player.Stamina))
|
||||
|
||||
case msg @ ZipLineMessage(player_guid,forwards,action,path_id,pos) =>
|
||||
log.info("ZipLineMessage: " + msg)
|
||||
|
|
@ -4882,25 +4854,10 @@ class WorldSessionActor extends Actor
|
|||
log.warn(s"LootItem: can not find where to put $item_guid")
|
||||
}
|
||||
|
||||
case msg @ AvatarImplantMessage(_, action, slot, status) => //(player_guid, unk1, unk2, implant) =>
|
||||
case msg @ AvatarImplantMessage(player_guid, action, slot, status) =>
|
||||
log.info("AvatarImplantMessage: " + msg)
|
||||
if (avatar.Implants(slot).Initialized) {
|
||||
if(action == ImplantAction.Activation && status == 1) { // active
|
||||
avatar.Implants(slot).Active = true
|
||||
continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(player.GUID, 28, avatar.Implant(slot).id * 2 + 1))
|
||||
if (avatar.Implant(slot).id == 3) {
|
||||
timeDL = System.currentTimeMillis()
|
||||
player.Stamina = player.Stamina - 3
|
||||
sendResponse(PlanetsideAttributeMessage(player.GUID, 2, player.Stamina))
|
||||
}
|
||||
if (avatar.Implant(slot).id == 9) timeSurge = System.currentTimeMillis()
|
||||
} else if(action == ImplantAction.Activation && status == 0) { //desactive
|
||||
avatar.Implants(slot).Active = false
|
||||
continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(player.GUID, 28, avatar.Implant(slot).id * 2))
|
||||
if (avatar.Implant(slot).id == 3) timeDL = 0
|
||||
if (avatar.Implant(slot).id == 9) timeSurge = 0
|
||||
}
|
||||
sendResponse(AvatarImplantMessage(PlanetSideGUID(player.GUID.guid),action,slot,status))
|
||||
if(action == ImplantAction.Activation) {
|
||||
player.Actor ! Player.ImplantActivation(slot, status)
|
||||
}
|
||||
|
||||
case msg @ UseItemMessage(avatar_guid, item_used_guid, object_guid, unk2, unk3, unk4, unk5, unk6, unk7, unk8, itemType) =>
|
||||
|
|
@ -5073,7 +5030,6 @@ class WorldSessionActor extends Actor
|
|||
sendResponse(ObjectDeleteMessage(kit.GUID, 0))
|
||||
taskResolver ! GUIDTask.UnregisterEquipment(kit)(continent.GUID)
|
||||
player.Stamina = player.Stamina + 100
|
||||
sendResponse(PlanetsideAttributeMessage(avatar_guid, 2, player.Stamina))
|
||||
case None =>
|
||||
log.error(s"UseItem: anticipated a $kit, but can't find it")
|
||||
}
|
||||
|
|
@ -5652,7 +5608,6 @@ 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
|
||||
sendResponse(PlanetsideAttributeMessage(player.GUID, 2, 0))
|
||||
}
|
||||
|
||||
prefire = shooting.orElse(Some(weapon_guid))
|
||||
|
|
@ -8292,6 +8247,7 @@ class WorldSessionActor extends Actor
|
|||
def RespawnClone(tplayer : Player) : Player = {
|
||||
val faction = tplayer.Faction
|
||||
val obj = Player.Respawn(tplayer)
|
||||
obj.ResetAllImplants()
|
||||
LoadClassicDefault(obj)
|
||||
obj
|
||||
}
|
||||
|
|
@ -11066,39 +11022,9 @@ class WorldSessionActor extends Actor
|
|||
projectilesToCleanUp(local_index) = false
|
||||
}
|
||||
|
||||
/**
|
||||
* Deactivate all active implants.
|
||||
* This method is intended to support only the current Live server implants that are functional,
|
||||
* the darklight vision implant and the surge implant.
|
||||
*/
|
||||
def DeactivateImplants() : Unit = {
|
||||
DeactivateImplantDarkLight()
|
||||
DeactivateImplantSurge()
|
||||
}
|
||||
|
||||
/**
|
||||
* Deactivate the darklight vision implant.
|
||||
* This method is intended to support only the current Live server implants.
|
||||
*/
|
||||
def DeactivateImplantDarkLight() : Unit = {
|
||||
if(avatar.Implants(0).Active && avatar.Implants(0).Implant == ImplantType.DarklightVision) {
|
||||
avatar.Implants(0).Active = false
|
||||
continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(player.GUID, 28, avatar.Implant(0).id * 2))
|
||||
sendResponse(AvatarImplantMessage(PlanetSideGUID(player.GUID.guid), ImplantAction.Activation, 0, 0))
|
||||
timeDL = 0
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deactivate the surge implant.
|
||||
* This method is intended to support only the current Live server implants.
|
||||
*/
|
||||
def DeactivateImplantSurge() : Unit = {
|
||||
if(avatar.Implants(1).Active && avatar.Implants(0).Implant == ImplantType.Surge) {
|
||||
avatar.Implants(1).Active = false
|
||||
continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(player.GUID, 28, avatar.Implant(1).id * 2))
|
||||
sendResponse(AvatarImplantMessage(PlanetSideGUID(player.GUID.guid), ImplantAction.Activation, 1, 0))
|
||||
timeSurge = 0
|
||||
for(slot <- 0 to player.Implants.length - 1) {
|
||||
player.Actor ! Player.ImplantActivation(slot, 0)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue