mirror of
https://github.com/2revoemag/PSF-BotServer.git
synced 2026-04-29 15:55:23 +00:00
Locker Fix (#1005)
* movement of items between locker and player repaired * fixed saving and loading of lockers contents
This commit is contained in:
parent
9d2be17c1c
commit
ed705d2cb0
7 changed files with 325 additions and 269 deletions
|
|
@ -2,7 +2,6 @@
|
||||||
package net.psforever.actors.session
|
package net.psforever.actors.session
|
||||||
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger
|
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}
|
||||||
|
|
@ -22,7 +21,7 @@ import net.psforever.packet.game._
|
||||||
import net.psforever.types._
|
import net.psforever.types._
|
||||||
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, Database, DefinitionUtil}
|
||||||
import org.joda.time.{LocalDateTime, Seconds}
|
import org.joda.time.{LocalDateTime, Seconds}
|
||||||
import net.psforever.services.ServiceManager
|
import net.psforever.services.ServiceManager
|
||||||
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
|
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||||
|
|
@ -32,6 +31,7 @@ import scala.concurrent.{ExecutionContextExecutor, Future, Promise}
|
||||||
import scala.util.{Failure, Success}
|
import scala.util.{Failure, Success}
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
import net.psforever.services.Service
|
import net.psforever.services.Service
|
||||||
|
import org.log4s.Logger
|
||||||
|
|
||||||
object AvatarActor {
|
object AvatarActor {
|
||||||
def apply(sessionActor: ActorRef[SessionActor.Command]): Behavior[Command] =
|
def apply(sessionActor: ActorRef[SessionActor.Command]): Behavior[Command] =
|
||||||
|
|
@ -191,6 +191,122 @@ object AvatarActor {
|
||||||
|
|
||||||
final case class AvatarLoginResponse(avatar: Avatar)
|
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 = {
|
def changeRibbons(ribbons: RibbonBars, ribbon: MeritCommendation.Value, bar: RibbonBarSlot.Value): RibbonBars = {
|
||||||
bar match {
|
bar match {
|
||||||
case RibbonBarSlot.Top => ribbons.copy(upper = ribbon)
|
case RibbonBarSlot.Top => ribbons.copy(upper = ribbon)
|
||||||
|
|
@ -368,8 +484,7 @@ class AvatarActor(
|
||||||
|
|
||||||
val result = for {
|
val result = for {
|
||||||
_ <- ctx.run(
|
_ <- ctx.run(
|
||||||
query[persistence.Avatar]
|
query[persistence.Avatar].filter(_.id == lift(avatar.id))
|
||||||
.filter(_.id == lift(avatar.id))
|
|
||||||
.update(_.lastLogin -> lift(LocalDateTime.now()))
|
.update(_.lastLogin -> lift(LocalDateTime.now()))
|
||||||
)
|
)
|
||||||
loadouts <- initializeAllLoadouts()
|
loadouts <- initializeAllLoadouts()
|
||||||
|
|
@ -422,14 +537,13 @@ class AvatarActor(
|
||||||
val replace = certification.replaces.intersect(avatar.certifications)
|
val replace = certification.replaces.intersect(avatar.certifications)
|
||||||
Future
|
Future
|
||||||
.sequence(replace.map(cert => {
|
.sequence(replace.map(cert => {
|
||||||
ctx
|
ctx.run(
|
||||||
.run(
|
query[persistence.Certification]
|
||||||
query[persistence.Certification]
|
.filter(_.avatarId == lift(avatar.id))
|
||||||
.filter(_.avatarId == lift(avatar.id))
|
.filter(_.id == lift(cert.value))
|
||||||
.filter(_.id == lift(cert.value))
|
.delete
|
||||||
.delete
|
)
|
||||||
)
|
.map(_ => cert)
|
||||||
.map(_ => cert)
|
|
||||||
}))
|
}))
|
||||||
.onComplete {
|
.onComplete {
|
||||||
case Failure(exception) =>
|
case Failure(exception) =>
|
||||||
|
|
@ -443,11 +557,10 @@ class AvatarActor(
|
||||||
PlanetsideAttributeMessage(session.get.player.GUID, 25, cert.value)
|
PlanetsideAttributeMessage(session.get.player.GUID, 25, cert.value)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
ctx
|
ctx.run(
|
||||||
.run(
|
query[persistence.Certification]
|
||||||
query[persistence.Certification]
|
.insert(_.id -> lift(certification.value), _.avatarId -> lift(avatar.id))
|
||||||
.insert(_.id -> lift(certification.value), _.avatarId -> lift(avatar.id))
|
)
|
||||||
)
|
|
||||||
.onComplete {
|
.onComplete {
|
||||||
case Failure(exception) =>
|
case Failure(exception) =>
|
||||||
log.error(exception)("db failure")
|
log.error(exception)("db failure")
|
||||||
|
|
@ -494,14 +607,13 @@ class AvatarActor(
|
||||||
avatar.certifications
|
avatar.certifications
|
||||||
.intersect(requiredByCert)
|
.intersect(requiredByCert)
|
||||||
.map(cert => {
|
.map(cert => {
|
||||||
ctx
|
ctx.run(
|
||||||
.run(
|
query[persistence.Certification]
|
||||||
query[persistence.Certification]
|
.filter(_.avatarId == lift(avatar.id))
|
||||||
.filter(_.avatarId == lift(avatar.id))
|
.filter(_.id == lift(cert.value))
|
||||||
.filter(_.id == lift(cert.value))
|
.delete
|
||||||
.delete
|
)
|
||||||
)
|
.map(_ => cert)
|
||||||
.map(_ => cert)
|
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.onComplete {
|
.onComplete {
|
||||||
|
|
@ -551,13 +663,12 @@ class AvatarActor(
|
||||||
sessionActor ! SessionActor.SendResponse(
|
sessionActor ! SessionActor.SendResponse(
|
||||||
PlanetsideAttributeMessage(session.get.player.GUID, 25, cert.value)
|
PlanetsideAttributeMessage(session.get.player.GUID, 25, cert.value)
|
||||||
)
|
)
|
||||||
ctx
|
ctx.run(
|
||||||
.run(
|
query[persistence.Certification]
|
||||||
query[persistence.Certification]
|
.filter(_.avatarId == lift(avatar.id))
|
||||||
.filter(_.avatarId == lift(avatar.id))
|
.filter(_.id == lift(cert.value))
|
||||||
.filter(_.id == lift(cert.value))
|
.delete
|
||||||
.delete
|
)
|
||||||
)
|
|
||||||
}) ++
|
}) ++
|
||||||
certifications
|
certifications
|
||||||
.diff(avatar.certifications)
|
.diff(avatar.certifications)
|
||||||
|
|
@ -642,25 +753,24 @@ class AvatarActor(
|
||||||
index match {
|
index match {
|
||||||
case Some(_index) =>
|
case Some(_index) =>
|
||||||
import ctx._
|
import ctx._
|
||||||
ctx
|
ctx.run(
|
||||||
.run(
|
query[persistence.Implant]
|
||||||
query[persistence.Implant]
|
.filter(_.name == lift(definition.Name))
|
||||||
.filter(_.name == lift(definition.Name))
|
.filter(_.avatarId == lift(avatar.id))
|
||||||
.filter(_.avatarId == lift(avatar.id))
|
.delete
|
||||||
.delete
|
)
|
||||||
)
|
.onComplete {
|
||||||
.onComplete {
|
case Success(_) =>
|
||||||
case Success(_) =>
|
replaceAvatar(avatar.copy(implants = avatar.implants.updated(_index, None)))
|
||||||
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)
|
)
|
||||||
)
|
context.self ! ResetImplants()
|
||||||
context.self ! ResetImplants()
|
case Failure(exception) => log.error(exception)("db failure")
|
||||||
case Failure(exception) => log.error(exception)("db failure")
|
}
|
||||||
}
|
|
||||||
|
|
||||||
case None =>
|
case None =>
|
||||||
log.warn("attempted to sell implant but could not find slot")
|
log.warn("attempted to sell implant but could not find slot")
|
||||||
|
|
@ -772,13 +882,17 @@ class AvatarActor(
|
||||||
case UpdatePurchaseTime(definition, time) =>
|
case UpdatePurchaseTime(definition, time) =>
|
||||||
// TODO save to db
|
// TODO save to db
|
||||||
var newTimes = avatar.purchaseTimes
|
var newTimes = avatar.purchaseTimes
|
||||||
resolveSharedPurchaseTimeNames(resolvePurchaseTimeName(avatar.faction, definition)).foreach {
|
AvatarActor.resolveSharedPurchaseTimeNames(AvatarActor.resolvePurchaseTimeName(avatar.faction, definition)).foreach {
|
||||||
case (item, name) =>
|
case (item, name) =>
|
||||||
Avatar.purchaseCooldowns.get(item) match {
|
Avatar.purchaseCooldowns.get(item) match {
|
||||||
case Some(cooldown) =>
|
case Some(cooldown) =>
|
||||||
//only send for items with cooldowns
|
//only send for items with cooldowns
|
||||||
newTimes = newTimes.updated(name, time)
|
newTimes = newTimes.updated(name, time)
|
||||||
updatePurchaseTimer(name, cooldown.toSeconds, unk1 = true)
|
updatePurchaseTimer(
|
||||||
|
name,
|
||||||
|
cooldown.toSeconds,
|
||||||
|
DefinitionUtil.fromString(name).isInstanceOf[VehicleDefinition]
|
||||||
|
)
|
||||||
case _ => ;
|
case _ => ;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -976,13 +1090,12 @@ class AvatarActor(
|
||||||
val implants = avatar.implants.zipWithIndex.map {
|
val implants = avatar.implants.zipWithIndex.map {
|
||||||
case (implant, index) =>
|
case (implant, index) =>
|
||||||
if (index >= BattleRank.withExperience(bep).implantSlots && implant.isDefined) {
|
if (index >= BattleRank.withExperience(bep).implantSlots && implant.isDefined) {
|
||||||
ctx
|
ctx.run(
|
||||||
.run(
|
query[persistence.Implant]
|
||||||
query[persistence.Implant]
|
.filter(_.name == lift(implant.get.definition.Name))
|
||||||
.filter(_.name == lift(implant.get.definition.Name))
|
.filter(_.avatarId == lift(avatar.id))
|
||||||
.filter(_.avatarId == lift(avatar.id))
|
.delete
|
||||||
.delete
|
)
|
||||||
)
|
|
||||||
.onComplete {
|
.onComplete {
|
||||||
case Success(_) =>
|
case Success(_) =>
|
||||||
sessionActor ! SessionActor.SendResponse(
|
sessionActor ! SessionActor.SendResponse(
|
||||||
|
|
@ -1071,12 +1184,11 @@ class AvatarActor(
|
||||||
val p = Promise[Unit]()
|
val p = Promise[Unit]()
|
||||||
|
|
||||||
import ctx._
|
import ctx._
|
||||||
ctx
|
ctx.run(
|
||||||
.run(
|
query[persistence.Avatar]
|
||||||
query[persistence.Avatar]
|
.filter(_.id == lift(avatar.id))
|
||||||
.filter(_.id == lift(avatar.id))
|
.update(_.cosmetics -> lift(Some(Cosmetic.valuesToObjectCreateValue(cosmetics)): Option[Int]))
|
||||||
.update(_.cosmetics -> lift(Some(Cosmetic.valuesToObjectCreateValue(cosmetics)): Option[Int]))
|
)
|
||||||
)
|
|
||||||
.onComplete {
|
.onComplete {
|
||||||
case Success(_) =>
|
case Success(_) =>
|
||||||
avatarCopy(avatar.copy(cosmetics = Some(cosmetics)))
|
avatarCopy(avatar.copy(cosmetics = Some(cosmetics)))
|
||||||
|
|
@ -1089,7 +1201,6 @@ class AvatarActor(
|
||||||
case Failure(exception) =>
|
case Failure(exception) =>
|
||||||
p.failure(exception)
|
p.failure(exception)
|
||||||
}
|
}
|
||||||
|
|
||||||
p.future
|
p.future
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1343,9 +1454,7 @@ class AvatarActor(
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
player.GUID = PlanetSideGUID(gen.getAndIncrement)
|
player.GUID = PlanetSideGUID(gen.getAndIncrement)
|
||||||
|
|
||||||
player.Spawn()
|
player.Spawn()
|
||||||
|
|
||||||
sessionActor ! SessionActor.SendResponse(
|
sessionActor ! SessionActor.SendResponse(
|
||||||
ObjectCreateDetailedMessage(
|
ObjectCreateDetailedMessage(
|
||||||
ObjectClass.avatar,
|
ObjectClass.avatar,
|
||||||
|
|
@ -1353,7 +1462,6 @@ class AvatarActor(
|
||||||
converter.DetailedConstructorData(player).get
|
converter.DetailedConstructorData(player).get
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
sessionActor ! SessionActor.SendResponse(
|
sessionActor ! SessionActor.SendResponse(
|
||||||
CharacterInfoMessage(
|
CharacterInfoMessage(
|
||||||
15,
|
15,
|
||||||
|
|
@ -1364,7 +1472,6 @@ class AvatarActor(
|
||||||
secondsSinceLastLogin
|
secondsSinceLastLogin
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
/** After the user has selected a character to load from the "character select screen,"
|
/** 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.
|
* 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.
|
* Characters that were not selected may be destroyed along with their temporary GUIDs.
|
||||||
|
|
@ -1385,7 +1492,6 @@ class AvatarActor(
|
||||||
)
|
)
|
||||||
player.Invalidate()
|
player.Invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
sessionActor ! SessionActor.SendResponse(
|
sessionActor ! SessionActor.SendResponse(
|
||||||
CharacterInfoMessage(15, PlanetSideZoneID(0), 0, PlanetSideGUID(0), finished = true, 0)
|
CharacterInfoMessage(15, PlanetSideZoneID(0), 0, PlanetSideGUID(0), finished = true, 0)
|
||||||
)
|
)
|
||||||
|
|
@ -1404,16 +1510,15 @@ class AvatarActor(
|
||||||
.zipWithIndex
|
.zipWithIndex
|
||||||
.collect {
|
.collect {
|
||||||
case (slot, index) if slot.Equipment.nonEmpty =>
|
case (slot, index) if slot.Equipment.nonEmpty =>
|
||||||
clobber.append(encodeLoadoutClobFragment(slot.Equipment.get, index))
|
clobber.append(AvatarActor.encodeLoadoutClobFragment(slot.Equipment.get, index))
|
||||||
}
|
}
|
||||||
//encode inventory
|
//encode inventory
|
||||||
owner.Inventory.Items.foreach {
|
owner.Inventory.Items.foreach {
|
||||||
case InventoryItem(obj, index) =>
|
case InventoryItem(obj, index) =>
|
||||||
clobber.append(encodeLoadoutClobFragment(obj, index))
|
clobber.append(AvatarActor.encodeLoadoutClobFragment(obj, index))
|
||||||
}
|
}
|
||||||
clobber.mkString.drop(1)
|
clobber.mkString.drop(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
loadouts <- ctx.run(
|
loadouts <- ctx.run(
|
||||||
query[persistence.Loadout].filter(_.avatarId == lift(owner.CharId)).filter(_.loadoutNumber == lift(line))
|
query[persistence.Loadout].filter(_.avatarId == lift(owner.CharId)).filter(_.loadoutNumber == lift(line))
|
||||||
|
|
@ -1447,12 +1552,12 @@ class AvatarActor(
|
||||||
vehicle.Weapons
|
vehicle.Weapons
|
||||||
.collect {
|
.collect {
|
||||||
case (index, slot: EquipmentSlot) if slot.Equipment.nonEmpty =>
|
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
|
//encode inventory
|
||||||
vehicle.Inventory.Items.foreach {
|
vehicle.Inventory.Items.foreach {
|
||||||
case InventoryItem(obj, index) =>
|
case InventoryItem(obj, index) =>
|
||||||
clobber.append(encodeLoadoutClobFragment(obj, index))
|
clobber.append(AvatarActor.encodeLoadoutClobFragment(obj, index))
|
||||||
}
|
}
|
||||||
clobber.mkString.drop(1)
|
clobber.mkString.drop(1)
|
||||||
}
|
}
|
||||||
|
|
@ -1486,32 +1591,15 @@ class AvatarActor(
|
||||||
|
|
||||||
def storeNewLocker(): Unit = {
|
def storeNewLocker(): Unit = {
|
||||||
if (_avatar.nonEmpty) {
|
if (_avatar.nonEmpty) {
|
||||||
val items: String = {
|
pushLockerClobToDataBase(AvatarActor.encodeLockerClob(avatar.locker))
|
||||||
val clobber: StringBuilder = new StringBuilder()
|
.onComplete {
|
||||||
avatar.locker.Inventory.Items.foreach {
|
case Success(_) =>
|
||||||
case InventoryItem(obj, index) =>
|
saveLockerFunc = storeLocker
|
||||||
clobber.append(encodeLoadoutClobFragment(obj, index))
|
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 = {
|
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}")
|
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(
|
ctx.run(
|
||||||
query[persistence.Locker]
|
query[persistence.Locker]
|
||||||
.filter(_.avatarId == lift(avatar.id))
|
.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]]] = {
|
def initializeAllLoadouts(): Future[Seq[Option[Loadout]]] = {
|
||||||
for {
|
for {
|
||||||
infantry <- loadLoadouts().andThen {
|
infantry <- loadLoadouts().andThen {
|
||||||
|
|
@ -1571,7 +1642,7 @@ class AvatarActor(
|
||||||
loadouts.map { loadout =>
|
loadouts.map { loadout =>
|
||||||
val doll = new Player(Avatar(0, "doll", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute))
|
val doll = new Player(Avatar(0, "doll", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute))
|
||||||
doll.ExoSuit = ExoSuitType(loadout.exosuitId)
|
doll.ExoSuit = ExoSuitType(loadout.exosuitId)
|
||||||
buildContainedEquipmentFromClob(doll, loadout.items)
|
AvatarActor.buildContainedEquipmentFromClob(doll, loadout.items, log)
|
||||||
|
|
||||||
val result = (loadout.loadoutNumber, Loadout.Create(doll, loadout.name))
|
val result = (loadout.loadoutNumber, Loadout.Create(doll, loadout.name))
|
||||||
(0 until 4).foreach(index => {
|
(0 until 4).foreach(index => {
|
||||||
|
|
@ -1592,7 +1663,7 @@ class AvatarActor(
|
||||||
loadouts.map { loadout =>
|
loadouts.map { loadout =>
|
||||||
val definition = DefinitionUtil.idToDefinition(loadout.vehicle).asInstanceOf[VehicleDefinition]
|
val definition = DefinitionUtil.idToDefinition(loadout.vehicle).asInstanceOf[VehicleDefinition]
|
||||||
val toy = new Vehicle(definition)
|
val toy = new Vehicle(definition)
|
||||||
buildContainedEquipmentFromClob(toy, loadout.items)
|
AvatarActor.buildContainedEquipmentFromClob(toy, loadout.items, log)
|
||||||
|
|
||||||
val result = (loadout.loadoutNumber, Loadout.Create(toy, loadout.name))
|
val result = (loadout.loadoutNumber, Loadout.Create(toy, loadout.name))
|
||||||
toy.Weapons.values.foreach(slot => {
|
toy.Weapons.values.foreach(slot => {
|
||||||
|
|
@ -1699,61 +1770,34 @@ class AvatarActor(
|
||||||
|
|
||||||
def loadLocker(): Future[LockerContainer] = {
|
def loadLocker(): Future[LockerContainer] = {
|
||||||
val locker = Avatar.makeLocker()
|
val locker = Avatar.makeLocker()
|
||||||
|
var notLoaded: Boolean = false
|
||||||
import ctx._
|
import ctx._
|
||||||
ctx
|
val out = ctx.run(query[persistence.Locker]
|
||||||
.run(query[persistence.Locker].filter(_.avatarId == lift(avatar.id)))
|
.filter(_.avatarId == lift(avatar.id)))
|
||||||
.map { entry =>
|
.map { entry =>
|
||||||
saveLockerFunc = storeLocker
|
notLoaded = false
|
||||||
entry.foreach { contents => buildContainedEquipmentFromClob(locker, contents.items) }
|
entry.foreach { contents => AvatarActor.buildContainedEquipmentFromClob(locker, contents.items, log) }
|
||||||
}
|
}
|
||||||
.map { _ => locker }
|
.map { _ => locker }
|
||||||
}
|
out.onComplete {
|
||||||
|
case Success(_) =>
|
||||||
def buildContainedEquipmentFromClob(container: Container, clob: String): Unit = {
|
saveLockerFunc = storeLocker
|
||||||
clob.split("/").filter(_.trim.nonEmpty).foreach { value =>
|
case Failure(_) =>
|
||||||
val (objectType, objectIndex, objectId, toolAmmo) = value.split(",") match {
|
notLoaded = true
|
||||||
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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
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 = {
|
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 = {
|
def refreshPurchaseTimes(keys: Set[String]): Unit = {
|
||||||
var keysToDrop: Seq[String] = Nil
|
var keysToDrop: Seq[String] = Nil
|
||||||
keys.foreach { key =>
|
keys.foreach { key =>
|
||||||
|
|
@ -1836,8 +1833,12 @@ 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 (_, name) = resolvePurchaseTimeName(avatar.faction, obj)
|
val (_, name) = AvatarActor.resolvePurchaseTimeName(avatar.faction, obj)
|
||||||
updatePurchaseTimer(name, cooldown.toSeconds - secondsSincePurchase, unk1 = true)
|
updatePurchaseTimer(
|
||||||
|
name,
|
||||||
|
cooldown.toSeconds - secondsSincePurchase,
|
||||||
|
DefinitionUtil.fromString(name).isInstanceOf[VehicleDefinition]
|
||||||
|
)
|
||||||
|
|
||||||
case _ =>
|
case _ =>
|
||||||
keysToDrop = keysToDrop :+ key //key has timed-out
|
keysToDrop = keysToDrop :+ key //key has timed-out
|
||||||
|
|
@ -1850,10 +1851,9 @@ class AvatarActor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def updatePurchaseTimer(name: String, time: Long, unk1: Boolean): Unit = {
|
def updatePurchaseTimer(name: String, time: Long, isActuallyAVehicle: Boolean): Unit = {
|
||||||
//TODO? unk1 is: vehicles = true, everything else = false
|
|
||||||
sessionActor ! SessionActor.SendResponse(
|
sessionActor ! SessionActor.SendResponse(
|
||||||
AvatarVehicleTimerMessage(session.get.player.GUID, name, time, unk1 = true)
|
AvatarVehicleTimerMessage(session.get.player.GUID, name, time, isActuallyAVehicle)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -282,6 +282,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
var heightLast: Float = 0f
|
var heightLast: Float = 0f
|
||||||
var heightTrend: Boolean = false //up = true, down = false
|
var heightTrend: Boolean = false //up = true, down = false
|
||||||
var heightHistory: Float = 0f
|
var heightHistory: Float = 0f
|
||||||
|
var contextSafeEntity: PlanetSideGUID = PlanetSideGUID(0)
|
||||||
val collisionHistory: mutable.HashMap[ActorRef, Long] = mutable.HashMap()
|
val collisionHistory: mutable.HashMap[ActorRef, Long] = mutable.HashMap()
|
||||||
var populateAvatarAwardRibbonsFunc: (Int, Long) => Unit = setupAvatarAwardMessageDelivery
|
var populateAvatarAwardRibbonsFunc: (Int, Long) => Unit = setupAvatarAwardMessageDelivery
|
||||||
|
|
||||||
|
|
@ -586,7 +587,11 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
case Some(_: LocalLockerItem) =>
|
case Some(_: LocalLockerItem) =>
|
||||||
player.avatar.locker.Inventory.hasItem(guid) match {
|
player.avatar.locker.Inventory.hasItem(guid) match {
|
||||||
case out @ Some(_) =>
|
case out @ Some(_) =>
|
||||||
|
contextSafeEntity = guid
|
||||||
out
|
out
|
||||||
|
case None if contextSafeEntity == guid =>
|
||||||
|
//safeguard
|
||||||
|
None
|
||||||
case None =>
|
case None =>
|
||||||
//delete stale entity reference from client
|
//delete stale entity reference from client
|
||||||
log.warn(
|
log.warn(
|
||||||
|
|
@ -614,6 +619,10 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
sendResponse(ObjectDeleteMessage(guid, 0))
|
sendResponse(ObjectDeleteMessage(guid, 0))
|
||||||
None
|
None
|
||||||
|
|
||||||
|
case None if contextSafeEntity == guid =>
|
||||||
|
//safeguard
|
||||||
|
None
|
||||||
|
|
||||||
case _ =>
|
case _ =>
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
@ -4951,10 +4960,10 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
ValidObject(item_guid, decorator = "MoveItem")
|
ValidObject(item_guid, decorator = "MoveItem")
|
||||||
) match {
|
) match {
|
||||||
case (
|
case (
|
||||||
Some(source: PlanetSideServerObject with Container),
|
Some(source: PlanetSideServerObject with Container),
|
||||||
Some(destination: PlanetSideServerObject with Container),
|
Some(destination: PlanetSideServerObject with Container),
|
||||||
Some(item: Equipment)
|
Some(item: Equipment)
|
||||||
) =>
|
) =>
|
||||||
ContainableMoveItem(player.Name, source, destination, item, destination.SlotMapResolution(dest))
|
ContainableMoveItem(player.Name, source, destination, item, destination.SlotMapResolution(dest))
|
||||||
case (None, _, _) =>
|
case (None, _, _) =>
|
||||||
log.error(
|
log.error(
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ package net.psforever.login
|
||||||
import akka.actor.ActorRef
|
import akka.actor.ActorRef
|
||||||
import akka.pattern.{AskTimeoutException, ask}
|
import akka.pattern.{AskTimeoutException, ask}
|
||||||
import akka.util.Timeout
|
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.guid._
|
||||||
import net.psforever.objects.inventory.{Container, InventoryItem}
|
import net.psforever.objects.inventory.{Container, InventoryItem}
|
||||||
import net.psforever.objects.locker.LockerContainer
|
import net.psforever.objects.locker.LockerContainer
|
||||||
|
|
@ -653,39 +653,77 @@ object WorldSession {
|
||||||
item: Equipment,
|
item: Equipment,
|
||||||
dest: Int
|
dest: Int
|
||||||
): Unit = {
|
): Unit = {
|
||||||
TaskWorkflow.execute(TaskBundle(
|
val (performSwap, swapItemGUID): (Boolean, Option[PlanetSideGUID]) = {
|
||||||
new StraightforwardTask() {
|
val destInv = destination.Inventory
|
||||||
val localGUID = item.GUID //original GUID
|
if (destInv.Offset <= dest && destInv.Offset + destInv.TotalCapacity >= dest) {
|
||||||
val localChannel = toChannel
|
val tile = item.Definition.Tile
|
||||||
val localSource = source
|
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 localDestination = destination
|
||||||
val localItem = item
|
val localItem = item
|
||||||
val localSlot = dest
|
val localDestSlot = dest
|
||||||
/*
|
val localSrcSlot = toSlot
|
||||||
source is a locker container that has its own internal unique number system
|
val localMoveOnComplete: Try[Any] => Unit = {
|
||||||
the item is currently registered to this system
|
case Success(Containable.ItemPutInSlot(_, _, _, Some(swapItem))) =>
|
||||||
the item will be moved into the system in which the destination operates
|
//swapItem is not registered right now, we can not drop the item without re-registering it
|
||||||
to facilitate the transfer, the item needs to be partially unregistered from the source's system
|
TaskWorkflow.execute(PutNewEquipmentInInventorySlot(localSource)(swapItem, localSrcSlot))
|
||||||
to facilitate the transfer, the item needs to be preemptively registered to the destination's system
|
case _ => ;
|
||||||
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 _ => ;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override def description(): String = s"registering $localItem in ${localDestination.Zone.id} before removing from $localSource"
|
override def description(): String = s"registering $localItem in ${localDestination.Zone.id} before removing from $localSource"
|
||||||
|
|
||||||
def action(): Future[Any] = {
|
def action(): Future[Any] = {
|
||||||
val zone = localSource.Zone
|
localGUID match {
|
||||||
//see LockerContainerControl.RemoveItemFromSlotCallback
|
case Some(guid) =>
|
||||||
zone.AvatarEvents ! AvatarServiceMessage(localChannel, AvatarAction.ObjectDelete(Service.defaultPlayerGUID, localGUID))
|
//see LockerContainerControl.RemoveItemFromSlotCallback
|
||||||
ask(localSource.Actor, Containable.MoveItem(localDestination, localItem, localSlot))
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,10 @@ class LocalLockerItem extends PlanetSideServerObject {
|
||||||
|
|
||||||
object LocalLockerItem {
|
object LocalLockerItem {
|
||||||
import net.psforever.objects.definition.ObjectDefinition
|
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.
|
* Instantiate and configure a `LocalProjectile` object.
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,10 @@ class LocalProjectile extends PlanetSideServerObject {
|
||||||
|
|
||||||
object LocalProjectile {
|
object LocalProjectile {
|
||||||
import net.psforever.objects.definition.ObjectDefinition
|
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.
|
* Instantiate and configure a `LocalProjectile` object.
|
||||||
|
|
|
||||||
|
|
@ -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`
|
* 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 locker the governed player-facing locker component
|
||||||
* @param toChannel the channel to which to publish events, typically the owning player's name
|
* @param toChannel the channel to which to publish events, typically the owning player's name
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -339,13 +339,6 @@ object Zones {
|
||||||
turretWeaponGuid
|
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 =>
|
lattice.asObject.get(mapid).foreach { obj =>
|
||||||
obj.asArray.get.foreach { entry =>
|
obj.asArray.get.foreach { entry =>
|
||||||
val arr = entry.asArray.get
|
val arr = entry.asArray.get
|
||||||
|
|
@ -687,6 +680,16 @@ object Zones {
|
||||||
override def SetupNumberPools() : Unit = addPoolsFunc()
|
override def SetupNumberPools() : Unit = addPoolsFunc()
|
||||||
|
|
||||||
override def init(implicit context: ActorContext): Unit = {
|
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)
|
super.init(context)
|
||||||
|
|
||||||
if (!info.id.startsWith("tz")) {
|
if (!info.id.startsWith("tz")) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue