mirror of
https://github.com/2revoemag/PSF-BotServer.git
synced 2026-07-13 23:44:40 +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
5 changed files with 208 additions and 191 deletions
|
|
@ -82,6 +82,9 @@ game {
|
||||||
# Modify the amount of NTU drain per autorepair tick for facility amenities
|
# Modify the amount of NTU drain per autorepair tick for facility amenities
|
||||||
amenity-autorepair-drain-rate = 0.5
|
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 system, shuttles and facilities
|
||||||
hart {
|
hart {
|
||||||
# How long the shuttle is not boarding passengers (going through the motions)
|
# 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.Cancellable
|
||||||
import akka.actor.typed.scaladsl.{ActorContext, Behaviors, StashBuffer}
|
import akka.actor.typed.scaladsl.{ActorContext, Behaviors, StashBuffer}
|
||||||
import akka.actor.typed.{ActorRef, Behavior, PostStop, SupervisorStrategy}
|
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.converter.CharacterSelectConverter
|
||||||
import net.psforever.objects.definition.{
|
import net.psforever.objects.definition._
|
||||||
AmmoBoxDefinition,
|
|
||||||
BasicDefinition,
|
|
||||||
ConstructionItemDefinition,
|
|
||||||
ImplantDefinition,
|
|
||||||
KitDefinition,
|
|
||||||
SimpleItemDefinition,
|
|
||||||
ToolDefinition
|
|
||||||
}
|
|
||||||
import net.psforever.objects.equipment.Equipment
|
import net.psforever.objects.equipment.Equipment
|
||||||
import net.psforever.objects.inventory.InventoryItem
|
import net.psforever.objects.inventory.InventoryItem
|
||||||
import net.psforever.objects.loadouts.{InfantryLoadout, Loadout}
|
import net.psforever.objects.loadouts.{InfantryLoadout, Loadout}
|
||||||
import net.psforever.objects.{
|
import net.psforever.objects._
|
||||||
Account,
|
|
||||||
AmmoBox,
|
|
||||||
ConstructionItem,
|
|
||||||
GlobalDefinitions,
|
|
||||||
Kit,
|
|
||||||
Player,
|
|
||||||
Session,
|
|
||||||
SimpleItem,
|
|
||||||
Tool
|
|
||||||
}
|
|
||||||
import net.psforever.packet.game.objectcreate.ObjectClass
|
import net.psforever.packet.game.objectcreate.ObjectClass
|
||||||
import net.psforever.packet.game.{
|
import net.psforever.packet.game._
|
||||||
ActionProgressMessage,
|
import net.psforever.types._
|
||||||
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.util.Database._
|
import net.psforever.util.Database._
|
||||||
import net.psforever.persistence
|
import net.psforever.persistence
|
||||||
import net.psforever.util.{Config, DefinitionUtil}
|
import net.psforever.util.{Config, DefinitionUtil}
|
||||||
|
|
@ -269,8 +228,8 @@ class AvatarActor(
|
||||||
|
|
||||||
def postStartBehaviour(): Behavior[Command] = {
|
def postStartBehaviour(): Behavior[Command] = {
|
||||||
account match {
|
account match {
|
||||||
case Some(account) =>
|
case Some(_account) =>
|
||||||
buffer.unstashAll(active(account))
|
buffer.unstashAll(active(_account))
|
||||||
case _ =>
|
case _ =>
|
||||||
Behaviors.same
|
Behaviors.same
|
||||||
}
|
}
|
||||||
|
|
@ -328,7 +287,7 @@ class AvatarActor(
|
||||||
|
|
||||||
result.onComplete {
|
result.onComplete {
|
||||||
case Success(_) =>
|
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)
|
sessionActor ! SessionActor.SendResponse(ActionResultMessage.Pass)
|
||||||
sendAvatars(account)
|
sendAvatars(account)
|
||||||
case Failure(e) => log.error(e)("db failure")
|
case Failure(e) => log.error(e)("db failure")
|
||||||
|
|
@ -445,8 +404,8 @@ class AvatarActor(
|
||||||
sessionActor ! SessionActor.SendResponse(
|
sessionActor ! SessionActor.SendResponse(
|
||||||
ItemTransactionResultMessage(terminalGuid, TransactionType.Sell, success = false)
|
ItemTransactionResultMessage(terminalGuid, TransactionType.Sell, success = false)
|
||||||
)
|
)
|
||||||
case Success(replace) =>
|
case Success(_replace) =>
|
||||||
replace.foreach { cert =>
|
_replace.foreach { cert =>
|
||||||
sessionActor ! SessionActor.SendResponse(
|
sessionActor ! SessionActor.SendResponse(
|
||||||
PlanetsideAttributeMessage(session.get.player.GUID, 25, cert.value)
|
PlanetsideAttributeMessage(session.get.player.GUID, 25, cert.value)
|
||||||
)
|
)
|
||||||
|
|
@ -519,15 +478,31 @@ class AvatarActor(
|
||||||
ItemTransactionResultMessage(terminalGuid, TransactionType.Sell, success = false)
|
ItemTransactionResultMessage(terminalGuid, TransactionType.Sell, success = false)
|
||||||
)
|
)
|
||||||
case Success(certs) =>
|
case Success(certs) =>
|
||||||
|
val player = session.get.player
|
||||||
context.self ! ReplaceAvatar(avatar.copy(certifications = avatar.certifications.diff(certs)))
|
context.self ! ReplaceAvatar(avatar.copy(certifications = avatar.certifications.diff(certs)))
|
||||||
certs.foreach { cert =>
|
certs.foreach { cert =>
|
||||||
sessionActor ! SessionActor.SendResponse(
|
sessionActor ! SessionActor.SendResponse(
|
||||||
PlanetsideAttributeMessage(session.get.player.GUID, 25, cert.value)
|
PlanetsideAttributeMessage(player.GUID, 25, cert.value)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
sessionActor ! SessionActor.SendResponse(
|
sessionActor ! SessionActor.SendResponse(
|
||||||
ItemTransactionResultMessage(terminalGuid, TransactionType.Sell, success = true)
|
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
|
Behaviors.same
|
||||||
|
|
@ -591,24 +566,24 @@ class AvatarActor(
|
||||||
case LearnImplant(terminalGuid, definition) =>
|
case LearnImplant(terminalGuid, definition) =>
|
||||||
// TODO there used to be a terminal check here, do we really need it?
|
// TODO there used to be a terminal check here, do we really need it?
|
||||||
val index = avatar.implants.zipWithIndex.collectFirst {
|
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
|
||||||
case (None, index) if index < avatar.br.implantSlots => index
|
case (None, _index) if _index < avatar.br.implantSlots => _index
|
||||||
}
|
}
|
||||||
index match {
|
index match {
|
||||||
case Some(index) =>
|
case Some(_index) =>
|
||||||
import ctx._
|
import ctx._
|
||||||
ctx
|
ctx
|
||||||
.run(query[persistence.Implant].insert(_.name -> lift(definition.Name), _.avatarId -> lift(avatar.id)))
|
.run(query[persistence.Implant].insert(_.name -> lift(definition.Name), _.avatarId -> lift(avatar.id)))
|
||||||
.onComplete {
|
.onComplete {
|
||||||
case Success(_) =>
|
case Success(_) =>
|
||||||
context.self ! ReplaceAvatar(
|
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(
|
sessionActor ! SessionActor.SendResponse(
|
||||||
AvatarImplantMessage(
|
AvatarImplantMessage(
|
||||||
session.get.player.GUID,
|
session.get.player.GUID,
|
||||||
ImplantAction.Add,
|
ImplantAction.Add,
|
||||||
index,
|
_index,
|
||||||
definition.implantType.value
|
definition.implantType.value
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -630,10 +605,10 @@ class AvatarActor(
|
||||||
case SellImplant(terminalGuid, definition) =>
|
case SellImplant(terminalGuid, definition) =>
|
||||||
// TODO there used to be a terminal check here, do we really need it?
|
// TODO there used to be a terminal check here, do we really need it?
|
||||||
val index = avatar.implants.zipWithIndex.collectFirst {
|
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 {
|
index match {
|
||||||
case Some(index) =>
|
case Some(_index) =>
|
||||||
import ctx._
|
import ctx._
|
||||||
ctx
|
ctx
|
||||||
.run(
|
.run(
|
||||||
|
|
@ -644,9 +619,9 @@ class AvatarActor(
|
||||||
)
|
)
|
||||||
.onComplete {
|
.onComplete {
|
||||||
case Success(_) =>
|
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(
|
sessionActor ! SessionActor.SendResponse(
|
||||||
AvatarImplantMessage(session.get.player.GUID, ImplantAction.Remove, index, 0)
|
AvatarImplantMessage(session.get.player.GUID, ImplantAction.Remove, _index, 0)
|
||||||
)
|
)
|
||||||
sessionActor ! SessionActor.SendResponse(
|
sessionActor ! SessionActor.SendResponse(
|
||||||
ItemTransactionResultMessage(terminalGuid, TransactionType.Sell, success = true)
|
ItemTransactionResultMessage(terminalGuid, TransactionType.Sell, success = true)
|
||||||
|
|
@ -721,15 +696,19 @@ class AvatarActor(
|
||||||
Behaviors.same
|
Behaviors.same
|
||||||
|
|
||||||
case UpdatePurchaseTime(definition, time) =>
|
case UpdatePurchaseTime(definition, time) =>
|
||||||
//only send for items with cooldowns
|
// TODO save to db
|
||||||
Avatar.purchaseCooldowns.get(definition) match {
|
var newTimes = avatar.purchaseTimes
|
||||||
case Some(cooldown) =>
|
resolveSharedPurchaseTimeNames(resolvePurchaseTimeName(avatar.faction, definition)).foreach {
|
||||||
// TODO save to db
|
case (item, name) =>
|
||||||
avatar = avatar.copy(purchaseTimes = avatar.purchaseTimes.updated(definition.Name, time))
|
Avatar.purchaseCooldowns.get(item) match {
|
||||||
updatePurchaseTimer(definition.Name, cooldown.toSeconds, unk1 = true)
|
case Some(cooldown) =>
|
||||||
case None => ;
|
//only send for items with cooldowns
|
||||||
//log.warn(s"UpdatePurchaseTime message for item '${definition.Name}' without cooldown")
|
newTimes = newTimes.updated(item.Name, time)
|
||||||
|
updatePurchaseTimer(name, cooldown.toSeconds, unk1 = true)
|
||||||
|
case _ => ;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
avatar = avatar.copy(purchaseTimes = newTimes)
|
||||||
Behaviors.same
|
Behaviors.same
|
||||||
|
|
||||||
case UpdateUseTime(definition, time) =>
|
case UpdateUseTime(definition, time) =>
|
||||||
|
|
@ -858,7 +837,7 @@ class AvatarActor(
|
||||||
Behaviors.same
|
Behaviors.same
|
||||||
|
|
||||||
case ConsumeStamina(stamina) =>
|
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)
|
consumeStamina(stamina)
|
||||||
Behaviors.same
|
Behaviors.same
|
||||||
|
|
||||||
|
|
@ -1260,7 +1239,7 @@ class AvatarActor(
|
||||||
doll.ExoSuit = ExoSuitType(loadout.exosuitId)
|
doll.ExoSuit = ExoSuitType(loadout.exosuitId)
|
||||||
|
|
||||||
loadout.items.split("/").foreach {
|
loadout.items.split("/").foreach {
|
||||||
case value =>
|
value =>
|
||||||
val (objectType, objectIndex, objectId, toolAmmo) = value.split(",") match {
|
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) => (a, b.toInt, c.toInt, None)
|
||||||
case Array(a, b: String, c: String, d) => (a, b.toInt, c.toInt, Some(d))
|
case Array(a, b: String, c: String, d) => (a, b.toInt, c.toInt, Some(d))
|
||||||
|
|
@ -1318,9 +1297,9 @@ class AvatarActor(
|
||||||
def defaultStaminaRegen(): Cancellable = {
|
def defaultStaminaRegen(): Cancellable = {
|
||||||
context.system.scheduler.scheduleWithFixedDelay(0.5 seconds, 0.5 seconds)(() => {
|
context.system.scheduler.scheduleWithFixedDelay(0.5 seconds, 0.5 seconds)(() => {
|
||||||
(session, _avatar) match {
|
(session, _avatar) match {
|
||||||
case (Some(session), Some(_)) =>
|
case (Some(_session), Some(_)) =>
|
||||||
if (
|
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)
|
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 = {
|
def refreshPurchaseTimes(keys: Set[String]): Unit = {
|
||||||
var keysToDrop: Seq[String] = Nil
|
var keysToDrop: Seq[String] = Nil
|
||||||
keys.foreach { key =>
|
keys.foreach { key =>
|
||||||
|
|
@ -1348,23 +1368,7 @@ class AvatarActor(
|
||||||
val secondsSincePurchase = Seconds.secondsBetween(purchaseTime, LocalDateTime.now()).getSeconds
|
val secondsSincePurchase = Seconds.secondsBetween(purchaseTime, LocalDateTime.now()).getSeconds
|
||||||
Avatar.purchaseCooldowns.find(_._1.Name == name) match {
|
Avatar.purchaseCooldowns.find(_._1.Name == name) match {
|
||||||
case Some((obj, cooldown)) if cooldown.toSeconds - secondsSincePurchase > 0 =>
|
case Some((obj, cooldown)) if cooldown.toSeconds - secondsSincePurchase > 0 =>
|
||||||
val faction : String = avatar.faction.toString.toLowerCase
|
val (_, name) = resolvePurchaseTimeName(avatar.faction, obj)
|
||||||
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
|
|
||||||
}
|
|
||||||
updatePurchaseTimer(name, cooldown.toSeconds - secondsSincePurchase, unk1 = true)
|
updatePurchaseTimer(name, cooldown.toSeconds - secondsSincePurchase, unk1 = true)
|
||||||
|
|
||||||
case _ =>
|
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) =>
|
case Terminal.TerminalMessage(_, msg, order) =>
|
||||||
order match {
|
order match {
|
||||||
case Terminal.BuyExosuit(exosuit, subtype) =>
|
case Terminal.BuyExosuit(exosuit, subtype) =>
|
||||||
var toDelete: List[InventoryItem] = Nil
|
val result = setExoSuit(exosuit, subtype)
|
||||||
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
|
|
||||||
}
|
|
||||||
player.Zone.AvatarEvents ! AvatarServiceMessage(
|
player.Zone.AvatarEvents ! AvatarServiceMessage(
|
||||||
player.Name,
|
player.Name,
|
||||||
AvatarAction.TerminalOrderResult(msg.terminal_guid, msg.transaction_type, result)
|
AvatarAction.TerminalOrderResult(msg.terminal_guid, msg.transaction_type, result)
|
||||||
|
|
@ -511,6 +417,107 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
|
||||||
case _ => ;
|
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(
|
override protected def PerformDamage(
|
||||||
target: Target,
|
target: Target,
|
||||||
applyDamageTo: Output
|
applyDamageTo: Output
|
||||||
|
|
@ -1139,6 +1146,9 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
|
||||||
}
|
}
|
||||||
|
|
||||||
object PlayerControl {
|
object PlayerControl {
|
||||||
|
/** na */
|
||||||
|
final case class SetExoSuit(exosuit: ExoSuitType.Value, subtype: Int)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transform an applicable Aura effect into its `PlanetsideAttributeMessage` value.
|
* Transform an applicable Aura effect into its `PlanetsideAttributeMessage` value.
|
||||||
* @see `Aura`
|
* @see `Aura`
|
||||||
|
|
|
||||||
|
|
@ -88,8 +88,7 @@ object InfantryLoadout {
|
||||||
/**
|
/**
|
||||||
* The sub-type of the player's uniform, as used in `FavoritesMessage`.<br>
|
* The sub-type of the player's uniform, as used in `FavoritesMessage`.<br>
|
||||||
* <br>
|
* <br>
|
||||||
* The values for `Standard`, `Infiltration`, and the generic `MAX` are not perfectly known.
|
* The values for a specific `MAX` type is only known by knowing the subtype.
|
||||||
* The latter-most exo-suit option is presumed.
|
|
||||||
* @param suit the player's uniform
|
* @param suit the player's uniform
|
||||||
* @param subtype the mechanized assault exo-suit subtype as determined by their arm weapons
|
* @param subtype the mechanized assault exo-suit subtype as determined by their arm weapons
|
||||||
* @return the numeric subtype
|
* @return the numeric subtype
|
||||||
|
|
|
||||||
|
|
@ -137,7 +137,8 @@ case class GameConfig(
|
||||||
bepRate: Double,
|
bepRate: Double,
|
||||||
cepRate: Double,
|
cepRate: Double,
|
||||||
newAvatar: NewAvatar,
|
newAvatar: NewAvatar,
|
||||||
hart: HartConfig
|
hart: HartConfig,
|
||||||
|
sharedMaxCooldown: Boolean
|
||||||
)
|
)
|
||||||
|
|
||||||
case class NewAvatar(
|
case class NewAvatar(
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue