mirror of
https://github.com/psforever/PSF-LoginServer.git
synced 2026-01-19 18:44:45 +00:00
Relog Fix (#1012)
* separating stages of client interaction with the session's avatar; connection closing is caught to avoid unnecessary log messages; changed how ActionResultMessage views its parsing format * fixed issue with relogging while persisting as dead (thanks Scrawny) * almost forgot to turn this back on after I finished testing
This commit is contained in:
parent
d68ccdfd8d
commit
3bd50dc89c
|
|
@ -297,13 +297,21 @@ class MiddlewareActor(
|
||||||
send(ServerStart(nonce, serverNonce), None, None)
|
send(ServerStart(nonce, serverNonce), None, None)
|
||||||
cryptoSetup()
|
cryptoSetup()
|
||||||
|
|
||||||
/** Unknown30 is used to reuse an existing crypto session when switching from login to world
|
|
||||||
* When not handling it, it appears that the client will fall back to using ClientStart
|
|
||||||
* TODO implement this
|
|
||||||
*/
|
|
||||||
case (Unknown30(nonce), _) =>
|
case (Unknown30(nonce), _) =>
|
||||||
|
/*
|
||||||
|
Unknown30 is used to reuse an existing crypto session when switching from login to world
|
||||||
|
When not handling it, it appears that the client will fall back to using ClientStart
|
||||||
|
Do we need to implement this?
|
||||||
|
*/
|
||||||
connectionClose()
|
connectionClose()
|
||||||
|
|
||||||
|
case (ConnectionClose(), _) =>
|
||||||
|
/*
|
||||||
|
indicates the user has willingly quit the game world
|
||||||
|
we do not need to implement this
|
||||||
|
*/
|
||||||
|
Behaviors.same
|
||||||
|
|
||||||
// TODO ResetSequence
|
// TODO ResetSequence
|
||||||
case _ =>
|
case _ =>
|
||||||
log.warn(s"Unexpected packet type $packet in start (before crypto)")
|
log.warn(s"Unexpected packet type $packet in start (before crypto)")
|
||||||
|
|
|
||||||
|
|
@ -7,15 +7,26 @@ 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.vital.{DamagingActivity, HealingActivity}
|
import net.psforever.objects.vital.{DamagingActivity, HealingActivity}
|
||||||
import org.joda.time.{LocalDateTime, Seconds}
|
import org.joda.time.{LocalDateTime, Seconds}
|
||||||
//import org.log4s.Logger
|
|
||||||
import scala.collection.mutable
|
import scala.collection.mutable
|
||||||
import scala.concurrent.{ExecutionContextExecutor, Future, Promise}
|
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.objects.avatar.{Friend => AvatarFriend, Ignored => AvatarIgnored, Shortcut => AvatarShortcut, _}
|
import net.psforever.objects.avatar.{
|
||||||
import net.psforever.objects.definition.converter.CharacterSelectConverter
|
Avatar,
|
||||||
|
BattleRank,
|
||||||
|
Certification,
|
||||||
|
Cooldowns,
|
||||||
|
Cosmetic,
|
||||||
|
Friend => AvatarFriend,
|
||||||
|
Ignored => AvatarIgnored,
|
||||||
|
Implant,
|
||||||
|
MemberLists,
|
||||||
|
PlayerControl,
|
||||||
|
Shortcut => AvatarShortcut
|
||||||
|
}
|
||||||
import net.psforever.objects.definition._
|
import net.psforever.objects.definition._
|
||||||
|
import net.psforever.objects.definition.converter.CharacterSelectConverter
|
||||||
import net.psforever.objects.inventory.Container
|
import net.psforever.objects.inventory.Container
|
||||||
import net.psforever.objects.equipment.{Equipment, EquipmentSlot}
|
import net.psforever.objects.equipment.{Equipment, EquipmentSlot}
|
||||||
import net.psforever.objects.inventory.InventoryItem
|
import net.psforever.objects.inventory.InventoryItem
|
||||||
|
|
@ -26,19 +37,30 @@ import net.psforever.objects.locker.LockerContainer
|
||||||
import net.psforever.objects.vital.HealFromImplant
|
import net.psforever.objects.vital.HealFromImplant
|
||||||
import net.psforever.packet.game.objectcreate.{ObjectClass, RibbonBars}
|
import net.psforever.packet.game.objectcreate.{ObjectClass, RibbonBars}
|
||||||
import net.psforever.packet.game.{Friend => GameFriend, _}
|
import net.psforever.packet.game.{Friend => GameFriend, _}
|
||||||
import net.psforever.types.{MemberAction, PlanetSideEmpire, _}
|
import net.psforever.types.{
|
||||||
|
CharacterVoice,
|
||||||
|
CharacterSex,
|
||||||
|
ExoSuitType,
|
||||||
|
ImplantType,
|
||||||
|
LoadoutType,
|
||||||
|
MemberAction,
|
||||||
|
MeritCommendation,
|
||||||
|
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, Database, DefinitionUtil}
|
import net.psforever.util.{Config, Database, DefinitionUtil}
|
||||||
import net.psforever.services.{Service, ServiceManager}
|
import net.psforever.services.Service
|
||||||
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
|
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||||
|
|
||||||
object AvatarActor {
|
object AvatarActor {
|
||||||
def apply(sessionActor: ActorRef[SessionActor.Command]): Behavior[Command] =
|
def apply(sessionActor: ActorRef[SessionActor.Command]): Behavior[Command] =
|
||||||
Behaviors
|
Behaviors
|
||||||
.supervise[Command] {
|
.supervise[Command] {
|
||||||
Behaviors.withStash(100) { buffer =>
|
Behaviors.withStash(capacity = 100) { buffer =>
|
||||||
Behaviors.setup(context => new AvatarActor(context, buffer, sessionActor).start())
|
Behaviors.setup(context => new AvatarActor(context, buffer, sessionActor).login())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onFailure[Exception](SupervisorStrategy.restart)
|
.onFailure[Exception](SupervisorStrategy.restart)
|
||||||
|
|
@ -75,7 +97,7 @@ object AvatarActor {
|
||||||
/** Log in the currently selected avatar. Must have first sent SelectAvatar. */
|
/** Log in the currently selected avatar. Must have first sent SelectAvatar. */
|
||||||
final case class LoginAvatar(replyTo: ActorRef[AvatarLoginResponse]) extends Command
|
final case class LoginAvatar(replyTo: ActorRef[AvatarLoginResponse]) extends Command
|
||||||
|
|
||||||
/** Send implants to client - TODO this can be done better using a event system on SessionActor */
|
/** Send implants to client */
|
||||||
final case class CreateImplants() extends Command
|
final case class CreateImplants() extends Command
|
||||||
|
|
||||||
/** Replace avatar instance with the provided one */
|
/** Replace avatar instance with the provided one */
|
||||||
|
|
@ -181,8 +203,6 @@ object AvatarActor {
|
||||||
|
|
||||||
final case class SetRibbon(ribbon: MeritCommendation.Value, bar: RibbonBarSlot.Value) extends Command
|
final case class SetRibbon(ribbon: MeritCommendation.Value, bar: RibbonBarSlot.Value) extends Command
|
||||||
|
|
||||||
private case class ServiceManagerLookupResult(result: ServiceManager.LookupResult) extends Command
|
|
||||||
|
|
||||||
final case class SetStamina(stamina: Int) extends Command
|
final case class SetStamina(stamina: Int) extends Command
|
||||||
|
|
||||||
private case class SetImplantInitialized(implantType: ImplantType) extends Command
|
private case class SetImplantInitialized(implantType: ImplantType) extends Command
|
||||||
|
|
@ -385,7 +405,7 @@ object AvatarActor {
|
||||||
}
|
}
|
||||||
|
|
||||||
def encodeLockerClob(container: Container): String = {
|
def encodeLockerClob(container: Container): String = {
|
||||||
val clobber: mutable.StringBuilder = new StringBuilder()
|
val clobber: mutable.StringBuilder = new mutable.StringBuilder()
|
||||||
container.Inventory.Items.foreach {
|
container.Inventory.Items.foreach {
|
||||||
case InventoryItem(obj, index) =>
|
case InventoryItem(obj, index) =>
|
||||||
clobber.append(encodeLoadoutClobFragment(obj, index))
|
clobber.append(encodeLoadoutClobFragment(obj, index))
|
||||||
|
|
@ -564,7 +584,7 @@ object AvatarActor {
|
||||||
}
|
}
|
||||||
out.future
|
out.future
|
||||||
}
|
}
|
||||||
//TODO should return number of rows inserted?
|
|
||||||
/**
|
/**
|
||||||
* Query the database on information retained in regards to a certain character
|
* Query the database on information retained in regards to a certain character
|
||||||
* when that character had last logged out of the game.
|
* when that character had last logged out of the game.
|
||||||
|
|
@ -664,7 +684,7 @@ object AvatarActor {
|
||||||
val queryResult = ctx.run(query[persistence.Savedplayer].filter { _.avatarId == lift(avatarId) })
|
val queryResult = ctx.run(query[persistence.Savedplayer].filter { _.avatarId == lift(avatarId) })
|
||||||
queryResult.onComplete {
|
queryResult.onComplete {
|
||||||
case Success(results) if results.nonEmpty =>
|
case Success(results) if results.nonEmpty =>
|
||||||
val res=ctx.run(query[persistence.Savedplayer]
|
ctx.run(query[persistence.Savedplayer]
|
||||||
.filter { _.avatarId == lift(avatarId) }
|
.filter { _.avatarId == lift(avatarId) }
|
||||||
.update(
|
.update(
|
||||||
_.px -> lift((position.x * 1000).toInt),
|
_.px -> lift((position.x * 1000).toInt),
|
||||||
|
|
@ -771,7 +791,7 @@ class AvatarActor(
|
||||||
sessionActor ! SessionActor.SetAvatar(avatar)
|
sessionActor ! SessionActor.SetAvatar(avatar)
|
||||||
}
|
}
|
||||||
|
|
||||||
def start(): Behavior[Command] = {
|
def login(): Behavior[Command] = {
|
||||||
Behaviors
|
Behaviors
|
||||||
.receiveMessage[Command] {
|
.receiveMessage[Command] {
|
||||||
case SetAccount(newAccount) =>
|
case SetAccount(newAccount) =>
|
||||||
|
|
@ -785,11 +805,11 @@ class AvatarActor(
|
||||||
}
|
}
|
||||||
case Failure(e) => log.error(e)("db failure")
|
case Failure(e) => log.error(e)("db failure")
|
||||||
}
|
}
|
||||||
postStartBehaviour()
|
postLoginBehaviour()
|
||||||
|
|
||||||
case SetSession(newSession) =>
|
case SetSession(newSession) =>
|
||||||
session = Some(newSession)
|
session = Some(newSession)
|
||||||
postStartBehaviour()
|
postLoginBehaviour()
|
||||||
|
|
||||||
case other =>
|
case other =>
|
||||||
buffer.stash(other)
|
buffer.stash(other)
|
||||||
|
|
@ -797,34 +817,22 @@ class AvatarActor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def postStartBehaviour(): Behavior[Command] = {
|
def postLoginBehaviour(): Behavior[Command] = {
|
||||||
account match {
|
(account, session) match {
|
||||||
case Some(_account) =>
|
case (Some(_account), Some(_)) => characterSelect(_account)
|
||||||
buffer.unstashAll(active(_account))
|
case _ => Behaviors.same
|
||||||
case _ =>
|
|
||||||
Behaviors.same
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def active(account: Account): Behavior[Command] = {
|
def characterSelect(account: Account): Behavior[Command] = {
|
||||||
Behaviors
|
Behaviors
|
||||||
.receiveMessagePartial[Command] {
|
.receiveMessage[Command] {
|
||||||
case SetSession(newSession) =>
|
case SetSession(newSession) =>
|
||||||
session = Some(newSession)
|
session = Some(newSession)
|
||||||
Behaviors.same
|
Behaviors.same
|
||||||
|
|
||||||
case SetLookingForSquad(lfs) =>
|
|
||||||
avatarCopy(avatar.copy(lookingForSquad = lfs))
|
|
||||||
sessionActor ! SessionActor.SendResponse(PlanetsideAttributeMessage(session.get.player.GUID, 53, 0))
|
|
||||||
session.get.zone.AvatarEvents ! AvatarServiceMessage(
|
|
||||||
avatar.faction.toString,
|
|
||||||
AvatarAction.PlanetsideAttribute(session.get.player.GUID, 53, if (lfs) 1 else 0)
|
|
||||||
)
|
|
||||||
Behaviors.same
|
|
||||||
|
|
||||||
case CreateAvatar(name, head, voice, gender, empire) =>
|
case CreateAvatar(name, head, voice, gender, empire) =>
|
||||||
import ctx._
|
import ctx._
|
||||||
|
|
||||||
ctx.run(query[persistence.Avatar].filter(_.name ilike lift(name)).filter(!_.deleted)).onComplete {
|
ctx.run(query[persistence.Avatar].filter(_.name ilike lift(name)).filter(!_.deleted)).onComplete {
|
||||||
case Success(characters) =>
|
case Success(characters) =>
|
||||||
characters.headOption match {
|
characters.headOption match {
|
||||||
|
|
@ -844,7 +852,6 @@ class AvatarActor(
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
} yield ()
|
} yield ()
|
||||||
|
|
||||||
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}")
|
||||||
|
|
@ -858,14 +865,14 @@ class AvatarActor(
|
||||||
}
|
}
|
||||||
case Failure(e) =>
|
case Failure(e) =>
|
||||||
log.error(e)("db failure")
|
log.error(e)("db failure")
|
||||||
sessionActor ! SessionActor.SendResponse(ActionResultMessage.Fail(4))
|
sessionActor ! SessionActor.SendResponse(ActionResultMessage.Fail(3))
|
||||||
sendAvatars(account)
|
sendAvatars(account)
|
||||||
}
|
}
|
||||||
Behaviors.same
|
Behaviors.same
|
||||||
|
|
||||||
case DeleteAvatar(id) =>
|
case DeleteAvatar(id) =>
|
||||||
import ctx._
|
import ctx._
|
||||||
val result = for {
|
val performDeletion = for {
|
||||||
_ <- ctx.run(query[persistence.Implant].filter(_.avatarId == lift(id)).delete)
|
_ <- ctx.run(query[persistence.Implant].filter(_.avatarId == lift(id)).delete)
|
||||||
_ <- ctx.run(query[persistence.Loadout].filter(_.avatarId == lift(id)).delete)
|
_ <- ctx.run(query[persistence.Loadout].filter(_.avatarId == lift(id)).delete)
|
||||||
_ <- ctx.run(query[persistence.Locker].filter(_.avatarId == lift(id)).delete)
|
_ <- ctx.run(query[persistence.Locker].filter(_.avatarId == lift(id)).delete)
|
||||||
|
|
@ -876,24 +883,36 @@ class AvatarActor(
|
||||||
_ <- ctx.run(query[persistence.Savedplayer].filter(_.avatarId == lift(id)).delete)
|
_ <- ctx.run(query[persistence.Savedplayer].filter(_.avatarId == lift(id)).delete)
|
||||||
r <- ctx.run(query[persistence.Avatar].filter(_.id == lift(id)))
|
r <- ctx.run(query[persistence.Avatar].filter(_.id == lift(id)))
|
||||||
} yield r
|
} yield r
|
||||||
|
performDeletion.onComplete {
|
||||||
result.onComplete {
|
|
||||||
case Success(deleted) =>
|
case Success(deleted) =>
|
||||||
deleted.headOption match {
|
deleted.headOption match {
|
||||||
case Some(a) if !a.deleted =>
|
case Some(a) if !a.deleted =>
|
||||||
ctx.run(query[persistence.Avatar]
|
val flagDeletion = for {
|
||||||
|
_ <- ctx.run(query[persistence.Avatar]
|
||||||
.filter(_.id == lift(id))
|
.filter(_.id == lift(id))
|
||||||
.update(
|
.update(
|
||||||
_.deleted -> lift(true),
|
_.deleted -> lift(true),
|
||||||
_.lastModified -> lift(LocalDateTime.now())
|
_.lastModified -> lift(LocalDateTime.now())
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
} yield ()
|
||||||
|
flagDeletion.onComplete {
|
||||||
|
case Success(_) =>
|
||||||
log.debug(s"AvatarActor: avatar $id deleted")
|
log.debug(s"AvatarActor: avatar $id deleted")
|
||||||
sessionActor ! SessionActor.SendResponse(ActionResultMessage.Pass)
|
sessionActor ! SessionActor.SendResponse(ActionResultMessage.Pass)
|
||||||
case _ => ;
|
|
||||||
}
|
|
||||||
sendAvatars(account)
|
sendAvatars(account)
|
||||||
case Failure(e) => log.error(e)("db failure")
|
case Failure(e) =>
|
||||||
|
log.error(e)("db failure")
|
||||||
|
sessionActor ! SessionActor.SendResponse(ActionResultMessage.Fail(error = 4))
|
||||||
|
sendAvatars(account)
|
||||||
|
}
|
||||||
|
case _ =>
|
||||||
|
sessionActor ! SessionActor.SendResponse(ActionResultMessage.Fail(error = 4))
|
||||||
|
sendAvatars(account)
|
||||||
|
}
|
||||||
|
case Failure(e) =>
|
||||||
|
log.error(e)("db failure")
|
||||||
|
sessionActor ! SessionActor.SendResponse(ActionResultMessage.Fail(error = 4))
|
||||||
}
|
}
|
||||||
Behaviors.same
|
Behaviors.same
|
||||||
|
|
||||||
|
|
@ -905,9 +924,13 @@ class AvatarActor(
|
||||||
case Some(character) =>
|
case Some(character) =>
|
||||||
avatar = character.toAvatar
|
avatar = character.toAvatar
|
||||||
replyTo ! AvatarResponse(avatar)
|
replyTo ! AvatarResponse(avatar)
|
||||||
case None => log.error(s"selected character $charId not found")
|
case None =>
|
||||||
|
log.error(s"selected character $charId not found")
|
||||||
|
sessionActor ! SessionActor.SendResponse(ActionResultMessage.Fail(error = 5))
|
||||||
}
|
}
|
||||||
case Failure(e) => log.error(e)("db failure")
|
case Failure(e) =>
|
||||||
|
log.error(e)("db failure")
|
||||||
|
sessionActor ! SessionActor.SendResponse(ActionResultMessage.Fail(error = 5))
|
||||||
}
|
}
|
||||||
Behaviors.same
|
Behaviors.same
|
||||||
|
|
||||||
|
|
@ -946,14 +969,43 @@ class AvatarActor(
|
||||||
)
|
)
|
||||||
} yield true
|
} yield true
|
||||||
inits.onComplete {
|
inits.onComplete {
|
||||||
case Success(_) => performAvatarLogin(avatarId, account.id, replyTo)
|
case Success(_) =>
|
||||||
case Failure(e) => log.error(e)("db failure")
|
performAvatarLogin(avatarId, account.id, replyTo)
|
||||||
|
case Failure(e) =>
|
||||||
|
log.error(e)("db failure")
|
||||||
|
sessionActor ! SessionActor.SendResponse(ActionResultMessage.Fail(error = 6))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
performAvatarLogin(avatarId, account.id, replyTo)
|
performAvatarLogin(avatarId, account.id, replyTo)
|
||||||
}
|
}
|
||||||
case Failure(e) => log.error(e)("db failure")
|
case Failure(e) =>
|
||||||
|
log.error(e)("db failure")
|
||||||
|
sessionActor ! SessionActor.SendResponse(ActionResultMessage.Fail(error = 6))
|
||||||
}
|
}
|
||||||
|
postCharacterSelectBehaviour()
|
||||||
|
|
||||||
|
case ReplaceAvatar(newAvatar) =>
|
||||||
|
replaceAvatar(newAvatar)
|
||||||
|
postCharacterSelectBehaviour()
|
||||||
|
|
||||||
|
case other =>
|
||||||
|
buffer.stash(other)
|
||||||
|
Behaviors.same
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def postCharacterSelectBehaviour(): Behavior[Command] = {
|
||||||
|
_avatar match {
|
||||||
|
case Some(_) => buffer.unstashAll(gameplay)
|
||||||
|
case _ => Behaviors.same
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def gameplay: Behavior[Command] = {
|
||||||
|
Behaviors
|
||||||
|
.receiveMessagePartial[Command] {
|
||||||
|
case SetSession(newSession) =>
|
||||||
|
session = Some(newSession)
|
||||||
Behaviors.same
|
Behaviors.same
|
||||||
|
|
||||||
case ReplaceAvatar(newAvatar) =>
|
case ReplaceAvatar(newAvatar) =>
|
||||||
|
|
@ -961,6 +1013,15 @@ class AvatarActor(
|
||||||
startIfStoppedStaminaRegen(initialDelay = 0.5f seconds)
|
startIfStoppedStaminaRegen(initialDelay = 0.5f seconds)
|
||||||
Behaviors.same
|
Behaviors.same
|
||||||
|
|
||||||
|
case SetLookingForSquad(lfs) =>
|
||||||
|
avatarCopy(avatar.copy(lookingForSquad = lfs))
|
||||||
|
sessionActor ! SessionActor.SendResponse(PlanetsideAttributeMessage(session.get.player.GUID, 53, 0))
|
||||||
|
session.get.zone.AvatarEvents ! AvatarServiceMessage(
|
||||||
|
avatar.faction.toString,
|
||||||
|
AvatarAction.PlanetsideAttribute(session.get.player.GUID, 53, if (lfs) 1 else 0)
|
||||||
|
)
|
||||||
|
Behaviors.same
|
||||||
|
|
||||||
case AddFirstTimeEvent(event) =>
|
case AddFirstTimeEvent(event) =>
|
||||||
val decor = avatar.decoration
|
val decor = avatar.decoration
|
||||||
avatarCopy(avatar.copy(decoration = decor.copy(firstTimeEvents = decor.firstTimeEvents ++ Set(event))))
|
avatarCopy(avatar.copy(decoration = decor.copy(firstTimeEvents = decor.firstTimeEvents ++ Set(event))))
|
||||||
|
|
@ -1521,7 +1582,6 @@ class AvatarActor(
|
||||||
|
|
||||||
case SetBep(bep) =>
|
case SetBep(bep) =>
|
||||||
import ctx._
|
import ctx._
|
||||||
|
|
||||||
val result = for {
|
val result = for {
|
||||||
_ <-
|
_ <-
|
||||||
if (BattleRank.withExperience(bep).value < BattleRank.BR24.value) setCosmetics(Set())
|
if (BattleRank.withExperience(bep).value < BattleRank.BR24.value) setCosmetics(Set())
|
||||||
|
|
@ -2145,7 +2205,7 @@ class AvatarActor(
|
||||||
def storeVehicleLoadout(owner: Player, label: String, line: Int, vehicle: Vehicle): Future[Loadout] = {
|
def storeVehicleLoadout(owner: Player, label: String, line: Int, vehicle: Vehicle): Future[Loadout] = {
|
||||||
import ctx._
|
import ctx._
|
||||||
val items: String = {
|
val items: String = {
|
||||||
val clobber: mutable.StringBuilder = new StringBuilder()
|
val clobber: mutable.StringBuilder = new mutable.StringBuilder()
|
||||||
//encode holsters
|
//encode holsters
|
||||||
vehicle.Weapons
|
vehicle.Weapons
|
||||||
.collect {
|
.collect {
|
||||||
|
|
|
||||||
|
|
@ -1453,7 +1453,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
val oldZone = continent
|
val oldZone = continent
|
||||||
session = session.copy(zone = zone)
|
session = session.copy(zone = zone)
|
||||||
//the only zone-level event system subscription necessary before BeginZoningMessage (for persistence purposes)
|
//the only zone-level event system subscription necessary before BeginZoningMessage (for persistence purposes)
|
||||||
continent.AvatarEvents ! Service.Join(player.Name)
|
zone.AvatarEvents ! Service.Join(player.Name)
|
||||||
persist()
|
persist()
|
||||||
oldZone.AvatarEvents ! Service.Leave()
|
oldZone.AvatarEvents ! Service.Leave()
|
||||||
oldZone.LocalEvents ! Service.Leave()
|
oldZone.LocalEvents ! Service.Leave()
|
||||||
|
|
@ -1470,7 +1470,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
} else {
|
} else {
|
||||||
zoneReload = true
|
zoneReload = true
|
||||||
cluster ! ICS.GetNearbySpawnPoint(
|
cluster ! ICS.GetNearbySpawnPoint(
|
||||||
continent.Number,
|
zone.Number,
|
||||||
player,
|
player,
|
||||||
Seq(SpawnGroup.Facility, SpawnGroup.Tower),
|
Seq(SpawnGroup.Facility, SpawnGroup.Tower),
|
||||||
context.self
|
context.self
|
||||||
|
|
@ -1744,7 +1744,6 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
deadState = DeadState.Dead
|
deadState = DeadState.Dead
|
||||||
session = session.copy(player = p, avatar = a)
|
session = session.copy(player = p, avatar = a)
|
||||||
persist()
|
persist()
|
||||||
player.Zone = inZone
|
|
||||||
HandleReleaseAvatar(p, inZone)
|
HandleReleaseAvatar(p, inZone)
|
||||||
avatarActor ! AvatarActor.ReplaceAvatar(a)
|
avatarActor ! AvatarActor.ReplaceAvatar(a)
|
||||||
avatarLoginResponse(a)
|
avatarLoginResponse(a)
|
||||||
|
|
@ -2098,13 +2097,13 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
loadConfZone = true
|
loadConfZone = true
|
||||||
val oldZone = session.zone
|
val oldZone = session.zone
|
||||||
session = session.copy(zone = foundZone)
|
session = session.copy(zone = foundZone)
|
||||||
//the only zone-level event system subscription necessary before BeginZoningMessage (for persistence purposes)
|
|
||||||
continent.AvatarEvents ! Service.Join(player.Name)
|
|
||||||
persist()
|
persist()
|
||||||
oldZone.AvatarEvents ! Service.Leave()
|
oldZone.AvatarEvents ! Service.Leave()
|
||||||
oldZone.LocalEvents ! Service.Leave()
|
oldZone.LocalEvents ! Service.Leave()
|
||||||
oldZone.VehicleEvents ! Service.Leave()
|
oldZone.VehicleEvents ! Service.Leave()
|
||||||
continent.Population ! Zone.Population.Join(avatar)
|
//the only zone-level event system subscription necessary before BeginZoningMessage (for persistence purposes)
|
||||||
|
foundZone.AvatarEvents ! Service.Join(player.Name)
|
||||||
|
foundZone.Population ! Zone.Population.Join(avatar)
|
||||||
player.avatar = avatar
|
player.avatar = avatar
|
||||||
interstellarFerry match {
|
interstellarFerry match {
|
||||||
case Some(vehicle) if vehicle.PassengerInSeat(player).contains(0) =>
|
case Some(vehicle) if vehicle.PassengerInSeat(player).contains(0) =>
|
||||||
|
|
@ -9513,7 +9512,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
*/
|
*/
|
||||||
def TurnCounterDuringInterim(guid: PlanetSideGUID): Unit = {
|
def TurnCounterDuringInterim(guid: PlanetSideGUID): Unit = {
|
||||||
upstreamMessageCount = 0
|
upstreamMessageCount = 0
|
||||||
if (player != null && player.GUID == guid && player.Zone == continent) {
|
if (player != null && player.HasGUID && player.GUID == guid && player.Zone == continent) {
|
||||||
turnCounterFunc = NormalTurnCounter
|
turnCounterFunc = NormalTurnCounter
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,33 +7,33 @@ import scodec.codecs._
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Is sent by the server when the client has performed an action from a menu item
|
* Is sent by the server when the client has performed an action from a menu item
|
||||||
* (i.e create character, delete character, etc...)
|
* (i.e create character, delete character, etc...).
|
||||||
|
* Error messages usually are accompanied by an angry beep.<br>
|
||||||
|
* Error 0 is a common code but it doesn't do anything specific on its own.<br>
|
||||||
|
* Error 1 generates the message box: a character with that name already exists.<br>
|
||||||
|
* Error 2 generates the message box: something to do with the word filter.<br>
|
||||||
|
* Other errors during the character login screen generate a generic error message box and list the code.
|
||||||
*/
|
*/
|
||||||
final case class ActionResultMessage(successful: Boolean, errorCode: Option[Long]) extends PlanetSideGamePacket {
|
final case class ActionResultMessage(errorCode: Option[Long]) extends PlanetSideGamePacket {
|
||||||
type Packet = ActionResultMessage
|
type Packet = ActionResultMessage
|
||||||
def opcode = GamePacketOpcode.ActionResultMessage
|
def opcode = GamePacketOpcode.ActionResultMessage
|
||||||
def encode = ActionResultMessage.encode(this)
|
def encode = ActionResultMessage.encode(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
object ActionResultMessage extends Marshallable[ActionResultMessage] {
|
object ActionResultMessage extends Marshallable[ActionResultMessage] {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A message where the result is always a pass.
|
* A message where the result is always a pass.
|
||||||
* @return an `ActionResultMessage` object
|
* @return an `ActionResultMessage` object
|
||||||
*/
|
*/
|
||||||
def Pass: ActionResultMessage = ActionResultMessage(true, None)
|
def Pass: ActionResultMessage = ActionResultMessage(None)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A message where the result is always a failure.
|
* A message where the result is always a failure.
|
||||||
* @param error the error code
|
* @param error the error code
|
||||||
* @return an `ActionResultMessage` object
|
* @return an `ActionResultMessage` object
|
||||||
*/
|
*/
|
||||||
def Fail(error: Long): ActionResultMessage = ActionResultMessage(false, Some(error))
|
def Fail(error: Long): ActionResultMessage = ActionResultMessage(Some(error))
|
||||||
|
|
||||||
implicit val codec: Codec[ActionResultMessage] = (
|
implicit val codec: Codec[ActionResultMessage] =
|
||||||
("successful" | bool) >>:~ { res =>
|
("error_code" | optional(bool.xmap[Boolean](state => !state, state => !state), uint32L)).as[ActionResultMessage]
|
||||||
// if not successful, look for an error code
|
|
||||||
conditional(!res, "error_code" | uint32L).hlist
|
|
||||||
}
|
|
||||||
).as[ActionResultMessage]
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,9 +12,8 @@ class ActionResultMessageTest extends Specification {
|
||||||
|
|
||||||
"decode (pass)" in {
|
"decode (pass)" in {
|
||||||
PacketCoding.decodePacket(string_pass).require match {
|
PacketCoding.decodePacket(string_pass).require match {
|
||||||
case ActionResultMessage(okay, code) =>
|
case ActionResultMessage(code) =>
|
||||||
okay mustEqual true
|
code.isEmpty mustEqual true
|
||||||
code mustEqual None
|
|
||||||
case _ =>
|
case _ =>
|
||||||
ko
|
ko
|
||||||
}
|
}
|
||||||
|
|
@ -22,16 +21,15 @@ class ActionResultMessageTest extends Specification {
|
||||||
|
|
||||||
"decode (fail)" in {
|
"decode (fail)" in {
|
||||||
PacketCoding.decodePacket(string_fail).require match {
|
PacketCoding.decodePacket(string_fail).require match {
|
||||||
case ActionResultMessage(okay, code) =>
|
case ActionResultMessage(code) =>
|
||||||
okay mustEqual false
|
code.contains(1) mustEqual true
|
||||||
code mustEqual Some(1)
|
|
||||||
case _ =>
|
case _ =>
|
||||||
ko
|
ko
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
"encode (pass, full)" in {
|
"encode (pass, full)" in {
|
||||||
val msg = ActionResultMessage(true, None)
|
val msg = ActionResultMessage(None)
|
||||||
val pkt = PacketCoding.encodePacket(msg).require.toByteVector
|
val pkt = PacketCoding.encodePacket(msg).require.toByteVector
|
||||||
|
|
||||||
pkt mustEqual string_pass
|
pkt mustEqual string_pass
|
||||||
|
|
@ -45,7 +43,7 @@ class ActionResultMessageTest extends Specification {
|
||||||
}
|
}
|
||||||
|
|
||||||
"encode (fail, full)" in {
|
"encode (fail, full)" in {
|
||||||
val msg = ActionResultMessage(false, Some(1))
|
val msg = ActionResultMessage(Some(1))
|
||||||
val pkt = PacketCoding.encodePacket(msg).require.toByteVector
|
val pkt = PacketCoding.encodePacket(msg).require.toByteVector
|
||||||
|
|
||||||
pkt mustEqual string_fail
|
pkt mustEqual string_fail
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue