Locker Fix (#1005)

* movement of items between locker and player repaired

* fixed saving and loading of lockers contents
This commit is contained in:
Fate-JH 2022-07-24 23:53:17 -04:00 committed by GitHub
parent 9d2be17c1c
commit ed705d2cb0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 325 additions and 269 deletions

View file

@ -2,7 +2,6 @@
package net.psforever.actors.session
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}
@ -22,7 +21,7 @@ import net.psforever.packet.game._
import net.psforever.types._
import net.psforever.util.Database._
import net.psforever.persistence
import net.psforever.util.{Config, DefinitionUtil}
import net.psforever.util.{Config, Database, DefinitionUtil}
import org.joda.time.{LocalDateTime, Seconds}
import net.psforever.services.ServiceManager
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
@ -32,6 +31,7 @@ import scala.concurrent.{ExecutionContextExecutor, Future, Promise}
import scala.util.{Failure, Success}
import scala.concurrent.duration._
import net.psforever.services.Service
import org.log4s.Logger
object AvatarActor {
def apply(sessionActor: ActorRef[SessionActor.Command]): Behavior[Command] =
@ -191,6 +191,122 @@ object AvatarActor {
final case class AvatarLoginResponse(avatar: Avatar)
def buildContainedEquipmentFromClob(container: Container, clob: String, log: org.log4s.Logger): Unit = {
clob.split("/").filter(_.trim.nonEmpty).foreach { 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))
case _ =>
log.warn(s"ignoring invalid item string: '$value'")
return
}
objectType match {
case "Tool" =>
container.Slot(objectIndex).Equipment =
Tool(DefinitionUtil.idToDefinition(objectId).asInstanceOf[ToolDefinition])
case "AmmoBox" =>
container.Slot(objectIndex).Equipment =
AmmoBox(DefinitionUtil.idToDefinition(objectId).asInstanceOf[AmmoBoxDefinition])
case "ConstructionItem" =>
container.Slot(objectIndex).Equipment = ConstructionItem(
DefinitionUtil.idToDefinition(objectId).asInstanceOf[ConstructionItemDefinition]
)
case "SimpleItem" =>
container.Slot(objectIndex).Equipment =
SimpleItem(DefinitionUtil.idToDefinition(objectId).asInstanceOf[SimpleItemDefinition])
case "Kit" =>
container.Slot(objectIndex).Equipment =
Kit(DefinitionUtil.idToDefinition(objectId).asInstanceOf[KitDefinition])
case "Telepad" | "BoomerTrigger" => ;
//special types of equipment that are not actually loaded
case name =>
log.error(s"failing to add unknown equipment to a locker - $name")
}
toolAmmo foreach { toolAmmo =>
toolAmmo.split("_").drop(1).foreach { value =>
val (ammoSlots, ammoTypeIndex, ammoBoxDefinition) = value.split("-") match {
case Array(a: String, b: String, c: String) => (a.toInt, b.toInt, c.toInt)
}
container.Slot(objectIndex).Equipment.get.asInstanceOf[Tool].AmmoSlots(ammoSlots).AmmoTypeIndex =
ammoTypeIndex
container.Slot(objectIndex).Equipment.get.asInstanceOf[Tool].AmmoSlots(ammoSlots).Box =
AmmoBox(AmmoBoxDefinition(ammoBoxDefinition))
}
}
}
}
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 (definition, 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 {
definition match {
case vdef: VehicleDefinition if GlobalDefinitions.isBattleFrameFlightVehicle(vdef) =>
val bframe = name.substring(0, name.indexOf('_'))
val gunner = bframe + "_gunner"
Seq((DefinitionUtil.fromString(gunner), gunner), (vdef, name))
case vdef: VehicleDefinition if GlobalDefinitions.isBattleFrameGunnerVehicle(vdef) =>
val bframe = name.substring(0, name.indexOf('_'))
val flight = bframe + "_flight"
Seq((vdef, name), (DefinitionUtil.fromString(flight), flight))
case _ =>
Seq(pair)
}
}
}
def encodeLockerClob(container: Container): String = {
val clobber: StringBuilder = new StringBuilder()
container.Inventory.Items.foreach {
case InventoryItem(obj, index) =>
clobber.append(encodeLoadoutClobFragment(obj, index))
}
clobber.mkString.drop(1)
}
def encodeLoadoutClobFragment(equipment: Equipment, index: Int): String = {
val ammoInfo: String = equipment match {
case tool: Tool =>
tool.AmmoSlots.zipWithIndex.collect {
case (ammoSlot, index2) if ammoSlot.AmmoTypeIndex != 0 =>
s"_$index2-${ammoSlot.AmmoTypeIndex}-${ammoSlot.AmmoType.id}"
}.mkString
case _ =>
""
}
s"/${equipment.getClass.getSimpleName},$index,${equipment.Definition.ObjectId},$ammoInfo"
}
def changeRibbons(ribbons: RibbonBars, ribbon: MeritCommendation.Value, bar: RibbonBarSlot.Value): RibbonBars = {
bar match {
case RibbonBarSlot.Top => ribbons.copy(upper = ribbon)
@ -368,8 +484,7 @@ class AvatarActor(
val result = for {
_ <- ctx.run(
query[persistence.Avatar]
.filter(_.id == lift(avatar.id))
query[persistence.Avatar].filter(_.id == lift(avatar.id))
.update(_.lastLogin -> lift(LocalDateTime.now()))
)
loadouts <- initializeAllLoadouts()
@ -422,14 +537,13 @@ class AvatarActor(
val replace = certification.replaces.intersect(avatar.certifications)
Future
.sequence(replace.map(cert => {
ctx
.run(
query[persistence.Certification]
.filter(_.avatarId == lift(avatar.id))
.filter(_.id == lift(cert.value))
.delete
)
.map(_ => cert)
ctx.run(
query[persistence.Certification]
.filter(_.avatarId == lift(avatar.id))
.filter(_.id == lift(cert.value))
.delete
)
.map(_ => cert)
}))
.onComplete {
case Failure(exception) =>
@ -443,11 +557,10 @@ class AvatarActor(
PlanetsideAttributeMessage(session.get.player.GUID, 25, cert.value)
)
}
ctx
.run(
query[persistence.Certification]
.insert(_.id -> lift(certification.value), _.avatarId -> lift(avatar.id))
)
ctx.run(
query[persistence.Certification]
.insert(_.id -> lift(certification.value), _.avatarId -> lift(avatar.id))
)
.onComplete {
case Failure(exception) =>
log.error(exception)("db failure")
@ -494,14 +607,13 @@ class AvatarActor(
avatar.certifications
.intersect(requiredByCert)
.map(cert => {
ctx
.run(
query[persistence.Certification]
.filter(_.avatarId == lift(avatar.id))
.filter(_.id == lift(cert.value))
.delete
)
.map(_ => cert)
ctx.run(
query[persistence.Certification]
.filter(_.avatarId == lift(avatar.id))
.filter(_.id == lift(cert.value))
.delete
)
.map(_ => cert)
})
)
.onComplete {
@ -551,13 +663,12 @@ class AvatarActor(
sessionActor ! SessionActor.SendResponse(
PlanetsideAttributeMessage(session.get.player.GUID, 25, cert.value)
)
ctx
.run(
query[persistence.Certification]
.filter(_.avatarId == lift(avatar.id))
.filter(_.id == lift(cert.value))
.delete
)
ctx.run(
query[persistence.Certification]
.filter(_.avatarId == lift(avatar.id))
.filter(_.id == lift(cert.value))
.delete
)
}) ++
certifications
.diff(avatar.certifications)
@ -642,25 +753,24 @@ class AvatarActor(
index match {
case Some(_index) =>
import ctx._
ctx
.run(
query[persistence.Implant]
.filter(_.name == lift(definition.Name))
.filter(_.avatarId == lift(avatar.id))
.delete
)
.onComplete {
case Success(_) =>
replaceAvatar(avatar.copy(implants = avatar.implants.updated(_index, None)))
sessionActor ! SessionActor.SendResponse(
AvatarImplantMessage(session.get.player.GUID, ImplantAction.Remove, _index, 0)
)
sessionActor ! SessionActor.SendResponse(
ItemTransactionResultMessage(terminalGuid, TransactionType.Sell, success = true)
)
context.self ! ResetImplants()
case Failure(exception) => log.error(exception)("db failure")
}
ctx.run(
query[persistence.Implant]
.filter(_.name == lift(definition.Name))
.filter(_.avatarId == lift(avatar.id))
.delete
)
.onComplete {
case Success(_) =>
replaceAvatar(avatar.copy(implants = avatar.implants.updated(_index, None)))
sessionActor ! SessionActor.SendResponse(
AvatarImplantMessage(session.get.player.GUID, ImplantAction.Remove, _index, 0)
)
sessionActor ! SessionActor.SendResponse(
ItemTransactionResultMessage(terminalGuid, TransactionType.Sell, success = true)
)
context.self ! ResetImplants()
case Failure(exception) => log.error(exception)("db failure")
}
case None =>
log.warn("attempted to sell implant but could not find slot")
@ -772,13 +882,17 @@ class AvatarActor(
case UpdatePurchaseTime(definition, time) =>
// TODO save to db
var newTimes = avatar.purchaseTimes
resolveSharedPurchaseTimeNames(resolvePurchaseTimeName(avatar.faction, definition)).foreach {
AvatarActor.resolveSharedPurchaseTimeNames(AvatarActor.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(name, time)
updatePurchaseTimer(name, cooldown.toSeconds, unk1 = true)
updatePurchaseTimer(
name,
cooldown.toSeconds,
DefinitionUtil.fromString(name).isInstanceOf[VehicleDefinition]
)
case _ => ;
}
}
@ -976,13 +1090,12 @@ class AvatarActor(
val implants = avatar.implants.zipWithIndex.map {
case (implant, index) =>
if (index >= BattleRank.withExperience(bep).implantSlots && implant.isDefined) {
ctx
.run(
query[persistence.Implant]
.filter(_.name == lift(implant.get.definition.Name))
.filter(_.avatarId == lift(avatar.id))
.delete
)
ctx.run(
query[persistence.Implant]
.filter(_.name == lift(implant.get.definition.Name))
.filter(_.avatarId == lift(avatar.id))
.delete
)
.onComplete {
case Success(_) =>
sessionActor ! SessionActor.SendResponse(
@ -1071,12 +1184,11 @@ class AvatarActor(
val p = Promise[Unit]()
import ctx._
ctx
.run(
query[persistence.Avatar]
.filter(_.id == lift(avatar.id))
.update(_.cosmetics -> lift(Some(Cosmetic.valuesToObjectCreateValue(cosmetics)): Option[Int]))
)
ctx.run(
query[persistence.Avatar]
.filter(_.id == lift(avatar.id))
.update(_.cosmetics -> lift(Some(Cosmetic.valuesToObjectCreateValue(cosmetics)): Option[Int]))
)
.onComplete {
case Success(_) =>
avatarCopy(avatar.copy(cosmetics = Some(cosmetics)))
@ -1089,7 +1201,6 @@ class AvatarActor(
case Failure(exception) =>
p.failure(exception)
}
p.future
}
@ -1343,9 +1454,7 @@ class AvatarActor(
}
)
player.GUID = PlanetSideGUID(gen.getAndIncrement)
player.Spawn()
sessionActor ! SessionActor.SendResponse(
ObjectCreateDetailedMessage(
ObjectClass.avatar,
@ -1353,7 +1462,6 @@ class AvatarActor(
converter.DetailedConstructorData(player).get
)
)
sessionActor ! SessionActor.SendResponse(
CharacterInfoMessage(
15,
@ -1364,7 +1472,6 @@ class AvatarActor(
secondsSinceLastLogin
)
)
/** After the user has selected a character to load from the "character select screen,"
* the temporary global unique identifiers used for that screen are stripped from the underlying `Player` object that was selected.
* Characters that were not selected may be destroyed along with their temporary GUIDs.
@ -1385,7 +1492,6 @@ class AvatarActor(
)
player.Invalidate()
}
sessionActor ! SessionActor.SendResponse(
CharacterInfoMessage(15, PlanetSideZoneID(0), 0, PlanetSideGUID(0), finished = true, 0)
)
@ -1404,16 +1510,15 @@ class AvatarActor(
.zipWithIndex
.collect {
case (slot, index) if slot.Equipment.nonEmpty =>
clobber.append(encodeLoadoutClobFragment(slot.Equipment.get, index))
clobber.append(AvatarActor.encodeLoadoutClobFragment(slot.Equipment.get, index))
}
//encode inventory
owner.Inventory.Items.foreach {
case InventoryItem(obj, index) =>
clobber.append(encodeLoadoutClobFragment(obj, index))
clobber.append(AvatarActor.encodeLoadoutClobFragment(obj, index))
}
clobber.mkString.drop(1)
}
for {
loadouts <- ctx.run(
query[persistence.Loadout].filter(_.avatarId == lift(owner.CharId)).filter(_.loadoutNumber == lift(line))
@ -1447,12 +1552,12 @@ class AvatarActor(
vehicle.Weapons
.collect {
case (index, slot: EquipmentSlot) if slot.Equipment.nonEmpty =>
clobber.append(encodeLoadoutClobFragment(slot.Equipment.get, index))
clobber.append(AvatarActor.encodeLoadoutClobFragment(slot.Equipment.get, index))
}
//encode inventory
vehicle.Inventory.Items.foreach {
case InventoryItem(obj, index) =>
clobber.append(encodeLoadoutClobFragment(obj, index))
clobber.append(AvatarActor.encodeLoadoutClobFragment(obj, index))
}
clobber.mkString.drop(1)
}
@ -1486,32 +1591,15 @@ class AvatarActor(
def storeNewLocker(): Unit = {
if (_avatar.nonEmpty) {
val items: String = {
val clobber: StringBuilder = new StringBuilder()
avatar.locker.Inventory.Items.foreach {
case InventoryItem(obj, index) =>
clobber.append(encodeLoadoutClobFragment(obj, index))
pushLockerClobToDataBase(AvatarActor.encodeLockerClob(avatar.locker))
.onComplete {
case Success(_) =>
saveLockerFunc = storeLocker
log.debug(s"saving locker contents belonging to ${avatar.name}")
case Failure(e) =>
saveLockerFunc = doNotStoreLocker
log.error(e)("db failure")
}
clobber.mkString.drop(1)
}
if (items.nonEmpty) {
saveLockerFunc = storeLocker
import ctx._
ctx
.run(
query[persistence.Locker].insert(
_.avatarId -> lift(avatar.id),
_.items -> lift(items)
)
)
.onComplete {
case Success(_) =>
log.debug(s"saving locker contents belonging to ${avatar.name}")
case Failure(e) =>
saveLockerFunc = doNotStoreLocker
log.error(e)("db failure")
}
}
}
}
@ -1520,16 +1608,12 @@ class AvatarActor(
}
def storeLocker(): Unit = {
import ctx._
val items: String = {
val clobber: StringBuilder = new StringBuilder()
avatar.locker.Inventory.Items.foreach {
case InventoryItem(obj, index) =>
clobber.append(encodeLoadoutClobFragment(obj, index))
}
clobber.mkString.drop(1)
}
log.debug(s"saving locker contents belonging to ${avatar.name}")
pushLockerClobToDataBase(AvatarActor.encodeLockerClob(avatar.locker))
}
def pushLockerClobToDataBase(items: String): Database.ctx.Result[Database.ctx.RunActionResult] = {
import ctx._
ctx.run(
query[persistence.Locker]
.filter(_.avatarId == lift(avatar.id))
@ -1537,19 +1621,6 @@ class AvatarActor(
)
}
def encodeLoadoutClobFragment(equipment: Equipment, index: Int): String = {
val ammoInfo: String = equipment match {
case tool: Tool =>
tool.AmmoSlots.zipWithIndex.collect {
case (ammoSlot, index2) if ammoSlot.AmmoTypeIndex != 0 =>
s"_$index2-${ammoSlot.AmmoTypeIndex}-${ammoSlot.AmmoType.id}"
}.mkString
case _ =>
""
}
s"/${equipment.getClass.getSimpleName},$index,${equipment.Definition.ObjectId},$ammoInfo"
}
def initializeAllLoadouts(): Future[Seq[Option[Loadout]]] = {
for {
infantry <- loadLoadouts().andThen {
@ -1571,7 +1642,7 @@ class AvatarActor(
loadouts.map { loadout =>
val doll = new Player(Avatar(0, "doll", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute))
doll.ExoSuit = ExoSuitType(loadout.exosuitId)
buildContainedEquipmentFromClob(doll, loadout.items)
AvatarActor.buildContainedEquipmentFromClob(doll, loadout.items, log)
val result = (loadout.loadoutNumber, Loadout.Create(doll, loadout.name))
(0 until 4).foreach(index => {
@ -1592,7 +1663,7 @@ class AvatarActor(
loadouts.map { loadout =>
val definition = DefinitionUtil.idToDefinition(loadout.vehicle).asInstanceOf[VehicleDefinition]
val toy = new Vehicle(definition)
buildContainedEquipmentFromClob(toy, loadout.items)
AvatarActor.buildContainedEquipmentFromClob(toy, loadout.items, log)
val result = (loadout.loadoutNumber, Loadout.Create(toy, loadout.name))
toy.Weapons.values.foreach(slot => {
@ -1699,61 +1770,34 @@ class AvatarActor(
def loadLocker(): Future[LockerContainer] = {
val locker = Avatar.makeLocker()
var notLoaded: Boolean = false
import ctx._
ctx
.run(query[persistence.Locker].filter(_.avatarId == lift(avatar.id)))
val out = ctx.run(query[persistence.Locker]
.filter(_.avatarId == lift(avatar.id)))
.map { entry =>
saveLockerFunc = storeLocker
entry.foreach { contents => buildContainedEquipmentFromClob(locker, contents.items) }
notLoaded = false
entry.foreach { contents => AvatarActor.buildContainedEquipmentFromClob(locker, contents.items, log) }
}
.map { _ => locker }
}
def buildContainedEquipmentFromClob(container: Container, clob: String): Unit = {
clob.split("/").filter(_.trim.nonEmpty).foreach { 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))
case _ =>
log.warn(s"ignoring invalid item string: '$value'")
return
}
objectType match {
case "Tool" =>
container.Slot(objectIndex).Equipment =
Tool(DefinitionUtil.idToDefinition(objectId).asInstanceOf[ToolDefinition])
case "AmmoBox" =>
container.Slot(objectIndex).Equipment =
AmmoBox(DefinitionUtil.idToDefinition(objectId).asInstanceOf[AmmoBoxDefinition])
case "ConstructionItem" =>
container.Slot(objectIndex).Equipment = ConstructionItem(
DefinitionUtil.idToDefinition(objectId).asInstanceOf[ConstructionItemDefinition]
)
case "SimpleItem" =>
container.Slot(objectIndex).Equipment =
SimpleItem(DefinitionUtil.idToDefinition(objectId).asInstanceOf[SimpleItemDefinition])
case "Kit" =>
container.Slot(objectIndex).Equipment =
Kit(DefinitionUtil.idToDefinition(objectId).asInstanceOf[KitDefinition])
case "Telepad" | "BoomerTrigger" => ;
//special types of equipment that are not actually loaded
case name =>
log.error(s"failing to add unknown equipment to a locker - $name")
}
toolAmmo foreach { toolAmmo =>
toolAmmo.split("_").drop(1).foreach { value =>
val (ammoSlots, ammoTypeIndex, ammoBoxDefinition) = value.split("-") match {
case Array(a: String, b: String, c: String) => (a.toInt, b.toInt, c.toInt)
}
container.Slot(objectIndex).Equipment.get.asInstanceOf[Tool].AmmoSlots(ammoSlots).AmmoTypeIndex =
ammoTypeIndex
container.Slot(objectIndex).Equipment.get.asInstanceOf[Tool].AmmoSlots(ammoSlots).Box =
AmmoBox(AmmoBoxDefinition(ammoBoxDefinition))
}
}
out.onComplete {
case Success(_) =>
saveLockerFunc = storeLocker
case Failure(_) =>
notLoaded = true
}
if (notLoaded) {
//default empty locker
ctx.run(query[persistence.Locker]
.insert(_.avatarId -> lift(avatar.id), _.items -> lift("")))
.onComplete {
case Success(_) =>
saveLockerFunc = storeLocker
case Failure(e) =>
saveLockerFunc = doNotStoreLocker
log.error(e)("db failure")
}
}
out
}
def startIfStoppedStaminaRegen(initialDelay: FiniteDuration): Unit = {
@ -1781,53 +1825,6 @@ 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 (definition, 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 {
definition match {
case vdef: VehicleDefinition if GlobalDefinitions.isBattleFrameFlightVehicle(vdef) =>
val bframe = name.substring(0, name.indexOf('_'))
val gunner = bframe + "_gunner"
Seq((DefinitionUtil.fromString(gunner), gunner), (vdef, name))
case vdef: VehicleDefinition if GlobalDefinitions.isBattleFrameGunnerVehicle(vdef) =>
val bframe = name.substring(0, name.indexOf('_'))
val flight = bframe + "_flight"
Seq((vdef, name), (DefinitionUtil.fromString(flight), flight))
case _ =>
Seq(pair)
}
}
}
def refreshPurchaseTimes(keys: Set[String]): Unit = {
var keysToDrop: Seq[String] = Nil
keys.foreach { key =>
@ -1836,8 +1833,12 @@ 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 (_, name) = resolvePurchaseTimeName(avatar.faction, obj)
updatePurchaseTimer(name, cooldown.toSeconds - secondsSincePurchase, unk1 = true)
val (_, name) = AvatarActor.resolvePurchaseTimeName(avatar.faction, obj)
updatePurchaseTimer(
name,
cooldown.toSeconds - secondsSincePurchase,
DefinitionUtil.fromString(name).isInstanceOf[VehicleDefinition]
)
case _ =>
keysToDrop = keysToDrop :+ key //key has timed-out
@ -1850,10 +1851,9 @@ class AvatarActor(
}
}
def updatePurchaseTimer(name: String, time: Long, unk1: Boolean): Unit = {
//TODO? unk1 is: vehicles = true, everything else = false
def updatePurchaseTimer(name: String, time: Long, isActuallyAVehicle: Boolean): Unit = {
sessionActor ! SessionActor.SendResponse(
AvatarVehicleTimerMessage(session.get.player.GUID, name, time, unk1 = true)
AvatarVehicleTimerMessage(session.get.player.GUID, name, time, isActuallyAVehicle)
)
}

View file

@ -282,6 +282,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
var heightLast: Float = 0f
var heightTrend: Boolean = false //up = true, down = false
var heightHistory: Float = 0f
var contextSafeEntity: PlanetSideGUID = PlanetSideGUID(0)
val collisionHistory: mutable.HashMap[ActorRef, Long] = mutable.HashMap()
var populateAvatarAwardRibbonsFunc: (Int, Long) => Unit = setupAvatarAwardMessageDelivery
@ -586,7 +587,11 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
case Some(_: LocalLockerItem) =>
player.avatar.locker.Inventory.hasItem(guid) match {
case out @ Some(_) =>
contextSafeEntity = guid
out
case None if contextSafeEntity == guid =>
//safeguard
None
case None =>
//delete stale entity reference from client
log.warn(
@ -614,6 +619,10 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
sendResponse(ObjectDeleteMessage(guid, 0))
None
case None if contextSafeEntity == guid =>
//safeguard
None
case _ =>
None
}
@ -4951,10 +4960,10 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
ValidObject(item_guid, decorator = "MoveItem")
) match {
case (
Some(source: PlanetSideServerObject with Container),
Some(destination: PlanetSideServerObject with Container),
Some(item: Equipment)
) =>
Some(source: PlanetSideServerObject with Container),
Some(destination: PlanetSideServerObject with Container),
Some(item: Equipment)
) =>
ContainableMoveItem(player.Name, source, destination, item, destination.SlotMapResolution(dest))
case (None, _, _) =>
log.error(

View file

@ -3,7 +3,7 @@ package net.psforever.login
import akka.actor.ActorRef
import akka.pattern.{AskTimeoutException, ask}
import akka.util.Timeout
import net.psforever.objects.equipment.{Ammo, Equipment}
import net.psforever.objects.equipment.{Ammo, Equipment, EquipmentSize}
import net.psforever.objects.guid._
import net.psforever.objects.inventory.{Container, InventoryItem}
import net.psforever.objects.locker.LockerContainer
@ -653,39 +653,77 @@ object WorldSession {
item: Equipment,
dest: Int
): Unit = {
TaskWorkflow.execute(TaskBundle(
new StraightforwardTask() {
val localGUID = item.GUID //original GUID
val localChannel = toChannel
val localSource = source
val (performSwap, swapItemGUID): (Boolean, Option[PlanetSideGUID]) = {
val destInv = destination.Inventory
if (destInv.Offset <= dest && destInv.Offset + destInv.TotalCapacity >= dest) {
val tile = item.Definition.Tile
destInv.CheckCollisionsVar(dest, tile.Width, tile.Height)
} else {
val slot = destination.Slot(dest)
if (slot.Size != EquipmentSize.Blocked) {
slot.Equipment match {
case Some(thing) => Success(List(InventoryItem(thing, dest)))
case None => Success(Nil)
}
} else {
Failure(new Exception(""))
}
}
} match {
case Success(Nil) =>
//no swap item
(true, None)
case Success(List(swapEntry: InventoryItem)) =>
//the swap item is to be registered to the source's zone
(true, Some(swapEntry.obj.GUID))
case _ =>
//too many swap items or other error; this attempt will not execute
(false, None)
}
if (performSwap) {
def moveItemTaskFunc(toSlot: Int): Task = new StraightforwardTask() {
val localGUID = swapItemGUID //the swap item's original GUID, if any swap item
val localChannel = toChannel
val localSource = source
val localDestination = destination
val localItem = item
val localSlot = dest
/*
source is a locker container that has its own internal unique number system
the item is currently registered to this system
the item will be moved into the system in which the destination operates
to facilitate the transfer, the item needs to be partially unregistered from the source's system
to facilitate the transfer, the item needs to be preemptively registered to the destination's system
invalidating the current unique number is sufficient for both of these steps
*/
localItem.Invalidate()
localItem match {
case t: Tool => t.AmmoSlots.foreach { _.Box.Invalidate() }
case _ => ;
val localItem = item
val localDestSlot = dest
val localSrcSlot = toSlot
val localMoveOnComplete: Try[Any] => Unit = {
case Success(Containable.ItemPutInSlot(_, _, _, Some(swapItem))) =>
//swapItem is not registered right now, we can not drop the item without re-registering it
TaskWorkflow.execute(PutNewEquipmentInInventorySlot(localSource)(swapItem, localSrcSlot))
case _ => ;
}
override def description(): String = s"registering $localItem in ${localDestination.Zone.id} before removing from $localSource"
def action(): Future[Any] = {
val zone = localSource.Zone
//see LockerContainerControl.RemoveItemFromSlotCallback
zone.AvatarEvents ! AvatarServiceMessage(localChannel, AvatarAction.ObjectDelete(Service.defaultPlayerGUID, localGUID))
ask(localSource.Actor, Containable.MoveItem(localDestination, localItem, localSlot))
localGUID match {
case Some(guid) =>
//see LockerContainerControl.RemoveItemFromSlotCallback
localSource.Zone.AvatarEvents ! AvatarServiceMessage(
localChannel,
AvatarAction.ObjectDelete(Service.defaultPlayerGUID, guid)
)
case None => ;
}
val moveResult = ask(localDestination.Actor, Containable.PutItemInSlotOrAway(localItem, Some(localDestSlot)))
moveResult.onComplete(localMoveOnComplete)
moveResult
}
},
GUIDTask.registerEquipment(destination.Zone.GUID, item))
)
}
val resultOnComplete: Try[Any] => Unit = {
case Success(Containable.ItemFromSlot(fromSource, Some(itemToMove), Some(fromSlot))) =>
TaskWorkflow.execute(TaskBundle(
moveItemTaskFunc(fromSlot),
GUIDTask.registerEquipment(fromSource.Zone.GUID, itemToMove)
))
case _ => ;
}
val result = ask(source.Actor, Containable.RemoveItemFromSlot(item))
result.onComplete(resultOnComplete)
}
}
/**

View file

@ -23,7 +23,10 @@ class LocalLockerItem extends PlanetSideServerObject {
object LocalLockerItem {
import net.psforever.objects.definition.ObjectDefinition
def local = new ObjectDefinition(0) { Name = "locker-equipment" }
def local = new ObjectDefinition(0) {
Name = "locker-equipment"
registerAs = "locker-contents"
}
/**
* Instantiate and configure a `LocalProjectile` object.

View file

@ -22,7 +22,10 @@ class LocalProjectile extends PlanetSideServerObject {
object LocalProjectile {
import net.psforever.objects.definition.ObjectDefinition
def local = new ObjectDefinition(0) { Name = "projectile" }
def local = new ObjectDefinition(0) {
Name = "projectile"
registerAs = "projectiles"
}
/**
* Instantiate and configure a `LocalProjectile` object.

View file

@ -12,7 +12,7 @@ import net.psforever.types.{PlanetSideEmpire, Vector3}
/**
* A control agency mainly for manipulating the equipment stowed by a player in a `LockerContainer`
* and reporting back to a specific xchannel in the event system about these changes.
* and reporting back to a specific channel in the event system about these changes.
* @param locker the governed player-facing locker component
* @param toChannel the channel to which to publish events, typically the owning player's name
*/

View file

@ -339,13 +339,6 @@ object Zones {
turretWeaponGuid
)
(Projectile.baseUID until Projectile.rangeUID) foreach {
zoneMap.addLocalObject(_, LocalProjectile.Constructor)
}
40150 until 40450 foreach {
zoneMap.addLocalObject(_, LocalLockerItem.Constructor)
}
lattice.asObject.get(mapid).foreach { obj =>
obj.asArray.get.foreach { entry =>
val arr = entry.asArray.get
@ -687,6 +680,16 @@ object Zones {
override def SetupNumberPools() : Unit = addPoolsFunc()
override def init(implicit context: ActorContext): Unit = {
guids.find { pool => pool.name.equals("projectiles") } match {
case Some(pool) =>
(pool.start to pool.max).foreach { map.addLocalObject(_, LocalProjectile.Constructor) }
case None => ;
}
guids.find { pool => pool.name.equals("locker-contents") } match {
case Some(pool) =>
(pool.start to pool.max).foreach { map.addLocalObject(_, LocalLockerItem.Constructor) }
case None => ;
}
super.init(context)
if (!info.id.startsWith("tz")) {