Disconnect (#499)

* better kicking; a quitting that eliminates persistence

* GenericActionMessage comments; integer delay time

* TeardownConnection corresponds to closing the client directly

* messaging path for CMT_QUIT immediate logout that intersects zoning logic for IA and Recall

* slightly improved kicking, and the posibility of longer kicking

* restoring a turn counter instance

* player character will now clean up like normal; immediately turns into corpse; kick delay exists only on the persistence monitor
This commit is contained in:
Fate-JH 2020-06-24 23:08:22 -04:00 committed by GitHub
parent a5403298e3
commit e91e282d3a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 254 additions and 75 deletions

View file

@ -10,7 +10,8 @@ object Zoning {
val
None,
InstantAction,
Recall
Recall,
Quit
= Value
}
@ -35,6 +36,8 @@ object Zoning {
final val Enemy = TimeType(30, "Enemy")
}
final case class Quit()
object InstantAction {
final case class Request(faction : PlanetSideEmpire.Value)

View file

@ -26,9 +26,10 @@ import scodec.codecs._
* 24 - message: you have been imprinted (updates imprinted status; does it?)<br>
* 25 - message: you are no longer imprinted (updates imprinted status; does it?)<br>
* 27 - event: purchase timers reset (does it?)<br>
* 31 - switch to first person view, attempt to deconstruct but fail;
* 31 - forced into first person view;
* in third person view, player character sinks into the ground; green deconstruction particle effect under feet<br>
* 32 - forced into first person view, attempt to deconstruct but fail;
* event: fail to deconstruct due to having a "parent vehicle"<br>
* 32 - switch to first person view<br>
* 33 - event: fail to deconstruct<br>
* 43 - prompt: friendly fire in virtual reality zone<br>
* 45 - ?<br>

View file

@ -11,9 +11,8 @@ import net.psforever.objects._
import net.psforever.objects.serverobject.mount.Mountable
import net.psforever.objects.zones.Zone
import net.psforever.types.Vector3
import services.{RemoverActor, Service, ServiceManager}
import services.{Service, ServiceManager}
import services.avatar.{AvatarAction, AvatarServiceMessage}
import services.vehicle.VehicleServiceMessage
/**
* A global service that manages user behavior as divided into the following three categories:
@ -85,10 +84,34 @@ class AccountPersistenceService extends Actor {
case Some(ref) =>
ref ! msg
case None =>
log.warn(s"tried to update a player entry ($name) that did not yet exist; rebuilding entry ...")
log.warn(s"tried to update a player entry for $name that did not yet exist; rebuilding entry ...")
CreateNewPlayerToken(name).tell(msg, sender)
}
case msg @ AccountPersistenceService.PersistDelay(name, _) =>
accounts.get(name) match {
case Some(ref) =>
ref ! msg
case _ =>
log.warn(s"player entry for $name not found; not logged in")
}
case msg @ AccountPersistenceService.Kick(name, _) =>
accounts.get(name) match {
case Some(ref) =>
ref ! msg
case _ =>
log.warn(s"player entry for $name not found; not logged in")
}
case AccountPersistenceService.Logout(name) =>
accounts.remove(name) match {
case Some(ref) =>
ref ! Logout(name)
case _ =>
log.warn(s"player entry for $name not found; not logged in")
}
case Logout(target) => //TODO use context.watch and Terminated?
accounts.remove(target)
@ -114,7 +137,7 @@ class AccountPersistenceService extends Actor {
}
case msg =>
log.warn(s"Not yet started; received a $msg that will go unhandled")
log.warn(s"not yet started; received a $msg that will go unhandled")
}
/**
@ -166,6 +189,22 @@ object AccountPersistenceService {
* @param position the location of the player in game world coordinates
*/
final case class Update(name : String, zone : Zone, position : Vector3)
final case class Kick(name : String, time : Option[Long] = None)
/**
* Update the persistence monitor that was setup for a user for a custom persistence delay.
* If set to `None`, the default persistence time should assert itself.
* @param name the unique name of the player
* @param time the duration that this user's player characters will persist without update in seconds
*/
final case class PersistDelay(name : String, time : Option[Long])
/**
* Message that indicates that persistence is no longer necessary for this player character.
* @param name the unique name of the player
*/
final case class Logout(name : String)
}
/**
@ -189,6 +228,12 @@ class PersistenceMonitor(name : String, squadService : ActorRef, taskResolver :
var inZone : Zone = Zone.Nowhere
/** the last-reported game coordinate position of this player */
var lastPosition : Vector3 = Vector3.Zero
/** */
var kicked : Boolean = false
/** */
var kickTime : Option[Long] = None
/** a custom logout time for this player; 60s by default */
var persistTime : Option[Long] = None
/** the ongoing amount of permissible inactivity */
var timer : Cancellable = Default.Cancellable
/** the sparingly-used log */
@ -204,17 +249,44 @@ class PersistenceMonitor(name : String, squadService : ActorRef, taskResolver :
def receive : Receive = {
case AccountPersistenceService.Login(_) =>
sender ! PlayerToken.LoginInfo(name, inZone, lastPosition)
UpdateTimer()
sender ! (if(kicked) {
PlayerToken.CanNotLogin(name, PlayerToken.DeniedLoginReason.Kicked)
}
else {
UpdateTimer()
PlayerToken.LoginInfo(name, inZone, lastPosition)
})
case AccountPersistenceService.Update(_, z, p) =>
case AccountPersistenceService.Update(_, z, p) if !kicked =>
inZone = z
lastPosition = p
UpdateTimer()
case AccountPersistenceService.PersistDelay(_, delay) if !kicked =>
persistTime = delay
UpdateTimer()
case AccountPersistenceService.Kick(_, time) =>
persistTime = None
kickTime match {
case None if kicked =>
UpdateTimer()
case _ => ;
}
kicked = true
kickTime = time.orElse(Some(300L))
case Logout(_) =>
context.parent ! Logout(name)
context.stop(self)
kickTime match {
case Some(time) =>
PerformLogout()
kickTime = None
timer.cancel
timer = context.system.scheduler.scheduleOnce(time seconds, self, Logout(name))
case None =>
context.parent ! Logout(name)
context.stop(self)
}
case _ => ;
}
@ -224,7 +296,7 @@ class PersistenceMonitor(name : String, squadService : ActorRef, taskResolver :
*/
def UpdateTimer() : Unit = {
timer.cancel
timer = context.system.scheduler.scheduleOnce(60 seconds, self, Logout(name))
timer = context.system.scheduler.scheduleOnce(persistTime.getOrElse(60L) seconds, self, Logout(name))
}
/**
@ -248,7 +320,6 @@ class PersistenceMonitor(name : String, squadService : ActorRef, taskResolver :
* but should be uncommon.
*/
def PerformLogout() : Unit = {
log.info(s"logout of $name")
(inZone.Players.find(p => p.name == name), inZone.LivePlayers.find(p => p.Name == name)) match {
case (Some(avatar), Some(player)) if player.VehicleSeated.nonEmpty =>
//alive or dead in a vehicle
@ -336,6 +407,7 @@ class PersistenceMonitor(name : String, squadService : ActorRef, taskResolver :
squadService.tell(Service.Leave(Some(charId.toString)), parent)
Deployables.Disown(inZone, avatar, parent)
inZone.Population.tell(Zone.Population.Leave(avatar), parent)
log.info(s"logout of ${avatar.name}")
}
}
@ -347,6 +419,13 @@ class PersistenceMonitor(name : String, squadService : ActorRef, taskResolver :
private[this] case class Logout(name : String)
object PlayerToken {
object DeniedLoginReason extends Enumeration {
val
Denied, //generic
Kicked
= Value
}
/**
* Message dispatched to confirm that a player with given locational attributes exists.
* Agencies outside of the `AccountPersistanceService`/`PlayerToken` system make use of this message.
@ -356,4 +435,6 @@ object PlayerToken {
* @param position where in the zone the player is located
*/
final case class LoginInfo(name : String, zone : Zone, position : Vector3)
final case class CanNotLogin(name : String, reason : DeniedLoginReason.Value)
}