Implant definitions, stamina audit and initialization

This commit is contained in:
Mazo 2020-03-09 22:14:12 +00:00
parent 28960ad24f
commit 404e2579ef
11 changed files with 273 additions and 201 deletions

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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)))
}
}

View file

@ -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)
}

View file

@ -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]
}

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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
}
}
}