diff --git a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala index 7a6a1bcd..330cba5b 100644 --- a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala +++ b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala @@ -270,6 +270,42 @@ object GlobalDefinitions { } } + /* + Implants + */ + val + advanced_regen = ImplantDefinition(0) + + val + targeting = ImplantDefinition(1) + + val + audio_amplifier = ImplantDefinition(2) + + val + darklight_vision = ImplantDefinition(3) + + val + melee_booster = ImplantDefinition(4) + + val + personal_shield = ImplantDefinition(5) + + val + range_magnifier = ImplantDefinition(6) + + val + second_wind = ImplantDefinition(7) + + val + silent_run = ImplantDefinition(8) + + val + surge = ImplantDefinition(9) + + /* + Equipment (locker_container, kits, ammunition, weapons) + */ import net.psforever.packet.game.objectcreate.ObjectClass val locker_container = new EquipmentDefinition(456) { diff --git a/common/src/main/scala/net/psforever/objects/Implant.scala b/common/src/main/scala/net/psforever/objects/Implant.scala deleted file mode 100644 index 2d7483f7..00000000 --- a/common/src/main/scala/net/psforever/objects/Implant.scala +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright (c) 2017 PSForever -package net.psforever.objects - -import net.psforever.objects.definition.{ImplantDefinition, Stance} -import net.psforever.types.{ExoSuitType, ImplantType} - -/** - * A type of installable player utility that grants a perk, usually in exchange for stamina (energy).
- *
- * An implant starts with a never-to-initialized timer value of -1 and will not report as `Ready` until the timer is 0. - * The `Timer`, however, will report to the user a time of 0 since negative time does not make sense. - * Although the `Timer` can be manually set, using `Reset` is the better way to default the initialization timer to the correct amount. - * An external script will be necessary to operate the actual initialization countdown. - * An implant must be `Ready` before it can be `Active`. - * The `Timer` must be set (or reset) (or countdown) to 0 to be `Ready` and then it must be activated. - * @param implantDef the `ObjectDefinition` that constructs this item and maintains some of its immutable fields - */ -class Implant(implantDef : ImplantDefinition) { - private var active : Boolean = false - private var initTimer : Long = -1L - - def Name : String = implantDef.Name - - def Ready : Boolean = initTimer == 0L - - def Active : Boolean = active - - def Active_=(isActive : Boolean) : Boolean = { - active = Ready && isActive - Active - } - - def Timer : Long = math.max(0, initTimer) - - def Timer_=(time : Long) : Long = { - initTimer = math.max(0, time) - Timer - } - - def MaxTimer : Long = implantDef.Initialization - - def ActivationCharge : Int = Definition.ActivationCharge - - /** - * 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 = { - if(active) { - implantDef.DurationChargeBase + implantDef.DurationChargeByExoSuit(suit) + implantDef.DurationChargeByStance(stance) - } - else { - 0 - } - } - - /** - * Place an implant back in its initializing state. - */ - def Reset() : Unit = { - Active = false - Timer = MaxTimer - } - - /** - * Place an implant back in its pre-initialization state. - * The implant is inactive and can not proceed to a `Ready` condition naturally from this state. - */ - def Jammed() : Unit = { - Active = false - Timer = -1 - } - - def Definition : ImplantDefinition = implantDef -} - -object Implant { - def default : Implant = new Implant(ImplantDefinition(ImplantType.RangeMagnifier)) - - def apply(implantDef : ImplantDefinition) : Implant = { - new Implant(implantDef) - } -} diff --git a/common/src/main/scala/net/psforever/objects/ImplantSlot.scala b/common/src/main/scala/net/psforever/objects/ImplantSlot.scala index bf6d0b01..736b03f1 100644 --- a/common/src/main/scala/net/psforever/objects/ImplantSlot.scala +++ b/common/src/main/scala/net/psforever/objects/ImplantSlot.scala @@ -1,61 +1,117 @@ // Copyright (c) 2017 PSForever package net.psforever.objects -import net.psforever.objects.definition.ImplantDefinition -import net.psforever.types.ImplantType +import net.psforever.objects.definition.{ImplantDefinition, Stance} +import net.psforever.types.{ExoSuitType, ImplantType} /** - * A slot "on the player" into which an implant is installed.
+ * A slot "on the player" into which an implant is installed. + * In total, players have three implant slots.
*
- * In total, players have three implant slots. - * At battle rank one (BR1), however, all of those slots are locked. - * The player earns implants at BR16, BR12, and BR18. - * A locked implant slot can not be used. - * (The code uses "not yet unlocked" logic.) - * When unlocked, an implant may be installed into that slot.
- *
- * The default implant that the underlying slot utilizes is the "Range Magnifier." - * Until the `Installed` condition is some value other than `None`, however, the implant in the slot will not work. + * All implants slots start as "locked" and must be "unlocked" through battle rank advancement. + * Only after it is "unlocked" may an implant be "installed" into the slot. + * Upon installation, it undergoes an initialization period and then, after which, it is ready for user activation. + * Being jammed de-activates the implant, put it into a state of "not being ready," and causes the initialization to repeat. */ class ImplantSlot { /** is this slot available for holding an implant */ private var unlocked : Boolean = false + /** whether this implant is ready for use */ + private var initialized : Boolean = false + /** 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 installed : Option[ImplantType.Value] = None - /** the entry for that specific implant used by the a player; always occupied by some type of implant */ - private var implant : Implant = ImplantSlot.default + private var implant : Option[ImplantDefinition] = None def Unlocked : Boolean = unlocked def Unlocked_=(lock : Boolean) : Boolean = { - unlocked = lock + unlocked = lock || unlocked Unlocked } - def Installed : Option[ImplantType.Value] = installed + def Initialized : Boolean = initialized - def Implant : Option[Implant] = if(Installed.isDefined) { Some(implant) } else { None } + def Initialized_=(init : Boolean) : Boolean = { + initialized = Installed.isDefined && init + Active = Active && initialized //can not be active just yet + Initialized + } - def Implant_=(anImplant : Option[Implant]) : Option[Implant] = { - anImplant match { - case Some(module) => - Implant = module - case None => - installed = None + def Active : Boolean = active + + def Active_=(state : Boolean) : Boolean = { + active = Initialized && state + Active + } + + def Implant : ImplantType.Value = if(Installed.isDefined) { + implant.get.Type + } + else { + Active = false + Initialized = false + ImplantType.None + } + + def Implant_=(anImplant : ImplantDefinition) : ImplantType.Value = { + Implant_=(Some(anImplant)) + } + + def Implant_=(anImplant : Option[ImplantDefinition]) : ImplantType.Value = { + if(Unlocked) { + anImplant match { + case Some(_) => + implant = anImplant + case None => + implant = None + } } Implant } - def Implant_=(anImplant : Implant) : Option[Implant] = { - implant = anImplant - installed = Some(anImplant.Definition.Type) - Implant + def Installed : Option[ImplantDefinition] = implant + + def MaxTimer : Long = Implant match { + case ImplantType.None => + -1L + case _ => + Installed.get.Initialization + } + + def ActivationCharge : Int = { + if(Active) { + Installed.get.ActivationCharge + } + else { + 0 + } + } + + /** + * 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 = { + if(Active) { + val inst = Installed.get + inst.DurationChargeBase + inst.DurationChargeByExoSuit(suit) + inst.DurationChargeByStance(stance) + } + else { + 0 + } + } + + def Jammed() : Unit = { + Active = false + Initialized = false } } object ImplantSlot { - private val default = new Implant(ImplantDefinition(ImplantType.None)) - def apply() : ImplantSlot = { new ImplantSlot() } diff --git a/common/src/main/scala/net/psforever/objects/Player.scala b/common/src/main/scala/net/psforever/objects/Player.scala index 0873c1c9..9d59f733 100644 --- a/common/src/main/scala/net/psforever/objects/Player.scala +++ b/common/src/main/scala/net/psforever/objects/Player.scala @@ -1,7 +1,7 @@ // Copyright (c) 2017 PSForever package net.psforever.objects -import net.psforever.objects.definition.AvatarDefinition +import net.psforever.objects.definition.{AvatarDefinition, ImplantDefinition} import net.psforever.objects.equipment.{Equipment, EquipmentSize} import net.psforever.objects.inventory.{GridInventory, InventoryItem} import net.psforever.packet.game.PlanetSideGUID @@ -334,26 +334,22 @@ class Player(private val name : String, def Implants : Array[ImplantSlot] = implants - def Implant(slot : Int) : Option[ImplantType.Value] = { - if(-1 < slot && slot < implants.length) { implants(slot).Installed } else { None } + def Implant(slot : Int) : ImplantType.Value = { + if(-1 < slot && slot < implants.length) { implants(slot).Implant } else { ImplantType.None } } - def Implant(implantType : ImplantType.Value) : Option[Implant] = { - implants.find(_.Installed.contains(implantType)) match { - case Some(slot) => - slot.Implant - case None => - None - } - } - - def InstallImplant(implant : Implant) : Boolean = { - getAvailableImplantSlot(implants.iterator, implant.Definition.Type) match { - case Some(slot) => - slot.Implant = implant - slot.Implant.get.Reset() - true + def InstallImplant(implant : ImplantDefinition) : Boolean = { + implants.find({p => p.Installed.contains(implant)}) match { //try to find the installed implant case None => + //install in a free slot + getAvailableImplantSlot(implants.iterator, implant.Type) match { + case Some(slot) => + slot.Implant = implant + true + case None => + false + } + case Some(_) => false } } @@ -364,7 +360,7 @@ class Player(private val name : String, } else { val slot = iter.next - if(!slot.Unlocked || slot.Installed.contains(implantType)) { + if(!slot.Unlocked || slot.Implant == implantType) { None } else if(slot.Installed.isEmpty) { @@ -377,7 +373,7 @@ class Player(private val name : String, } def UninstallImplant(implantType : ImplantType.Value) : Boolean = { - implants.find({slot => slot.Installed.contains(implantType)}) match { + implants.find({slot => slot.Implant == implantType}) match { case Some(slot) => slot.Implant = None true @@ -388,9 +384,9 @@ class Player(private val name : String, def ResetAllImplants() : Unit = { implants.foreach(slot => { - slot.Implant match { - case Some(implant) => - implant.Reset() + slot.Installed match { + case Some(_) => + slot.Initialized = false case None => ; } }) @@ -590,7 +586,7 @@ object Player { //hand over implants (0 until 3).foreach(index => { if(obj.Implants(index).Unlocked = player.Implants(index).Unlocked) { - obj.Implants(index).Implant = player.Implants(index).Implant + obj.Implants(index).Implant = player.Implants(index).Installed } }) //hand over knife diff --git a/common/src/main/scala/net/psforever/objects/definition/ImplantDefinition.scala b/common/src/main/scala/net/psforever/objects/definition/ImplantDefinition.scala index 1825b58e..7984b310 100644 --- a/common/src/main/scala/net/psforever/objects/definition/ImplantDefinition.scala +++ b/common/src/main/scala/net/psforever/objects/definition/ImplantDefinition.scala @@ -9,10 +9,13 @@ import scala.collection.mutable * An `Enumeration` of a variety of poses or generalized movement. */ object Stance extends Enumeration { - val Crouching, - Standing, - Walking, //not used, but should still be defined - Running = Value + val + Crouching, + CrouchWalking, //not used, but should still be defined + Standing, + Walking, //not used, but should still be defined + Running + = Value } /** diff --git a/common/src/main/scala/net/psforever/objects/definition/converter/AvatarConverter.scala b/common/src/main/scala/net/psforever/objects/definition/converter/AvatarConverter.scala index 963a7acb..b20812fd 100644 --- a/common/src/main/scala/net/psforever/objects/definition/converter/AvatarConverter.scala +++ b/common/src/main/scala/net/psforever/objects/definition/converter/AvatarConverter.scala @@ -1,9 +1,9 @@ // Copyright (c) 2017 PSForever package net.psforever.objects.definition.converter -import net.psforever.objects.{EquipmentSlot, Player} +import net.psforever.objects.{EquipmentSlot, GlobalDefinitions, ImplantSlot, Player} import net.psforever.objects.equipment.Equipment -import net.psforever.packet.game.objectcreate.{BasicCharacterData, CharacterAppearanceData, CharacterData, DetailedCharacterData, DrawnSlot, ImplantEntry, InternalSlot, InventoryData, PlacementData, RibbonBars, UniformStyle} +import net.psforever.packet.game.objectcreate.{BasicCharacterData, CharacterAppearanceData, CharacterData, DetailedCharacterData, DrawnSlot, ImplantEffects, ImplantEntry, InternalSlot, InventoryData, PlacementData, RibbonBars, UniformStyle} import net.psforever.types.{GrenadeState, ImplantType} import scala.annotation.tailrec @@ -18,9 +18,9 @@ class AvatarConverter extends ObjectCreateConverter[Player]() { obj.Armor / obj.MaxArmor * 255, //TODO not precise DressBattleRank(obj), DressCommandRank(obj), + recursiveMakeImplantEffects(obj.Implants.iterator), None, //TODO cosmetics - None, //TODO implant effects - InventoryData(MakeHolsters(obj, BuildEquipment).sortBy(_.parentSlot)), + InventoryData(MakeHolsters(obj, BuildEquipment).sortBy(_.parentSlot)), //TODO is sorting necessary? GetDrawnSlot(obj) ) ) @@ -132,14 +132,14 @@ class AvatarConverter extends ObjectCreateConverter[Player]() { * @see `ImplantEntry` in `DetailedCharacterData` */ private def MakeImplantEntries(obj : Player) : List[ImplantEntry] = { - obj.Implants.map(impl => { - impl.Installed match { - case Some(module) => - if(impl.Implant.get.Ready) { - ImplantEntry(module, None) + obj.Implants.map(slot => { + slot.Installed match { + case Some(_) => + if(slot.Initialized) { + ImplantEntry(slot.Implant, None) } else { - ImplantEntry(module, Some(impl.Implant.get.Timer.toInt)) + ImplantEntry(slot.Implant, Some(slot.Installed.get.Initialization.toInt)) } case None => ImplantEntry(ImplantType.None, None) @@ -147,6 +147,35 @@ class AvatarConverter extends ObjectCreateConverter[Player]() { }).toList } + /** + * Find an active implant whose effect will be displayed on this player. + * @param iter an `Iterator` of `ImplantSlot` objects + * @return the effect of an active implant + */ + @tailrec private def recursiveMakeImplantEffects(iter : Iterator[ImplantSlot]) : Option[ImplantEffects.Value] = { + if(!iter.hasNext) { + None + } + else { + val slot = iter.next + if(slot.Active) { + import GlobalDefinitions._ + slot.Installed match { + case Some(`advanced_regen`) => + Some(ImplantEffects.RegenEffects) + case Some(`darklight_vision`) => + Some(ImplantEffects.DarklightEffects) + case Some(`personal_shield`) => + Some(ImplantEffects.PersonalShieldEffects) + case Some(`surge`) => + Some(ImplantEffects.SurgeEffects) + case _ => ; + } + } + recursiveMakeImplantEffects(iter) + } + } + /** * Given a player with an inventory, convert the contents of that inventory into converted-decoded packet data. * The inventory is not represented in a `0x17` `Player`, so the conversion is only valid for `0x18` avatars. @@ -211,6 +240,14 @@ class AvatarConverter extends ObjectCreateConverter[Player]() { InternalSlot(equip.Definition.ObjectId, equip.GUID, index, equip.Definition.Packet.DetailedConstructorData(equip).get) } + /** + * Given some equipment holsters, convert the contents of those holsters into converted-decoded packet data. + * @param iter an `Iterator` of `EquipmentSlot` objects that are a part of the player's holsters + * @param builder the function used to transform to the decoded packet form + * @param list the current `List` of transformed data + * @param index which holster is currently being explored + * @return the `List` of inventory data created from the holsters + */ @tailrec private def recursiveMakeHolsters(iter : Iterator[EquipmentSlot], builder : ((Int, Equipment) => InternalSlot), list : List[InternalSlot] = Nil, index : Int = 0) : List[InternalSlot] = { if(!iter.hasNext) { list diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/DetailedCharacterData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/DetailedCharacterData.scala index 721f5978..13ccf6ee 100644 --- a/common/src/main/scala/net/psforever/packet/game/objectcreate/DetailedCharacterData.scala +++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/DetailedCharacterData.scala @@ -14,7 +14,8 @@ import scala.annotation.tailrec * An entry in the `List` of valid implant slots in `DetailedCharacterData`. * `activation`, if defined, indicates the time remaining (in seconds?) before an implant becomes usable. * @param implant the type of implant - * @param activation the activation timer + * @param activation the activation timer; + * technically, this is "unconfirmed" * @see `ImplantType` */ final case class ImplantEntry(implant : ImplantType.Value, diff --git a/common/src/test/scala/objects/ImplantTest.scala b/common/src/test/scala/objects/ImplantTest.scala index e1f47936..e7bc2390 100644 --- a/common/src/test/scala/objects/ImplantTest.scala +++ b/common/src/test/scala/objects/ImplantTest.scala @@ -1,7 +1,7 @@ // Copyright (c) 2017 PSForever package objects -import net.psforever.objects.Implant +import net.psforever.objects.ImplantSlot import net.psforever.objects.definition.{ImplantDefinition, Stance} import net.psforever.types.{ExoSuitType, ImplantType} import org.specs2.mutable._ @@ -16,61 +16,122 @@ class ImplantTest extends Specification { sample.DurationChargeByExoSuit += ExoSuitType.Standard -> 1 sample.DurationChargeByStance += Stance.Running -> 1 - "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.Type mustEqual ImplantType.SilentRun + "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.Type mustEqual ImplantType.SilentRun + } } - "construct" in { - val obj = new Implant(sample) - obj.Definition.Type mustEqual sample.Type - obj.Active mustEqual false - obj.Ready mustEqual false - obj.Timer mustEqual 0 - } + "ImplantSlot" should { + "construct" in { + val obj = new ImplantSlot + obj.Unlocked mustEqual false + obj.Initialized mustEqual false + obj.Active mustEqual false + obj.Implant mustEqual ImplantType.None + obj.Installed mustEqual None + } - "reset/init their timer" in { - val obj = new Implant(sample) - obj.Timer mustEqual 0 - obj.Reset() - obj.Timer mustEqual 90000 - } + "load an implant when locked" in { + val obj = new ImplantSlot + obj.Unlocked mustEqual false + obj.Implant mustEqual ImplantType.None - "reset/init their readiness condition" in { - val obj = new Implant(sample) - obj.Ready mustEqual false - obj.Timer = 0 - obj.Ready mustEqual true - obj.Reset() - obj.Ready mustEqual false - } + obj.Implant = sample + obj.Implant mustEqual ImplantType.None + } - "not activate until they are ready" in { - val obj = new Implant(sample) - obj.Active = true - obj.Active mustEqual false - obj.Timer = 0 - obj.Active = true - obj.Active mustEqual true - } + "load an implant when unlocked" in { + val obj = new ImplantSlot + obj.Unlocked mustEqual false + obj.Implant mustEqual ImplantType.None + sample.Type mustEqual ImplantType.SilentRun - "not cost energy while not active" in { - val obj = new Implant(sample) - obj.Charge(ExoSuitType.Reinforced, Stance.Running) mustEqual 0 - } + obj.Unlocked = true + obj.Implant = sample + obj.Implant mustEqual ImplantType.SilentRun + } - "cost energy while active" in { - val obj = new Implant(sample) - obj.Timer = 0 - obj.Active = true - obj.Charge(ExoSuitType.Reinforced, Stance.Running) mustEqual 4 + "can not re-lock an unlocked implant slot" in { + val obj = new ImplantSlot + obj.Unlocked mustEqual false + + obj.Unlocked = false + obj.Unlocked mustEqual false + obj.Unlocked = true + obj.Unlocked mustEqual true + obj.Unlocked = false + obj.Unlocked mustEqual true + } + + "initialize without an implant" in { + val obj = new ImplantSlot + obj.Initialized mustEqual false + obj.Initialized = true + obj.Initialized mustEqual false + } + + "initialize an implant" in { + val obj = new ImplantSlot + obj.Initialized mustEqual false + + obj.Unlocked = true + obj.Implant = sample + obj.Initialized = true + obj.Initialized mustEqual true + } + + "activate an uninitialized implant" in { + val obj = new ImplantSlot + obj.Unlocked = true + obj.Implant = sample + obj.Initialized mustEqual false + obj.Active mustEqual false + + obj.Active = true + obj.Active mustEqual false + } + + "activate an initialized implant" in { + val obj = new ImplantSlot + obj.Unlocked = true + obj.Implant = sample + obj.Initialized mustEqual false + obj.Active mustEqual false + + obj.Initialized = true + obj.Active = true + obj.Active mustEqual true + } + + "not cost energy while not active" in { + val obj = new ImplantSlot + obj.Unlocked = true + obj.Implant = sample + obj.Initialized = true + obj.Active mustEqual false + obj.ActivationCharge mustEqual 0 + obj.Charge(ExoSuitType.Reinforced, Stance.Running) mustEqual 0 + } + + "cost energy while active" in { + val obj = new ImplantSlot + obj.Unlocked = true + obj.Implant = sample + obj.Initialized = true + obj.Active = true + obj.Active mustEqual true + obj.ActivationCharge mustEqual 3 + obj.Charge(ExoSuitType.Reinforced, Stance.Running) mustEqual 4 + } } } diff --git a/common/src/test/scala/objects/PlayerTest.scala b/common/src/test/scala/objects/PlayerTest.scala index fd2d3fc5..2a95f598 100644 --- a/common/src/test/scala/objects/PlayerTest.scala +++ b/common/src/test/scala/objects/PlayerTest.scala @@ -1,7 +1,7 @@ // Copyright (c) 2017 PSForever package objects -import net.psforever.objects.{Implant, Player, SimpleItem} +import net.psforever.objects.{Player, SimpleItem} import net.psforever.objects.definition.{ImplantDefinition, SimpleItemDefinition} import net.psforever.objects.equipment.EquipmentSize import net.psforever.types.{CharacterGender, ExoSuitType, ImplantType, PlanetSideEmpire} @@ -106,31 +106,39 @@ class PlayerTest extends Specification { obj.LastDrawnSlot mustEqual 1 } - "install no implants until a slot is unlocked" in { - val testplant : Implant = Implant(ImplantDefinition(1)) + "install an implant" in { + val testplant : ImplantDefinition = ImplantDefinition(1) val obj = new Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) - obj.Implants(0).Unlocked mustEqual false - obj.Implant(0) mustEqual None - obj.InstallImplant(testplant) - obj.Implant(0) mustEqual None - obj.Implant(ImplantType(1)) mustEqual None - obj.Implants(0).Unlocked = true - obj.InstallImplant(testplant) - obj.Implant(0) mustEqual Some(testplant.Definition.Type) - obj.Implant(ImplantType(1)) mustEqual Some(testplant) + obj.InstallImplant(testplant) mustEqual true + obj.Implants.find({p => p.Implant == ImplantType(1)}) match { //find the installed implant + case Some(slot) => + slot.Installed mustEqual Some(testplant) + case _ => + ko + } + ok + } + + "can not install the same type of implant twice" in { + val testplant1 : ImplantDefinition = ImplantDefinition(1) + val testplant2 : ImplantDefinition = ImplantDefinition(1) + val obj = new Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) + obj.Implants(0).Unlocked = true + obj.Implants(1).Unlocked = true + obj.InstallImplant(testplant1) mustEqual true + obj.InstallImplant(testplant2) mustEqual false } "uninstall implants" in { - val testplant : Implant = Implant(ImplantDefinition(1)) + val testplant : ImplantDefinition = ImplantDefinition(1) val obj = new Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5) obj.Implants(0).Unlocked = true - obj.InstallImplant(testplant) - obj.Implant(ImplantType(1)) mustEqual Some(testplant) + obj.InstallImplant(testplant) mustEqual true + obj.Implants(0).Installed mustEqual Some(testplant) - obj.UninstallImplant(ImplantType(1)) - obj.Implant(0) mustEqual None - obj.Implant(ImplantType(1)) mustEqual None + obj.UninstallImplant(testplant.Type) + obj.Implants(0).Installed mustEqual None } "administrate" in {