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,