mirror of
https://github.com/psforever/PSF-LoginServer.git
synced 2026-01-20 02:54:46 +00:00
Forget How to Wear Clothes (#806)
* in forgetting an exo-suit certification, one must not be wearing that type of exo-suit afterwards * making the conditional more straightforward * fixed issue with purchase times; can now share max purchase cooldowns
This commit is contained in:
parent
cbb48d1442
commit
262b7d2ec6
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 _ =>
|
||||
|
|
|
|||
|
|
@ -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`
|
||||
|
|
|
|||
|
|
@ -88,8 +88,7 @@ object InfantryLoadout {
|
|||
/**
|
||||
* The sub-type of the player's uniform, as used in `FavoritesMessage`.<br>
|
||||
* <br>
|
||||
* 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
|
||||
|
|
|
|||
|
|
@ -137,7 +137,8 @@ case class GameConfig(
|
|||
bepRate: Double,
|
||||
cepRate: Double,
|
||||
newAvatar: NewAvatar,
|
||||
hart: HartConfig
|
||||
hart: HartConfig,
|
||||
sharedMaxCooldown: Boolean
|
||||
)
|
||||
|
||||
case class NewAvatar(
|
||||
|
|
|
|||
Loading…
Reference in a new issue