diff --git a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala
index cbe37685d..2a05dc25e 100644
--- a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala
+++ b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala
@@ -26,23 +26,26 @@ import scala.collection.mutable
import scala.concurrent.duration._
object GlobalDefinitions {
- /*
- characters
- */
+ // Characters
val avatar = new AvatarDefinition(121)
/*
exo-suits
*/
val Standard = ExoSuitDefinition(ExoSuitType.Standard)
-
+
val Agile = ExoSuitDefinition(ExoSuitType.Agile)
-
+
val Reinforced = ExoSuitDefinition(ExoSuitType.Reinforced)
-
+
val Infiltration = ExoSuitDefinition(ExoSuitType.Infiltration)
-
- val MAX = SpecialExoSuitDefinition(ExoSuitType.MAX)
+
+ val VSMAX = SpecialExoSuitDefinition(ExoSuitType.MAX)
+
+ val TRMAX = SpecialExoSuitDefinition(ExoSuitType.MAX)
+
+ val NCMAX = SpecialExoSuitDefinition(ExoSuitType.MAX)
init_exosuit()
+
/*
Implants
*/
@@ -1527,7 +1530,7 @@ object GlobalDefinitions {
Standard.ResistanceSplash = 15
Standard.ResistanceAggravated = 8
- Agile.Name = "lite_armor"
+ Agile.Name = "agile"
Agile.MaxArmor = 100
Agile.InventoryScale = InventoryTile.Tile99
Agile.InventoryOffset = 6
@@ -1539,7 +1542,7 @@ object GlobalDefinitions {
Agile.ResistanceSplash = 25
Agile.ResistanceAggravated = 10
- Reinforced.Name = "med_armor"
+ Reinforced.Name = "reinforced"
Reinforced.Permissions = List(CertificationType.ReinforcedExoSuit)
Reinforced.MaxArmor = 200
Reinforced.InventoryScale = InventoryTile.Tile1209
@@ -1561,18 +1564,39 @@ object GlobalDefinitions {
Infiltration.Holster(0, EquipmentSize.Pistol)
Infiltration.Holster(4, EquipmentSize.Melee)
- MAX.Permissions = List(CertificationType.AIMAX,CertificationType.AVMAX, CertificationType.AAMAX, CertificationType.UniMAX)
- MAX.MaxArmor = 650
- MAX.InventoryScale = InventoryTile.Tile1612
- MAX.InventoryOffset = 6
- MAX.Holster(0, EquipmentSize.Max)
- MAX.Holster(4, EquipmentSize.Melee)
- MAX.Subtract.Damage1 = -2
- MAX.ResistanceDirectHit = 6
- MAX.ResistanceSplash = 35
- MAX.ResistanceAggravated = 10
- MAX.Damage = StandardMaxDamage
- MAX.Model = StandardResolutions.Max
+ def CommonMaxConfig(max : SpecialExoSuitDefinition): Unit = {
+ max.Permissions = List(CertificationType.AIMAX,CertificationType.AVMAX, CertificationType.AAMAX, CertificationType.UniMAX)
+ max.MaxArmor = 650
+ max.InventoryScale = InventoryTile.Tile1612
+ max.InventoryOffset = 6
+ max.Holster(0, EquipmentSize.Max)
+ max.Holster(4, EquipmentSize.Melee)
+ max.Subtract.Damage1 = -2
+ max.ResistanceDirectHit = 6
+ max.ResistanceSplash = 35
+ max.ResistanceAggravated = 10
+ max.Damage = StandardMaxDamage
+ max.Model = StandardResolutions.Max
+ }
+
+ CommonMaxConfig(VSMAX)
+ VSMAX.MaxCapacitor = 50
+ VSMAX.CapacitorRechargeDelayMillis = 5000
+ VSMAX.CapacitorRechargePerSecond = 3
+ VSMAX.CapacitorDrainPerSecond = 20
+
+ CommonMaxConfig(TRMAX)
+ TRMAX.MaxCapacitor = 300
+ TRMAX.CapacitorRechargeDelayMillis = 10000
+ TRMAX.CapacitorRechargePerSecond = 10
+ TRMAX.CapacitorDrainPerSecond = 30
+
+ CommonMaxConfig(NCMAX)
+ NCMAX.MaxCapacitor = 400
+ NCMAX.CapacitorRechargeDelayMillis = 10000
+ NCMAX.CapacitorRechargePerSecond = 4
+ NCMAX.CapacitorDrainPerSecond = 4
+
}
/**
* Initialize `AmmoBoxDefinition` globals.
diff --git a/common/src/main/scala/net/psforever/objects/Player.scala b/common/src/main/scala/net/psforever/objects/Player.scala
index aadc50084..3c398d343 100644
--- a/common/src/main/scala/net/psforever/objects/Player.scala
+++ b/common/src/main/scala/net/psforever/objects/Player.scala
@@ -27,6 +27,12 @@ class Player(private val core : Avatar) extends PlanetSideGameObject
private var health : Int = 0
private var stamina : Int = 0
private var armor : Int = 0
+
+ private var capacitor : Float = 0f
+ private var capacitorState : CapacitorStateType.Value = CapacitorStateType.Idle
+ private var capacitorLastUsedMillis : Long = 0
+ private var capacitorLastChargedMillis : Long = 0
+
private var maxHealth : Int = 100 //TODO affected by empire benefits, territory benefits, and bops
private var maxStamina : Int = 100 //does anything affect this?
@@ -87,6 +93,7 @@ class Player(private val core : Avatar) extends PlanetSideGameObject
Health = MaxHealth
Stamina = MaxStamina
Armor = MaxArmor
+ Capacitor = 0
ResetAllImplants()
}
isAlive
@@ -151,6 +158,44 @@ class Player(private val core : Avatar) extends PlanetSideGameObject
def MaxArmor : Int = exosuit.MaxArmor
+ def Capacitor : Float = capacitor
+
+ def Capacitor_=(value : Float) : Float = {
+ val newValue = math.min(math.max(0, value), ExoSuitDef.MaxCapacitor)
+
+ if(newValue < capacitor) {
+ capacitorLastUsedMillis = System.currentTimeMillis()
+ capacitorLastChargedMillis = 0
+ }
+ else if(newValue > capacitor && newValue < ExoSuitDef.MaxCapacitor) {
+ capacitorLastChargedMillis = System.currentTimeMillis()
+ capacitorLastUsedMillis = 0
+ }
+ else if(newValue > capacitor && newValue == ExoSuitDef.MaxCapacitor) {
+ capacitorLastChargedMillis = 0
+ capacitorLastUsedMillis = 0
+ capacitorState = CapacitorStateType.Idle
+ }
+
+ capacitor = newValue
+ capacitor
+ }
+
+ def CapacitorState : CapacitorStateType.Value = capacitorState
+ def CapacitorState_=(value : CapacitorStateType.Value) : CapacitorStateType.Value = {
+ value match {
+ case CapacitorStateType.Charging => capacitorLastChargedMillis = System.currentTimeMillis()
+ case CapacitorStateType.Discharging => capacitorLastUsedMillis = System.currentTimeMillis()
+ case _ => ;
+ }
+
+ capacitorState = value
+ capacitorState
+ }
+
+ def CapacitorLastUsedMillis = capacitorLastUsedMillis
+ def CapacitorLastChargedMillis = capacitorLastChargedMillis
+
def VisibleSlots : Set[Int] = if(exosuit.SuitType == ExoSuitType.MAX) {
Set(0)
}
@@ -290,9 +335,10 @@ class Player(private val core : Avatar) extends PlanetSideGameObject
def LastDrawnSlot : Int = lastDrawnSlot
def ExoSuit : ExoSuitType.Value = exosuit.SuitType
+ def ExoSuitDef : ExoSuitDefinition = exosuit
def ExoSuit_=(suit : ExoSuitType.Value) : Unit = {
- val eSuit = ExoSuitDefinition.Select(suit)
+ val eSuit = ExoSuitDefinition.Select(suit, Faction)
exosuit = eSuit
Player.SuitSetup(this, eSuit)
ChangeSpecialAbility()
@@ -509,9 +555,9 @@ class Player(private val core : Avatar) extends PlanetSideGameObject
SpecialExoSuitDefinition.Mode.Normal
}
- def isAnchored : Boolean = ExoSuit == ExoSuitType.MAX && Faction == PlanetSideEmpire.NC && UsingSpecial == SpecialExoSuitDefinition.Mode.Anchored
+ def isAnchored : Boolean = ExoSuit == ExoSuitType.MAX && Faction == PlanetSideEmpire.TR && UsingSpecial == SpecialExoSuitDefinition.Mode.Anchored
- def isOverdrived : Boolean = ExoSuit == ExoSuitType.MAX && Faction == PlanetSideEmpire.NC && UsingSpecial == SpecialExoSuitDefinition.Mode.Overdrive
+ def isOverdrived : Boolean = ExoSuit == ExoSuitType.MAX && Faction == PlanetSideEmpire.TR && UsingSpecial == SpecialExoSuitDefinition.Mode.Overdrive
def isShielded : Boolean = ExoSuit == ExoSuitType.MAX && Faction == PlanetSideEmpire.NC && UsingSpecial == SpecialExoSuitDefinition.Mode.Shielded
diff --git a/common/src/main/scala/net/psforever/objects/definition/ExoSuitDefinition.scala b/common/src/main/scala/net/psforever/objects/definition/ExoSuitDefinition.scala
index f2d68a2e0..a59f9b33a 100644
--- a/common/src/main/scala/net/psforever/objects/definition/ExoSuitDefinition.scala
+++ b/common/src/main/scala/net/psforever/objects/definition/ExoSuitDefinition.scala
@@ -6,7 +6,7 @@ import net.psforever.objects.equipment.EquipmentSize
import net.psforever.objects.inventory.InventoryTile
import net.psforever.objects.vital._
import net.psforever.objects.vital.resistance.ResistanceProfileMutators
-import net.psforever.types.{CertificationType, ExoSuitType}
+import net.psforever.types.{CertificationType, ExoSuitType, PlanetSideEmpire}
/**
* A definition for producing the personal armor the player wears.
@@ -21,6 +21,10 @@ class ExoSuitDefinition(private val suitType : ExoSuitType.Value) extends BasicD
protected val holsters : Array[EquipmentSize.Value] = Array.fill[EquipmentSize.Value](5)(EquipmentSize.Blocked)
protected var inventoryScale : InventoryTile = InventoryTile.Tile11 //override with custom InventoryTile
protected var inventoryOffset : Int = 0
+ protected var maxCapacitor : Int = 0
+ protected var capacitorRechargeDelayMillis : Int = 0
+ protected var capacitorRechargePerSecond : Int = 0
+ protected var capacitorDrainPerSecond : Int = 0
Name = "exo-suit"
Damage = StandardInfantryDamage
Resistance = StandardInfantryResistance
@@ -35,6 +39,34 @@ class ExoSuitDefinition(private val suitType : ExoSuitType.Value) extends BasicD
MaxArmor
}
+ def MaxCapacitor : Int = maxCapacitor
+
+ def MaxCapacitor_=(value : Int) : Int = {
+ maxCapacitor = value
+ maxCapacitor
+ }
+
+ def CapacitorRechargeDelayMillis : Int = capacitorRechargeDelayMillis
+
+ def CapacitorRechargeDelayMillis_=(value : Int) : Int = {
+ capacitorRechargeDelayMillis = value
+ capacitorRechargeDelayMillis
+ }
+
+ def CapacitorRechargePerSecond : Int = capacitorRechargePerSecond
+
+ def CapacitorRechargePerSecond_=(value : Int) : Int = {
+ capacitorRechargePerSecond = value
+ capacitorRechargePerSecond
+ }
+
+ def CapacitorDrainPerSecond : Int = capacitorDrainPerSecond
+
+ def CapacitorDrainPerSecond_=(value : Int) : Int = {
+ capacitorDrainPerSecond = value
+ capacitorDrainPerSecond
+ }
+
def InventoryScale : InventoryTile = inventoryScale
def InventoryScale_=(scale : InventoryTile) : InventoryTile = {
@@ -96,6 +128,10 @@ class SpecialExoSuitDefinition(private val suitType : ExoSuitType.Value) extends
val obj = new SpecialExoSuitDefinition(SuitType)
obj.Permissions = Permissions
obj.MaxArmor = MaxArmor
+ obj.MaxCapacitor = MaxCapacitor
+ obj.CapacitorRechargePerSecond = CapacitorRechargePerSecond
+ obj.CapacitorDrainPerSecond = CapacitorDrainPerSecond
+ obj.CapacitorRechargeDelayMillis = CapacitorRechargeDelayMillis
obj.InventoryScale = InventoryScale
obj.InventoryOffset = InventoryOffset
obj.Subtract.Damage0 = Subtract.Damage0
@@ -138,14 +174,19 @@ object ExoSuitDefinition {
/**
* A function to retrieve the correct defintion of an exo-suit from the type of exo-suit.
* @param suit the `Enumeration` corresponding to this exo-suit
+ * @param faction the faction the player belongs to for this exosuit
* @return the exo-suit definition
*/
- def Select(suit : ExoSuitType.Value) : ExoSuitDefinition = {
+ def Select(suit : ExoSuitType.Value, faction : PlanetSideEmpire.Value) : ExoSuitDefinition = {
suit match {
- case ExoSuitType.Agile => GlobalDefinitions.Agile.Use
case ExoSuitType.Infiltration => GlobalDefinitions.Infiltration.Use
- case ExoSuitType.MAX => GlobalDefinitions.MAX.Use
+ case ExoSuitType.Agile => GlobalDefinitions.Agile.Use
case ExoSuitType.Reinforced => GlobalDefinitions.Reinforced.Use
+ case ExoSuitType.MAX => faction match {
+ case PlanetSideEmpire.TR => GlobalDefinitions.TRMAX.Use
+ case PlanetSideEmpire.NC => GlobalDefinitions.NCMAX.Use
+ case PlanetSideEmpire.VS => GlobalDefinitions.VSMAX.Use
+ }
case _ => GlobalDefinitions.Standard.Use
}
}
diff --git a/common/src/main/scala/net/psforever/objects/vital/resistance/ResistanceCalculations.scala b/common/src/main/scala/net/psforever/objects/vital/resistance/ResistanceCalculations.scala
index 6082baee3..43433f85f 100644
--- a/common/src/main/scala/net/psforever/objects/vital/resistance/ResistanceCalculations.scala
+++ b/common/src/main/scala/net/psforever/objects/vital/resistance/ResistanceCalculations.scala
@@ -106,13 +106,13 @@ object ResistanceCalculations {
//extractors
def NoResistExtractor(target : SourceEntry) : Int = 0
- def ExoSuitDirectExtractor(target : PlayerSource) : Int = ExoSuitDefinition.Select(target.ExoSuit).ResistanceDirectHit
+ def ExoSuitDirectExtractor(target : PlayerSource) : Int = ExoSuitDefinition.Select(target.ExoSuit, target.Faction).ResistanceDirectHit
- def ExoSuitSplashExtractor(target : PlayerSource) : Int = ExoSuitDefinition.Select(target.ExoSuit).ResistanceSplash
+ def ExoSuitSplashExtractor(target : PlayerSource) : Int = ExoSuitDefinition.Select(target.ExoSuit, target.Faction).ResistanceSplash
- def ExoSuitAggravatedExtractor(target : PlayerSource) : Int = ExoSuitDefinition.Select(target.ExoSuit).ResistanceAggravated
+ def ExoSuitAggravatedExtractor(target : PlayerSource) : Int = ExoSuitDefinition.Select(target.ExoSuit, target.Faction).ResistanceAggravated
- def ExoSuitRadiationExtractor(target : PlayerSource) : Float = ExoSuitDefinition.Select(target.ExoSuit).RadiationShielding
+ def ExoSuitRadiationExtractor(target : PlayerSource) : Float = ExoSuitDefinition.Select(target.ExoSuit, target.Faction).RadiationShielding
def VehicleDirectExtractor(target : VehicleSource) : Int = target.Definition.ResistanceDirectHit
diff --git a/common/src/main/scala/net/psforever/objects/vital/resolution/ResolutionCalculations.scala b/common/src/main/scala/net/psforever/objects/vital/resolution/ResolutionCalculations.scala
index 06774500c..1e2d968ab 100644
--- a/common/src/main/scala/net/psforever/objects/vital/resolution/ResolutionCalculations.scala
+++ b/common/src/main/scala/net/psforever/objects/vital/resolution/ResolutionCalculations.scala
@@ -109,27 +109,53 @@ object ResolutionCalculations {
def NoApplication(damageValue : Int, data : ResolvedProjectile)(target : Any) : Unit = { }
+ def SubtractWithRemainder(current : Int, damage : Int) : (Int, Int) = {
+ val a = Math.max(0, current - damage)
+ val remainingDamage = Math.abs(current - damage - a)
+
+ (a, remainingDamage)
+ }
+
/**
* The expanded `(Any)=>Unit` function for infantry.
- * Apply the damage values to the health field and personal armor field for an infantry target.
+ * Apply the damage values to the capacitor (if shielded NC max), health field and personal armor field for an infantry target.
* @param damageValues a tuple containing damage values for: health, personal armor
* @param data the historical `ResolvedProjectile` information
* @param target the `Player` object to be affected by these damage values (at some point)
*/
def InfantryApplication(damageValues : (Int, Int), data : ResolvedProjectile)(target : Any) : Unit = target match {
case player : Player =>
- val (a, b) = damageValues
+ var (a, b) = damageValues
+ var result = (0, 0)
//TODO Personal Shield implant test should go here and modify the values a and b
if(player.isAlive && !(a == 0 && b == 0)) {
player.History(data)
- if(player.Armor - b < 0) {
- player.Health = player.Health - a - (b - player.Armor)
- player.Armor = 0
- }
- else {
- player.Armor = player.Armor - b
- player.Health = player.Health - a
+ if(player.Capacitor.toInt > 0 && player.isShielded) {
+ // Subtract armour damage from capacitor
+ result = SubtractWithRemainder(player.Capacitor.toInt, b)
+ player.Capacitor = result._1
+ b = result._2
+
+ // Then follow up with health damage if any capacitor is left
+ result = SubtractWithRemainder(player.Capacitor.toInt, a)
+ player.Capacitor = result._1
+ a = result._2
}
+
+ // Subtract any remaining armour damage from armour
+ result = SubtractWithRemainder(player.Armor, b)
+ player.Armor = result._1
+ b = result._2
+
+ // Then bleed through to health if armour ran out
+ result = SubtractWithRemainder(player.Health, b)
+ player.Health = result._1
+ b = result._2
+
+ // Finally, apply health damage to health
+ result = SubtractWithRemainder(player.Health, a)
+ player.Health = result._1
+ a = result._2
}
case _ =>
}
diff --git a/common/src/main/scala/net/psforever/packet/game/GenericActionMessage.scala b/common/src/main/scala/net/psforever/packet/game/GenericActionMessage.scala
index bdab947b7..3c4df4ba8 100644
--- a/common/src/main/scala/net/psforever/packet/game/GenericActionMessage.scala
+++ b/common/src/main/scala/net/psforever/packet/game/GenericActionMessage.scala
@@ -34,10 +34,15 @@ import scodec.codecs._
* 45 - ?
*
* Actions (when sent from client):
+ * 15 - Max anchor
+ * 16 - Max unanchor
+ * 20 - Client requests MAX special effect (NC shield and TR overdrive. VS jump jets are handled by the jump_thrust boolean on PlayerStateMessageUpstream)
+ * 21 - Disable MAX special effect (NC shield)
* 29 - AFK
* 30 - back in game
* 36 - turn on "Looking for Squad"
* 37 - turn off "Looking for Squad"
+ *
* @param action what this packet does
*/
final case class GenericActionMessage(action : Int)
diff --git a/common/src/main/scala/net/psforever/types/CapacitorStateType.scala b/common/src/main/scala/net/psforever/types/CapacitorStateType.scala
new file mode 100644
index 000000000..795fb67ff
--- /dev/null
+++ b/common/src/main/scala/net/psforever/types/CapacitorStateType.scala
@@ -0,0 +1,10 @@
+package net.psforever.types
+
+object CapacitorStateType extends Enumeration {
+ type Type = Value
+
+ val Idle = Value(0)
+ val Charging = Value(1)
+ val ChargeDelay = Value(2)
+ val Discharging = Value(3)
+}
diff --git a/common/src/test/scala/objects/DamageModelTests.scala b/common/src/test/scala/objects/DamageModelTests.scala
index 5a477c881..bc6e6fad1 100644
--- a/common/src/test/scala/objects/DamageModelTests.scala
+++ b/common/src/test/scala/objects/DamageModelTests.scala
@@ -223,7 +223,7 @@ class ResolutionCalculationsTests extends Specification {
InfantryDamageAfterResist(100,100)(50, 10) mustEqual (40,10)
}
- "calculate health and armor damage, with bonus damage, for infantry target" in {
+ "calculate health and armor damage, with bleed through damage, for infantry target" in {
//health = 100, armor = 5 -> resist 10 but only have 5, so rollover extra -> damages (40+5, 5)
InfantryDamageAfterResist(100,5)(50, 10) mustEqual (45,5)
}
diff --git a/common/src/test/scala/objects/ExoSuitTest.scala b/common/src/test/scala/objects/ExoSuitTest.scala
index c7db0ac86..f1437dc7c 100644
--- a/common/src/test/scala/objects/ExoSuitTest.scala
+++ b/common/src/test/scala/objects/ExoSuitTest.scala
@@ -1,10 +1,11 @@
// Copyright (c) 2017 PSForever
package objects
+import net.psforever.objects.GlobalDefinitions
import net.psforever.objects.definition.{ExoSuitDefinition, SpecialExoSuitDefinition}
import net.psforever.objects.equipment._
import net.psforever.objects.inventory.InventoryTile
-import net.psforever.types.ExoSuitType
+import net.psforever.types.{ExoSuitType, PlanetSideEmpire}
import org.specs2.mutable._
class ExoSuitTest extends Specification {
@@ -127,15 +128,47 @@ class ExoSuitTest extends Specification {
"ExoSuitDefinition.Select" should {
"produce common, shared instances of exo suits" in {
- ExoSuitDefinition.Select(ExoSuitType.Standard) eq ExoSuitDefinition.Select(ExoSuitType.Standard)
- ExoSuitDefinition.Select(ExoSuitType.Agile) eq ExoSuitDefinition.Select(ExoSuitType.Agile)
- ExoSuitDefinition.Select(ExoSuitType.Reinforced) eq ExoSuitDefinition.Select(ExoSuitType.Reinforced)
- ExoSuitDefinition.Select(ExoSuitType.Infiltration) eq ExoSuitDefinition.Select(ExoSuitType.Infiltration)
+ ExoSuitDefinition.Select(ExoSuitType.Standard, PlanetSideEmpire.VS) eq ExoSuitDefinition.Select(ExoSuitType.Standard, PlanetSideEmpire.VS)
+ ExoSuitDefinition.Select(ExoSuitType.Agile, PlanetSideEmpire.VS) eq ExoSuitDefinition.Select(ExoSuitType.Agile, PlanetSideEmpire.VS)
+ ExoSuitDefinition.Select(ExoSuitType.Reinforced, PlanetSideEmpire.VS) eq ExoSuitDefinition.Select(ExoSuitType.Reinforced, PlanetSideEmpire.VS)
+ ExoSuitDefinition.Select(ExoSuitType.Infiltration, PlanetSideEmpire.VS) eq ExoSuitDefinition.Select(ExoSuitType.Infiltration, PlanetSideEmpire.VS)
+ }
+
+ "produce common, shared instances of exo suits across factions" in {
+ ExoSuitDefinition.Select(ExoSuitType.Standard, PlanetSideEmpire.VS) eq ExoSuitDefinition.Select(ExoSuitType.Standard, PlanetSideEmpire.TR)
+ ExoSuitDefinition.Select(ExoSuitType.Standard, PlanetSideEmpire.VS) eq ExoSuitDefinition.Select(ExoSuitType.Standard, PlanetSideEmpire.NC)
+
+ ExoSuitDefinition.Select(ExoSuitType.Agile, PlanetSideEmpire.VS) eq ExoSuitDefinition.Select(ExoSuitType.Agile, PlanetSideEmpire.TR)
+ ExoSuitDefinition.Select(ExoSuitType.Agile, PlanetSideEmpire.VS) eq ExoSuitDefinition.Select(ExoSuitType.Agile, PlanetSideEmpire.NC)
+
+ ExoSuitDefinition.Select(ExoSuitType.Reinforced, PlanetSideEmpire.VS) eq ExoSuitDefinition.Select(ExoSuitType.Reinforced, PlanetSideEmpire.TR)
+ ExoSuitDefinition.Select(ExoSuitType.Reinforced, PlanetSideEmpire.VS) eq ExoSuitDefinition.Select(ExoSuitType.Reinforced, PlanetSideEmpire.NC)
+
+ ExoSuitDefinition.Select(ExoSuitType.Infiltration, PlanetSideEmpire.VS) eq ExoSuitDefinition.Select(ExoSuitType.Infiltration, PlanetSideEmpire.TR)
+ ExoSuitDefinition.Select(ExoSuitType.Infiltration, PlanetSideEmpire.VS) eq ExoSuitDefinition.Select(ExoSuitType.Infiltration, PlanetSideEmpire.NC)
+ }
+
+ "can select max for TR" in {
+ val obj = ExoSuitDefinition.Select(ExoSuitType.MAX, PlanetSideEmpire.TR)
+ obj.isInstanceOf[SpecialExoSuitDefinition] mustEqual true
+ obj.MaxCapacitor mustEqual 300
+ }
+
+ "can select max for VS" in {
+ val obj = ExoSuitDefinition.Select(ExoSuitType.MAX, PlanetSideEmpire.VS)
+ obj.isInstanceOf[SpecialExoSuitDefinition] mustEqual true
+ obj.MaxCapacitor mustEqual 50
+ }
+
+ "can select max for NC" in {
+ val obj = ExoSuitDefinition.Select(ExoSuitType.MAX, PlanetSideEmpire.NC)
+ obj.isInstanceOf[SpecialExoSuitDefinition] mustEqual true
+ obj.MaxCapacitor mustEqual 400
}
"produces unique instances of the mechanized assault exo suit" in {
- val obj = ExoSuitDefinition.Select(ExoSuitType.MAX)
- obj ne ExoSuitDefinition.Select(ExoSuitType.MAX)
+ val obj = ExoSuitDefinition.Select(ExoSuitType.MAX, PlanetSideEmpire.VS)
+ obj ne ExoSuitDefinition.Select(ExoSuitType.MAX, PlanetSideEmpire.VS)
obj.isInstanceOf[SpecialExoSuitDefinition] mustEqual true
}
}
diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala
index 2854e782f..676d90550 100644
--- a/pslogin/src/main/scala/WorldSessionActor.scala
+++ b/pslogin/src/main/scala/WorldSessionActor.scala
@@ -1269,14 +1269,30 @@ class WorldSessionActor extends Actor with MDCContextAware {
if(player.isAlive) {
val originalHealth = player.Health
val originalArmor = player.Armor
+ val originalCapacitor = player.Capacitor.toInt
resolution_function(target)
val health = player.Health
val armor = player.Armor
+ val capacitor = player.Capacitor.toInt
val damageToHealth = originalHealth - health
val damageToArmor = originalArmor - armor
- damageLog.info(s"${player.Name}-infantry: BEFORE=$originalHealth/$originalArmor, AFTER=$health/$armor, CHANGE=$damageToHealth/$damageToArmor")
- if(damageToHealth != 0 || damageToArmor != 0) {
+ val damageToCapacitor = originalCapacitor - capacitor
+ damageLog.info(s"${player.Name}-infantry: BEFORE=$originalHealth/$originalArmor/$originalCapacitor, AFTER=$health/$armor/$capacitor, CHANGE=$damageToHealth/$damageToArmor/$damageToCapacitor")
+ if(damageToHealth != 0 || damageToArmor != 0 || damageToCapacitor != 0) {
val playerGUID = player.GUID
+ if(damageToHealth > 0) {
+ sendResponse(PlanetsideAttributeMessage(playerGUID, 0, health))
+ avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(playerGUID, 0, health))
+ }
+
+ if(damageToArmor > 0) {
+ sendResponse(PlanetsideAttributeMessage(playerGUID, 4, armor))
+ avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(playerGUID, 4, armor))
+ }
+
+ if(damageToCapacitor > 0) {
+ sendResponse(PlanetsideAttributeMessage(playerGUID, 7, capacitor))
+ }
sendResponse(PlanetsideAttributeMessage(playerGUID, 0, health))
sendResponse(PlanetsideAttributeMessage(playerGUID, 4, armor))
continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(playerGUID, 0, health))
@@ -2067,7 +2083,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
val fallbackSubtype = 0
//a loadout with a prohibited exo-suit type will result in a fallback exo-suit type
val (nextSuit : ExoSuitType.Value, nextSubtype : Int) =
- if(ExoSuitDefinition.Select(exosuit).Permissions match {
+ if(ExoSuitDefinition.Select(exosuit, player.Faction).Permissions match {
case Nil =>
true
case permissions if subtype != 0 =>
@@ -2122,7 +2138,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
)
}
else {
- val newSuitDef = ExoSuitDefinition.Select(nextSuit)
+ val newSuitDef = ExoSuitDefinition.Select(nextSuit, player.Faction)
val (afterInventory, extra) = GridInventory.recoverInventory(
inventory.filterNot(dropPred),
tplayer.Inventory
@@ -3911,6 +3927,8 @@ class WorldSessionActor extends Actor with MDCContextAware {
else {
player.Stamina > 0
}
+
+ CapacitorTick(jump_thrust)
}
else {
timeDL = 0
@@ -5347,7 +5365,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
tool.ToFireMode = convertFireModeIndex
sendResponse(ChangeFireModeMessage(tool.GUID, convertFireModeIndex))
case _ =>
- log.info(s"GenericObject: $player is MAX with an unexpected weapon - ${definition.Name}")
+ log.warn(s"GenericObject: $player is MAX with an unexpected weapon - ${definition.Name}")
}
}
else if(action == 16) { //max deployment
@@ -5365,7 +5383,25 @@ class WorldSessionActor extends Actor with MDCContextAware {
tool.ToFireMode = convertFireModeIndex
sendResponse(ChangeFireModeMessage(tool.GUID, convertFireModeIndex))
case _ =>
- log.info(s"GenericObject: $player is MAX with an unexpected weapon - ${definition.Name}")
+ log.warn(s"GenericObject: $player is MAX with an unexpected weapon - ${definition.Name}")
+ }
+ }
+ else if (action == 20) {
+ if(player.ExoSuit == ExoSuitType.MAX) {
+ ToggleMaxSpecialState(enable = true)
+ } else {
+ log.warn("Got GenericActionMessage 20 but can't handle it")
+ }
+ }
+ else if (action == 21) {
+ if(player.ExoSuit == ExoSuitType.MAX) {
+ player.Faction match {
+ case PlanetSideEmpire.NC =>
+ ToggleMaxSpecialState(enable = false)
+ case _ => log.warn(s"Player ${player.Name} tried to cancel an uncancellable MAX special ability")
+ }
+ } else {
+ log.warn("Got GenericActionMessage 21 but can't handle it")
}
}
else if(action == 36) { //Looking For Squad ON
@@ -5462,6 +5498,10 @@ class WorldSessionActor extends Actor with MDCContextAware {
case msg @ WeaponFireMessage(seq_time, weapon_guid, projectile_guid, shot_origin, unk1, unk2, unk3, unk4, unk5, unk6, unk7) =>
log.info(s"WeaponFire: $msg")
+ if(player.isShielded) {
+ // Cancel NC MAX shield if it's active
+ ToggleMaxSpecialState(enable = false)
+ }
FindContainedWeapon match {
case (Some(obj), Some(tool : Tool)) =>
if(tool.Magazine <= 0) { //safety: enforce ammunition depletion
@@ -7807,6 +7847,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
val player_guid = tplayer.GUID
val pos = tplayer.Position
val respawnTimer = 300000 //milliseconds
+ ToggleMaxSpecialState(enable = false)
tplayer.Die
deadState = DeadState.Dead
timeDL = 0
@@ -10102,6 +10143,75 @@ class WorldSessionActor extends Actor with MDCContextAware {
squadUpdateCounter = (squadUpdateCounter + 1) % queuedSquadActions.length
}
+ def CapacitorTick(jump_thrust : Boolean): Unit = {
+ if(player.ExoSuit == ExoSuitType.MAX) {
+ //Discharge
+ if(jump_thrust || player.isOverdrived || player.isShielded) {
+ if(player.CapacitorState == CapacitorStateType.Discharging) {
+ // Previous tick was already discharging, calculate how much energy to drain from time between the two ticks
+ val timeDiff = (System.currentTimeMillis() - player.CapacitorLastUsedMillis).toFloat / 1000
+ val drainAmount = player.ExoSuitDef.CapacitorDrainPerSecond.toFloat * timeDiff
+ player.Capacitor -= drainAmount
+ sendResponse(PlanetsideAttributeMessage(player.GUID, 7, player.Capacitor.toInt))
+ } else {
+ // Start discharging
+ player.CapacitorState = CapacitorStateType.Discharging
+ }
+ }
+ // Charge
+ else if(player.Capacitor < player.ExoSuitDef.MaxCapacitor
+ && (player.CapacitorState == CapacitorStateType.Idle || player.CapacitorState == CapacitorStateType.Charging || (player.CapacitorState == CapacitorStateType.ChargeDelay && System.currentTimeMillis() - player.CapacitorLastUsedMillis > player.ExoSuitDef.CapacitorRechargeDelayMillis)))
+ {
+ if(player.CapacitorState == CapacitorStateType.Charging) {
+ val timeDiff = (System.currentTimeMillis() - player.CapacitorLastChargedMillis).toFloat / 1000
+ val chargeAmount = player.ExoSuitDef.CapacitorRechargePerSecond * timeDiff
+ player.Capacitor += chargeAmount
+ sendResponse(PlanetsideAttributeMessage(player.GUID, 7, player.Capacitor.toInt))
+ } else {
+ player.CapacitorState = CapacitorStateType.Charging
+ }
+ }
+
+ if(player.Faction == PlanetSideEmpire.VS) {
+ // Start charge delay for VS when not boosting
+ if(!jump_thrust && player.CapacitorState == CapacitorStateType.Discharging ) {
+ player.CapacitorState = CapacitorStateType.ChargeDelay
+ }
+ }
+ else {
+ // Start charge delay for other factions if capacitor is empty or special ability is off
+ if(player.CapacitorState == CapacitorStateType.Discharging && (player.Capacitor == 0 || (!player.isOverdrived && !player.isShielded))) {
+ player.CapacitorState = CapacitorStateType.ChargeDelay
+ ToggleMaxSpecialState(enable = false)
+ }
+ }
+ }
+ else {
+ if(player.CapacitorState != CapacitorStateType.Idle) {
+ player.CapacitorState = CapacitorStateType.Idle
+ }
+ }
+ }
+
+ def ToggleMaxSpecialState(enable : Boolean): Unit = {
+ if(player.ExoSuit == ExoSuitType.MAX) {
+ if(enable) {
+ player.Faction match {
+ case PlanetSideEmpire.TR => if(player.Capacitor == player.ExoSuitDef.MaxCapacitor) player.UsingSpecial = SpecialExoSuitDefinition.Mode.Overdrive
+ case PlanetSideEmpire.NC => if(player.Capacitor > 0) player.UsingSpecial = SpecialExoSuitDefinition.Mode.Shielded
+ case _ => log.warn(s"Player ${player.Name} tried to use a MAX special ability but their faction doesn't have one")
+ }
+
+ if(player.UsingSpecial == SpecialExoSuitDefinition.Mode.Overdrive || player.UsingSpecial == SpecialExoSuitDefinition.Mode.Shielded) {
+ avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttributeToAll(player.GUID, 8, 1))
+ }
+ }
+ else {
+ player.UsingSpecial = SpecialExoSuitDefinition.Mode.Normal
+ avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttributeToAll(player.GUID, 8, 0))
+ }
+ }
+
/**
* The main purpose of this method is to determine which targets will receive "locked on" warnings from remote projectiles.
* For a given series of globally unique identifiers, indicating targets,