diff --git a/src/main/resources/application.conf b/src/main/resources/application.conf
index 0bc62ab18..8504f693a 100644
--- a/src/main/resources/application.conf
+++ b/src/main/resources/application.conf
@@ -82,6 +82,9 @@ game {
# Modify the amount of NTU drain per autorepair tick for facility amenities
amenity-autorepair-drain-rate = 0.5
+ # Purchases timers for the mechanized assault exo-suits all update at the same time when any of them would update
+ shared-max-cooldown = no
+
# HART system, shuttles and facilities
hart {
# How long the shuttle is not boarding passengers (going through the motions)
diff --git a/src/main/scala/net/psforever/actors/session/AvatarActor.scala b/src/main/scala/net/psforever/actors/session/AvatarActor.scala
index 2981b5c4e..e5435fd80 100644
--- a/src/main/scala/net/psforever/actors/session/AvatarActor.scala
+++ b/src/main/scala/net/psforever/actors/session/AvatarActor.scala
@@ -5,57 +5,16 @@ import java.util.concurrent.atomic.AtomicInteger
import akka.actor.Cancellable
import akka.actor.typed.scaladsl.{ActorContext, Behaviors, StashBuffer}
import akka.actor.typed.{ActorRef, Behavior, PostStop, SupervisorStrategy}
-import net.psforever.objects.avatar.{Avatar, BattleRank, Certification, Cosmetic, Implant}
+import net.psforever.objects.avatar._
import net.psforever.objects.definition.converter.CharacterSelectConverter
-import net.psforever.objects.definition.{
- AmmoBoxDefinition,
- BasicDefinition,
- ConstructionItemDefinition,
- ImplantDefinition,
- KitDefinition,
- SimpleItemDefinition,
- ToolDefinition
-}
+import net.psforever.objects.definition._
import net.psforever.objects.equipment.Equipment
import net.psforever.objects.inventory.InventoryItem
import net.psforever.objects.loadouts.{InfantryLoadout, Loadout}
-import net.psforever.objects.{
- Account,
- AmmoBox,
- ConstructionItem,
- GlobalDefinitions,
- Kit,
- Player,
- Session,
- SimpleItem,
- Tool
-}
+import net.psforever.objects._
import net.psforever.packet.game.objectcreate.ObjectClass
-import net.psforever.packet.game.{
- ActionProgressMessage,
- ActionResultMessage,
- AvatarImplantMessage,
- AvatarVehicleTimerMessage,
- BattleExperienceMessage,
- CharacterInfoMessage,
- CreateShortcutMessage,
- FavoritesMessage,
- ImplantAction,
- ItemTransactionResultMessage,
- ObjectCreateDetailedMessage,
- PlanetSideZoneID,
- PlanetsideAttributeMessage
-}
-import net.psforever.types.{
- CharacterSex,
- CharacterVoice,
- ExoSuitType,
- ImplantType,
- LoadoutType,
- PlanetSideEmpire,
- PlanetSideGUID,
- TransactionType
-}
+import net.psforever.packet.game._
+import net.psforever.types._
import net.psforever.util.Database._
import net.psforever.persistence
import net.psforever.util.{Config, DefinitionUtil}
@@ -269,8 +228,8 @@ class AvatarActor(
def postStartBehaviour(): Behavior[Command] = {
account match {
- case Some(account) =>
- buffer.unstashAll(active(account))
+ case Some(_account) =>
+ buffer.unstashAll(active(_account))
case _ =>
Behaviors.same
}
@@ -328,7 +287,7 @@ class AvatarActor(
result.onComplete {
case Success(_) =>
- log.debug(s"AvatarActor: created character ${name} for account ${account.name}")
+ log.debug(s"AvatarActor: created character $name for account ${account.name}")
sessionActor ! SessionActor.SendResponse(ActionResultMessage.Pass)
sendAvatars(account)
case Failure(e) => log.error(e)("db failure")
@@ -445,8 +404,8 @@ class AvatarActor(
sessionActor ! SessionActor.SendResponse(
ItemTransactionResultMessage(terminalGuid, TransactionType.Sell, success = false)
)
- case Success(replace) =>
- replace.foreach { cert =>
+ case Success(_replace) =>
+ _replace.foreach { cert =>
sessionActor ! SessionActor.SendResponse(
PlanetsideAttributeMessage(session.get.player.GUID, 25, cert.value)
)
@@ -519,15 +478,31 @@ class AvatarActor(
ItemTransactionResultMessage(terminalGuid, TransactionType.Sell, success = false)
)
case Success(certs) =>
+ val player = session.get.player
context.self ! ReplaceAvatar(avatar.copy(certifications = avatar.certifications.diff(certs)))
certs.foreach { cert =>
sessionActor ! SessionActor.SendResponse(
- PlanetsideAttributeMessage(session.get.player.GUID, 25, cert.value)
+ PlanetsideAttributeMessage(player.GUID, 25, cert.value)
)
}
sessionActor ! SessionActor.SendResponse(
ItemTransactionResultMessage(terminalGuid, TransactionType.Sell, success = true)
)
+ //wearing invalid armor?
+ if (
+ if (certification == Certification.ReinforcedExoSuit) player.ExoSuit == ExoSuitType.Reinforced
+ else if (certification == Certification.InfiltrationSuit) player.ExoSuit == ExoSuitType.Infiltration
+ else if (player.ExoSuit == ExoSuitType.MAX) {
+ lazy val subtype = InfantryLoadout.DetermineSubtypeA(ExoSuitType.MAX, player.Slot(slot = 0).Equipment)
+ if (certification == Certification.UniMAX) true
+ else if (certification == Certification.AAMAX) subtype == 1
+ else if (certification == Certification.AIMAX) subtype == 2
+ else if (certification == Certification.AVMAX) subtype == 3
+ else false
+ } else false
+ ) {
+ player.Actor ! PlayerControl.SetExoSuit(ExoSuitType.Standard, 0)
+ }
}
}
Behaviors.same
@@ -591,24 +566,24 @@ class AvatarActor(
case LearnImplant(terminalGuid, definition) =>
// TODO there used to be a terminal check here, do we really need it?
val index = avatar.implants.zipWithIndex.collectFirst {
- case (Some(implant), index) if implant.definition.implantType == definition.implantType => index
- case (None, index) if index < avatar.br.implantSlots => index
+ case (Some(implant), _index) if implant.definition.implantType == definition.implantType => _index
+ case (None, _index) if _index < avatar.br.implantSlots => _index
}
index match {
- case Some(index) =>
+ case Some(_index) =>
import ctx._
ctx
.run(query[persistence.Implant].insert(_.name -> lift(definition.Name), _.avatarId -> lift(avatar.id)))
.onComplete {
case Success(_) =>
context.self ! ReplaceAvatar(
- avatar.copy(implants = avatar.implants.updated(index, Some(Implant(definition))))
+ avatar.copy(implants = avatar.implants.updated(_index, Some(Implant(definition))))
)
sessionActor ! SessionActor.SendResponse(
AvatarImplantMessage(
session.get.player.GUID,
ImplantAction.Add,
- index,
+ _index,
definition.implantType.value
)
)
@@ -630,10 +605,10 @@ class AvatarActor(
case SellImplant(terminalGuid, definition) =>
// TODO there used to be a terminal check here, do we really need it?
val index = avatar.implants.zipWithIndex.collectFirst {
- case (Some(implant), index) if implant.definition.implantType == definition.implantType => index
+ case (Some(implant), _index) if implant.definition.implantType == definition.implantType => _index
}
index match {
- case Some(index) =>
+ case Some(_index) =>
import ctx._
ctx
.run(
@@ -644,9 +619,9 @@ class AvatarActor(
)
.onComplete {
case Success(_) =>
- context.self ! ReplaceAvatar(avatar.copy(implants = avatar.implants.updated(index, None)))
+ context.self ! ReplaceAvatar(avatar.copy(implants = avatar.implants.updated(_index, None)))
sessionActor ! SessionActor.SendResponse(
- AvatarImplantMessage(session.get.player.GUID, ImplantAction.Remove, index, 0)
+ AvatarImplantMessage(session.get.player.GUID, ImplantAction.Remove, _index, 0)
)
sessionActor ! SessionActor.SendResponse(
ItemTransactionResultMessage(terminalGuid, TransactionType.Sell, success = true)
@@ -721,15 +696,19 @@ class AvatarActor(
Behaviors.same
case UpdatePurchaseTime(definition, time) =>
- //only send for items with cooldowns
- Avatar.purchaseCooldowns.get(definition) match {
- case Some(cooldown) =>
- // TODO save to db
- avatar = avatar.copy(purchaseTimes = avatar.purchaseTimes.updated(definition.Name, time))
- updatePurchaseTimer(definition.Name, cooldown.toSeconds, unk1 = true)
- case None => ;
- //log.warn(s"UpdatePurchaseTime message for item '${definition.Name}' without cooldown")
+ // TODO save to db
+ var newTimes = avatar.purchaseTimes
+ resolveSharedPurchaseTimeNames(resolvePurchaseTimeName(avatar.faction, definition)).foreach {
+ case (item, name) =>
+ Avatar.purchaseCooldowns.get(item) match {
+ case Some(cooldown) =>
+ //only send for items with cooldowns
+ newTimes = newTimes.updated(item.Name, time)
+ updatePurchaseTimer(name, cooldown.toSeconds, unk1 = true)
+ case _ => ;
+ }
}
+ avatar = avatar.copy(purchaseTimes = newTimes)
Behaviors.same
case UpdateUseTime(definition, time) =>
@@ -858,7 +837,7 @@ class AvatarActor(
Behaviors.same
case ConsumeStamina(stamina) =>
- assert(stamina > 0, s"consumed stamina must be larger than 0, but is: ${stamina}")
+ assert(stamina > 0, s"consumed stamina must be larger than 0, but is: $stamina")
consumeStamina(stamina)
Behaviors.same
@@ -1260,7 +1239,7 @@ class AvatarActor(
doll.ExoSuit = ExoSuitType(loadout.exosuitId)
loadout.items.split("/").foreach {
- case value =>
+ value =>
val (objectType, objectIndex, objectId, toolAmmo) = value.split(",") match {
case Array(a, b: String, c: String) => (a, b.toInt, c.toInt, None)
case Array(a, b: String, c: String, d) => (a, b.toInt, c.toInt, Some(d))
@@ -1318,9 +1297,9 @@ class AvatarActor(
def defaultStaminaRegen(): Cancellable = {
context.system.scheduler.scheduleWithFixedDelay(0.5 seconds, 0.5 seconds)(() => {
(session, _avatar) match {
- case (Some(session), Some(_)) =>
+ case (Some(_session), Some(_)) =>
if (
- !avatar.staminaFull && (session.player.VehicleSeated.nonEmpty || !session.player.isMoving && !session.player.Jumping)
+ !avatar.staminaFull && (_session.player.VehicleSeated.nonEmpty || !_session.player.isMoving && !_session.player.Jumping)
) {
context.self ! RestoreStamina(1)
}
@@ -1340,6 +1319,47 @@ class AvatarActor(
})
}
+ def resolvePurchaseTimeName(faction: PlanetSideEmpire.Value, item: BasicDefinition): (BasicDefinition, String) = {
+ val factionName : String = faction.toString.toLowerCase
+ val name = item match {
+ case GlobalDefinitions.trhev_dualcycler |
+ GlobalDefinitions.nchev_scattercannon |
+ GlobalDefinitions.vshev_quasar =>
+ s"${factionName}hev_antipersonnel"
+ case GlobalDefinitions.trhev_pounder |
+ GlobalDefinitions.nchev_falcon |
+ GlobalDefinitions.vshev_comet =>
+ s"${factionName}hev_antivehicular"
+ case GlobalDefinitions.trhev_burster |
+ GlobalDefinitions.nchev_sparrow |
+ GlobalDefinitions.vshev_starfire =>
+ s"${factionName}hev_antiaircraft"
+ case _ =>
+ item.Name
+ }
+ (item, name)
+ }
+
+ def resolveSharedPurchaseTimeNames(pair: (BasicDefinition, String)): Seq[(BasicDefinition, String)] = {
+ val (_, name) = pair
+ if (name.matches("(tr|nc|vs)hev_.+") && Config.app.game.sharedMaxCooldown) {
+ val faction = name.take(2)
+ (if (faction.equals("nc")) {
+ Seq(GlobalDefinitions.nchev_scattercannon, GlobalDefinitions.nchev_falcon, GlobalDefinitions.nchev_sparrow)
+ }
+ else if (faction.equals("vs")) {
+ Seq(GlobalDefinitions.vshev_quasar, GlobalDefinitions.vshev_comet, GlobalDefinitions.vshev_starfire)
+ }
+ else {
+ Seq(GlobalDefinitions.trhev_dualcycler, GlobalDefinitions.trhev_pounder, GlobalDefinitions.trhev_burster)
+ }).zip(
+ Seq(s"${faction}hev_antipersonnel", s"${faction}hev_antivehicular", s"${faction}hev_antiaircraft")
+ )
+ } else {
+ Seq(pair)
+ }
+ }
+
def refreshPurchaseTimes(keys: Set[String]): Unit = {
var keysToDrop: Seq[String] = Nil
keys.foreach { key =>
@@ -1348,23 +1368,7 @@ class AvatarActor(
val secondsSincePurchase = Seconds.secondsBetween(purchaseTime, LocalDateTime.now()).getSeconds
Avatar.purchaseCooldowns.find(_._1.Name == name) match {
case Some((obj, cooldown)) if cooldown.toSeconds - secondsSincePurchase > 0 =>
- val faction : String = avatar.faction.toString.toLowerCase
- val name = obj match {
- case GlobalDefinitions.trhev_dualcycler |
- GlobalDefinitions.nchev_scattercannon |
- GlobalDefinitions.vshev_quasar =>
- s"${faction}hev_antipersonnel"
- case GlobalDefinitions.trhev_pounder |
- GlobalDefinitions.nchev_falcon |
- GlobalDefinitions.vshev_comet =>
- s"${faction}hev_antivehicular"
- case GlobalDefinitions.trhev_burster |
- GlobalDefinitions.nchev_sparrow |
- GlobalDefinitions.vshev_starfire =>
- s"${faction}hev_antiaircraft"
- case _ =>
- obj.Name
- }
+ val (_, name) = resolvePurchaseTimeName(avatar.faction, obj)
updatePurchaseTimer(name, cooldown.toSeconds - secondsSincePurchase, unk1 = true)
case _ =>
diff --git a/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala b/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala
index 2b6a0e4a6..cde96181f 100644
--- a/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala
+++ b/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala
@@ -221,107 +221,13 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
}
}
+ case PlayerControl.SetExoSuit(exosuit: ExoSuitType.Value, subtype: Int) =>
+ setExoSuit(exosuit, subtype)
+
case Terminal.TerminalMessage(_, msg, order) =>
order match {
case Terminal.BuyExosuit(exosuit, subtype) =>
- var toDelete: List[InventoryItem] = Nil
- val originalSuit = player.ExoSuit
- val originalSubtype = Loadout.DetermineSubtype(player)
- val requestToChangeArmor = originalSuit != exosuit || originalSubtype != subtype
- val allowedToChangeArmor = Players.CertificationToUseExoSuit(player, exosuit, subtype) &&
- (if (exosuit == ExoSuitType.MAX) {
- val weapon = GlobalDefinitions.MAXArms(subtype, player.Faction)
- player.avatar.purchaseCooldown(weapon) match {
- case Some(_) =>
- false
- case None =>
- avatarActor ! AvatarActor.UpdatePurchaseTime(weapon)
- true
- }
- } else {
- true
- })
- val result = if (requestToChangeArmor && allowedToChangeArmor) {
- log.info(s"${player.Name} wants to change to a different exo-suit - $exosuit")
- val beforeHolsters = Players.clearHolsters(player.Holsters().iterator)
- val beforeInventory = player.Inventory.Clear()
- //change suit
- val originalArmor = player.Armor
- player.ExoSuit = exosuit //changes the value of MaxArmor to reflect the new exo-suit
- val toMaxArmor = player.MaxArmor
- val toArmor = if (originalSuit != exosuit || originalSubtype != subtype || originalArmor > toMaxArmor) {
- player.History(HealFromExoSuitChange(PlayerSource(player), exosuit))
- player.Armor = toMaxArmor
- } else {
- player.Armor = originalArmor
- }
- //ensure arm is down, even if it needs to go back up
- if (player.DrawnSlot != Player.HandsDownSlot) {
- player.DrawnSlot = Player.HandsDownSlot
- }
- val normalHolsters = if (originalSuit == ExoSuitType.MAX) {
- val (maxWeapons, normalWeapons) = beforeHolsters.partition(elem => elem.obj.Size == EquipmentSize.Max)
- toDelete ++= maxWeapons
- normalWeapons
- } else {
- beforeHolsters
- }
- //populate holsters
- val (afterHolsters, finalInventory) = if (exosuit == ExoSuitType.MAX) {
- (
- normalHolsters,
- Players.fillEmptyHolsters(List(player.Slot(4)).iterator, normalHolsters) ++ beforeInventory
- )
- } else if (originalSuit == exosuit) { //note - this will rarely be the situation
- (normalHolsters, Players.fillEmptyHolsters(player.Holsters().iterator, normalHolsters))
- } else {
- val (afterHolsters, toInventory) =
- normalHolsters.partition(elem => elem.obj.Size == player.Slot(elem.start).Size)
- afterHolsters.foreach({ elem => player.Slot(elem.start).Equipment = elem.obj })
- val remainder = Players.fillEmptyHolsters(player.Holsters().iterator, toInventory ++ beforeInventory)
- (
- player.HolsterItems(),
- remainder
- )
- }
- //put items back into inventory
- val (stow, drop) = if (originalSuit == exosuit) {
- (finalInventory, Nil)
- } else {
- val (a, b) = GridInventory.recoverInventory(finalInventory, player.Inventory)
- (
- a,
- b.map {
- InventoryItem(_, -1)
- }
- )
- }
- stow.foreach { elem =>
- player.Inventory.InsertQuickly(elem.start, elem.obj)
- }
- //deactivate non-passive implants
- avatarActor ! AvatarActor.DeactivateActiveImplants()
- player.Zone.AvatarEvents ! AvatarServiceMessage(
- player.Zone.id,
- AvatarAction.ChangeExosuit(
- player.GUID,
- toArmor,
- exosuit,
- subtype,
- player.LastDrawnSlot,
- exosuit == ExoSuitType.MAX && requestToChangeArmor,
- beforeHolsters.map { case InventoryItem(obj, _) => (obj, obj.GUID) },
- afterHolsters,
- beforeInventory.map { case InventoryItem(obj, _) => (obj, obj.GUID) },
- stow,
- drop,
- toDelete.map { case InventoryItem(obj, _) => (obj, obj.GUID) }
- )
- )
- true
- } else {
- false
- }
+ val result = setExoSuit(exosuit, subtype)
player.Zone.AvatarEvents ! AvatarServiceMessage(
player.Name,
AvatarAction.TerminalOrderResult(msg.terminal_guid, msg.transaction_type, result)
@@ -511,6 +417,107 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
case _ => ;
}
+ def setExoSuit(exosuit: ExoSuitType.Value, subtype: Int): Boolean = {
+ var toDelete: List[InventoryItem] = Nil
+ val originalSuit = player.ExoSuit
+ val originalSubtype = Loadout.DetermineSubtype(player)
+ val requestToChangeArmor = originalSuit != exosuit || originalSubtype != subtype
+ val allowedToChangeArmor = Players.CertificationToUseExoSuit(player, exosuit, subtype) &&
+ (if (exosuit == ExoSuitType.MAX) {
+ val weapon = GlobalDefinitions.MAXArms(subtype, player.Faction)
+ player.avatar.purchaseCooldown(weapon) match {
+ case Some(_) =>
+ false
+ case None =>
+ avatarActor ! AvatarActor.UpdatePurchaseTime(weapon)
+ true
+ }
+ } else {
+ true
+ })
+ if (requestToChangeArmor && allowedToChangeArmor) {
+ log.info(s"${player.Name} wants to change to a different exo-suit - $exosuit")
+ val beforeHolsters = Players.clearHolsters(player.Holsters().iterator)
+ val beforeInventory = player.Inventory.Clear()
+ //change suit
+ val originalArmor = player.Armor
+ player.ExoSuit = exosuit //changes the value of MaxArmor to reflect the new exo-suit
+ val toMaxArmor = player.MaxArmor
+ val toArmor = if (originalSuit != exosuit || originalSubtype != subtype || originalArmor > toMaxArmor) {
+ player.History(HealFromExoSuitChange(PlayerSource(player), exosuit))
+ player.Armor = toMaxArmor
+ } else {
+ player.Armor = originalArmor
+ }
+ //ensure arm is down, even if it needs to go back up
+ if (player.DrawnSlot != Player.HandsDownSlot) {
+ player.DrawnSlot = Player.HandsDownSlot
+ }
+ val normalHolsters = if (originalSuit == ExoSuitType.MAX) {
+ val (maxWeapons, normalWeapons) = beforeHolsters.partition(elem => elem.obj.Size == EquipmentSize.Max)
+ toDelete ++= maxWeapons
+ normalWeapons
+ } else {
+ beforeHolsters
+ }
+ //populate holsters
+ val (afterHolsters, finalInventory) = if (exosuit == ExoSuitType.MAX) {
+ (
+ normalHolsters,
+ Players.fillEmptyHolsters(List(player.Slot(4)).iterator, normalHolsters) ++ beforeInventory
+ )
+ } else if (originalSuit == exosuit) { //note - this will rarely be the situation
+ (normalHolsters, Players.fillEmptyHolsters(player.Holsters().iterator, normalHolsters))
+ } else {
+ val (afterHolsters, toInventory) =
+ normalHolsters.partition(elem => elem.obj.Size == player.Slot(elem.start).Size)
+ afterHolsters.foreach({ elem => player.Slot(elem.start).Equipment = elem.obj })
+ val remainder = Players.fillEmptyHolsters(player.Holsters().iterator, toInventory ++ beforeInventory)
+ (
+ player.HolsterItems(),
+ remainder
+ )
+ }
+ //put items back into inventory
+ val (stow, drop) = if (originalSuit == exosuit) {
+ (finalInventory, Nil)
+ } else {
+ val (a, b) = GridInventory.recoverInventory(finalInventory, player.Inventory)
+ (
+ a,
+ b.map {
+ InventoryItem(_, -1)
+ }
+ )
+ }
+ stow.foreach { elem =>
+ player.Inventory.InsertQuickly(elem.start, elem.obj)
+ }
+ //deactivate non-passive implants
+ avatarActor ! AvatarActor.DeactivateActiveImplants()
+ player.Zone.AvatarEvents ! AvatarServiceMessage(
+ player.Zone.id,
+ AvatarAction.ChangeExosuit(
+ player.GUID,
+ toArmor,
+ exosuit,
+ subtype,
+ player.LastDrawnSlot,
+ exosuit == ExoSuitType.MAX && requestToChangeArmor,
+ beforeHolsters.map { case InventoryItem(obj, _) => (obj, obj.GUID) },
+ afterHolsters,
+ beforeInventory.map { case InventoryItem(obj, _) => (obj, obj.GUID) },
+ stow,
+ drop,
+ toDelete.map { case InventoryItem(obj, _) => (obj, obj.GUID) }
+ )
+ )
+ true
+ } else {
+ false
+ }
+ }
+
override protected def PerformDamage(
target: Target,
applyDamageTo: Output
@@ -1139,6 +1146,9 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
}
object PlayerControl {
+ /** na */
+ final case class SetExoSuit(exosuit: ExoSuitType.Value, subtype: Int)
+
/**
* Transform an applicable Aura effect into its `PlanetsideAttributeMessage` value.
* @see `Aura`
diff --git a/src/main/scala/net/psforever/objects/loadouts/InfantryLoadout.scala b/src/main/scala/net/psforever/objects/loadouts/InfantryLoadout.scala
index 97498cc72..1f307e084 100644
--- a/src/main/scala/net/psforever/objects/loadouts/InfantryLoadout.scala
+++ b/src/main/scala/net/psforever/objects/loadouts/InfantryLoadout.scala
@@ -88,8 +88,7 @@ object InfantryLoadout {
/**
* The sub-type of the player's uniform, as used in `FavoritesMessage`.
*
- * The values for `Standard`, `Infiltration`, and the generic `MAX` are not perfectly known.
- * The latter-most exo-suit option is presumed.
+ * The values for a specific `MAX` type is only known by knowing the subtype.
* @param suit the player's uniform
* @param subtype the mechanized assault exo-suit subtype as determined by their arm weapons
* @return the numeric subtype
diff --git a/src/main/scala/net/psforever/util/Config.scala b/src/main/scala/net/psforever/util/Config.scala
index e80a5bbf0..d3057fcb7 100644
--- a/src/main/scala/net/psforever/util/Config.scala
+++ b/src/main/scala/net/psforever/util/Config.scala
@@ -137,7 +137,8 @@ case class GameConfig(
bepRate: Double,
cepRate: Double,
newAvatar: NewAvatar,
- hart: HartConfig
+ hart: HartConfig,
+ sharedMaxCooldown: Boolean
)
case class NewAvatar(