// Copyright (c) 2017-2020 PSForever
//language imports
import akka.actor.{Actor, ActorRef, Cancellable, MDCContextAware}
import com.github.mauricio.async.db.general.ArrayRowData
import com.github.mauricio.async.db.{Connection, QueryResult}
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicInteger
import org.log4s.{Logger, MDC}
import scala.annotation.{switch, tailrec}
import scala.collection.mutable.LongMap
import scala.concurrent.{Await, Future, Promise}
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.Success
import scodec.bits.ByteVector
//project imports
import csr.{CSRWarp, CSRZone, Traveler}
import MDCContextAware.Implicits._
import net.psforever.objects._
import net.psforever.objects.avatar.{Certification, DeployableToolbox, FirstTimeEvents}
import net.psforever.objects.ballistics.{PlayerSource, Projectile, ProjectileResolution, ResolvedProjectile, SourceEntry}
import net.psforever.objects.ce.{ComplexDeployable, Deployable, DeployableCategory, DeployedItem, SimpleDeployable, TelepadLike}
import net.psforever.objects.definition._
import net.psforever.objects.definition.converter.{CorpseConverter, DestroyedVehicleConverter}
import net.psforever.objects.entity.{SimpleWorldEntity, WorldEntity}
import net.psforever.objects.equipment.{Ammo, CItem, EffectTarget, Equipment, EquipmentSize, EquipmentSlot, FireModeSwitch, JammableUnit}
import net.psforever.objects.GlobalDefinitions
import net.psforever.objects.guid.{GUIDTask, Task, TaskResolver}
import net.psforever.objects.inventory.{Container, GridInventory, InventoryItem}
import net.psforever.objects.loadouts.{InfantryLoadout, Loadout, SquadLoadout, VehicleLoadout}
import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject}
import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.serverobject.damage.Damageable
import net.psforever.objects.serverobject.deploy.Deployment
import net.psforever.objects.serverobject.doors.Door
import net.psforever.objects.serverobject.generator.Generator
import net.psforever.objects.serverobject.hackable.{Hackable, GenericHackables}
import net.psforever.objects.serverobject.implantmech.ImplantTerminalMech
import net.psforever.objects.serverobject.locks.{IFFLock, IFFLocks}
import net.psforever.objects.serverobject.mblocker.Locker
import net.psforever.objects.serverobject.mount.Mountable
import net.psforever.objects.serverobject.pad.{VehicleSpawnControl, VehicleSpawnPad}
import net.psforever.objects.serverobject.painbox.Painbox
import net.psforever.objects.serverobject.resourcesilo.ResourceSilo
import net.psforever.objects.serverobject.structures.{Amenity, Building, SphereOfInfluence, StructureType, WarpGate}
import net.psforever.objects.serverobject.terminals.{CaptureTerminal, CaptureTerminals, MatrixTerminalDefinition, MedicalTerminalDefinition, ProximityDefinition, ProximityTerminal, ProximityUnit, Terminal}
import net.psforever.objects.serverobject.terminals.Terminal.TerminalMessage
import net.psforever.objects.serverobject.tube.SpawnTube
import net.psforever.objects.serverobject.turret.{FacilityTurret, TurretUpgrade, WeaponTurret, WeaponTurrets}
import net.psforever.objects.serverobject.zipline.ZipLinePath
import net.psforever.objects.teamwork.Squad
import net.psforever.objects.vehicles.{AccessPermissionGroup, Cargo, CargoBehavior, MountedWeapons, Utility, UtilityType, VehicleLockState}
import net.psforever.objects.vehicles.Utility.InternalTelepad
import net.psforever.objects.vital.{DamageFromPainbox, HealFromExoSuitChange, HealFromKit, HealFromTerm, PlayerSuicide, RepairFromKit, Vitality, VitalityDefinition}
import net.psforever.objects.zones.{InterstellarCluster, Zone, ZoneHotSpotProjector, Zoning}
import net.psforever.packet._
import net.psforever.packet.control._
import net.psforever.packet.game._
import net.psforever.packet.game.objectcreate.{ConstructorData, DetailedCharacterData, DroppedItemData, ObjectClass, ObjectCreateMessageParent, PlacementData}
import net.psforever.packet.game.{HotSpotInfo => PacketHotSpotInfo}
import net.psforever.types._
import services.{RemoverActor, Service, ServiceManager}
import services.account.{AccountPersistenceService, PlayerToken, ReceiveAccountData, RetrieveAccountData}
import services.avatar.{AvatarAction, AvatarResponse, AvatarServiceMessage, AvatarServiceResponse}
import services.chat.{ChatAction, ChatResponse, ChatServiceMessage, ChatServiceResponse}
import services.galaxy.{GalaxyAction, GalaxyResponse, GalaxyServiceMessage, GalaxyServiceResponse}
import services.local.{LocalAction, LocalResponse, LocalServiceMessage, LocalServiceResponse}
import services.local.support.{HackCaptureActor, RouterTelepadActivation}
import services.ServiceManager.LookupResult
import services.support.SupportActor
import services.teamwork.{SquadResponse, SquadService, SquadServiceMessage, SquadServiceResponse, SquadAction => SquadServiceAction}
import services.vehicle.{VehicleAction, VehicleResponse, VehicleServiceMessage, VehicleServiceResponse}

class WorldSessionActor extends Actor
  with MDCContextAware {

  import WorldSessionActor._

  private[this] val log = org.log4s.getLogger
  private[this] val damageLog = org.log4s.getLogger(Damageable.LogChannel)
  var sessionId : Long = 0
  var leftRef : ActorRef = ActorRef.noSender
  var rightRef : ActorRef = ActorRef.noSender
  var accountIntermediary : ActorRef = ActorRef.noSender
  var accountPersistence : ActorRef = ActorRef.noSender
  var chatService : ActorRef = ActorRef.noSender
  var galaxyService : ActorRef = ActorRef.noSender
  var squadService : ActorRef = ActorRef.noSender
  var taskResolver : ActorRef = Actor.noSender
  var cluster : ActorRef = Actor.noSender
  var continent : Zone = Zone.Nowhere
  var account : Account = null
  var player : Player = null
  var avatar : Avatar = null
  var progressBarValue : Option[Float] = None
  var shooting : Option[PlanetSideGUID] = None //ChangeFireStateMessage_Start
  var prefire : Option[PlanetSideGUID] = None //if WeaponFireMessage precedes ChangeFireStateMessage_Start
  var shotsWhileDead : Int = 0
  var accessedContainer : Option[PlanetSideGameObject with Container] = None
  var connectionState : Int = 25
  var flying : Boolean = false
  var speed : Float = 1.0f
  var admin : Boolean = false
  var loadConfZone : Boolean = false
  var noSpawnPointHere : Boolean = false
  var usingMedicalTerminal : Option[PlanetSideGUID] = None
  var controlled : Option[Int] = None
  //keep track of avatar's ServerVehicleOverride state
  var traveler : Traveler = null
  var deadState : DeadState.Value = DeadState.Dead
  var whenUsedLastAAMAX : Long = 0
  var whenUsedLastAIMAX : Long = 0
  var whenUsedLastAVMAX : Long = 0
  var whenUsedLastMAX : Array[Long] = Array.fill[Long](4)(0L)
  var whenUsedLastMAXName : Array[String] = Array.fill[String](4)("")
  var whenUsedLastItem : Array[Long] = Array.fill[Long](1020)(0L)
  var whenUsedLastItemName : Array[String] = Array.fill[String](1020)("")
  var whenUsedLastKit : Long = 0
  var whenUsedLastSMKit : Long = 0
  var whenUsedLastSAKit : Long = 0
  var whenUsedLastSSKit : Long = 0
  val projectiles : Array[Option[Projectile]] = Array.fill[Option[Projectile]](Projectile.RangeUID - Projectile.BaseUID)(None)
  val projectilesToCleanUp : Array[Boolean] = Array.fill[Boolean](Projectile.RangeUID - Projectile.BaseUID)(false)
  var drawDeloyableIcon : PlanetSideGameObject with Deployable => Unit = RedrawDeployableIcons
  var updateSquad : () => Unit = NoSquadUpdates
  var recentTeleportAttempt : Long = 0
  var lastTerminalOrderFulfillment : Boolean = true
  var shiftPosition : Option[Vector3] = None
  var shiftOrientation : Option[Vector3] = None
  var setupAvatarFunc : () => Unit = AvatarCreate
  var beginZoningSetCurrentAvatarFunc : (Player) => Unit = SetCurrentAvatarNormally
  var persist : () => Unit = NoPersistence
  /**
    * used during zone transfers to maintain reference to seated vehicle (which does not yet exist in the new zone)
    * used during intrazone gate transfers, but not in a way distinct from prior zone transfer procedures
    * should only be set during the transient period when moving between one spawn point and the next
    * leaving set prior to a subsequent transfers may cause unstable vehicle associations, with memory leak potential
    */
  var interstellarFerry : Option[Vehicle] = None
  /**
    * used during zone transfers for cleanup to refer to the vehicle that instigated a transfer
    * "top level" is the carrier in a carrier/ferried association or a projected carrier/(ferried carrier)/ferried association
    * inherited from parent (carrier) to child (ferried) through the `TransferPassenger` message
    * the old-zone unique identifier for the carrier
    * no harm should come from leaving the field set to an old unique identifier value after the transfer period
    */
  var interstellarFerryTopLevelGUID : Option[PlanetSideGUID] = None
  val squadUI : LongMap[SquadUIElement] = new LongMap[SquadUIElement]()
  var squad_supplement_id : Int = 0
  /**
    * When joining or creating a squad, the original state of the avatar's internal LFS variable is blanked.
    * This `WSA`-local variable is then used to indicate the ongoing state of the LFS UI component,
    * now called "Looking for Squad Member."
    * Only the squad leader may toggle the LFSM marquee.
    * Upon leaving or disbanding a squad, this value is made false.
    * Control switching between the `Avatar`-local and the `WorldSessionActor`-local variable is contingent on `squadUI` being populated.
    */
  var lfsm : Boolean = false
  var squadChannel : Option[String] = None
  var squadSetup : () => Unit = FirstTimeSquadSetup
  var squadUpdateCounter : Int = 0
  val queuedSquadActions : Seq[() => Unit] = Seq(SquadUpdates, NoSquadUpdates, NoSquadUpdates, NoSquadUpdates)
  /** Keeps track of the number of PlayerStateMessageUpstream messages received by the client
    * As they should arrive roughly every 250 milliseconds this allows for a very crude method of scheduling tasks up to four times per second */
  private var playerStateMessageUpstreamCount = 0
  var zoningType : Zoning.Method.Value = Zoning.Method.None
  var zoningChatMessageType : ChatMessageType.Value = ChatMessageType.CMT_QUIT
  var zoningStatus : Zoning.Status.Value = Zoning.Status.None
  var zoningCounter : Int = 0
  var instantActionFallbackDestination : Option[Zoning.InstantAction.Located] = None
  lazy val unsignedIntMaxValue : Long = Int.MaxValue.toLong * 2L + 1L
  var serverTime : Long = 0
  var amsSpawnPoints : List[SpawnPoint] = Nil

  var clientKeepAlive : Cancellable = DefaultCancellable.obj
  var progressBarUpdate : Cancellable = DefaultCancellable.obj
  var reviveTimer : Cancellable = DefaultCancellable.obj
  var respawnTimer : Cancellable = DefaultCancellable.obj
  var cargoMountTimer : Cancellable = DefaultCancellable.obj
  var cargoDismountTimer : Cancellable = DefaultCancellable.obj
  var antChargingTick : Cancellable = DefaultCancellable.obj
  var antDischargingTick : Cancellable = DefaultCancellable.obj
  var zoningTimer : Cancellable = DefaultCancellable.obj
  var zoningReset : Cancellable = DefaultCancellable.obj
  /**
    * Convert a boolean value into an integer value.
    * Use: `true:Int` or `false:Int`
    * @param b `true` or `false` (or `null`)
    * @return 1 for `true`; 0 for `false`
    */

  import scala.language.implicitConversions

  implicit def boolToInt(b : Boolean) : Int = if(b) 1
  else 0

  override def postStop() : Unit = {
    //normally, the player avatar persists a minute or so after disconnect; we are subject to the SessionReaper
    clientKeepAlive.cancel
    progressBarUpdate.cancel
    reviveTimer.cancel
    respawnTimer.cancel
    cargoMountTimer.cancel
    cargoDismountTimer.cancel
    antChargingTick.cancel
    antDischargingTick.cancel
    chatService ! Service.Leave()
    galaxyService ! Service.Leave()
    continent.AvatarEvents ! Service.Leave()
    continent.LocalEvents ! Service.Leave()
    continent.VehicleEvents ! Service.Leave()
    if(avatar != null) {
      //TODO put any temporary values back into the avatar
      squadService ! Service.Leave(Some(s"${avatar.faction}"))
      if(player != null && player.HasGUID) {
        prefire.orElse(shooting) match {
          case Some(guid) =>
            continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.ChangeFireState_Stop(player.GUID, guid))
          case None => ;
        }
      }
    }
  }

  def receive = Initializing

  def Initializing : Receive = {
    case HelloFriend(inSessionId, pipe) =>
      this.sessionId = inSessionId
      leftRef = sender()
      if(pipe.hasNext) {
        rightRef = pipe.next
        rightRef !> HelloFriend(sessionId, pipe)
      }
      else {
        rightRef = sender()
      }
      context.become(Started)
      import services.ServiceManager.Lookup
      val serviceManager = ServiceManager.serviceManager
      serviceManager ! Lookup("accountIntermediary")
      serviceManager ! Lookup("accountPersistence")
      serviceManager ! Lookup("chat")
      serviceManager ! Lookup("taskResolver")
      serviceManager ! Lookup("cluster")
      serviceManager ! Lookup("galaxy")
      serviceManager ! Lookup("squad")

    case _ =>
      log.error("Unknown message")
      context.stop(self)
  }

  def ValidObject(id : Int) : Option[PlanetSideGameObject] = ValidObject(Some(PlanetSideGUID(id)))

  def ValidObject(id : PlanetSideGUID) : Option[PlanetSideGameObject] = ValidObject(Some(id))

  def ValidObject(id : Option[PlanetSideGUID]) : Option[PlanetSideGameObject] = continent.GUID(id) match {
    case out@Some(obj) if obj.HasGUID =>
      out
    case None if id.nonEmpty && id.get != PlanetSideGUID(0) =>
      //delete stale entity reference from client
      log.warn(s"Player ${player.Name} has an invalid reference to GUID ${id.get} in zone ${continent.Id}.")
      //sendResponse(ObjectDeleteMessage(id.get, 0))
      None
    case _ =>
      None
  }

  def Started : Receive = {
    case LookupResult("accountIntermediary", endpoint) =>
      accountIntermediary = endpoint
      log.info("ID: " + sessionId + " Got account intermediary service " + endpoint)
    case LookupResult("accountPersistence", endpoint) =>
      accountPersistence = endpoint
      log.info("ID: " + sessionId + " Got account persistence service " + endpoint)
    case LookupResult("chat", endpoint) =>
      chatService = endpoint
      log.info("ID: " + sessionId + " Got chat service " + endpoint)
    case LookupResult("taskResolver", endpoint) =>
      taskResolver = endpoint
      log.info("ID: " + sessionId + " Got task resolver service " + endpoint)
    case LookupResult("galaxy", endpoint) =>
      galaxyService = endpoint
      log.info("ID: " + sessionId + " Got galaxy service " + endpoint)
    case LookupResult("cluster", endpoint) =>
      cluster = endpoint
      log.info("ID: " + sessionId + " Got cluster service " + endpoint)
    case LookupResult("squad", endpoint) =>
      squadService = endpoint
      log.info("ID: " + sessionId + " Got squad service " + endpoint)

    case ControlPacket(_, ctrl) =>
      handleControlPkt(ctrl)
    case GamePacket(_, _, pkt) =>
      handleGamePkt(pkt)

    case PokeClient() =>
      persist()
      sendResponse(KeepAliveMessage())

    case AvatarServiceResponse(toChannel, guid, reply) =>
      HandleAvatarServiceResponse(toChannel, guid, reply)

    case CommonMessages.Progress(rate, finishedAction, stepAction) =>
      if(progressBarValue.isEmpty) {
        progressBarValue = Some(-rate)
        self ! ProgressEvent(rate, finishedAction, stepAction)
      }

    case ProgressEvent(delta, finishedAction, stepAction) =>
      HandleProgressChange(delta, finishedAction, stepAction)

    case Door.DoorMessage(tplayer, msg, order) =>
      HandleDoorMessage(tplayer, msg, order)

    case GalaxyServiceResponse(_, reply) =>
      reply match {
        case GalaxyResponse.HotSpotUpdate(zone_index, priority, hot_spot_info) =>
          sendResponse(
            HotSpotUpdateMessage(
              zone_index,
              priority,
              hot_spot_info.map { spot => PacketHotSpotInfo(spot.DisplayLocation.x, spot.DisplayLocation.y, 40) }
            )
          )
        case GalaxyResponse.MapUpdate(msg) =>
          sendResponse(msg)

        case GalaxyResponse.TransferPassenger(temp_channel, vehicle, vehicle_to_delete, manifest) =>
          ((manifest.passengers.find { case (name, _) => player.Name.equals(name) } match {
            case Some((name, index)) if vehicle.Seats(index).Occupant.isEmpty =>
              vehicle.Seats(index).Occupant = player
              Some(vehicle)
            case Some((name, index)) =>
              log.warn(s"TransferPassenger: seat $index is already occupied")
              None
            case None =>
              None
          }).orElse(manifest.cargo.find { case (name, _) => player.Name.equals(name) } match {
            case Some((name, index)) =>
              vehicle.CargoHolds(index).Occupant match {
                case Some(cargo) =>
                  cargo.Seats(0).Occupant match {
                    case Some(driver) if driver.Name.equals(name) =>
                      Some(cargo)
                    case _ =>
                      None
                  }
                case None =>
                  None
              }
            case None =>
              None
          })
            ) match {
            case Some(v) =>
              galaxyService ! Service.Leave(Some(temp_channel)) //temporary vehicle-specific channel (see above)
              deadState = DeadState.Release
              sendResponse(AvatarDeadStateMessage(DeadState.Release, 0, 0, player.Position, player.Faction, true))
              interstellarFerry = Some(v) //on the other continent and registered to that continent's GUID system
              LoadZonePhysicalSpawnPoint(v.Continent, v.Position, v.Orientation, 1)
            case None =>
              interstellarFerry match {
                case None =>
                  galaxyService ! Service.Leave(Some(temp_channel)) //no longer being transferred between zones
                  interstellarFerryTopLevelGUID = None
                case Some(_) => ;
                //wait patiently
              }
          }
      }

    case LocalServiceResponse(toChannel, guid, reply) =>
      HandleLocalServiceResponse(toChannel, guid, reply)

    case ChatServiceResponse(toChannel, guid, avatar_name, cont, avatar_pos, avatar_faction, target, reply) =>
      HandleChatServiceResponse(toChannel, guid, avatar_name, cont, avatar_pos, avatar_faction, target, reply)

    case Mountable.MountMessages(tplayer, reply) =>
      HandleMountMessages(tplayer, reply)

    case Terminal.TerminalMessage(tplayer, msg, order) =>
      HandleTerminalMessage(tplayer, msg, order)

    case ProximityUnit.Action(term, target) =>
      SelectProximityUnitBehavior(term, target)

    case VehicleServiceResponse(toChannel, guid, reply) =>
      HandleVehicleServiceResponse(toChannel, guid, reply)

    case SquadServiceResponse(_, excluded, response) =>
      if(!excluded.exists(_ == avatar.CharId)) {
        response match {
          case SquadResponse.ListSquadFavorite(line, task) =>
            sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), line, SquadAction.ListSquadFavorite(task)))

          case SquadResponse.InitList(infos) =>
            sendResponse(ReplicationStreamMessage(infos))

          case SquadResponse.UpdateList(infos) if infos.nonEmpty =>
            sendResponse(
              ReplicationStreamMessage(6, None,
                infos.map { case (index, squadInfo) =>
                  SquadListing(index, squadInfo)
                }.toVector
              )
            )

          case SquadResponse.RemoveFromList(infos) if infos.nonEmpty =>
            sendResponse(
              ReplicationStreamMessage(1, None,
                infos.map { index =>
                  SquadListing(index, None)
                }.toVector
              )
            )

          case SquadResponse.Detail(guid, detail) =>
            sendResponse(SquadDetailDefinitionUpdateMessage(guid, detail))

          case SquadResponse.AssociateWithSquad(squad_guid) =>
            sendResponse(SquadDefinitionActionMessage(squad_guid, 0, SquadAction.AssociateWithSquad()))

          case SquadResponse.SetListSquad(squad_guid) =>
            sendResponse(SquadDefinitionActionMessage(squad_guid, 0, SquadAction.SetListSquad()))

          case SquadResponse.Membership(request_type, unk1, unk2, char_id, opt_char_id, player_name, unk5, unk6) =>
            val name = request_type match {
              case SquadResponseType.Invite if unk5 =>
                //player_name is our name; the name of the player indicated by unk3 is needed
                LivePlayerList.WorldPopulation({ case (_, a : Avatar) => char_id == a.CharId }).headOption match {
                  case Some(player) =>
                    player.name
                  case None =>
                    player_name
                }
              case _ =>
                player_name
            }
            sendResponse(SquadMembershipResponse(request_type, unk1, unk2, char_id, opt_char_id, name, unk5, unk6))

          case SquadResponse.WantsSquadPosition(_, name) =>
            sendResponse(
              ChatMsg(
                ChatMessageType.CMT_SQUAD, true, name,
                s"\\#6 would like to join your squad. (respond with \\#3/accept\\#6 or \\#3/reject\\#6)",
                None
              )
            )

          case SquadResponse.Join(squad, positionsToUpdate, toChannel) =>
            val leader = squad.Leader
            val membershipPositions = positionsToUpdate map squad.Membership.zipWithIndex
            StartBundlingPackets()
            membershipPositions.find({ case (member, _) => member.CharId == avatar.CharId }) match {
              case Some((ourMember, ourIndex)) =>
                //we are joining the squad
                //load each member's entry (our own too)
                squad_supplement_id = squad.GUID.guid + 1
                membershipPositions.foreach { case (member, index) =>
                  sendResponse(SquadMemberEvent.Add(squad_supplement_id, member.CharId, index, member.Name, member.ZoneId, unk7 = 0))
                  squadUI(member.CharId) = SquadUIElement(member.Name, index, member.ZoneId, member.Health, member.Armor, member.Position)
                }
                //repeat our entry
                sendResponse(SquadMemberEvent.Add(squad_supplement_id, ourMember.CharId, ourIndex, ourMember.Name, ourMember.ZoneId, unk7 = 0)) //repeat of our entry
              val playerGuid = player.GUID
                //turn lfs off
                val factionChannel = s"${player.Faction}"
                if(avatar.LFS) {
                  avatar.LFS = false
                  sendResponse(PlanetsideAttributeMessage(playerGuid, 53, 0))
                  continent.AvatarEvents ! AvatarServiceMessage(factionChannel, AvatarAction.PlanetsideAttribute(playerGuid, 53, 0))
                }
                //squad colors
                GiveSquadColorsInZone()
                continent.AvatarEvents ! AvatarServiceMessage(factionChannel, AvatarAction.PlanetsideAttribute(playerGuid, 31, squad_supplement_id))
                //associate with member position in squad
                sendResponse(PlanetsideAttributeMessage(playerGuid, 32, ourIndex))
                //a finalization? what does this do?
                sendResponse(SquadDefinitionActionMessage(squad.GUID, 0, SquadAction.Unknown(18)))
                updateSquad = PeriodicUpdatesWhenEnrolledInSquad
                squadChannel = Some(toChannel)
                chatService ! Service.Join(squadChannel.get)
              case _ =>
                //other player is joining our squad
                //load each member's entry
                GiveSquadColorsInZone(
                  membershipPositions.map { case (member, index) =>
                    val charId = member.CharId
                    sendResponse(SquadMemberEvent.Add(squad_supplement_id, charId, index, member.Name, member.ZoneId, unk7 = 0))
                    squadUI(charId) = SquadUIElement(member.Name, index, member.ZoneId, member.Health, member.Armor, member.Position)
                    charId
                  }
                )
            }
            StopBundlingPackets()
            //send an initial dummy update for map icon(s)
            sendResponse(SquadState(PlanetSideGUID(squad_supplement_id),
              membershipPositions
                .filterNot { case (member, _) => member.CharId == avatar.CharId }
                .map { case (member, _) => SquadStateInfo(member.CharId, member.Health, member.Armor, member.Position, 2, 2, false, 429, None, None) }
                .toList
            ))

          case SquadResponse.Leave(squad, positionsToUpdate) =>
            StartBundlingPackets()
            positionsToUpdate.find({ case (member, _) => member == avatar.CharId }) match {
              case Some((ourMember, ourIndex)) =>
                //we are leaving the squad
                //remove each member's entry (our own too)
                positionsToUpdate.foreach { case (member, index) =>
                  sendResponse(SquadMemberEvent.Remove(squad_supplement_id, member, index))
                  squadUI.remove(member)
                }
                //uninitialize
                val playerGuid = player.GUID
                sendResponse(SquadMemberEvent.Remove(squad_supplement_id, ourMember, ourIndex)) //repeat of our entry
                sendResponse(PlanetsideAttributeMessage(playerGuid, 31, 0)) //disassociate with squad?
                continent.AvatarEvents ! AvatarServiceMessage(s"${player.Faction}", AvatarAction.PlanetsideAttribute(playerGuid, 31, 0))
                sendResponse(PlanetsideAttributeMessage(playerGuid, 32, 0)) //disassociate with member position in squad?
                sendResponse(PlanetsideAttributeMessage(playerGuid, 34, 4294967295L)) //unknown, perhaps unrelated?
                lfsm = false
                //a finalization? what does this do?
                sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SquadAction.Unknown(18)))
                squad_supplement_id = 0
                squadUpdateCounter = 0
                updateSquad = NoSquadUpdates
                chatService ! Service.Leave(squadChannel)
                squadChannel = None
              case _ =>
                //remove each member's entry
                GiveSquadColorsInZone(
                  positionsToUpdate.map { case (member, index) =>
                    sendResponse(SquadMemberEvent.Remove(squad_supplement_id, member, index))
                    squadUI.remove(member)
                    member
                  },
                  value = 0
                )
            }
            StopBundlingPackets()

          case SquadResponse.AssignMember(squad, from_index, to_index) =>
            //we've already swapped position internally; now we swap the cards
            SwapSquadUIElements(squad, from_index, to_index)

          case SquadResponse.PromoteMember(squad, char_id, from_index, to_index) =>
            val charId = player.CharId
            val guid = player.GUID
            lazy val factionChannel = s"${player.Faction}"
            //are we being demoted?
            if(squadUI(charId).index == 0) {
              //lfsm -> lfs
              if(lfsm) {
                sendResponse(PlanetsideAttributeMessage(guid, 53, 0))
                continent.AvatarEvents ! AvatarServiceMessage(factionChannel, AvatarAction.PlanetsideAttribute(guid, 53, 0))
              }
              lfsm = false
              sendResponse(PlanetsideAttributeMessage(guid, 32, from_index)) //associate with member position in squad
            }
            //are we being promoted?
            else if(charId == char_id) {
              sendResponse(PlanetsideAttributeMessage(guid, 32, 0)) //associate with member position in squad
            }
            continent.AvatarEvents ! AvatarServiceMessage(factionChannel, AvatarAction.PlanetsideAttribute(guid, 31, squad_supplement_id))
            //we must fix the squad cards backend
            SwapSquadUIElements(squad, from_index, to_index)

          case SquadResponse.UpdateMembers(squad, positions) =>
            val pairedEntries = positions.collect {
              case entry if squadUI.contains(entry.char_id) =>
                (entry, squadUI(entry.char_id))
            }
            //prune entries
            val updatedEntries = pairedEntries
              .collect({
                case (entry, element) if entry.zone_number != element.zone =>
                  //zone gets updated for these entries
                  sendResponse(SquadMemberEvent.UpdateZone(squad_supplement_id, entry.char_id, element.index, entry.zone_number))
                  squadUI(entry.char_id) = SquadUIElement(element.name, element.index, entry.zone_number, entry.health, entry.armor, entry.pos)
                  entry
                case (entry, element) if entry.health != element.health || entry.armor != element.armor || entry.pos != element.position =>
                  //other elements that need to be updated
                  squadUI(entry.char_id) = SquadUIElement(element.name, element.index, entry.zone_number, entry.health, entry.armor, entry.pos)
                  entry
              })
              .filterNot(_.char_id == avatar.CharId) //we want to update our backend, but not our frontend
            if(updatedEntries.nonEmpty) {
              sendResponse(
                SquadState(
                  PlanetSideGUID(squad_supplement_id),
                  updatedEntries.map { entry => SquadStateInfo(entry.char_id, entry.health, entry.armor, entry.pos, 2, 2, false, 429, None, None) }
                )
              )
            }

          case SquadResponse.SquadSearchResults() =>
            //I don't actually know how to return search results
            sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SquadAction.NoSquadSearchResults()))

          case SquadResponse.InitWaypoints(char_id, waypoints) =>
            StartBundlingPackets()
            waypoints.foreach { case (waypoint_type, info, unk) =>
              sendResponse(SquadWaypointEvent.Add(squad_supplement_id, char_id, waypoint_type, WaypointEvent(info.zone_number, info.pos, unk)))
            }
            StopBundlingPackets()

          case SquadResponse.WaypointEvent(WaypointEventAction.Add, char_id, waypoint_type, _, Some(info), unk) =>
            sendResponse(SquadWaypointEvent.Add(squad_supplement_id, char_id, waypoint_type, WaypointEvent(info.zone_number, info.pos, unk)))

          case SquadResponse.WaypointEvent(WaypointEventAction.Remove, char_id, waypoint_type, _, _, _) =>
            sendResponse(SquadWaypointEvent.Remove(squad_supplement_id, char_id, waypoint_type))

          case _ => ;
        }
      }

    case Deployment.CanDeploy(obj, state) =>
      val vehicle_guid = obj.GUID
      //TODO remove this arbitrary allowance angle when no longer helpful
      if(obj.Orientation.x > 30 && obj.Orientation.x < 330) {
        obj.DeploymentState = DriveState.Mobile
        CanNotChangeDeployment(obj, state, "ground too steep")
      }
      else if(state == DriveState.Deploying) {
        log.info(s"DeployRequest: $obj transitioning to deploy state")
        obj.Velocity = Some(Vector3.Zero) //no velocity
        sendResponse(DeployRequestMessage(player.GUID, vehicle_guid, state, 0, false, Vector3.Zero))
        continent.VehicleEvents ! VehicleServiceMessage(continent.Id, VehicleAction.DeployRequest(player.GUID, vehicle_guid, state, 0, false, Vector3.Zero))
        DeploymentActivities(obj)
        import scala.concurrent.ExecutionContext.Implicits.global
        context.system.scheduler.scheduleOnce(obj.DeployTime milliseconds, obj.Actor, Deployment.TryDeploy(DriveState.Deployed))
      }
      else if(state == DriveState.Deployed) {
        log.info(s"DeployRequest: $obj has been Deployed")
        obj.Velocity = Some(Vector3.Zero)
        sendResponse(DeployRequestMessage(player.GUID, vehicle_guid, state, 0, false, Vector3.Zero))
        continent.VehicleEvents ! VehicleServiceMessage(continent.Id, VehicleAction.DeployRequest(player.GUID, vehicle_guid, state, 0, false, Vector3.Zero))
        DeploymentActivities(obj)
        //...
      }
      else {
        CanNotChangeDeployment(obj, state, "incorrect deploy state")
      }

    case Deployment.CanUndeploy(obj, state) =>
      val vehicle_guid = obj.GUID
      if(state == DriveState.Undeploying) {
        log.info(s"DeployRequest: $obj transitioning to undeploy state")
        sendResponse(DeployRequestMessage(player.GUID, vehicle_guid, state, 0, false, Vector3.Zero))
        continent.VehicleEvents ! VehicleServiceMessage(continent.Id, VehicleAction.DeployRequest(player.GUID, vehicle_guid, state, 0, false, Vector3.Zero))
        DeploymentActivities(obj)
        import scala.concurrent.ExecutionContext.Implicits.global
        context.system.scheduler.scheduleOnce(obj.UndeployTime milliseconds, obj.Actor, Deployment.TryUndeploy(DriveState.Mobile))
      }
      else if(state == DriveState.Mobile) {
        log.info(s"DeployRequest: $obj is Mobile")
        sendResponse(DeployRequestMessage(player.GUID, vehicle_guid, state, 0, false, Vector3.Zero))
        continent.VehicleEvents ! VehicleServiceMessage(continent.Id, VehicleAction.DeployRequest(player.GUID, vehicle_guid, state, 0, false, Vector3.Zero))
        DeploymentActivities(obj)
        //...
      }
      else {
        CanNotChangeDeployment(obj, state, "incorrect undeploy state")
      }

    case Deployment.CanNotChangeDeployment(obj, state, reason) =>
      CanNotChangeDeployment(obj, state, reason)

    case ResourceSilo.ResourceSiloMessage(tplayer, msg, order) =>
      continent.GUID(msg.avatar_guid) match {
        case Some(vehicle : Vehicle) =>
          val silo_guid = msg.object_guid
          order match {
            case ResourceSilo.ChargeEvent() =>
              antChargingTick.cancel() // If an ANT is refilling an NTU silo it isn't in a warpgate, so disable NTU regeneration
              antDischargingTick.cancel()
              antDischargingTick = context.system.scheduler.scheduleOnce(1000 milliseconds, self, NtuDischarging(player, vehicle, msg.object_guid))
            case _ => ;
          }
        case _ => ;
      }

    case CreateCharacter(name, head, voice, gender, empire) =>
      log.info(s"Creating new character $name...")
      Database.getConnection.connect.onComplete {
        case scala.util.Success(connection) =>
          val accountUserName : String = account.Username
          connection.inTransaction {
            c =>
              c.sendPreparedStatement(
                "INSERT INTO characters (name, account_id, faction_id, gender_id, head_id, voice_id) VALUES(?,?,?,?,?,?) RETURNING id",
                Array(name, account.AccountId, empire.id, gender.id, head, voice.id)
              )
          }.onComplete {
            case scala.util.Success(insertResult) =>
              if(connection.isConnected) connection.disconnect
              insertResult match {
                case result : QueryResult =>
                  if(result.rows.nonEmpty) {
                    log.info(s"CreateCharacter: successfully created new character for $accountUserName")
                    sendResponse(ActionResultMessage.Pass)
                    self ! ListAccountCharacters()
                  }
                  else {
                    log.error(s"CreateCharacter: new character for $accountUserName was not created")
                    sendResponse(ActionResultMessage.Fail(0))
                    self ! ListAccountCharacters()
                  }
                case e =>
                  log.error(s"CreateCharacter: unexpected error while creating new character for $accountUserName")
                  sendResponse(ActionResultMessage.Fail(3))
                  self ! ListAccountCharacters()
              }
            case scala.util.Failure(e) =>
              if(connection.isConnected) connection.disconnect
              failWithError(s"CreateCharacter: query failed - ${e.getMessage}")
          }
        case scala.util.Failure(e) =>
          log.error(s"CreateCharacter: no connection - ${e.getMessage}?")
      }

    case ListAccountCharacters() =>
      Database.getConnection.connect.onComplete {
        case scala.util.Success(connection) =>
          val accountUserName : String = account.Username
          connection.sendPreparedStatement(
            "SELECT id, name, faction_id, gender_id, head_id, voice_id, deleted, last_login FROM characters where account_id=? ORDER BY last_login", Array(account.AccountId)
          ).onComplete {
            case scala.util.Success(result : QueryResult) =>
              if(connection.isConnected) connection.disconnect
              if(result.rows.nonEmpty) {
                import net.psforever.objects.definition.converter.CharacterSelectConverter
                val gen : AtomicInteger = new AtomicInteger(1)
                val converter : CharacterSelectConverter = new CharacterSelectConverter
                result.rows foreach { row =>
                  log.trace(s"char list : ${row.toString()}")
                  val nowTimeInSeconds = System.currentTimeMillis() / 1000
                  var avatarArray : Array[Avatar] = Array.ofDim(row.length)
                  var playerArray : Array[Player] = Array.ofDim(row.length)
                  row.zipWithIndex.foreach { case (value, i) =>
                    val lName : String = value(1).asInstanceOf[String]
                    val lFaction : PlanetSideEmpire.Value = PlanetSideEmpire(value(2).asInstanceOf[Int])
                    val lGender : CharacterGender.Value = CharacterGender(value(3).asInstanceOf[Int])
                    val lHead : Int = value(4).asInstanceOf[Int]
                    val lVoice : CharacterVoice.Value = CharacterVoice(value(5).asInstanceOf[Int])
                    val lDeleted : Boolean = value(6).asInstanceOf[Boolean]
                    val lTime = value(7).asInstanceOf[org.joda.time.LocalDateTime].toDateTime().getMillis() / 1000
                    val secondsSinceLastLogin = nowTimeInSeconds - lTime
                    if(!lDeleted) {
                      avatarArray(i) = new Avatar(value(0).asInstanceOf[Int], lName, lFaction, lGender, lHead, lVoice)
                      AwardCharacterSelectBattleExperiencePoints(avatarArray(i), 20000000L)
                      avatarArray(i).CEP = 600000
                      playerArray(i) = new Player(avatarArray(i))
                      playerArray(i).ExoSuit = ExoSuitType.Reinforced
                      playerArray(i).Slot(0).Equipment = Tool(GlobalDefinitions.StandardPistol(playerArray(i).Faction))
                      playerArray(i).Slot(1).Equipment = Tool(GlobalDefinitions.MediumPistol(playerArray(i).Faction))
                      playerArray(i).Slot(2).Equipment = Tool(GlobalDefinitions.HeavyRifle(playerArray(i).Faction))
                      playerArray(i).Slot(3).Equipment = Tool(GlobalDefinitions.AntiVehicularLauncher(playerArray(i).Faction))
                      playerArray(i).Slot(4).Equipment = Tool(GlobalDefinitions.katana)
                      SetCharacterSelectScreenGUID(playerArray(i), gen)
                      val health = playerArray(i).Health
                      val stamina = playerArray(i).Stamina
                      val armor = playerArray(i).Armor
                      playerArray(i).Spawn
                      sendResponse(ObjectCreateDetailedMessage(ObjectClass.avatar, playerArray(i).GUID, converter.DetailedConstructorData(playerArray(i)).get))
                      if(health > 0) { //player can not be dead; stay spawned as alive
                        playerArray(i).Health = health
                        playerArray(i).Stamina = stamina
                        playerArray(i).Armor = armor
                      }
                      sendResponse(CharacterInfoMessage(15, PlanetSideZoneID(4), value(0).asInstanceOf[Int], playerArray(i).GUID, false, secondsSinceLastLogin))
                      RemoveCharacterSelectScreenGUID(playerArray(i))
                    }
                  }
                  sendResponse(CharacterInfoMessage(0, PlanetSideZoneID(1), 0, PlanetSideGUID(0), true, 0))
                }
              }
              Thread.sleep(50)

            case scala.util.Success(result) =>
              if(connection.isConnected) connection.disconnect //pre-empt failWithError
              failWithError(s"ListAccountCharacters: unexpected query result format - ${result.getClass}")

            case scala.util.Failure(e) =>
              if(connection.isConnected) connection.disconnect //pre-empt failWithError
              failWithError(s"ListAccountCharacters: query failed - ${e.getMessage}")
          }

        case scala.util.Failure(e) =>
          failWithError(s"ListAccountCharacters: no connection - ${e.getMessage}")
      }

    case VehicleLoaded(_ /*vehicle*/) => ;
    //currently being handled by VehicleSpawnPad.LoadVehicle during testing phase

    case Zone.ClientInitialization(zone) =>
      Thread.sleep(connectionState)
      val continentNumber = zone.Number
      val poplist = zone.Players
      val popBO = 0
      //TODO black ops test (partition)
      val popTR = poplist.count(_.faction == PlanetSideEmpire.TR)
      val popNC = poplist.count(_.faction == PlanetSideEmpire.NC)
      val popVS = poplist.count(_.faction == PlanetSideEmpire.VS)

      // StopBundlingPackets() is called on ClientInitializationComplete
      StartBundlingPackets()
      zone.Buildings.foreach({ case (id, building) => initBuilding(continentNumber, building.MapId, building) })
      sendResponse(ZonePopulationUpdateMessage(continentNumber, 414, 138, popTR, 138, popNC, 138, popVS, 138, popBO))
      if(continentNumber == 11) sendResponse(ContinentalLockUpdateMessage(continentNumber, PlanetSideEmpire.NC)) // "The NC have captured the NC Sanctuary."
      else if(continentNumber == 12) sendResponse(ContinentalLockUpdateMessage(continentNumber, PlanetSideEmpire.TR)) // "The TR have captured the TR Sanctuary."
      else if(continentNumber == 13) sendResponse(ContinentalLockUpdateMessage(continentNumber, PlanetSideEmpire.VS)) // "The VS have captured the VS Sanctuary."
      else sendResponse(ContinentalLockUpdateMessage(continentNumber, PlanetSideEmpire.NEUTRAL))
      //CaptureFlagUpdateMessage()
      //VanuModuleUpdateMessage()
      //ModuleLimitsMessage()
      sendResponse(ZoneInfoMessage(continentNumber, true, 0))
      sendResponse(ZoneLockInfoMessage(continentNumber, false, true))
      sendResponse(ZoneForcedCavernConnectionsMessage(continentNumber, 0))
      sendResponse(HotSpotUpdateMessage(
        continentNumber,
        1,
        ZoneHotSpotProjector.SpecificHotSpotInfo(player.Faction, zone.HotSpots)
          .map { spot => PacketHotSpotInfo(spot.DisplayLocation.x, spot.DisplayLocation.y, 40) }
      )) //normally set for all zones in bulk; should be fine manually updating per zone like this

      StopBundlingPackets()
    case Zone.Population.PlayerHasLeft(zone, None) =>
      log.info(s"$avatar does not have a body on ${zone.Id}")

    case Zone.Population.PlayerHasLeft(zone, Some(tplayer)) =>
      if(tplayer.isAlive) {
        log.info(s"$tplayer has left zone ${zone.Id}")
      }

    case Zone.Population.PlayerCanNotSpawn(zone, tplayer) =>
      log.warn(s"$tplayer can not spawn in zone ${zone.Id}; why?")

    case Zone.Population.PlayerAlreadySpawned(zone, tplayer) =>
      log.warn(s"$tplayer is already spawned on zone ${zone.Id}; a clerical error?")

    case Zone.Lattice.SpawnPoint(zone_id, spawn_tube) =>
      CancelZoningProcess()
      var (pos, ori) = spawn_tube.SpecificPoint(continent.GUID(player.VehicleSeated) match {
        case Some(obj : Vehicle) if !obj.Destroyed =>
          obj
        case _ =>
          player
      })
      spawn_tube.Owner match {
        case building : Building =>
          log.info(s"Zone.Lattice.SpawnPoint: spawn point on $zone_id in building ${building.MapId} selected")
        case vehicle : Vehicle =>
          log.info(s"Zone.Lattice.SpawnPoint: spawn point on $zone_id at ams ${vehicle.GUID.guid} selected")
        case owner =>
          log.warn(s"Zone.Lattice.SpawnPoint: spawn point on $zone_id at ${spawn_tube.Position} has unexpected owner $owner")
      }
      LoadZonePhysicalSpawnPoint(zone_id, pos, ori, CountSpawnDelay(zone_id, spawn_tube, continent.Id))

    case Zone.Lattice.NoValidSpawnPoint(zone_number, None) =>
      log.warn(s"Zone.Lattice.SpawnPoint: zone $zone_number could not be accessed as requested")
      reviveTimer.cancel
      RequestSanctuaryZoneSpawn(player, zone_number)

    case Zone.Lattice.NoValidSpawnPoint(zone_number, Some(spawn_group)) =>
      log.warn(s"Zone.Lattice.SpawnPoint: zone $zone_number has no available ${player.Faction} targets in spawn group $spawn_group")
      reviveTimer.cancel
      if(spawn_group == 2) {
        sendResponse(ChatMsg(ChatMessageType.CMT_OPEN, false, "", "No friendly AMS is deployed in this region.", None))
        cluster ! Zone.Lattice.RequestSpawnPoint(zone_number, player, 0)
      }
      else {
        RequestSanctuaryZoneSpawn(player, zone_number)
      }

    case msg@Zone.Vehicle.CanNotSpawn(zone, vehicle, reason) =>
      log.warn(s"$msg")

    case msg@Zone.Vehicle.CanNotDespawn(zone, vehicle, reason) =>
      log.warn(s"$msg")

    case Zone.Ground.ItemOnGround(item : BoomerTrigger, pos, orient) =>
      //dropped the trigger, no longer own the boomer; make certain whole faction is aware of that
      val playerGUID = player.GUID
      continent.GUID(item.Companion) match {
        case Some(obj : BoomerDeployable) =>
          val guid = obj.GUID
          val factionChannel = s"${player.Faction}"
          obj.AssignOwnership(None)
          avatar.Deployables.Remove(obj)
          UpdateDeployableUIElements(avatar.Deployables.UpdateUIElement(obj.Definition.Item))
          continent.LocalEvents ! LocalServiceMessage.Deployables(RemoverActor.AddTask(obj, continent))
          obj.Faction = PlanetSideEmpire.NEUTRAL
          sendResponse(SetEmpireMessage(guid, PlanetSideEmpire.NEUTRAL))
          continent.AvatarEvents ! AvatarServiceMessage(factionChannel, AvatarAction.SetEmpire(playerGUID, guid, PlanetSideEmpire.NEUTRAL))
          val info = DeployableInfo(guid, DeployableIcon.Boomer, obj.Position, PlanetSideGUID(0))
          sendResponse(DeployableObjectsInfoMessage(DeploymentAction.Dismiss, info))
          continent.LocalEvents ! LocalServiceMessage(factionChannel, LocalAction.DeployableMapIcon(playerGUID, DeploymentAction.Dismiss, info))
          PutItemOnGround(item, pos, orient)
        case Some(_) | None =>
          //pointless trigger
          val guid = item.GUID
          continent.Ground ! Zone.Ground.RemoveItem(guid) //undo; no callback
          continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.ObjectDelete(PlanetSideGUID(0), guid))
          taskResolver ! GUIDTask.UnregisterObjectTask(item)(continent.GUID)
      }

    case Zone.Ground.ItemOnGround(item : ConstructionItem, pos, orient) =>
      //defensively, reset CItem configuration
      item.FireModeIndex = 0
      item.AmmoTypeIndex = 0
      PutItemOnGround(item, pos, orient)

    case Zone.Ground.ItemOnGround(item : PlanetSideGameObject, pos, orient) =>
      PutItemOnGround(item, pos, orient)

    case Zone.Ground.CanNotDropItem(zone, item, reason) =>
      log.warn(s"DropItem: $player tried to drop a $item on the ground, but $reason")
      if(!item.HasGUID) {
        log.warn(s"DropItem: zone ${continent.Id} contents may be in disarray")
      }

    case Zone.Ground.ItemInHand(item : BoomerTrigger) =>
      if(PutItemInHand(item)) {
        //pick up the trigger, own the boomer; make certain whole faction is aware of that
        continent.GUID(item.Companion) match {
          case Some(obj : BoomerDeployable) =>
            val guid = obj.GUID
            val playerGUID = player.GUID
            val faction = player.Faction
            val factionChannel = s"$faction"
            obj.AssignOwnership(player)
            obj.Faction = faction
            avatar.Deployables.Add(obj)
            UpdateDeployableUIElements(avatar.Deployables.UpdateUIElement(obj.Definition.Item))
            continent.LocalEvents ! LocalServiceMessage.Deployables(RemoverActor.ClearSpecific(List(obj), continent))
            sendResponse(SetEmpireMessage(guid, faction))
            continent.AvatarEvents ! AvatarServiceMessage(factionChannel, AvatarAction.SetEmpire(playerGUID, guid, faction))
            val info = DeployableInfo(obj.GUID, DeployableIcon.Boomer, obj.Position, obj.Owner.getOrElse(PlanetSideGUID(0)))
            sendResponse(DeployableObjectsInfoMessage(DeploymentAction.Build, info))
            continent.LocalEvents ! LocalServiceMessage(factionChannel, LocalAction.DeployableMapIcon(playerGUID, DeploymentAction.Build, info))
          case Some(_) | None => ; //pointless trigger; see Zone.Ground.ItemOnGround(BoomerTrigger, ...)
        }
      }

    case Zone.Ground.ItemInHand(item : Equipment) =>
      PutItemInHand(item)

    case Zone.Ground.CanNotPickupItem(zone, item_guid, _) =>
      zone.GUID(item_guid) match {
        case Some(item) =>
          log.warn(s"DropItem: finding a $item on the ground was suggested, but $player can not reach it")
        case None =>
          log.warn(s"DropItem: finding an item ($item_guid) on the ground was suggested, but $player can not see it")
          sendResponse(ObjectDeleteMessage(item_guid, 0))
      }

    case Zone.Deployable.DeployableIsBuilt(obj, tool) =>
      val index = player.Find(tool) match {
        case Some(x) =>
          x
        case None =>
          player.LastDrawnSlot
      }
      if(avatar.Deployables.Accept(obj) || (avatar.Deployables.Valid(obj) && !avatar.Deployables.Contains(obj))) {
        tool.Definition match {
          case GlobalDefinitions.ace =>
            continent.LocalEvents ! LocalServiceMessage(continent.Id, LocalAction.TriggerEffectLocation(player.GUID, "spawn_object_effect", obj.Position, obj.Orientation))
          case GlobalDefinitions.advanced_ace =>
            sendResponse(GenericObjectActionMessage(player.GUID, 53)) //put fdu down; it will be removed from the client's holster
            continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.PutDownFDU(player.GUID))
          case GlobalDefinitions.router_telepad => ;
          case _ =>
            log.warn(s"Zone.Deployable.DeployableIsBuilt: not sure what kind of construction item to animate - ${tool.Definition}")
        }
        import scala.concurrent.ExecutionContext.Implicits.global
        context.system.scheduler.scheduleOnce(
          obj.Definition.DeployTime milliseconds,
          self,
          WorldSessionActor.FinalizeDeployable(obj, tool, index)
        )
      }
      else {
        TryDropConstructionTool(tool, index, obj.Position)
        sendResponse(ObjectDeployedMessage.Failure(obj.Definition.Name))
        obj.Position = Vector3.Zero
        obj.AssignOwnership(None)
        continent.Deployables ! Zone.Deployable.Dismiss(obj)
      }

    case WorldSessionActor.FinalizeDeployable(obj : SensorDeployable, tool, index) =>
      //motion alarm sensor and sensor disruptor
      StartBundlingPackets()
      DeployableBuildActivity(obj)
      continent.LocalEvents ! LocalServiceMessage(continent.Id, LocalAction.TriggerEffectInfo(player.GUID, "on", obj.GUID, true, 1000))
      CommonDestroyConstructionItem(tool, index)
      FindReplacementConstructionItem(tool, index)
      StopBundlingPackets()

    case WorldSessionActor.FinalizeDeployable(obj : BoomerDeployable, tool, index) =>
      //boomers
      StartBundlingPackets()
      DeployableBuildActivity(obj)
      //TODO sufficiently delete the tool
      sendResponse(ObjectDeleteMessage(tool.GUID, 0))
      continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.ObjectDelete(player.GUID, tool.GUID))
      taskResolver ! GUIDTask.UnregisterEquipment(tool)(continent.GUID)
      val trigger = new BoomerTrigger
      trigger.Companion = obj.GUID
      obj.Trigger = trigger
      val holster = player.Slot(index)
      if(holster.Equipment.contains(tool)) {
        holster.Equipment = None
        taskResolver ! DelayedObjectHeld(player, index, List(PutEquipmentInSlot(player, trigger, index)))
      }
      else {
        //don't know where boomer trigger should go; drop it on the ground
        taskResolver ! NewItemDrop(player, continent, continent.AvatarEvents)(trigger)
      }
      StopBundlingPackets()

    case WorldSessionActor.FinalizeDeployable(obj : ExplosiveDeployable, tool, index) =>
      //mines
      StartBundlingPackets()
      DeployableBuildActivity(obj)
      CommonDestroyConstructionItem(tool, index)
      FindReplacementConstructionItem(tool, index)
      StopBundlingPackets()

    case WorldSessionActor.FinalizeDeployable(obj : ComplexDeployable, tool, index) =>
      //tank_traps, spitfires, deployable field turrets and the deployable_shield_generator
      StartBundlingPackets()
      DeployableBuildActivity(obj)
      CommonDestroyConstructionItem(tool, index)
      FindReplacementConstructionItem(tool, index)
      StopBundlingPackets()

    case WorldSessionActor.FinalizeDeployable(obj : TelepadDeployable, tool, index) =>
      StartBundlingPackets()
      if(obj.Health > 0) {
        val guid = obj.GUID
        //router telepad deployable
        val router = tool.asInstanceOf[Telepad].Router
        //router must exist and be deployed
        continent.GUID(router) match {
          case Some(vehicle : Vehicle) =>
            val routerGUID = router.get
            if(vehicle.Destroyed) {
              //the Telepad was successfully deployed; but, before it could configure, its Router was destroyed
              sendResponse(ChatMsg(ChatMessageType.UNK_229, false, "", "@Telepad_NoDeploy_RouterLost", None))
              continent.LocalEvents ! LocalServiceMessage.Deployables(RemoverActor.AddTask(obj, continent, Some(0 seconds)))
            }
            else {
              log.info(s"FinalizeDeployable: setup for telepad #${guid.guid} in zone ${continent.Id}")
              obj.Router = routerGUID //necessary; forwards link to the router
              DeployableBuildActivity(obj)
              CommonDestroyConstructionItem(tool, index)
              StopBundlingPackets()
              //it takes 60s for the telepad to become properly active
              continent.LocalEvents ! LocalServiceMessage.Telepads(RouterTelepadActivation.AddTask(obj, continent))
            }

          case _ =>
            //the Telepad was successfully deployed; but, before it could configure, its Router was deconstructed
            sendResponse(ChatMsg(ChatMessageType.UNK_229, false, "", "@Telepad_NoDeploy_RouterLost", None))
            continent.LocalEvents ! LocalServiceMessage.Deployables(RemoverActor.AddTask(obj, continent, Some(0 seconds)))
        }
      }
      StopBundlingPackets()

    case WorldSessionActor.FinalizeDeployable(obj : PlanetSideGameObject with Deployable, tool, index) =>
      val guid = obj.GUID
      val definition = obj.Definition
      StartBundlingPackets()
      sendResponse(GenericObjectActionMessage(guid, 21)) //reset build cooldown
      sendResponse(ObjectDeployedMessage.Failure(definition.Name))
      log.warn(s"FinalizeDeployable: deployable ${definition.asInstanceOf[BaseDeployableDefinition].Item}@$guid not handled by specific case")
      log.warn(s"FinalizeDeployable: deployable will be cleaned up, but may not get unregistered properly")
      TryDropConstructionTool(tool, index, obj.Position)
      obj.Position = Vector3.Zero
      continent.Deployables ! Zone.Deployable.Dismiss(obj)
      StopBundlingPackets()

    //!!only dispatch Zone.Deployable.Dismiss from WSA as cleanup if the target deployable was never fully introduced
    case Zone.Deployable.DeployableIsDismissed(obj : TurretDeployable) =>
      taskResolver ! GUIDTask.UnregisterDeployableTurret(obj)(continent.GUID)

    //!!only dispatch Zone.Deployable.Dismiss from WSA as cleanup if the target deployable was never fully introduced
    case Zone.Deployable.DeployableIsDismissed(obj) =>
      taskResolver ! GUIDTask.UnregisterObjectTask(obj)(continent.GUID)

    case InterstellarCluster.ClientInitializationComplete() =>
      LivePlayerList.Add(avatar.CharId, avatar)
      traveler = new Traveler(self, continent.Id)
      StartBundlingPackets()
      //PropertyOverrideMessage
      sendResponse(PlanetsideAttributeMessage(PlanetSideGUID(0), 112, 0)) // disable festive backpacks
      sendResponse(ReplicationStreamMessage(5, Some(6), Vector.empty)) //clear squad list
      sendResponse(FriendsResponse(FriendAction.InitializeFriendList, 0, true, true, Nil))
      sendResponse(FriendsResponse(FriendAction.InitializeIgnoreList, 0, true, true, Nil))
      //the following subscriptions last until character switch/logout
      chatService ! Service.Join("local")
      chatService ! Service.Join("squad")
      chatService ! Service.Join("platoon")
      chatService ! Service.Join("voice")
      chatService ! Service.Join("tell")
      chatService ! Service.Join("broadcast")
      chatService ! Service.Join("note")
      chatService ! Service.Join("gm")
      galaxyService ! Service.Join("galaxy") //for galaxy-wide messages
      galaxyService ! Service.Join(s"${avatar.faction}") //for hotspots
      squadService ! Service.Join(s"${avatar.faction}") //channel will be player.Faction
      squadService ! Service.Join(s"${avatar.CharId}") //channel will be player.CharId (in order to work with packets)
      player.Zone match {
        case Zone.Nowhere =>
          RequestSanctuaryZoneSpawn(player, currentZone = 0)
        case zone =>
          log.info(s"Zone ${zone.Id} will now load")
          loadConfZone = true
          val oldZone = continent
          continent = zone
          //the only zone-level event system subscription necessary before BeginZoningMessage (for persistence purposes)
          continent.AvatarEvents ! Service.Join(player.Name)
          persist()
          oldZone.AvatarEvents ! Service.Leave()
          oldZone.LocalEvents ! Service.Leave()
          oldZone.VehicleEvents ! Service.Leave()
          self ! NewPlayerLoaded(player)
      }
      StopBundlingPackets()

    case InterstellarCluster.GiveWorld(zoneId, zone) =>
      log.info(s"Zone $zoneId will now load")
      loadConfZone = true
      val oldZone = continent
      continent = zone
      //the only zone-level event system subscription necessary before BeginZoningMessage (for persistence purposes)
      continent.AvatarEvents ! Service.Join(player.Name)
      persist()
      oldZone.AvatarEvents ! Service.Leave()
      oldZone.LocalEvents ! Service.Leave()
      oldZone.VehicleEvents ! Service.Leave()
      continent.Population ! Zone.Population.Join(avatar)
      interstellarFerry match {
        case Some(vehicle) if vehicle.PassengerInSeat(player).contains(0) =>
          taskResolver ! RegisterDrivenVehicle(vehicle, player)
        case _ =>
          taskResolver ! RegisterNewAvatar(player)
      }

    case msg@Zoning.InstantAction.Located(zone, _, spawn_point) =>
      //in between subsequent reply messages, it does not matter if the destination changes
      //so long as there is at least one destination at all (including the fallback)
      if(ContemplateZoningResponse(Zoning.InstantAction.Request(player.Faction))) {
        val (pos, ori) = spawn_point.SpecificPoint(player)
        SpawnThroughZoningProcess(zone, pos, ori)
      }
      else if(zoningStatus != Zoning.Status.None) {
        instantActionFallbackDestination = Some(msg)
      }

    case Zoning.InstantAction.NotLocated() =>
      instantActionFallbackDestination match {
        case Some(Zoning.InstantAction.Located(zone, _, spawn_point)) if spawn_point.Owner.Faction == player.Faction && !spawn_point.Offline =>
          if(ContemplateZoningResponse(Zoning.InstantAction.Request(player.Faction))) {
            val (pos, ori) = spawn_point.SpecificPoint(player)
            SpawnThroughZoningProcess(zone, pos, ori)
          }
          else if(zoningCounter == 0) {
            CancelZoningProcessWithReason("@InstantActionNoHotspotsAvailable")
          }
        case _ =>
          //no instant action available
          CancelZoningProcessWithReason("@InstantActionNoHotspotsAvailable")
      }

    case Zoning.Recall.Located(zone, spawn_point) =>
      if(ContemplateZoningResponse(Zoning.Recall.Request(player.Faction, zone.Id))) {
        val (pos, ori) = spawn_point.SpecificPoint(player)
        SpawnThroughZoningProcess(zone, pos, ori)
      }

    case Zoning.Recall.Denied(reason) =>
      CancelZoningProcessWithReason(s"@norecall_sanctuary_$reason", Some(ChatMessageType.CMT_QUIT))

    case ZoningReset() =>
      CancelZoningProcess()

    case NewPlayerLoaded(tplayer) =>
      //new zone
      log.info(s"Player ${tplayer.Name} has been loaded")
      player = tplayer
      //LoadMapMessage causes the client to send BeginZoningMessage, eventually leading to SetCurrentAvatar
      val weaponsEnabled = (continent.Map.Name != "map11" && continent.Map.Name != "map12" && continent.Map.Name != "map13")
      sendResponse(LoadMapMessage(continent.Map.Name, continent.Id, 40100, 25, weaponsEnabled, continent.Map.Checksum))
      setupAvatarFunc() //important! the LoadMapMessage must be processed by the client before the avatar is created
      persist()

    case PlayerLoaded(tplayer) =>
      //same zone
      log.info(s"Player ${tplayer.Name} will respawn")
      player = tplayer
      setupAvatarFunc()
      persist()
      self ! SetCurrentAvatar(tplayer)

    case PlayerFailedToLoad(tplayer) =>
      player.Continent match {
        case _ =>
          failWithError(s"${tplayer.Name} failed to load anywhere")
      }

    case SetCurrentAvatar(tplayer) =>
      if(tplayer.Actor == ActorRef.noSender) {
        respawnTimer = context.system.scheduler.scheduleOnce(100 milliseconds, self, SetCurrentAvatar(tplayer))
      }
      else {
        HandleSetCurrentAvatar(tplayer)
      }

    case NtuCharging(tplayer, vehicle) =>
      HandleNtuCharging(tplayer, vehicle)

    case NtuDischarging(tplayer, vehicle, silo_guid) =>
      HandleNtuDischarging(tplayer, vehicle, silo_guid)

    case Vitality.DamageResolution(target : TelepadDeployable, _) =>
      //telepads
      if(target.Health <= 0) {
        //update if destroyed
        target.Destroyed = true
        val guid = target.GUID
        continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.ObjectDelete(player.GUID, guid))
        Deployables.AnnounceDestroyDeployable(target, Some(0 seconds))
      }

    case Vitality.DamageResolution(target : PlanetSideGameObject, _) =>
      log.warn(s"Vital target ${target.Definition.Name} damage resolution not supported using this method")

    case ResponseToSelf(pkt) =>
      //log.info(s"Received a direct message: $pkt")
      sendResponse(pkt)

    case ReceiveAccountData(account) =>
      log.info(s"Retrieved account data for accountId = ${account.AccountId}")
      this.account = account
      admin = account.GM
      Database.getConnection.connect.onComplete {
        case scala.util.Success(connection) =>
          Database.query(connection.sendPreparedStatement(
            "SELECT gm FROM accounts where id=?", Array(account.AccountId)
          )).onComplete {
            case scala.util.Success(queryResult) =>
              if(connection.isConnected) connection.disconnect
              queryResult match {
                case row : ArrayRowData => // If we got a row from the database
                  log.info(s"ReceiveAccountData: ready to load character list for ${account.Username}")
                  self ! ListAccountCharacters()
                case _ => // If the account didn't exist in the database
                  log.error(s"ReceiveAccountData: ${account.Username} data not found, or unexpected query result format - ${queryResult.getClass}")
                  Thread.sleep(50)
                  sendResponse(DropSession(sessionId, "You should not exist!"))
              }
            case scala.util.Failure(e) =>
              if(connection.isConnected) connection.disconnect
              log.error(s"ReceiveAccountData: ${e.getMessage}")
              Thread.sleep(50)
          }
        case scala.util.Failure(e) =>
          log.error(s"RetrieveAccountData: no connection ${e.getMessage}")
      }

    case LoadedRemoteProjectile(projectile_guid, Some(projectile)) =>
      if(projectile.profile.ExistsOnRemoteClients) {
        //spawn projectile on other clients
        val definition = projectile.Definition
        continent.AvatarEvents ! AvatarServiceMessage(
          continent.Id,
          AvatarAction.LoadProjectile(player.GUID, definition.ObjectId, projectile_guid, definition.Packet.ConstructorData(projectile).get)
        )
      }
      //immediately slated for deletion?
      CleanUpRemoteProjectile(projectile.GUID, projectile)

    case LoadedRemoteProjectile(projectile_guid, None) =>
      continent.GUID(projectile_guid) match {
        case Some(obj : Projectile) if obj.profile.ExistsOnRemoteClients =>
          //spawn projectile on other clients
          val definition = obj.Definition
          continent.AvatarEvents ! AvatarServiceMessage(
            continent.Id,
            AvatarAction.LoadProjectile(player.GUID, definition.ObjectId, projectile_guid, definition.Packet.ConstructorData(obj).get)
          )
        case _ => ;
      }

    case PlayerToken.LoginInfo(name, Zone.Nowhere, _) =>
      log.info(s"LoginInfo: player $name is considered a new character")
      //TODO poll the database for saved zone and coordinates?
      persist = UpdatePersistence(sender)
      //the original standard sim way to load data for this user for the user's avatar and player
      import net.psforever.types.CertificationType._
      val avatar = this.avatar
      avatar.Certifications += StandardAssault
      avatar.Certifications += MediumAssault
      avatar.Certifications += StandardExoSuit
      avatar.Certifications += AgileExoSuit
      avatar.Certifications += ReinforcedExoSuit
      avatar.Certifications += ATV
      //        avatar.Certifications += Harasser
      avatar.Certifications += InfiltrationSuit
      avatar.Certifications += UniMAX
      avatar.Certifications += Medical
      avatar.Certifications += AdvancedMedical
      avatar.Certifications += Engineering
      avatar.Certifications += CombatEngineering
      avatar.Certifications += FortificationEngineering
      avatar.Certifications += AssaultEngineering
      avatar.Certifications += Hacking
      avatar.Certifications += AdvancedHacking
      avatar.Certifications += ElectronicsExpert
      avatar.Certifications += Sniping
      avatar.Certifications += AntiVehicular
      avatar.Certifications += HeavyAssault
      avatar.Certifications += SpecialAssault
      avatar.Certifications += EliteAssault
      avatar.Certifications += GroundSupport
      avatar.Certifications += GroundTransport
      avatar.Certifications += Flail
      avatar.Certifications += Switchblade
      avatar.Certifications += AssaultBuggy
      avatar.Certifications += ArmoredAssault1
      avatar.Certifications += ArmoredAssault2
      avatar.Certifications += AirCavalryScout
      avatar.Certifications += AirCavalryAssault
      avatar.Certifications += AirCavalryInterceptor
      avatar.Certifications += AirSupport
      avatar.Certifications += GalaxyGunship
      avatar.Certifications += Phantasm
      //        avatar.Certifications += BattleFrameRobotics
      //        avatar.Certifications += BFRAntiInfantry
      //        avatar.Certifications += BFRAntiAircraft
      InitializeDeployableQuantities(avatar) //set deployables ui elements
      AwardBattleExperiencePoints(avatar, 20000000L)
      avatar.CEP = 600000
      avatar.Implants(0).Unlocked = true
      avatar.Implants(0).Implant = GlobalDefinitions.surge
      avatar.Implants(1).Unlocked = true
      avatar.Implants(1).Implant = GlobalDefinitions.audio_amplifier
      avatar.Implants(2).Unlocked = true
      avatar.Implants(2).Implant = GlobalDefinitions.targeting

      player = new Player(avatar)
      //xy-coordinates indicate sanctuary spawn bias:
      player.Position = math.abs(scala.util.Random.nextInt() % avatar.name.hashCode % 4) match {
        case 0 => Vector3(8192, 8192, 0) //NE
        case 1 => Vector3(8192, 0, 0) //SE
        case 2 => Vector3(0, 0, 0) //SW
        case 3 => Vector3(0, 8192, 0) //NW
      }
      LoadClassicDefault(player)
      LoadDataBaseLoadouts(player).onComplete {
        case _ =>
          UpdateLoginTimeThenDoClientInitialization()
      }

    case PlayerToken.LoginInfo(playerName, inZone, pos) =>
      log.info(s"LoginInfo: player $playerName is already logged in zone ${inZone.Id}; rejoining that character")
      persist = UpdatePersistence(sender)
      //tell the old WSA to kill itself by using its own subscriptions against itself
      inZone.AvatarEvents ! AvatarServiceMessage(playerName, AvatarAction.TeardownConnection())
      //find and reload previous player
      (inZone.Players.find(p => p.name.equals(playerName)), inZone.LivePlayers.find(p => p.Name.equals(playerName))) match {
        case (Some(a), Some(p)) if p.isAlive =>
          //rejoin current avatar/player
          avatar = a
          player = p
          persist()
          setupAvatarFunc = AvatarRejoin
          UpdateLoginTimeThenDoClientInitialization()

        case (Some(a), Some(p)) =>
          //convert player to a corpse (unless in vehicle); go to deployment map
          avatar = a
          player = p
          persist()
          player.Zone = inZone
          setupAvatarFunc = AvatarDeploymentPassOver
          beginZoningSetCurrentAvatarFunc = SetCurrentAvatarUponDeployment
          p.Release
          inZone.Population ! Zone.Population.Release(avatar)
          if(p.VehicleSeated.isEmpty) {
            PrepareToTurnPlayerIntoCorpse(p, inZone)
          }
          else {
            inZone.GUID(p.VehicleSeated) match {
              case Some(v : Vehicle) if v.Destroyed =>
                v.Actor ! Vehicle.Deconstruct(
                  if(v.Flying) {
                  //TODO gravity
                  None //immediate deconstruction
                }
                else {
                  v.Definition.DeconstructionTime //normal deconstruction
                })
              case _ => ;
            }
          }
          UpdateLoginTimeThenDoClientInitialization()

        case (Some(a), None) =>
          //respawn avatar as a new player; go to deployment map
          avatar = a
          player = inZone.Corpses.find(c => c.Name == playerName) match {
            case Some(c) =>
              c
            case None =>
              val tplayer = Player(a) //throwaway
              tplayer.Position = pos
              tplayer.Release //for proper respawn
              tplayer.Zone = inZone
              tplayer
          }
          setupAvatarFunc = AvatarDeploymentPassOver
          beginZoningSetCurrentAvatarFunc = SetCurrentAvatarUponDeployment
          UpdateLoginTimeThenDoClientInitialization()

        case _ =>
          //fall back to sanctuary/prior?
          log.error(s"LoginInfo: player $playerName could not be found in game world")
          self ! PlayerToken.LoginInfo(playerName, Zone.Nowhere, pos)
      }

    case default =>
      log.warn(s"Invalid packet class received: $default from $sender")
  }

  /**
    * Update this player avatar for persistence.
    * @param persistRef reference to the persistence monitor
    */
  def UpdatePersistence(persistRef : ActorRef)() : Unit = {
    persistRef ! AccountPersistenceService.Update(player.Name, continent, player.Position)
  }

  /**
    * Do not update this player avatar for persistence.
    */
  def NoPersistence() : Unit = {}

  /**
    * Common action to perform before starting the transition to client initialization.
    * That the operation completes before client initialization begins is important.
    */
  def UpdateLoginTimeThenDoClientInitialization() : Unit = {
    UpdateCharacterLoginTime(avatar.CharId).onComplete {
      case _ =>
        cluster ! InterstellarCluster.RequestClientInitialization()
    }
  }

  /**
    * Updating the character login time is an important bookkeeping aspect of a player who is (re)joining the server.
    * Logging into the server or relogging from an unexpected connection loss both qualify to update the time.<br>
    * <br>
    * The operation requires a database connection and completion of a database transaction,
    * both of which must completed independently of any subsequent tasking,
    * especially if that future tasking may require database use.
    * @see `Connection.sendPreparedStatement`
    * @see `Database.getConnection`
    * @see `Future`
    * @see `java.sql.Timestamp`
    * @see `Promise`
    * @param charId the character unique identifier number to update in the system
    * @return a `Future` predicated by the "promise" of the task being completed
    */
  def UpdateCharacterLoginTime(charId : Long) : Future[Any] = {
    val result : Promise[Any] = Promise[Any]()
    Database.getConnection.connect.onComplete {
      case scala.util.Success(connection) =>
        Database.query(connection.sendPreparedStatement(
          "UPDATE characters SET last_login = ? where id=?", Array(new java.sql.Timestamp(System.currentTimeMillis), charId)
        )).onComplete {
          case _ =>
            if(connection.isConnected) connection.disconnect
            result success true
        }
      case _ =>
        val msg = s"UpdateCharacterLoginTime: could not update login time for $charId"
        log.error(msg)
        result failure new Throwable(msg)
    }
    result.future
  }

  /**
    * A zoning message was received.
    * That doesn't matter.
    * In what stage of the zoning determination process is the client, and what is the next stage.<br>
    * <br>
    * To perform any actions involving zoning, an initial request must have been dispatched and marked as dispatched.
    * When invoked after, the process will switch over to a countdown of time until the zoning actually occurs.
    * The origin will be evaluated based on comparison of faction affinity with the client's player
    * and from that an initial time and a message will be generated.
    * Afterwards, the process will queue another inquiry for another zoning response.
    * Each time 5s of the countdown passes, another message will be sent and received;
    * and, this is another pass of the countdown.<br>
    * <br>
    * Once the countdown reaches 0, the transportation that has been promised by the zoning attempt may begin.
    * @param nextStepMsg send this message to the `InterGalacticCluster` for the next step of the zoning process,
    *                    if there will be a next step
    * @return `true`, if the zoning transportation process should start;
    *         `false`, otherwise
    */
  def ContemplateZoningResponse(nextStepMsg : Any) : Boolean = {
    val descriptor = zoningType.toString.toLowerCase
    if(zoningStatus == Zoning.Status.Request) {
      DeactivateImplants()
      zoningStatus = Zoning.Status.Countdown
      val (time, origin) = ZoningStartInitialMessageAndTimer()
      zoningCounter = time
      sendResponse(ChatMsg(ChatMessageType.CMT_QUIT, false, "", s"@${descriptor}_$origin", None))
      import scala.concurrent.ExecutionContext.Implicits.global
      zoningReset.cancel
      zoningTimer.cancel
      zoningReset = context.system.scheduler.scheduleOnce(10 seconds, self, ZoningReset())
      zoningTimer = context.system.scheduler.scheduleOnce(5 seconds, cluster, nextStepMsg)
      false
    }
    else if(zoningStatus == Zoning.Status.Countdown) {
      zoningCounter -= 5
      zoningReset.cancel
      zoningTimer.cancel
      if(zoningCounter > 0) {
        if(zoningCountdownMessages.contains(zoningCounter)) {
          sendResponse(ChatMsg(zoningChatMessageType, false, "", s"@${descriptor}_$zoningCounter", None))
        }
        //again
        zoningReset = context.system.scheduler.scheduleOnce(10 seconds, self, ZoningReset())
        zoningTimer = context.system.scheduler.scheduleOnce(5 seconds, cluster, nextStepMsg)
        false
      }
      else {
        //zoning deployment
        true
      }
    }
    else {
      false
    }
  }

  /**
    * The primary method of determination involves the faction affinity of the most favorable available region subset,
    * e.g., in the overlapping sphere of influences of a friendly field tower and an enemy major facility,
    * the time representative of the the tower has priority.
    * When no spheres of influence are being encroached, one is considered "in the wilderness".
    * The messaging is different but the location is normally treated the same as if in a neutral sphere of influence.
    * Being anywhere in one's faction's own sanctuary is a special case.
    * @return a `Tuple` composed of the initial countdown time and the descriptor for message composition
    */
  def ZoningStartInitialMessageAndTimer() : (Int, String) = {
    val location = (if(Zones.SanctuaryZoneNumber(player.Faction) == continent.Number) {
      Zoning.Time.Sanctuary
    }
    else {
      val playerPosition = player.Position.xy
      (continent.Buildings
        .values
        .filter { building =>
          val radius = building.Definition.SOIRadius
          Vector3.DistanceSquared(building.Position.xy, playerPosition) < radius * radius
        }) match {
        case Nil =>
          Zoning.Time.None
        case List(building) =>
          if(building.Faction == player.Faction) Zoning.Time.Friendly
          else if(building.Faction == PlanetSideEmpire.NEUTRAL) Zoning.Time.Neutral
          else Zoning.Time.Enemy
        case buildings =>
          if(buildings.exists(_.Faction == player.Faction)) Zoning.Time.Friendly
          else if(buildings.exists(_.Faction == PlanetSideEmpire.NEUTRAL)) Zoning.Time.Neutral
          else Zoning.Time.Enemy
      }
    })
    (location.id, location.descriptor.toLowerCase)
  }

  /**
    * Use the zoning process using some spawnable entity in the destination zone.
    * @param zone          the destination zone
    * @param spawnPosition the destination spawn position
    * @param spawnOrientation the destination spawn orientation
    */
  def SpawnThroughZoningProcess(zone : Zone, spawnPosition : Vector3, spawnOrientation : Vector3) : Unit = {
    CancelZoningProcess()
    PlayerActionsToCancel()
    CancelAllProximityUnits()
    continent.Population ! Zone.Population.Release(avatar)
    val respawnTime : Long = if(zone.Number == continent.Number) {
      //distract the user while he slips through the cracks of reality
      GoToDeploymentMap()
      1L
    }
    else {
      //zone loading will take long enough
      0L
    }
    LoadZonePhysicalSpawnPoint(zone.Id, spawnPosition, spawnOrientation, respawnTime)
  }

  /**
    * You can't instant action to respond to some activity using a droppod!
    * You can't.
    * You just can't.
    * @param zone            the destination zone
    * @param hotspotPosition where is the hotspot that is being addressed
    * @param spawnPosition   the destination spawn position (may not be related to a literal `SpawnPoint` entity)
    */
  def YouCantInstantActionUsingDroppod(zone : Zone, hotspotPosition : Vector3, spawnPosition : Vector3) : Unit = {
    CancelZoningProcess()
    PlayerActionsToCancel()
    CancelAllProximityUnits()
    //find a safe drop point
    var targetBuildings = zone.Buildings.values
    var whereToDroppod = spawnPosition.xy
    while(targetBuildings.nonEmpty) {
      (targetBuildings
        .filter { building =>
          val radius = building.Definition.SOIRadius
          Vector3.DistanceSquared(building.Position.xy, whereToDroppod) < radius * radius
        }) match {
        case Nil =>
          //no soi interference
          targetBuildings = Nil
        case List(building) =>
          //blocked by a single soi; find space just outside of this soi and confirm no new overlap
          val radius = Vector3(0, building.Definition.SOIRadius + 5, 0)
          whereToDroppod = building.Position.xy + Vector3.Rz(radius, math.abs(scala.util.Random.nextInt() % 360))
        case buildings =>
          //probably blocked by a facility and its tower (maximum overlap potential is 2?); find space outside of largest soi
          val largestBuilding = buildings.maxBy(_.Definition.SOIRadius)
          val radius = Vector3(0, largestBuilding.Definition.SOIRadius + 5, 0)
          whereToDroppod = largestBuilding.Position.xy + Vector3.Rz(radius, math.abs(scala.util.Random.nextInt() % 360))
          targetBuildings = buildings
      }
    }
    //droppod action
    val droppod = Vehicle(GlobalDefinitions.droppod)
    droppod.Faction = player.Faction
    droppod.Position = whereToDroppod.xy + Vector3.z(1024)
    droppod.Orientation = Vector3.z(180) //you always seems to land looking south; don't know why
    droppod.Seats(0).Occupant = player
    droppod.GUID = PlanetSideGUID(0) //droppod is not registered, we must jury-rig this
    droppod.Invalidate() //now, we must short-circuit the jury-rig
    interstellarFerry = Some(droppod) //leverage vehicle gating
    player.Position = droppod.Position
    continent.Population ! Zone.Population.Release(avatar)
    LoadZonePhysicalSpawnPoint(zone.Id, droppod.Position, Vector3.Zero, 0L)
    /* Don't even think about it. */
  }

  /**
    * The user no longer expects to perform a zoning event for this reason.
    * @param msg the message to the user
    */
  def CancelZoningProcessWithDescriptiveReason(msg : String) : Unit = {
    CancelZoningProcessWithReason(s"@${zoningType.toString.toLowerCase}_$msg", Some(zoningChatMessageType))
  }

  /**
    * The user no longer expects to perform a zoning event for this reason.
    * @param msg the message to the user
    * @param msgType the type of message, influencing how it is presented to the user;
    *                normally, this message uses the same value as `zoningChatMessageType`s
    *                defaults to `None`
    */
  def CancelZoningProcessWithReason(msg : String, msgType : Option[ChatMessageType.Value] = None) : Unit = {
    if(zoningStatus > Zoning.Status.None) {
      sendResponse(ChatMsg(msgType.getOrElse(zoningChatMessageType), false, "", msg, None))
    }
    CancelZoningProcess()
  }

  /**
    * The user no longer expects to perform a zoning event,
    * or the process is merely resetting its internal state.
    */
  def CancelZoningProcess() : Unit = {
    zoningTimer.cancel
    zoningReset.cancel
    zoningType = Zoning.Method.None
    zoningStatus = Zoning.Status.None
    zoningCounter = 0
    //instant action exclusive field
    instantActionFallbackDestination = None
  }

  /**
    * na
    * @param toChannel na
    * @param guid      na
    * @param reply     na
    */
  def HandleAvatarServiceResponse(toChannel : String, guid : PlanetSideGUID, reply : AvatarResponse.Response) : Unit = {
    val tplayer_guid = if(player.HasGUID) player.GUID
    else PlanetSideGUID(0)
    reply match {
      case AvatarResponse.TeardownConnection() =>
        log.info("ending session by event system request (relog)")
        context.stop(self)

      case AvatarResponse.SendResponse(msg) =>
        sendResponse(msg)

      case AvatarResponse.SendResponseTargeted(target_guid, msg) =>
        if(tplayer_guid == target_guid) {
          sendResponse(msg)
        }

      case AvatarResponse.Revive(target_guid) =>
        if(tplayer_guid == target_guid) {
          reviveTimer.cancel
          deadState = DeadState.Alive
          player.Revive
          val health = player.Health
          sendResponse(PlanetsideAttributeMessage(target_guid, 0, health))
          sendResponse(AvatarDeadStateMessage(DeadState.Alive, 0, 0, player.Position, player.Faction, true))
          continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttributeToAll(target_guid, 0, health))
        }

      case AvatarResponse.ArmorChanged(suit, subtype) =>
        if(tplayer_guid != guid) {
          sendResponse(ArmorChangedMessage(guid, suit, subtype))
        }

      case AvatarResponse.ChangeAmmo(weapon_guid, weapon_slot, previous_guid, ammo_id, ammo_guid, ammo_data) =>
        if(tplayer_guid != guid) {
          sendResponse(ObjectDetachMessage(weapon_guid, previous_guid, Vector3.Zero, 0))
          sendResponse(
            ObjectCreateMessage(
              ammo_id,
              ammo_guid,
              ObjectCreateMessageParent(weapon_guid, weapon_slot),
              ammo_data
            )
          )
          sendResponse(ChangeAmmoMessage(weapon_guid, 1))
        }

      case AvatarResponse.ChangeFireMode(item_guid, mode) =>
        if(tplayer_guid != guid) {
          sendResponse(ChangeFireModeMessage(item_guid, mode))
        }

      case AvatarResponse.ChangeFireState_Start(weapon_guid) =>
        if(tplayer_guid != guid) {
          sendResponse(ChangeFireStateMessage_Start(weapon_guid))
        }

      case AvatarResponse.ChangeFireState_Stop(weapon_guid) =>
        if(tplayer_guid != guid) {
          sendResponse(ChangeFireStateMessage_Stop(weapon_guid))
        }

      case AvatarResponse.ConcealPlayer() =>
        sendResponse(GenericObjectActionMessage(guid, 9))

      case AvatarResponse.EnvironmentalDamage(target, source, amount) =>
        if(player.isAlive && amount > 0) {
          val playerGUID = player.GUID
          val armor = player.Armor
          val capacitor = player.Capacitor
          val originalHealth = player.Health
          //history
          continent.GUID(source) match {
            case Some(obj : Painbox) =>
              player.History(DamageFromPainbox(PlayerSource(player), obj, amount))
            case _ => ;
          }
          CancelZoningProcessWithDescriptiveReason("cancel_dmg")
          player.Health = originalHealth - amount
          sendResponse(PlanetsideAttributeMessage(target, 0, player.Health))
          continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(target, 0, player.Health))
          damageLog.info(s"${player.Name}-infantry: BEFORE=$originalHealth/$armor/$capacitor, AFTER=${player.Health}/$armor/$capacitor, CHANGE=$amount/0/0")
          if(player.Health == 0 && player.isAlive) {
            player.Actor ! Player.Die()
          }
        }

      case AvatarResponse.DeactivateImplantSlot(slot) =>
        sendResponse(AvatarImplantMessage(PlanetSideGUID(player.GUID.guid), ImplantAction.Activation, slot, 0))

      case AvatarResponse.ActivateImplantSlot(slot) =>
        sendResponse(AvatarImplantMessage(PlanetSideGUID(player.GUID.guid), ImplantAction.Activation, slot, 1))

      case AvatarResponse.Destroy(victim, killer, weapon, pos) =>
        // guid = victim // killer = killer ;)
        sendResponse(DestroyMessage(victim, killer, weapon, pos))

      case AvatarResponse.DestroyDisplay(killer, victim, method, unk) =>
        sendResponse(DestroyDisplayMessage(killer, victim, method, unk))

      case AvatarResponse.DropItem(pkt) =>
        if(tplayer_guid != guid) {
          sendResponse(pkt)
        }

      case AvatarResponse.EquipmentInHand(pkt) =>
        if(tplayer_guid != guid) {
          sendResponse(pkt)
        }

      case AvatarResponse.GenericObjectAction(object_guid, action_code) =>
        if(tplayer_guid != guid) {
          sendResponse(GenericObjectActionMessage(object_guid, action_code))
        }

      case AvatarResponse.HitHint(source_guid) =>
        if(player.isAlive) {
          sendResponse(HitHint(source_guid, guid))
          CancelZoningProcessWithDescriptiveReason("cancel_dmg")
        }

      case AvatarResponse.Killed() =>
        val respawnTimer = 300000 //milliseconds
        ToggleMaxSpecialState(enable = false)
        zoningStatus = Zoning.Status.None
        deadState = DeadState.Dead
        continent.GUID(player.VehicleSeated) match {
          case Some(obj : Vehicle) =>
            TotalDriverVehicleControl(obj)
            UnAccessContents(obj)
          case _ => ;
        }
        PlayerActionsToCancel()
        CancelAllProximityUnits()
        CancelZoningProcessWithDescriptiveReason("cancel")
        if(shotsWhileDead > 0) {
          log.warn(s"KillPlayer/SHOTS_WHILE_DEAD: client of ${avatar.name} fired $shotsWhileDead rounds while character was dead on server")
          shotsWhileDead = 0
        }
        import scala.concurrent.ExecutionContext.Implicits.global
        reviveTimer = context.system.scheduler.scheduleOnce(respawnTimer milliseconds, cluster, Zone.Lattice.RequestSpawnPoint(Zones.SanctuaryZoneNumber(player.Faction), player, 7))

      case AvatarResponse.LoadPlayer(pkt) =>
        if(tplayer_guid != guid) {
          sendResponse(pkt)
        }

      case AvatarResponse.LoadProjectile(pkt) =>
        if(tplayer_guid != guid) {
          sendResponse(pkt)
        }

      case AvatarResponse.ObjectDelete(item_guid, unk) =>
        if(tplayer_guid != guid) {
          sendResponse(ObjectDeleteMessage(item_guid, unk))
        }

      case AvatarResponse.ObjectHeld(slot) =>
        if(tplayer_guid != guid) {
          sendResponse(ObjectHeldMessage(guid, slot, false))
        }

      case AvatarResponse.PlanetsideAttribute(attribute_type, attribute_value) =>
        if(tplayer_guid != guid) {
          sendResponse(PlanetsideAttributeMessage(guid, attribute_type, attribute_value))
        }

      case AvatarResponse.PlanetsideAttributeToAll(attribute_type, attribute_value) =>
        sendResponse(PlanetsideAttributeMessage(guid, attribute_type, attribute_value))

      case AvatarResponse.PlanetsideAttributeSelf(attribute_type, attribute_value) =>
        if(tplayer_guid == guid) {
          sendResponse(PlanetsideAttributeMessage(guid, attribute_type, attribute_value))
        }

      case AvatarResponse.PlayerState(pos, vel, yaw, pitch, yaw_upper, seq_time, is_crouching, is_jumping, jump_thrust, is_cloaking, spectating, weaponInHand) =>
        if(tplayer_guid != guid) {
          val now = System.currentTimeMillis()
          val (location, time, distanceSq) : (Vector3, Long, Float) = if(spectating) {
            val r = new scala.util.Random
            val r1 = 2 + r.nextInt(30)
            val r2 = 2 + r.nextInt(4000)
            (Vector3(r2, r2, r1), 0L, 0f)
          }
          else {
            val before = player.lastSeenStreamMessage(guid.guid)
            val dist = Vector3.DistanceSquared(player.Position, pos)
            (pos, now - before, dist)
          }
          if(spectating ||
            ((distanceSq < 900 || weaponInHand) && time > 200) ||
            (distanceSq < 10000 && time > 500) ||
            (distanceSq < 160000 && (
              (is_jumping || time < 200)) ||
              (!WorldEntity.isMoving(vel) && time > 2000) ||
              (time > 1000)) ||
            (distanceSq > 160000 && time > 5000)) {
            sendResponse(
              PlayerStateMessage(
                guid,
                location,
                vel,
                yaw,
                pitch,
                yaw_upper,
                timestamp = 0,
                is_crouching,
                is_jumping,
                jump_thrust,
                is_cloaking
              )
            )
            player.lastSeenStreamMessage(guid.guid) = now
          }
        }

      case AvatarResponse.ProjectileExplodes(projectile_guid, projectile) =>
        sendResponse(ProjectileStateMessage(projectile_guid, projectile.Position, Vector3.Zero, projectile.Orientation, 0, true, PlanetSideGUID(0)))
        sendResponse(ObjectDeleteMessage(projectile_guid, 2))

      case AvatarResponse.ProjectileAutoLockAwareness(mode) =>
        sendResponse(GenericActionMessage(mode))

      case AvatarResponse.ProjectileState(projectile_guid, shot_pos, shot_vel, shot_orient, seq, end, target_guid) =>
        if(tplayer_guid != guid) {
          sendResponse(ProjectileStateMessage(projectile_guid, shot_pos, shot_vel, shot_orient, seq, end, target_guid))
        }

      case AvatarResponse.PutDownFDU(target) =>
        if(tplayer_guid != guid) {
          sendResponse(GenericObjectActionMessage(target, 53))
        }

      case AvatarResponse.Release(tplayer) =>
        if(tplayer_guid != guid) {
          TurnPlayerIntoCorpse(tplayer)
        }

      case AvatarResponse.Reload(item_guid) =>
        if(tplayer_guid != guid) {
          sendResponse(ReloadMessage(item_guid, 1, 0))
        }

      case AvatarResponse.SetEmpire(object_guid, faction) =>
        if(tplayer_guid != guid) {
          sendResponse(SetEmpireMessage(object_guid, faction))
        }

      case AvatarResponse.StowEquipment(target, slot, item) =>
        if(tplayer_guid != guid) {
          val definition = item.Definition
          sendResponse(
            ObjectCreateDetailedMessage(
              definition.ObjectId,
              item.GUID,
              ObjectCreateMessageParent(target, slot),
              definition.Packet.DetailedConstructorData(item).get
            )
          )
        }

      case AvatarResponse.WeaponDryFire(weapon_guid) =>
        if(tplayer_guid != guid) {
          sendResponse(WeaponDryFireMessage(weapon_guid))
        }

      case _ => ;
    }
  }

  /**
    * na
    * @param tplayer na
    * @param msg     na
    * @param order   na
    */
  def HandleDoorMessage(tplayer : Player, msg : UseItemMessage, order : Door.Exchange) : Unit = {
    val door_guid = msg.object_guid
    order match {
      case Door.OpenEvent() =>
        continent.GUID(door_guid) match {
          case Some(door : Door) =>
            sendResponse(GenericObjectStateMsg(door_guid, 16))
            continent.LocalEvents ! LocalServiceMessage(continent.Id, LocalAction.DoorOpens(tplayer.GUID, continent, door))

          case _ =>
            log.warn(s"door $door_guid wanted to be opened but could not be found")
        }

      case Door.CloseEvent() =>
        sendResponse(GenericObjectStateMsg(door_guid, 17))
        continent.LocalEvents ! LocalServiceMessage(continent.Id, LocalAction.DoorCloses(tplayer.GUID, door_guid))

      case Door.NoEvent() => ;
    }
  }

  /**
    * na
    * @param toChannel na
    * @param guid      na
    * @param reply     na
    */
  def HandleLocalServiceResponse(toChannel : String, guid : PlanetSideGUID, reply : LocalResponse.Response) : Unit = {
    val tplayer_guid = if(player.HasGUID) player.GUID
    else PlanetSideGUID(0)
    reply match {
      case LocalResponse.AlertDestroyDeployable(obj) =>
        //the (former) owner (obj.OwnerName) should process this message
        avatar.Deployables.Remove(obj)
        UpdateDeployableUIElements(avatar.Deployables.UpdateUIElement(obj.Definition.Item))

      case LocalResponse.DeployableMapIcon(behavior, deployInfo) =>
        if(tplayer_guid != guid) {
          sendResponse(DeployableObjectsInfoMessage(behavior, deployInfo))
        }

      case LocalResponse.Detonate(guid, obj : BoomerDeployable) =>
        sendResponse(TriggerEffectMessage(guid, "detonate_boomer"))
        sendResponse(PlanetsideAttributeMessage(guid, 29, 1))
        sendResponse(ObjectDeleteMessage(guid, 0))

      case LocalResponse.Detonate(guid, obj : ExplosiveDeployable) =>
        sendResponse(GenericObjectActionMessage(guid, 19))
        sendResponse(PlanetsideAttributeMessage(guid, 29, 1))
        sendResponse(ObjectDeleteMessage(guid, 0))

      case LocalResponse.Detonate(guid, obj) =>
        log.warn(s"LocalResponse.Detonate: ${obj.Definition.Name} not configured to explode correctly")

      case LocalResponse.DoorOpens(door_guid) =>
        if(tplayer_guid != guid) {
          sendResponse(GenericObjectStateMsg(door_guid, 16))
        }

      case LocalResponse.DoorCloses(door_guid) => //door closes for everyone
        sendResponse(GenericObjectStateMsg(door_guid, 17))

      case LocalResponse.EliminateDeployable(obj : TurretDeployable, guid, pos) =>
        if(obj.Destroyed) {
          DeconstructDeployable(obj, guid, pos)
        }
        else {
          obj.Destroyed = true
          DeconstructDeployable(obj, guid, pos, obj.Orientation, if(obj.MountPoints.isEmpty) 2
          else 1)
        }

      case LocalResponse.EliminateDeployable(obj : ExplosiveDeployable, guid, pos) =>
        if(obj.Destroyed || obj.Jammed || obj.Health == 0) {
          DeconstructDeployable(obj, guid, pos)
        }
        else {
          obj.Destroyed = true
          DeconstructDeployable(obj, guid, pos, obj.Orientation, 2)
        }

      case LocalResponse.EliminateDeployable(obj : ComplexDeployable, guid, pos) =>
        if(obj.Destroyed) {
          DeconstructDeployable(obj, guid, pos)
        }
        else {
          obj.Destroyed = true
          DeconstructDeployable(obj, guid, pos, obj.Orientation, 1)
        }

      case LocalResponse.EliminateDeployable(obj : TelepadDeployable, guid, pos) =>
        //if active, deactivate
        if(obj.Active) {
          obj.Active = false
          sendResponse(GenericObjectActionMessage(guid, 29))
          sendResponse(GenericObjectActionMessage(guid, 30))
        }
        //determine if no replacement teleport system exists
        continent.GUID(obj.Router) match {
          case Some(router : Vehicle) =>
            //if the telepad was replaced, the new system is physically in place but not yet functional
            if(router.Utility(UtilityType.internal_router_telepad_deployable) match {
              case Some(internalTelepad : Utility.InternalTelepad) => internalTelepad.Telepad.contains(guid) //same telepad
              case _ => true
            }) {
              //there is no replacement telepad; shut down the system
              ToggleTeleportSystem(router, None)
            }
          case _ => ;
        }
        //standard deployable elimination behavior
        if(obj.Destroyed) {
          DeconstructDeployable(obj, guid, pos)
        }
        else {
          obj.Destroyed = true
          DeconstructDeployable(obj, guid, pos, obj.Orientation, 2)
        }

      case LocalResponse.EliminateDeployable(obj, guid, pos) =>
        if(obj.Destroyed) {
          DeconstructDeployable(obj, guid, pos)
        }
        else {
          obj.Destroyed = true
          DeconstructDeployable(obj, guid, pos, obj.Orientation, 2)
        }

      case LocalResponse.HackClear(target_guid, unk1, unk2) =>
        log.trace(s"Clearing hack for ${target_guid}")
        // Reset hack state for all players
        sendResponse(HackMessage(0, target_guid, guid, 0, unk1, HackState.HackCleared, unk2))

      case LocalResponse.HackObject(target_guid, unk1, unk2) =>
        HackObject(target_guid, unk1, unk2)

      case LocalResponse.HackCaptureTerminal(target_guid, unk1, unk2, isResecured) =>
        HackCaptureTerminal(target_guid, unk1, unk2, isResecured)

      case LocalResponse.ObjectDelete(object_guid, unk) =>
        if(tplayer_guid != guid) {
          sendResponse(ObjectDeleteMessage(object_guid, unk))
        }

      case LocalResponse.ProximityTerminalEffect(object_guid, true) =>
        sendResponse(ProximityTerminalUseMessage(PlanetSideGUID(0), object_guid, true))

      case LocalResponse.ProximityTerminalEffect(object_guid, false) =>
        sendResponse(ProximityTerminalUseMessage(PlanetSideGUID(0), object_guid, false))
        ForgetAllProximityTerminals(object_guid)

      case LocalResponse.RouterTelepadMessage(msg) =>
        sendResponse(ChatMsg(ChatMessageType.UNK_229, false, "", msg, None))

      case LocalResponse.RouterTelepadTransport(passenger_guid, src_guid, dest_guid) =>
        StartBundlingPackets()
        UseRouterTelepadEffect(passenger_guid, src_guid, dest_guid)
        StopBundlingPackets()

      case LocalResponse.SetEmpire(object_guid, empire) =>
        sendResponse(SetEmpireMessage(object_guid, empire))

      case LocalResponse.ToggleTeleportSystem(router, system_plan) =>
        ToggleTeleportSystem(router, system_plan)

      case LocalResponse.TriggerEffect(target_guid, effect, effectInfo, triggerLocation) =>
        sendResponse(TriggerEffectMessage(target_guid, effect, effectInfo, triggerLocation))

      case LocalResponse.TriggerSound(sound, pos, unk, volume) =>
        sendResponse(TriggerSoundMessage(sound, pos, unk, volume))

      case LocalResponse.UpdateForceDomeStatus(building_guid, activated) => {
        if(activated) {
          sendResponse(GenericObjectActionMessage(building_guid, 11))
        }
        else {
          sendResponse(GenericObjectActionMessage(building_guid, 12))
        }
      }

      case LocalResponse.RechargeVehicleWeapon(vehicle_guid, weapon_guid) => {
        if(tplayer_guid == guid) {
          continent.GUID(vehicle_guid) match {
            case Some(vehicle : Mountable with MountedWeapons) =>
              vehicle.PassengerInSeat(player) match {
                case Some(seat_num : Int) =>
                  vehicle.WeaponControlledFromSeat(seat_num) match {
                    case Some(equipment) if equipment.GUID == weapon_guid =>
                      val weapon = equipment.asInstanceOf[Tool]
                      sendResponse(InventoryStateMessage(weapon.AmmoSlot.Box.GUID, weapon.GUID, weapon.Magazine))
                    case _ => ;
                  }
                case _ => ;
              }
            case _ => ;
          }
        }
      }

      case _ => ;
    }
  }

  /**
    * na
    * @param toChannel   na
    * @param avatar_guid na
    * @param target      na
    * @param reply       na
    */
  def HandleChatServiceResponse(toChannel : String, avatar_guid : PlanetSideGUID, avatar_name : String, cont : Zone, avatar_pos : Vector3, avatar_faction : PlanetSideEmpire.Value, target : Int, reply : ChatMsg) : Unit = {
    val tplayer_guid = if(player.HasGUID) player.GUID
    else PlanetSideGUID(0)
    target match {
      case 0 => // for other(s) user(s)
        if(player.GUID != avatar_guid) {
          reply.messageType match {
            case ChatMessageType.CMT_TELL =>
              if (player.Name.equalsIgnoreCase(reply.recipient)) {
                sendResponse(ChatMsg(reply.messageType, reply.wideContents, avatar_name, reply.contents, reply.note))
              }
            case ChatMessageType.CMT_SILENCE =>
              val args = avatar_name.split(" ")
              var silence_name : String = ""
              var silence_time : Int = 5
              if(args.length == 1) {
                silence_name = args(0)
              }
              else if(args.length == 2) {
                silence_name = args(0)
                silence_time = args(1).toInt
              }
              if(player.Name == args(0)) {
                if(!player.silenced) {
                  sendResponse(ChatMsg(ChatMessageType.UNK_71, reply.wideContents, reply.recipient, "@silence_on", reply.note))
                  player.silenced = true
                  context.system.scheduler.scheduleOnce(silence_time minutes, chatService, ChatServiceMessage("gm", ChatAction.GM(PlanetSideGUID(0), player.Name, ChatMsg(ChatMessageType.CMT_SILENCE, true, "", player.Name, None))))
                }
                else {
                  sendResponse(ChatMsg(ChatMessageType.UNK_71, reply.wideContents, reply.recipient, "@silence_off", reply.note))
                  player.silenced = false
                }
              }
            case _ =>
              sendResponse(ChatMsg(reply.messageType, reply.wideContents, reply.recipient, reply.contents, reply.note))
          }
        }
      case 1 => // for player
        if(player.Name == avatar_name) {
          if((reply.contents.length > 1 && (reply.contents.dropRight(reply.contents.length - 1) != "!" || reply.contents.drop(1).dropRight(reply.contents.length - 2) == "!")) || reply.contents.length == 1) {
            sendResponse(ChatMsg(reply.messageType, reply.wideContents, reply.recipient, reply.contents, reply.note))
          }
        }
      case 2 => // both case
        if((reply.contents.length > 1 && (reply.contents.dropRight(reply.contents.length - 1) != "!" || reply.contents.drop(1).dropRight(reply.contents.length - 2) == "!")) || reply.contents.length == 1) {
          reply.messageType match {
            case ChatMessageType.CMT_OPEN =>
              if(Vector3.Distance(player.Position, avatar_pos) < 25 && player.Faction == avatar_faction && player.Continent == cont.Id) {
                sendResponse(ChatMsg(reply.messageType, reply.wideContents, reply.recipient, reply.contents, reply.note))
              }
            case ChatMessageType.CMT_COMMAND =>
              sendResponse(ChatMsg(reply.messageType, reply.wideContents, reply.recipient, reply.contents, reply.note))
            case ChatMessageType.CMT_SQUAD =>
              if(squadChannel.nonEmpty) {
                if("/Chat/"+squadChannel.get == toChannel) {
                  sendResponse(ChatMsg(reply.messageType, reply.wideContents, reply.recipient, reply.contents, reply.note))
                }
              }
            case ChatMessageType.CMT_PLATOON =>
              if (player.Faction == avatar_faction) {
                sendResponse(ChatMsg(reply.messageType, reply.wideContents, reply.recipient, reply.contents, reply.note))
              }
            case ChatMessageType.CMT_VOICE =>
              if(Vector3.Distance(player.Position, avatar_pos) < 25 && player.Continent == cont.Id) {
                sendResponse(ChatMsg(reply.messageType, reply.wideContents, reply.recipient, reply.contents, reply.note))
              }
            case _ =>
              sendResponse(ChatMsg(reply.messageType, reply.wideContents, reply.recipient, reply.contents, reply.note))
          }
        }
    }
  }

  /**
    * na
    * @param tplayer na
    * @param reply   na
    */
  def HandleMountMessages(tplayer : Player, reply : Mountable.Exchange) : Unit = {
    reply match {
      case Mountable.CanMount(obj : ImplantTerminalMech, seat_num) =>
        CancelZoningProcessWithDescriptiveReason("cancel_use")
        CancelAllProximityUnits()
        MountingAction(tplayer, obj, seat_num)

      case Mountable.CanMount(obj : Vehicle, seat_num) =>
        CancelZoningProcessWithDescriptiveReason("cancel_mount")
        val obj_guid : PlanetSideGUID = obj.GUID
        val player_guid : PlanetSideGUID = tplayer.GUID
        log.info(s"MountVehicleMsg: $player_guid mounts $obj_guid @ $seat_num")
        CancelAllProximityUnits()
        sendResponse(PlanetsideAttributeMessage(obj_guid, 0, obj.Health))
        sendResponse(PlanetsideAttributeMessage(obj_guid, 68, obj.Shields)) //shield health
        if(obj.Definition.MaxNtuCapacitor > 0) {
          val ntuCapacitor = scala.math.ceil((obj.NtuCapacitor.toFloat / obj.Definition.MaxNtuCapacitor.toFloat) * 10).toInt
          sendResponse(PlanetsideAttributeMessage(obj_guid, 45, ntuCapacitor))
        }
        if(obj.Definition.MaxCapacitor > 0) {
          val capacitor = scala.math.ceil((obj.Capacitor.toFloat / obj.Definition.MaxCapacitor.toFloat) * 10).toInt
          sendResponse(PlanetsideAttributeMessage(obj_guid, 113, capacitor))
        }
        if(seat_num == 0) {
          //simplistic vehicle ownership management
          obj.Owner match {
            case Some(owner_guid) =>
              continent.GUID(owner_guid) match {
                case Some(previous_owner : Player) =>
                  if(previous_owner.VehicleOwned.contains(obj_guid)) {
                    previous_owner.VehicleOwned = None //simplistic ownership management, player loses vehicle ownership
                  }
                case _ => ;
              }
            case None => ;
          }
          Vehicles.Own(obj, tplayer)
          if(obj.Definition == GlobalDefinitions.quadstealth) {
            //wraith cloak state matches the cloak state of the driver
            //phantasm doesn't uncloak if the driver is uncloaked and no other vehicle cloaks
            obj.Cloaked = tplayer.Cloaked
          }
        }
        AccessContents(obj)
        UpdateWeaponAtSeatPosition(obj, seat_num)
        MountingAction(tplayer, obj, seat_num)

      case Mountable.CanMount(obj : FacilityTurret, seat_num) =>
        CancelZoningProcessWithDescriptiveReason("cancel_mount")
        if(!obj.isUpgrading) {
          if(obj.Definition == GlobalDefinitions.vanu_sentry_turret) {
            obj.Zone.LocalEvents ! LocalServiceMessage(obj.Zone.Id, LocalAction.SetEmpire(obj.GUID, player.Faction))
          }
          sendResponse(PlanetsideAttributeMessage(obj.GUID, 0, obj.Health))
          UpdateWeaponAtSeatPosition(obj, seat_num)
          MountingAction(tplayer, obj, seat_num)
        }
        else {
          log.warn(s"MountVehicleMsg: ${tplayer.Name} wants to mount turret ${obj.GUID.guid}, but needs to wait until it finishes updating")
        }

      case Mountable.CanMount(obj : PlanetSideGameObject with WeaponTurret, seat_num) =>
        CancelZoningProcessWithDescriptiveReason("cancel_mount")
        sendResponse(PlanetsideAttributeMessage(obj.GUID, 0, obj.Health))
        UpdateWeaponAtSeatPosition(obj, seat_num)
        MountingAction(tplayer, obj, seat_num)

      case Mountable.CanMount(obj : Mountable, _) =>
        log.warn(s"MountVehicleMsg: $obj is some generic mountable object and nothing will happen")

      case Mountable.CanDismount(obj : ImplantTerminalMech, seat_num) =>
        DismountAction(tplayer, obj, seat_num)

      case Mountable.CanDismount(obj : Vehicle, seat_num) if obj.Definition == GlobalDefinitions.droppod =>
        UnAccessContents(obj)
        DismountAction(tplayer, obj, seat_num)

      case Mountable.CanDismount(obj : Vehicle, seat_num) =>
        val player_guid : PlanetSideGUID = tplayer.GUID
        if(player_guid == player.GUID) {
          //disembarking self
          TotalDriverVehicleControl(obj)
          UnAccessContents(obj)
          DismountAction(tplayer, obj, seat_num)
        }
        else {
          continent.VehicleEvents ! VehicleServiceMessage(continent.Id, VehicleAction.KickPassenger(player_guid, seat_num, true, obj.GUID))
        }

      case Mountable.CanDismount(obj : PlanetSideGameObject with WeaponTurret, seat_num) =>
        DismountAction(tplayer, obj, seat_num)

      case Mountable.CanDismount(obj : Mountable, _) =>
        log.warn(s"DismountVehicleMsg: $obj is some generic mountable object and nothing will happen")

      case Mountable.CanNotMount(obj : Vehicle, seat_num) =>
        log.warn(s"MountVehicleMsg: $tplayer attempted to mount $obj's seat $seat_num, but was not allowed")
        if(obj.SeatPermissionGroup(seat_num).contains(AccessPermissionGroup.Driver)) {
          sendResponse(ChatMsg(ChatMessageType.CMT_OPEN, false, "", "You are not the driver of this vehicle.", None))
        }

      case Mountable.CanNotMount(obj : Mountable, seat_num) =>
        log.warn(s"MountVehicleMsg: $tplayer attempted to mount $obj's seat $seat_num, but was not allowed")

      case Mountable.CanNotDismount(obj, seat_num) =>
        log.warn(s"DismountVehicleMsg: $tplayer attempted to dismount $obj's seat $seat_num, but was not allowed")
    }
  }

  /**
    * na
    * @param tplayer na
    * @param msg     na
    * @param order   na
    */
  def HandleTerminalMessage(tplayer : Player, msg : ItemTransactionMessage, order : Terminal.Exchange) : Unit = {
    order match {
      case Terminal.BuyExosuit(exosuit, subtype) =>
        //TODO check exo-suit permissions
        val originalSuit = tplayer.ExoSuit
        val originalSubtype = Loadout.DetermineSubtype(tplayer)
        val lTime = System.currentTimeMillis
        var changeArmor : Boolean = true
        if(lTime - whenUsedLastMAX(subtype) < 300000) {
          changeArmor = false
        }
        if(changeArmor && exosuit.id == 2) {
          for(i <- 1 to 3) {
            sendResponse(AvatarVehicleTimerMessage(tplayer.GUID, whenUsedLastMAXName(i), 300, true))
            whenUsedLastMAX(i) = lTime
          }
        }
        if(originalSuit != exosuit || originalSubtype != subtype && changeArmor) {
          sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, true))
          //prepare lists of valid objects
          val beforeInventory = tplayer.Inventory.Clear()
          val beforeHolsters = clearHolsters(tplayer.Holsters().iterator)
          //change suit (clear inventory and change holster sizes; holsters must be empty before this point)
          val originalArmor = tplayer.Armor
          tplayer.ExoSuit = exosuit //changes the value of MaxArmor to reflect the new exo-suit
          val toMaxArmor = tplayer.MaxArmor
          if(originalSuit != exosuit || originalSubtype != subtype || originalArmor > toMaxArmor) {
            tplayer.History(HealFromExoSuitChange(PlayerSource(tplayer), exosuit))
            tplayer.Armor = toMaxArmor
            sendResponse(PlanetsideAttributeMessage(tplayer.GUID, 4, toMaxArmor))
            continent.AvatarEvents ! AvatarServiceMessage(player.Continent, AvatarAction.PlanetsideAttribute(tplayer.GUID, 4, toMaxArmor))
          }
          else {
            tplayer.Armor = originalArmor
          }
          //ensure arm is down, even if it needs to go back up
          if(tplayer.DrawnSlot != Player.HandsDownSlot) {
            tplayer.DrawnSlot = Player.HandsDownSlot
            sendResponse(ObjectHeldMessage(tplayer.GUID, Player.HandsDownSlot, true))
            continent.AvatarEvents ! AvatarServiceMessage(tplayer.Continent, AvatarAction.ObjectHeld(tplayer.GUID, tplayer.LastDrawnSlot))
          }
          //delete everything not dropped
          (beforeHolsters ++ beforeInventory).foreach({ elem =>
            sendResponse(ObjectDeleteMessage(elem.obj.GUID, 0))
          })
          beforeHolsters.foreach({ elem =>
            continent.AvatarEvents ! AvatarServiceMessage(tplayer.Continent, AvatarAction.ObjectDelete(tplayer.GUID, elem.obj.GUID))
          })
          //report change
          sendResponse(ArmorChangedMessage(tplayer.GUID, exosuit, subtype))
          continent.AvatarEvents ! AvatarServiceMessage(player.Continent, AvatarAction.ArmorChanged(tplayer.GUID, exosuit, subtype))
          //sterilize holsters
          val normalHolsters = if(originalSuit == ExoSuitType.MAX) {
            val (maxWeapons, normalWeapons) = beforeHolsters.partition(elem => elem.obj.Size == EquipmentSize.Max)
            maxWeapons.foreach(entry => {
              taskResolver ! GUIDTask.UnregisterEquipment(entry.obj)(continent.GUID)
            })
            normalWeapons
          }
          else {
            beforeHolsters
          }
          //populate holsters
          val finalInventory = if(exosuit == ExoSuitType.MAX) {
            taskResolver ! DelayedObjectHeld(tplayer, 0, List(PutEquipmentInSlot(tplayer, Tool(GlobalDefinitions.MAXArms(subtype, tplayer.Faction)), 0)))
            fillEmptyHolsters(List(tplayer.Slot(4)).iterator, normalHolsters) ++ beforeInventory
          }
          else if(originalSuit == exosuit) { //note - this will rarely be the situation
            fillEmptyHolsters(tplayer.Holsters().iterator, normalHolsters)
          }
          else {
            val (afterHolsters, toInventory) = normalHolsters.partition(elem => elem.obj.Size == tplayer.Slot(elem.start).Size)
            afterHolsters.foreach({ elem => tplayer.Slot(elem.start).Equipment = elem.obj })
            fillEmptyHolsters(tplayer.Holsters().iterator, toInventory ++ beforeInventory)
          }
          //draw holsters
          tplayer.VisibleSlots.foreach({ index =>
            tplayer.Slot(index).Equipment match {
              case Some(obj) =>
                val definition = obj.Definition
                sendResponse(
                  ObjectCreateDetailedMessage(
                    definition.ObjectId,
                    obj.GUID,
                    ObjectCreateMessageParent(tplayer.GUID, index),
                    definition.Packet.DetailedConstructorData(obj).get
                  )
                )
                continent.AvatarEvents ! AvatarServiceMessage(player.Continent, AvatarAction.EquipmentInHand(player.GUID, player.GUID, index, obj))
              case None => ;
            }
          })
          //re-draw equipment held in free hand
          tplayer.FreeHand.Equipment match {
            case Some(item) =>
              val definition = item.Definition
              sendResponse(
                ObjectCreateDetailedMessage(
                  definition.ObjectId,
                  item.GUID,
                  ObjectCreateMessageParent(tplayer.GUID, Player.FreeHandSlot),
                  definition.Packet.DetailedConstructorData(item).get
                )
              )
            case None => ;
          }
          //put items back into inventory
          val (stow, drop) = if(originalSuit == exosuit) {
            (finalInventory, Nil)
          }
          else {
            GridInventory.recoverInventory(finalInventory, tplayer.Inventory)
          }
          stow.foreach(elem => {
            tplayer.Inventory.Insert(elem.start, elem.obj)
            val obj = elem.obj
            val definition = obj.Definition
            sendResponse(
              ObjectCreateDetailedMessage(
                definition.ObjectId,
                obj.GUID,
                ObjectCreateMessageParent(tplayer.GUID, elem.start),
                definition.Packet.DetailedConstructorData(obj).get
              )
            )
          })
          val (finalDroppedItems, retiredItems) = drop.map(item => InventoryItem(item, -1)).partition(DropPredicate(tplayer))
          //drop special items on ground
          val pos = tplayer.Position
          val orient = Vector3.z(tplayer.Orientation.z)
          finalDroppedItems.foreach(entry => {
            //TODO make a sound when dropping stuff
            continent.Ground ! Zone.Ground.DropItem(entry.obj, pos, orient)
          })
          //deconstruct normal items
          retiredItems.foreach({ entry =>
            taskResolver ! GUIDTask.UnregisterEquipment(entry.obj)(continent.GUID)
          })
        }
        else {
          sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, false))
        }
        lastTerminalOrderFulfillment = true

      case Terminal.BuyEquipment(item) =>
        continent.GUID(tplayer.VehicleSeated) match {
          //vehicle trunk
          case Some(vehicle : Vehicle) =>
            vehicle.Fit(item) match {
              case Some(index) =>
                item.Faction = tplayer.Faction
                sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, true))
                taskResolver ! StowNewEquipmentInVehicle(vehicle)(index, item)
              case None => //player free hand?
                tplayer.FreeHand.Equipment match {
                  case None =>
                    item.Faction = tplayer.Faction
                    sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, true))
                    taskResolver ! PutEquipmentInSlot(tplayer, item, Player.FreeHandSlot)
                  case Some(_) =>
                    sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, false))
                }
            }
          //player backpack or free hand
          case _ =>
            tplayer.Fit(item) match {
              case Some(index) =>
                item.Faction = tplayer.Faction
                sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, true))
                taskResolver ! PutEquipmentInSlot(tplayer, item, index)
              case None =>
                sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, false))
            }
        }
        lastTerminalOrderFulfillment = true

      case Terminal.SellEquipment() =>
        tplayer.FreeHand.Equipment match {
          case Some(item) =>
            if(item.GUID == msg.item_guid) {
              sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Sell, true))
              taskResolver ! RemoveEquipmentFromSlot(tplayer, item, Player.FreeHandSlot)
            }
          case None =>
            sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Sell, false))
        }
        lastTerminalOrderFulfillment = true

      case Terminal.InfantryLoadout(exosuit, subtype, holsters, inventory) =>
        log.info(s"$tplayer wants to change equipment loadout to their option #${msg.unk1 + 1}")
        sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Loadout, true))
        //sanitize exo-suit for change
        val originalSuit = player.ExoSuit
        val originalSubtype = Loadout.DetermineSubtype(tplayer)
        //prepare lists of valid objects
        val beforeFreeHand = tplayer.FreeHand.Equipment
        val dropPred = DropPredicate(tplayer)
        val (dropHolsters, beforeHolsters) = clearHolsters(tplayer.Holsters().iterator).partition(dropPred)
        val (dropInventory, beforeInventory) = tplayer.Inventory.Clear().partition(dropPred)
        tplayer.FreeHand.Equipment = None //terminal and inventory will close, so prematurely dropping should be fine
      val fallbackSuit = ExoSuitType.Standard
        val fallbackSubtype = 0
        //a loadout with a prohibited exo-suit type will result in a fallback exo-suit type
        val (nextSuit : ExoSuitType.Value, nextSubtype : Int) =
          if(ExoSuitDefinition.Select(exosuit, player.Faction).Permissions match {
            case Nil =>
              true
            case permissions if subtype != 0 =>
              val certs = tplayer.Certifications
              certs.intersect(permissions.toSet).nonEmpty &&
                certs.intersect(InfantryLoadout.DetermineSubtypeC(subtype)).nonEmpty
            case permissions =>
              tplayer.Certifications.intersect(permissions.toSet).nonEmpty
          }) {
            val lTime = System.currentTimeMillis
            if(lTime - whenUsedLastMAX(subtype) < 300000) { // PTS v3 hack
              (originalSuit, subtype)
            }
            else {
              if(lTime - whenUsedLastMAX(subtype) > 300000 && subtype != 0) {
                for(i <- 1 to 3) {
                  sendResponse(AvatarVehicleTimerMessage(tplayer.GUID, whenUsedLastMAXName(i), 300, true))
                  whenUsedLastMAX(i) = lTime
                }
              }
              (exosuit, subtype)
            }
          }
          else {
            log.warn(s"$tplayer no longer has permission to wear the exo-suit type $exosuit; will wear $fallbackSuit instead")
            (fallbackSuit, fallbackSubtype)
          }
        //update suit interally (holsters must be empty before this point)
        val originalArmor = player.Armor
        tplayer.ExoSuit = nextSuit
        val toMaxArmor = tplayer.MaxArmor
        if(originalSuit != nextSuit || originalSubtype != nextSubtype || originalArmor > toMaxArmor) {
          tplayer.History(HealFromExoSuitChange(PlayerSource(tplayer), nextSuit))
          tplayer.Armor = toMaxArmor
          sendResponse(PlanetsideAttributeMessage(tplayer.GUID, 4, toMaxArmor))
          continent.AvatarEvents ! AvatarServiceMessage(player.Continent, AvatarAction.PlanetsideAttribute(tplayer.GUID, 4, toMaxArmor))
        }
        else {
          tplayer.Armor = originalArmor
        }
        //ensure arm is down, even if it needs to go back up
        if(tplayer.DrawnSlot != Player.HandsDownSlot) {
          tplayer.DrawnSlot = Player.HandsDownSlot
          sendResponse(ObjectHeldMessage(tplayer.GUID, Player.HandsDownSlot, true))
          continent.AvatarEvents ! AvatarServiceMessage(tplayer.Continent, AvatarAction.ObjectHeld(tplayer.GUID, tplayer.LastDrawnSlot))
        }
        //a change due to exo-suit permissions mismatch will result in (more) items being re-arranged and/or dropped
        //dropped items can be forgotten safely
        val (afterHolsters, afterInventory) = if(nextSuit == exosuit) {
          (
            holsters.filterNot(dropPred),
            inventory.filterNot(dropPred)
          )
        }
        else {
          val newSuitDef = ExoSuitDefinition.Select(nextSuit, player.Faction)
          val (afterInventory, extra) = GridInventory.recoverInventory(
            inventory.filterNot(dropPred),
            tplayer.Inventory
          )
          val afterHolsters = {
            val preservedHolsters = if(exosuit == ExoSuitType.MAX) {
              holsters.filter(_.start == 4) //melee slot perservation
            }
            else {
              holsters
                .filterNot(dropPred)
                .collect {
                  case item@InventoryItem(obj, index) if newSuitDef.Holster(index) == obj.Size => item
                }
            }
            val size = newSuitDef.Holsters.size
            val indexMap = preservedHolsters.map { entry => entry.start }
            preservedHolsters ++ (extra.map { obj =>
              tplayer.Fit(obj) match {
                case Some(index : Int) if index < size && !indexMap.contains(index) =>
                  InventoryItem(obj, index)
                case _ =>
                  InventoryItem(obj, -1)
              }
            }).filterNot(entry => entry.start == -1)
          }
          (afterHolsters, afterInventory)
        }
        //delete everything (not dropped)
        beforeHolsters.foreach({ elem =>
          continent.AvatarEvents ! AvatarServiceMessage(tplayer.Continent, AvatarAction.ObjectDelete(tplayer.GUID, elem.obj.GUID))
        })
        (beforeHolsters ++ beforeInventory).foreach({ elem =>
          sendResponse(ObjectDeleteMessage(elem.obj.GUID, 0))
          taskResolver ! GUIDTask.UnregisterEquipment(elem.obj)(continent.GUID)
        })
        //report change
        sendResponse(ArmorChangedMessage(tplayer.GUID, nextSuit, nextSubtype))
        continent.AvatarEvents ! AvatarServiceMessage(tplayer.Continent, AvatarAction.ArmorChanged(tplayer.GUID, nextSuit, nextSubtype))
        if(nextSuit == ExoSuitType.MAX) {
          val (maxWeapons, otherWeapons) = afterHolsters.partition(entry => {
            entry.obj.Size == EquipmentSize.Max
          })
          val weapon = maxWeapons.headOption match {
            case Some(mweapon) =>
              mweapon.obj
            case None =>
              Tool(GlobalDefinitions.MAXArms(nextSubtype, tplayer.Faction))
          }
          taskResolver ! DelayedObjectHeld(tplayer, 0, List(PutEquipmentInSlot(tplayer, weapon, 0)))
          otherWeapons
        }
        else {
          afterHolsters
        }.foreach(entry => {
          entry.obj.Faction = tplayer.Faction
          taskResolver ! PutEquipmentInSlot(tplayer, entry.obj, entry.start)
        })
        //put items into inventory
        afterInventory.foreach(entry => {
          entry.obj.Faction = tplayer.Faction
          taskResolver ! PutEquipmentInSlot(tplayer, entry.obj, entry.start)
        })
        //drop stuff on ground
        val pos = tplayer.Position
        val orient = Vector3.z(tplayer.Orientation.z)
        ((beforeFreeHand match {
          case Some(item) => List(InventoryItem(item, -1)) //add the item previously in free hand, if any
          case None => Nil
        }) ++ dropHolsters ++ dropInventory).foreach(entry => {
          entry.obj.Faction = PlanetSideEmpire.NEUTRAL
          continent.Ground ! Zone.Ground.DropItem(entry.obj, pos, orient)
        })
        lastTerminalOrderFulfillment = true

      case Terminal.VehicleLoadout(definition, weapons, inventory) =>
        log.info(s"$tplayer wants to change their vehicle equipment loadout to their option #${msg.unk1 + 1}")
        FindLocalVehicle match {
          case Some(vehicle) =>
            sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Loadout, true))
            val (_, afterInventory) = inventory.partition(DropPredicate(tplayer))
            //dropped items are lost
            //remove old inventory
            val deleteEquipment : (Int, Equipment) => Unit = DeleteEquipmentFromVehicle(vehicle)
            vehicle.Inventory.Clear().foreach({ case InventoryItem(obj, index) => deleteEquipment(index, obj) })
            val stowEquipment : (Int, Equipment) => TaskResolver.GiveTask = StowNewEquipmentInVehicle(vehicle)
            (if(vehicle.Definition == definition) {
              //vehicles are the same type; transfer over weapon ammo
              //TODO ammo switching? no vehicle weapon does that currently but ...
              //TODO want to completely swap weapons, but holster icon vanishes temporarily after swap
              //TODO BFR arms must be swapped properly
              val channel = s"${vehicle.Actor}"
              weapons.foreach({ case InventoryItem(obj, index) =>
                val savedWeapon = obj.asInstanceOf[Tool]
                val existingWeapon = vehicle.Weapons(index).Equipment.get.asInstanceOf[Tool]
                (0 until existingWeapon.MaxAmmoSlot).foreach({ index =>
                  val existingBox = existingWeapon.AmmoSlots(index).Box
                  existingBox.Capacity = savedWeapon.AmmoSlots(index).Box.Capacity
                  //use VehicleAction.InventoryState2; VehicleAction.InventoryState temporarily glitches ammo count in ui
                  continent.VehicleEvents ! VehicleServiceMessage(channel, VehicleAction.InventoryState2(PlanetSideGUID(0), existingBox.GUID, existingWeapon.GUID, existingBox.Capacity))
                })
              })
              afterInventory
            }
            else {
              //do not transfer over weapon ammo
              if(vehicle.Definition.TrunkSize == definition.TrunkSize && vehicle.Definition.TrunkOffset == definition.TrunkOffset) {
                afterInventory
              }
              else {
                //accommodate as much of inventory as possible
                val (stow, _) = GridInventory.recoverInventory(afterInventory, vehicle.Inventory) //dropped items can be forgotten
                stow
              }
            }).foreach({ case InventoryItem(obj, index) =>
              obj.Faction = tplayer.Faction
              taskResolver ! stowEquipment(index, obj)
            })
          case None =>
            log.error(s"can not apply the loadout - can not find a vehicle")
            sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Loadout, false))
        }
        lastTerminalOrderFulfillment = true

      case Terminal.LearnCertification(cert) =>
        val name = tplayer.Name
        if(!tplayer.Certifications.contains(cert)) {
          val guid = tplayer.GUID
          log.info(s"$name is learning the $cert certification for ${Certification.Cost.Of(cert)} points")
          avatar.Certifications += cert
          StartBundlingPackets()
          AddToDeployableQuantities(cert, player.Certifications)
          sendResponse(PlanetsideAttributeMessage(guid, 24, cert.id))
          tplayer.Certifications.intersect(Certification.Dependencies.Like(cert)).foreach(entry => {
            log.info(s"$cert replaces the learned certification $entry that cost ${Certification.Cost.Of(entry)} points")
            avatar.Certifications -= entry
            sendResponse(PlanetsideAttributeMessage(guid, 25, entry.id))
          })
          StopBundlingPackets()
          sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Learn, true))
        }
        else {
          log.warn(s"$name already knows the $cert certification, so he can't learn it")
          sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Learn, false))
        }
        lastTerminalOrderFulfillment = true

      case Terminal.SellCertification(cert) =>
        val name = tplayer.Name
        if(tplayer.Certifications.contains(cert)) {
          val guid = tplayer.GUID
          log.info(s"$name is forgetting the $cert certification for ${Certification.Cost.Of(cert)} points")
          avatar.Certifications -= cert
          StartBundlingPackets()
          RemoveFromDeployablesQuantities(cert, player.Certifications)
          sendResponse(PlanetsideAttributeMessage(guid, 25, cert.id))
          tplayer.Certifications.intersect(Certification.Dependencies.FromAll(cert)).foreach(entry => {
            log.info(s"$name is also forgetting the ${Certification.Cost.Of(entry)}-point $entry certification which depends on $cert")
            avatar.Certifications -= entry
            RemoveFromDeployablesQuantities(entry, player.Certifications)
            sendResponse(PlanetsideAttributeMessage(guid, 25, entry.id))
          })
          StopBundlingPackets()
          sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Sell, true))
        }
        else {
          log.warn(s"$name doesn't know what a $cert certification is, so he can't forget it")
          sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Learn, false))
        }
        lastTerminalOrderFulfillment = true

      case Terminal.LearnImplant(implant) =>
        val terminal_guid = msg.terminal_guid
        val implant_type = implant.Type
        val message = s"Implants: $tplayer wants to learn $implant_type"
        val (interface, slotNumber) = tplayer.VehicleSeated match {
          case Some(mech_guid) =>
            (
              continent.Map.TerminalToInterface.get(mech_guid.guid),
              if(!avatar.Implants.exists({ slot => slot.Implant == implant_type })) {
                //no duplicates
                avatar.InstallImplant(implant)
              }
              else {
                None
              }
            )
          case _ =>
            (None, None)
        }
        if(interface.contains(terminal_guid.guid) && slotNumber.isDefined) {
          val slot = slotNumber.get
          log.info(s"$message - put in slot $slot")
          sendResponse(AvatarImplantMessage(tplayer.GUID, ImplantAction.Add, slot, implant_type.id))
          sendResponse(ItemTransactionResultMessage(terminal_guid, TransactionType.Learn, true))
          player.Actor ! Player.ImplantInitializationStart(slot)
        }
        else {
          if(interface.isEmpty) {
            log.warn(s"$message - not interacting with a terminal")
          }
          else if(!interface.contains(terminal_guid.guid)) {
            log.warn(s"$message - interacting with the wrong terminal, ${interface.get}")
          }
          else if(slotNumber.isEmpty) {
            log.warn(s"$message - already knows that implant")
          }
          else {
            log.warn(s"$message - forgot to sit at a terminal")
          }
          sendResponse(ItemTransactionResultMessage(terminal_guid, TransactionType.Learn, false))
        }
        lastTerminalOrderFulfillment = true

      case Terminal.SellImplant(implant) =>
        val terminal_guid = msg.terminal_guid
        val implant_type = implant.Type
        val (interface, slotNumber) = tplayer.VehicleSeated match {
          case Some(mech_guid) =>
            (
              continent.Map.TerminalToInterface.get(mech_guid.guid),
              avatar.UninstallImplant(implant_type)
            )
          case None =>
            (None, None)
        }
        if(interface.contains(terminal_guid.guid) && slotNumber.isDefined) {
          val slot = slotNumber.get
          log.info(s"$tplayer is selling $implant_type - take from slot $slot")
          player.Actor ! Player.UninitializeImplant(slot)
          sendResponse(AvatarImplantMessage(tplayer.GUID, ImplantAction.Remove, slot, 0))
          sendResponse(ItemTransactionResultMessage(terminal_guid, TransactionType.Sell, true))
        }
        else {
          val message = s"$tplayer can not sell $implant_type"
          if(interface.isEmpty) {
            log.warn(s"$message - not interacting with a terminal")
          }
          else if(!interface.contains(terminal_guid.guid)) {
            log.warn(s"$message - interacting with the wrong terminal, ${interface.get}")
          }
          else if(slotNumber.isEmpty) {
            log.warn(s"$message - does not know that implant")
          }
          else {
            log.warn(s"$message - forgot to sit at a terminal")
          }
          sendResponse(ItemTransactionResultMessage(terminal_guid, TransactionType.Sell, false))
        }
        lastTerminalOrderFulfillment = true

      case Terminal.BuyVehicle(vehicle, weapons, trunk) =>
        continent.Map.TerminalToSpawnPad.get(msg.terminal_guid.guid) match {
          case Some(pad_guid) =>
            val lTime = System.currentTimeMillis
            if(lTime - whenUsedLastItem(vehicle.Definition.ObjectId) > 300000) {
              whenUsedLastItem(vehicle.Definition.ObjectId) = lTime
              whenUsedLastItemName(vehicle.Definition.ObjectId) = msg.item_name
              sendResponse(AvatarVehicleTimerMessage(tplayer.GUID, msg.item_name, 300, true))
              val toFaction = tplayer.Faction
              val pad = continent.GUID(pad_guid).get.asInstanceOf[VehicleSpawnPad]
              vehicle.Faction = toFaction
              vehicle.Position = pad.Position
              vehicle.Orientation = pad.Orientation + Vector3.z(pad.Definition.VehicleCreationZOrientOffset)
              //default loadout, weapons
              val vWeapons = vehicle.Weapons
              weapons.foreach(entry => {
                val index = entry.start
                vWeapons.get(index) match {
                  case Some(slot) =>
                    entry.obj.Faction = toFaction
                    slot.Equipment = None
                    slot.Equipment = entry.obj
                  case None =>
                    log.warn(s"applying default loadout to $vehicle on spawn, but can not find a mounted weapon @ $index")
                }
              })
              //default loadout, trunk
              val vTrunk = vehicle.Trunk
              vTrunk.Clear()
              trunk.foreach(entry => {
                entry.obj.Faction = toFaction
                vTrunk += entry.start -> entry.obj
              })
              taskResolver ! RegisterVehicleFromSpawnPad(vehicle, pad)
              sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, true))
            }
            else {
              sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, false))
            }

          case None =>
            log.error(s"$tplayer wanted to spawn a vehicle, but there was no spawn pad associated with terminal ${msg.terminal_guid} to accept it")
        }
        lastTerminalOrderFulfillment = true

      case _ =>
        val order : String = if(msg == null) {
          s"order $msg"
        }
        else {
          "missing order"
        }
        log.warn(s"${tplayer.Name} made a request but the terminal rejected the $order")
        sendResponse(ItemTransactionResultMessage(msg.terminal_guid, msg.transaction_type, false))
        lastTerminalOrderFulfillment = true
    }
  }

  /**
    * na
    * @param toChannel na
    * @param guid      na
    * @param reply     na
    */
  def HandleVehicleServiceResponse(toChannel : String, guid : PlanetSideGUID, reply : VehicleResponse.Response) : Unit = {
    val tplayer_guid = if(player.HasGUID) player.GUID
    else PlanetSideGUID(0)
    reply match {
      case VehicleResponse.AttachToRails(vehicle_guid, pad_guid) =>
        sendResponse(ObjectAttachMessage(pad_guid, vehicle_guid, 3))

      case VehicleResponse.ChildObjectState(object_guid, pitch, yaw) =>
        if(tplayer_guid != guid) {
          sendResponse(ChildObjectStateMessage(object_guid, pitch, yaw))
        }

      case VehicleResponse.ConcealPlayer(player_guid) =>
        sendResponse(GenericObjectActionMessage(player_guid, 9))

      case VehicleResponse.DismountVehicle(bailType, wasKickedByDriver) =>
        if(tplayer_guid != guid) {
          sendResponse(DismountVehicleMsg(guid, bailType, wasKickedByDriver))
        }

      case VehicleResponse.DeployRequest(object_guid, state, unk1, unk2, pos) =>
        if(tplayer_guid != guid) {
          sendResponse(DeployRequestMessage(guid, object_guid, state, unk1, unk2, pos))
        }

      case VehicleResponse.DetachFromRails(vehicle_guid, pad_guid, pad_position, pad_orientation_z) =>
        val pad = continent.GUID(pad_guid).get.asInstanceOf[VehicleSpawnPad].Definition
        sendResponse(ObjectDetachMessage(pad_guid, vehicle_guid, pad_position + Vector3(0, 0, pad.VehicleCreationZOffset), pad_orientation_z + pad.VehicleCreationZOrientOffset))

      case VehicleResponse.EquipmentInSlot(pkt) =>
        if(tplayer_guid != guid) {
          sendResponse(pkt)
        }

      case VehicleResponse.HitHint(source_guid) =>
        if(player.isAlive) {
          sendResponse(HitHint(source_guid, player.GUID))
        }

      case VehicleResponse.InventoryState(obj, parent_guid, start, con_data) =>
        if(tplayer_guid != guid) {
          //TODO prefer ObjectDetachMessage, but how to force ammo pools to update properly?
          val obj_guid = obj.GUID
          sendResponse(ObjectDeleteMessage(obj_guid, 0))
          sendResponse(
            ObjectCreateDetailedMessage(
              obj.Definition.ObjectId,
              obj_guid,
              ObjectCreateMessageParent(parent_guid, start),
              con_data
            )
          )
        }

      case VehicleResponse.KickPassenger(seat_num, wasKickedByDriver, vehicle_guid) =>
        // seat_num seems to be correct if passenger is kicked manually by driver, but always seems to return 4 if user is kicked by seat permissions
        sendResponse(DismountVehicleMsg(guid, BailType.Kicked, wasKickedByDriver))
        if(tplayer_guid == guid) {
          continent.GUID(vehicle_guid) match {
            case Some(obj : Vehicle) =>
              UnAccessContents(obj)
            case _ => ;
          }
        }

      case VehicleResponse.InventoryState2(obj_guid, parent_guid, value) =>
        if(tplayer_guid != guid) {
          sendResponse(InventoryStateMessage(obj_guid, 0, parent_guid, value))
        }

      case VehicleResponse.LoadVehicle(vehicle, vtype, vguid, vdata) =>
        //this is not be suitable for vehicles with people who are seated in it before it spawns (if that is possible)
        if(tplayer_guid != guid) {
          sendResponse(ObjectCreateMessage(vtype, vguid, vdata))
          Vehicles.ReloadAccessPermissions(vehicle, player.Name)
        }

      case VehicleResponse.MountVehicle(vehicle_guid, seat) =>
        if(tplayer_guid != guid) {
          sendResponse(ObjectAttachMessage(vehicle_guid, guid, seat))
        }

      case VehicleResponse.Ownership(vehicle_guid) =>
        if(tplayer_guid == guid) { // Only the player that owns this vehicle needs the ownership packet
          player.VehicleOwned = Some(vehicle_guid)
          sendResponse(PlanetsideAttributeMessage(tplayer_guid, 21, vehicle_guid))
        }

      case VehicleResponse.PlanetsideAttribute(vehicle_guid, attribute_type, attribute_value) =>
        if(tplayer_guid != guid) {
          sendResponse(PlanetsideAttributeMessage(vehicle_guid, attribute_type, attribute_value))
        }

      case VehicleResponse.ResetSpawnPad(pad_guid) =>
        sendResponse(GenericObjectActionMessage(pad_guid, 23))

      case VehicleResponse.RevealPlayer(player_guid) =>
        sendResponse(GenericObjectActionMessage(player_guid, 10))

      case VehicleResponse.SeatPermissions(vehicle_guid, seat_group, permission) =>
        if(tplayer_guid != guid) {
          sendResponse(PlanetsideAttributeMessage(vehicle_guid, seat_group, permission))
        }

      case VehicleResponse.StowEquipment(vehicle_guid, slot, item_type, item_guid, item_data) =>
        if(tplayer_guid != guid) {
          //TODO prefer ObjectAttachMessage, but how to force ammo pools to update properly?
          sendResponse(
            ObjectCreateDetailedMessage(item_type, item_guid, ObjectCreateMessageParent(vehicle_guid, slot), item_data)
          )
        }

      case VehicleResponse.UnloadVehicle(vehicle, vehicle_guid) =>
        //if(tplayer_guid != guid) {
        BeforeUnloadVehicle(vehicle)
        sendResponse(ObjectDeleteMessage(vehicle_guid, 0))
      //}

      case VehicleResponse.UnstowEquipment(item_guid) =>
        if(tplayer_guid != guid) {
          //TODO prefer ObjectDetachMessage, but how to force ammo pools to update properly?
          sendResponse(ObjectDeleteMessage(item_guid, 0))
        }

      case VehicleResponse.VehicleState(vehicle_guid, unk1, pos, ang, vel, unk2, unk3, unk4, wheel_direction, unk5, unk6) =>
        if(tplayer_guid != guid) {
          sendResponse(VehicleStateMessage(vehicle_guid, unk1, pos, ang, vel, unk2, unk3, unk4, wheel_direction, unk5, unk6))
          if(player.VehicleSeated.contains(vehicle_guid)) {
            player.Position = pos
          }
        }
      case VehicleResponse.SendResponse(msg) =>
        sendResponse(msg)

      case VehicleResponse.UpdateAmsSpawnPoint(list) =>
        amsSpawnPoints = list.filter(tube => tube.Faction == player.Faction)
        DrawCurrentAmsSpawnPoint()

      case VehicleResponse.TransferPassengerChannel(old_channel, temp_channel, vehicle, vehicle_to_delete) =>
        if(tplayer_guid != guid) {
          interstellarFerry = Some(vehicle)
          interstellarFerryTopLevelGUID = Some(vehicle_to_delete)
          continent.VehicleEvents ! Service.Leave(Some(old_channel)) //old vehicle-specific channel (was s"${vehicle.Actor}")
          galaxyService ! Service.Join(temp_channel) //temporary vehicle-specific channel
        }

      case VehicleResponse.KickCargo(vehicle, speed, delay) =>
        if(player.VehicleSeated.nonEmpty && deadState == DeadState.Alive) {
          if(speed > 0) {
            val strafe = if(Vehicles.CargoOrientation(vehicle) == 1) 2
            else 1
            val reverseSpeed = if(strafe > 1) 0
            else speed
            //strafe or reverse, not both
            controlled = Some(reverseSpeed)
            sendResponse(ServerVehicleOverrideMsg(true, true, true, false, 0, strafe, reverseSpeed, Some(0)))
            import scala.concurrent.ExecutionContext.Implicits.global
            context.system.scheduler.scheduleOnce(delay milliseconds, self, VehicleServiceResponse(toChannel, PlanetSideGUID(0), VehicleResponse.KickCargo(vehicle, 0, delay)))
          }
          else {
            controlled = None
            sendResponse(ServerVehicleOverrideMsg(false, false, false, false, 0, 0, 0, None))
          }
        }

      case VehicleResponse.StartPlayerSeatedInVehicle(vehicle, pad) =>
        val vehicle_guid = vehicle.GUID
        PlayerActionsToCancel()
        CancelAllProximityUnits()
        if(player.VisibleSlots.contains(player.DrawnSlot)) {
          player.DrawnSlot = Player.HandsDownSlot
          sendResponse(ObjectHeldMessage(player.GUID, Player.HandsDownSlot, true))
          continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.ObjectHeld(player.GUID, player.LastDrawnSlot))
        }
        sendResponse(PlanetsideAttributeMessage(vehicle_guid, 22, 1L)) //mount points off
        sendResponse(PlanetsideAttributeMessage(player.GUID, 21, vehicle_guid)) //ownership
        vehicle.Actor ! Mountable.TryMount(player, 0)

      case VehicleResponse.PlayerSeatedInVehicle(vehicle, pad) =>
        val vehicle_guid = vehicle.GUID
        sendResponse(PlanetsideAttributeMessage(vehicle_guid, 22, 0L)) //mount points on
        Vehicles.ReloadAccessPermissions(vehicle, player.Name)
        ServerVehicleLock(vehicle)

      case VehicleResponse.ServerVehicleOverrideStart(vehicle, pad) =>
        val vdef = vehicle.Definition
        ServerVehicleOverride(vehicle, vdef.AutoPilotSpeed1, GlobalDefinitions.isFlightVehicle(vdef) : Int)

      case VehicleResponse.ServerVehicleOverrideEnd(vehicle, pad) =>
        DriverVehicleControl(vehicle, vehicle.Definition.AutoPilotSpeed2)

      case VehicleResponse.PeriodicReminder(cause, data) =>
        val msg : String = (cause match {
          case VehicleSpawnPad.Reminders.Blocked =>
            s"The vehicle spawn where you placed your order is blocked. ${data.getOrElse("")}"
          case VehicleSpawnPad.Reminders.Queue =>
            s"Your position in the vehicle spawn queue is ${data.getOrElse("last")}."
          case VehicleSpawnPad.Reminders.Cancelled =>
            "Your vehicle order has been cancelled."
        })
        sendResponse(ChatMsg(ChatMessageType.CMT_OPEN, true, "", msg, None))

      case _ => ;
    }
  }

  /**
    * Dispatch an `ObjectAttachMessage` packet and a `CargoMountPointStatusMessage` packet only to this client.
    * @see `CargoMountPointStatusMessage`
    * @see `ObjectAttachMessage`
    * @param carrier    the ferrying vehicle
    * @param cargo      the ferried vehicle
    * @param mountPoint the point on the ferryoing vehicle where the ferried vehicle is attached
    * @return a tuple composed of an `ObjectAttachMessage` packet and a `CargoMountPointStatusMessage` packet
    */
  def CargoMountBehaviorForUs(carrier : Vehicle, cargo : Vehicle, mountPoint : Int) : (ObjectAttachMessage, CargoMountPointStatusMessage) = {
    val msgs@(attachMessage, mountPointStatusMessage) = CargoBehavior.CargoMountMessages(carrier, cargo, mountPoint)
    CargoMountMessagesForUs(attachMessage, mountPointStatusMessage)
    msgs
  }

  /**
    * Dispatch an `ObjectAttachMessage` packet and a `CargoMountPointStatusMessage` packet only to this client.
    * @see `CargoMountPointStatusMessage`
    * @see `ObjectAttachMessage`
    * @param attachMessage           an `ObjectAttachMessage` packet suitable for initializing cargo operations
    * @param mountPointStatusMessage a `CargoMountPointStatusMessage` packet suitable for initializing cargo operations
    */
  def CargoMountMessagesForUs(attachMessage : ObjectAttachMessage, mountPointStatusMessage : CargoMountPointStatusMessage) : Unit = {
    sendResponse(attachMessage)
    sendResponse(mountPointStatusMessage)
  }

  /**
    * na
    * @param tplayer na
    * @param vehicle na
    */
  def HandleNtuCharging(tplayer : Player, vehicle : Vehicle) : Unit = {
    log.trace(s"NtuCharging: Vehicle ${vehicle.GUID} is charging NTU capacitor.")
    if(vehicle.NtuCapacitor < vehicle.Definition.MaxNtuCapacitor) {
      // Charging
      vehicle.NtuCapacitor += 100
      sendResponse(PlanetsideAttributeMessage(vehicle.GUID, 45, scala.math.ceil((vehicle.NtuCapacitor.toFloat / vehicle.Definition.MaxNtuCapacitor.toFloat) * 10).toInt)) // set ntu on vehicle UI
      continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(vehicle.GUID, 52, 1L)) // panel glow on
      continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(vehicle.GUID, 49, 1L)) // orb particle effect on

      antChargingTick = context.system.scheduler.scheduleOnce(1000 milliseconds, self, NtuCharging(player, vehicle)) // Repeat until fully charged
    }
    else {
      // Fully charged
      sendResponse(PlanetsideAttributeMessage(vehicle.GUID, 45, scala.math.ceil((vehicle.NtuCapacitor.toFloat / vehicle.Definition.MaxNtuCapacitor.toFloat) * 10).toInt)) // set ntu on vehicle UI

      // Turning off glow/orb effects on ANT doesn't seem to work when deployed. Try to undeploy ANT from server side
      context.system.scheduler.scheduleOnce(vehicle.UndeployTime milliseconds, vehicle.Actor, Deployment.TryUndeploy(DriveState.Undeploying))
    }
  }

  /**
    * na
    * @param tplayer   na
    * @param vehicle   na
    * @param silo_guid na
    */
  def HandleNtuDischarging(tplayer : Player, vehicle : Vehicle, silo_guid : PlanetSideGUID) : Unit = {
    log.trace(s"NtuDischarging: Vehicle ${vehicle.GUID} is discharging NTU into silo $silo_guid")
    continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(vehicle.GUID, 49, 0L)) // orb particle effect off
    continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(vehicle.GUID, 52, 1L)) // panel glow on

    var silo = continent.GUID(silo_guid).get.asInstanceOf[ResourceSilo]
    // Check vehicle is still deployed before continuing. User can undeploy manually or vehicle may not longer be present.
    if(vehicle.DeploymentState == DriveState.Deployed) {
      if(vehicle.NtuCapacitor > 0 && silo.ChargeLevel < silo.MaximumCharge) {

        // Make sure we don't exceed the silo maximum charge or remove much NTU from ANT if maximum is reached, or try to make ANT go below 0 NTU
        var chargeToDeposit = Math.min(Math.min(vehicle.NtuCapacitor, 100), (silo.MaximumCharge - silo.ChargeLevel))
        vehicle.NtuCapacitor -= chargeToDeposit
        silo.Actor ! ResourceSilo.UpdateChargeLevel(chargeToDeposit)
        continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(silo_guid, 49, 1L)) // panel glow on & orb particles on
        sendResponse(PlanetsideAttributeMessage(vehicle.GUID, 45, scala.math.ceil((vehicle.NtuCapacitor.toFloat / vehicle.Definition.MaxNtuCapacitor.toFloat) * 10).toInt)) // set ntu on vehicle UI

        //todo: grant BEP to user
        //todo: grant BEP to squad in range

        //todo: handle silo orb / panel glow properly if more than one person is refilling silo and one player stops. effects should stay on until all players stop

        if(vehicle.NtuCapacitor > 0 && silo.ChargeLevel < silo.MaximumCharge) {
          log.trace(s"NtuDischarging: ANT not empty and Silo not full. Scheduling another discharge")
          // Silo still not full and ant still has charge left - keep rescheduling ticks
          antDischargingTick = context.system.scheduler.scheduleOnce(1000 milliseconds, self, NtuDischarging(player, vehicle, silo_guid))
        }
        else {
          log.trace(s"NtuDischarging: ANT NTU empty or Silo NTU full.")
          // Turning off glow/orb effects on ANT doesn't seem to work when deployed. Try to undeploy ANT from server side
          context.system.scheduler.scheduleOnce(vehicle.UndeployTime milliseconds, vehicle.Actor, Deployment.TryUndeploy(DriveState.Undeploying))
          continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(silo_guid, 49, 0L)) // panel glow off & orb particles off
          antDischargingTick.cancel()
        }
      }
      else {
        // This shouldn't normally be run, only if the client thinks the ANT has capacitor charge when it doesn't, or thinks the silo isn't full when it is.
        log.warn(s"NtuDischarging: Invalid discharge state. ANT Capacitor: ${vehicle.NtuCapacitor} Silo Capacitor: ${silo.ChargeLevel}")
        // Turning off glow/orb effects on ANT doesn't seem to work when deployed. Try to undeploy ANT from server side
        context.system.scheduler.scheduleOnce(vehicle.UndeployTime milliseconds, vehicle.Actor, Deployment.TryUndeploy(DriveState.Undeploying))
        continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(silo_guid, 49, 0L)) // panel glow off & orb particles off
        antDischargingTick.cancel()
      }
    }
    else {
      log.trace(s"NtuDischarging: Vehicle is no longer deployed. Removing effects")
      // Vehicle has changed from deployed and this should be the last timer tick sent
      continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(vehicle.GUID, 52, 0L)) // panel glow off
      continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(silo_guid, 49, 0L)) // panel glow off & orb particles off
      antDischargingTick.cancel()
    }
  }

  /**
    * Handle the message that indicates the level of completion of a process.
    * The process is any form of user-driven activity with a certain eventual outcome
    * but indeterminate progress feedback per cycle.<br>
    * <br>
    * This task is broken down into the "progression" from its initial state to the eventual outcome
    * as is reported back to the player through some means of messaging window feedback.
    * Though common in practice, this is not a requirement
    * and the progress can accumulate without a user reportable method.
    * To ensure that completion is reported properly,
    * an exception is made that 99% completion is accounted uniquely
    * before the final 100% is achieved.
    * If the background process recording value is never set before running the initial operation
    * or gets unset by failing a `tickAction` check
    * the process is stopped.
    * @see `progressBarUpdate`
    * @see `progressBarValue`
    * @see `WorldSessionActor.Progress`
    * @param delta          how much the progress changes each tick
    * @param completeAction a custom action performed once the process is completed
    * @param tickAction     an optional action is is performed for each tick of progress;
    *                       also performs a continuity check to determine if the process has been disrupted
    */
  def HandleProgressChange(delta : Float, completionAction : () => Unit, tickAction : Float => Boolean) : Unit = {
    progressBarUpdate.cancel
    progressBarValue match {
      case Some(value) =>
        val next = value + delta
        if(value >= 100f) {
          //complete
          progressBarValue = None
          tickAction(100)
          completionAction()
        }
        else if(value < 100f && next >= 100f) {
          if(tickAction(99)) {
            //will complete after this turn
            progressBarValue = Some(next)
            import scala.concurrent.ExecutionContext.Implicits.global
            progressBarUpdate = context.system.scheduler.scheduleOnce(100 milliseconds, self,
              ProgressEvent(delta, completionAction, tickAction)
            )
          }
          else {
            progressBarValue = None
          }
        }
        else {
          if(tickAction(next)) {
            //normal progress activity
            progressBarValue = Some(next)
            import scala.concurrent.ExecutionContext.Implicits.global
            progressBarUpdate = context.system.scheduler.scheduleOnce(250 milliseconds, self,
              ProgressEvent(delta, completionAction, tickAction)
            )
          }
          else {
            progressBarValue = None
          }
        }
      case None => ;
    }
  }

  /**
    * Instruct the client to treat this player as the avatar.
    * Initialize all client-specific data that is dependent on some player beign decalred the "avatar".
    * @param tplayer the target player
    */
  def HandleSetCurrentAvatar(tplayer : Player) : Unit = {
    player = tplayer
    val guid = tplayer.GUID
    StartBundlingPackets()
    InitializeDeployableUIElements(avatar)
    sendResponse(PlanetsideAttributeMessage(PlanetSideGUID(0), 75, 0))
    sendResponse(SetCurrentAvatarMessage(guid, 0, 0))
    sendResponse(ChatMsg(ChatMessageType.CMT_EXPANSIONS, true, "", "1 on", None)) //CC on //TODO once per respawn?
    sendResponse(PlayerStateShiftMessage(ShiftState(1, shiftPosition.getOrElse(tplayer.Position), shiftOrientation.getOrElse(tplayer.Orientation).z)))
    shiftPosition = None
    shiftOrientation = None
    if(player.spectator) {
      sendResponse(ChatMsg(ChatMessageType.CMT_TOGGLESPECTATORMODE, false, "", "on", None))
    }
    (0 until DetailedCharacterData.numberOfImplantSlots(tplayer.BEP)).foreach(slot => {
      val implantSlot = player.ImplantSlot(slot)
      if(implantSlot.Initialized) {
        sendResponse(AvatarImplantMessage(guid, ImplantAction.Initialization, slot, 1))
      }
      else {
        player.Actor ! Player.ImplantInitializationStart(slot)
      }
      //TODO if this implant is Installed but does not have shortcut, add to a free slot or write over slot 61/62/63
      // for now, just write into slots 2, 3 and 4
      Shortcut.ImplantsMap(implantSlot.Implant) match {
        case Some(shortcut : Shortcut) =>
          sendResponse(CreateShortcutMessage(guid, slot + 2, 0, addShortcut = true, Some(shortcut)))
        case _ => log.warn(s"Could not find shortcut for implant ${implantSlot.Implant.toString()}")
      }
    })

    sendResponse(PlanetsideAttributeMessage(PlanetSideGUID(0), 82, 0))
    //TODO if Medkit does not have shortcut, add to a free slot or write over slot 64
    sendResponse(CreateShortcutMessage(guid, 1, 0, true, Shortcut.MEDKIT))
    sendResponse(ChangeShortcutBankMessage(guid, 0))
    //Favorites lists
    val (inf, veh) = avatar.EquipmentLoadouts.Loadouts.partition { case (index, _) => index < 10 }
    inf.foreach {
      case (index, loadout : InfantryLoadout) =>
        sendResponse(FavoritesMessage(LoadoutType.Infantry, guid, index, loadout.label, InfantryLoadout.DetermineSubtypeB(loadout.exosuit, loadout.subtype)))
    }
    veh.foreach {
      case (index, loadout : VehicleLoadout) =>
        sendResponse(FavoritesMessage(LoadoutType.Vehicle, guid, index - 10, loadout.label))
    }
    sendResponse(SetChatFilterMessage(ChatChannel.Platoon, false, ChatChannel.values.toList)) //TODO will not always be "on" like this
    deadState = DeadState.Alive
    sendResponse(AvatarDeadStateMessage(DeadState.Alive, 0, 0, tplayer.Position, player.Faction, true))
    //looking for squad (members)
    if(tplayer.LFS || lfsm) {
      sendResponse(PlanetsideAttributeMessage(guid, 53, 1))
      continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(guid, 53, 1))
    }
    sendResponse(AvatarSearchCriteriaMessage(guid, List(0, 0, 0, 0, 0, 0)))
    (1 to 73).foreach(i => {
      // not all GUID's are set, and not all of the set ones will always be zero; what does this section do?
      sendResponse(PlanetsideAttributeMessage(PlanetSideGUID(i), 67, 0))
    })
    (0 to 30).foreach(i => {
      //TODO 30 for a new character only?
      sendResponse(AvatarStatisticsMessage(2, Statistics(0L)))
    })
    //AvatarAwardMessage
    //DisplayAwardMessage
    sendResponse(PlanetsideStringAttributeMessage(guid, 0, "Outfit Name"))
    //squad stuff (loadouts, assignment)
    squadSetup()
    //MapObjectStateBlockMessage and ObjectCreateMessage?
    //TacticsMessage?
    //change the owner on our deployables (re-draw the icons for our deployables too)
    val name = tplayer.Name
    val faction = tplayer.Faction
    continent.DeployableList
      .filter(_.OwnerName.contains(name))
      .foreach(obj => {
        obj.Owner = guid
        drawDeloyableIcon(obj)
      })
    StopBundlingPackets()
    drawDeloyableIcon = DontRedrawIcons

    //assert or transfer vehicle ownership
    continent.GUID(player.VehicleOwned) match {
      case Some(vehicle : Vehicle) if vehicle.OwnerName.contains(tplayer.Name) =>
        vehicle.Owner = guid
        continent.VehicleEvents ! VehicleServiceMessage(s"${tplayer.Faction}", VehicleAction.Ownership(guid, vehicle.GUID))
      case _ =>
        player.VehicleOwned = None
    }
    GetVehicleAndSeat() match {
      //we're falling
      case (Some(vehicle), _) if vehicle.Definition == GlobalDefinitions.droppod =>
        sendResponse(DroppodFreefallingMessage(
          vehicle.GUID,
          vehicle.Position + Vector3.z(50),
          Vector3.z(-999),
          vehicle.Position + Vector3.z(25),
          Vector3(0, 70.3125f, 90), Vector3(0, 0, 90)
        ))
      case (Some(vehicle), Some(0)) =>
        //summon any passengers and cargo vehicles left behind on previous continent
        LoadZoneTransferPassengerMessages(
          guid,
          continent.Id,
          vehicle
        )
      case _ => ;
    }
    interstellarFerryTopLevelGUID = None
    if(loadConfZone && connectionState == 100) {
      configZone(continent)
      loadConfZone = false
    }
    if(noSpawnPointHere) {
      RequestSanctuaryZoneSpawn(player, continent.Number)
    }
    else if(player.Health == 0) {
      //player died during setup; probably a relog
      player.Actor ! Player.Die()
    }
  }

  /**
    * Instruct the client to treat this player as the avatar.
    * @see `SetCurrentAvatar`
    * @param tplayer the target player
    */
  def SetCurrentAvatarNormally(tplayer : Player) : Unit = {
    self ! SetCurrentAvatar(tplayer)
  }

  /**
    * An interruption of the normal procedure -
    * "instruct the client to treat this player as the avatar" -
    * in order to locate a spawn point for this player.
    * After a spawn point is located, the actual avatar designation will be made.
    * @see `beginZoningSetCurrentAvatarFunc`
    * @see `SetCurrentAvatarNormally`
    * @see `Zone.Lattice.RequestSpawnPoint`
    * @param tplayer the target player
    */
  def SetCurrentAvatarUponDeployment(tplayer : Player) : Unit = {
    beginZoningSetCurrentAvatarFunc = SetCurrentAvatarNormally
    continent.Actor ! Zone.Lattice.RequestSpawnPoint(continent.Number, tplayer, 0)
  }

  /**
    * These messages are dispatched when first starting up the client and connecting to the server for the first time.
    * While many of thee messages will be reused for other situations, they appear in this order only during startup.
    */
  def FirstTimeSquadSetup() : Unit = {
    sendResponse(SquadDetailDefinitionUpdateMessage.Init)
    sendResponse(ReplicationStreamMessage(5, Some(6), Vector.empty)) //clear squad list
    sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SquadAction.Unknown(6)))
    //only need to load these once - they persist between zone transfers and respawns
    avatar.SquadLoadouts.Loadouts.foreach {
      case (index, loadout : SquadLoadout) =>
        sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), index, SquadAction.ListSquadFavorite(loadout.task)))
    }
    //non-squad GUID-0 counts as the settings when not joined with a squad
    sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SquadAction.AssociateWithSquad()))
    sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SquadAction.SetListSquad()))
    sendResponse(SquadDefinitionActionMessage(PlanetSideGUID(0), 0, SquadAction.Unknown(18)))
    squadService ! SquadServiceMessage(player, continent, SquadServiceAction.InitSquadList())
    squadService ! SquadServiceMessage(player, continent, SquadServiceAction.InitCharId())
    squadSetup = RespawnSquadSetup
  }

  /**
    * These messages are used during each subsequent respawn to reset the squad colors on player nameplates and marquees.
    * By using `squadUI` to maintain relevant information about squad members,
    * especially the unique character identifier number,
    * only the zone-specific squad members will receive the important messages about their squad member's spawn.
    */
  def RespawnSquadSetup() : Unit = {
    if(squadUI.nonEmpty) {
      sendResponse(PlanetsideAttributeMessage(player.GUID, 31, squad_supplement_id))
      continent.AvatarEvents ! AvatarServiceMessage(s"${player.Faction}", AvatarAction.PlanetsideAttribute(player.GUID, 31, squad_supplement_id))
      sendResponse(PlanetsideAttributeMessage(player.GUID, 32, squadUI(player.CharId).index))
    }
  }

  /**
    * These messages are used during each subsequent respawn to reset the squad colors on player nameplates and marquees.
    * During a zone change,
    * on top of other squad mates in the zone needing to have their knowledge of this player's squad colors changed,
    * the player must also set squad colors for each other squad members.
    * Default respawn functionality may resume afterwards.
    */
  def ZoneChangeSquadSetup() : Unit = {
    RespawnSquadSetup()
    GiveSquadColorsInZone()
    squadSetup = RespawnSquadSetup
  }

  /**
    * Allocate all squad members in zone and give their nameplates and their marquees the appropriate squad color.
    */
  def GiveSquadColorsInZone() : Unit = {
    GiveSquadColorsInZone(squadUI.keys, squad_supplement_id)
  }

  /**
    * Allocate the listed squad members in zone and give their nameplates and their marquees the appropriate squad color.
    * @param members members of the squad to target
    */
  def GiveSquadColorsInZone(members : Iterable[Long]) : Unit = {
    GiveSquadColorsInZone(members, squad_supplement_id)
  }

  /**
    * Allocate the listed squad members in zone and give their nameplates and their marquees the appropriate squad color.
    * @see `PlanetsideAttributeMessage`
    * @param members members of the squad to target
    * @param value   the assignment value
    */
  def GiveSquadColorsInZone(members : Iterable[Long], value : Long) : Unit = {
    SquadMembersInZone(members).foreach {
      members => sendResponse(PlanetsideAttributeMessage(members.GUID, 31, value))
    }
  }

  /**
    * For the listed squad member unique character identifier numbers,
    * find and return all squad members in the current zone.
    * @param members members of the squad to target
    * @return a list of `Player` objects
    */
  def SquadMembersInZone(members : Iterable[Long]) : Iterable[Player] = {
    val players = continent.LivePlayers
    for {
      charId <- members
      player = players.find {
        _.CharId == charId
      }
      if player.nonEmpty
    } yield player.get
  }

  def handleControlPkt(pkt : PlanetSideControlPacket) = {
    pkt match {
      case sync@ControlSync(diff, _, _, _, _, _, fa, fb) =>
        log.trace(s"SYNC: $sync")
        val nextDiff = if(diff == 65535) {
          0
        }
        else {
          diff + 1
        }
        val serverTick = ServerTick
        sendResponse(ControlSyncResp(nextDiff, serverTick, fa, fb, fb, fa))

      case TeardownConnection(_) =>
        log.info("Good bye")

      case default =>
        log.warn(s"Unhandled ControlPacket $default")
    }
  }

  /**
    * Return a measure of server time as an unsigned 32-bit integer.
    * The server time started at 0 back at the beginning (POSIX time).
    * The server time will loop around to 0 again to maintain datatype integrity.
    * @see `Int.MaxValue`
    * @see `System.nanoTime`
    * @return a number that indicates server tick time
    */
  def ServerTick : Long = {
    serverTime = System.currentTimeMillis() & unsignedIntMaxValue
    serverTime
  }

  def handleGamePkt(pkt : PlanetSideGamePacket) = pkt match {
    case ConnectToWorldRequestMessage(server, token, majorVersion, minorVersion, revision, buildDate, unk) =>
      val clientVersion = s"Client Version: $majorVersion.$minorVersion.$revision, $buildDate"
      log.info(s"New world login to $server with Token:$token. $clientVersion")
      sendResponse(ChatMsg(ChatMessageType.CMT_CULLWATERMARK, false, "", "", None))
      Thread.sleep(40)
      import scala.concurrent.ExecutionContext.Implicits.global
      clientKeepAlive.cancel
      clientKeepAlive = context.system.scheduler.schedule(0 seconds, 500 milliseconds, self, PokeClient())
      accountIntermediary ! RetrieveAccountData(token)

    case msg@MountVehicleCargoMsg(player_guid, cargo_guid, carrier_guid, unk4) =>
      log.info(msg.toString)
      (continent.GUID(cargo_guid), continent.GUID(carrier_guid)) match {
        case (Some(cargo : Vehicle), Some(carrier : Vehicle)) =>
          carrier.CargoHolds.find({ case (_, hold) => !hold.isOccupied }) match {
            case Some((mountPoint, _)) => //try begin the mount process
              cargo.Actor ! CargoBehavior.CheckCargoMounting(carrier_guid, mountPoint, 0)
            case _ =>
              log.warn(s"MountVehicleCargoMsg: target carrier vehicle (${carrier.Definition.Name}) does not have a cargo hold")
          }
        case (None, _) | (Some(_), None) =>
          log.warn(s"MountVehicleCargoMsg: one or more of the target vehicles do not exist - $carrier_guid or $cargo_guid")
        case _ => ;
      }

    case msg@DismountVehicleCargoMsg(player_guid, cargo_guid, bailed, requestedByPassenger, kicked) =>
      log.info(msg.toString)
      //when kicked by carrier driver, player_guid will be PlanetSideGUID(0)
      //when exiting of the cargo vehicle driver's own accord, player_guid will be the cargo vehicle driver
      continent.GUID(cargo_guid) match {
        case Some(cargo : Vehicle) if !requestedByPassenger =>
          continent.GUID(cargo.MountedIn) match {
            case Some(carrier : Vehicle) =>
              CargoBehavior.HandleVehicleCargoDismount(continent, cargo_guid, bailed, requestedByPassenger, kicked)
            case _ => ;
          }
        case _ => ;
      }

    case msg@CharacterCreateRequestMessage(name, head, voice, gender, empire) =>
      log.info("Handling " + msg)
      Database.getConnection.connect.onComplete {
        case scala.util.Success(connection) =>
          Database.query(connection.sendPreparedStatement(
            "SELECT account_id FROM characters where name ILIKE ? AND deleted = false", Array(name)
          )).onComplete {
            case scala.util.Success(queryResult) =>
              if(connection.isConnected) connection.disconnect
              queryResult match {
                case row : ArrayRowData => // If we got a row from the database
                  if(row(0).asInstanceOf[Int] == account.AccountId) { // create char
                    self ! CreateCharacter(name, head, voice, gender, empire)
                    sendResponse(ActionResultMessage.Fail(1))
                    Thread.sleep(50)
                  }
                  else { // send "char already exist"
                    sendResponse(ActionResultMessage.Fail(1))
                    Thread.sleep(50)
                  }
                case _ => // If the char name didn't exist in the database, create char
                  self ! CreateCharacter(name, head, voice, gender, empire)
              }
            case scala.util.Failure(e) =>
              if(connection.isConnected) connection.disconnect
              sendResponse(ActionResultMessage.Fail(4))
              log.error("Returning to character list due to error " + e.getMessage)
              self ! ListAccountCharacters()
          }
        case scala.util.Failure(e) =>
          log.error(s"CharacterCreateRequest: no connection - ${e.getMessage}")
          sendResponse(ActionResultMessage.Fail(5))
      }

    case msg@CharacterRequestMessage(charId, action) =>
      log.info(s"Handling $msg")
      action match {
        case CharacterRequestAction.Delete =>
          Database.getConnection.connect.onComplete {
            case scala.util.Success(connection) =>
              Database.query(connection.sendPreparedStatement(
                "UPDATE characters SET deleted = true where id=?", Array(charId)
              )).onComplete {
                case scala.util.Success(_) =>
                  if(connection.isConnected) connection.disconnect
                  log.info(s"CharacterRequest/Delete: character id $charId deleted")
                  sendResponse(ActionResultMessage.Pass)
                  self ! ListAccountCharacters()
                case scala.util.Failure(e) =>
                  if(connection.isConnected) connection.disconnect
                  log.info(s"CharacterRequest/Delete: character id $charId NOT deleted - ${e.getMessage}")
                  sendResponse(ActionResultMessage.Fail(6))
                  Thread.sleep(50)
              }
            case scala.util.Failure(e) =>
              log.error(s"CharacterRequest/Delete: no connection - ${e.getMessage}")
          }

        case CharacterRequestAction.Select =>
          Database.getConnection.connect.onComplete {
            case scala.util.Success(connection) =>
              Database.query(connection.sendPreparedStatement(
                "SELECT id, name, faction_id, gender_id, head_id, voice_id FROM characters where id=?", Array(charId)
              )).onComplete {
                case Success(queryResult) =>
                  if(connection.isConnected) connection.disconnect
                  queryResult match {
                    case row : ArrayRowData =>
                      val lName : String = row(1).asInstanceOf[String]
                      val lFaction : PlanetSideEmpire.Value = PlanetSideEmpire(row(2).asInstanceOf[Int])
                      val lGender : CharacterGender.Value = CharacterGender(row(3).asInstanceOf[Int])
                      val lHead : Int = row(4).asInstanceOf[Int]
                      val lVoice : CharacterVoice.Value = CharacterVoice(row(5).asInstanceOf[Int])
                      log.info(s"CharacterRequest/Select: character $lName found in records")
                      avatar = new Avatar(charId, lName, lFaction, lGender, lHead, lVoice)
                      var faction : String = lFaction.toString.toLowerCase
                      whenUsedLastMAXName(2) = faction + "hev_antipersonnel"
                      whenUsedLastMAXName(3) = faction + "hev_antivehicular"
                      whenUsedLastMAXName(1) = faction + "hev_antiaircraft"
                      avatar.FirstTimeEvents = ftes
                      accountPersistence ! AccountPersistenceService.Login(lName)
                    case _ =>
                      log.error(s"CharacterRequest/Select: no character for $charId found")
                  }
                case e =>
                  if(connection.isConnected) connection.disconnect
                  log.error(s"CharacterRequest/Select: toto tata; unexpected query result format - ${e.getClass}")
              }

            case scala.util.Failure(e) =>
              log.error(s"CharacterRequest/Select: no connection - ${e.getMessage}")
          }

        case default =>
          log.error("Unsupported " + default + " in " + msg)
      }

    case KeepAliveMessage(code) =>
      sendResponse(KeepAliveMessage())

    case msg@BeginZoningMessage() =>
      log.info("Reticulating splines ...")
      val continentId = continent.Id
      traveler.zone = continentId
      val faction = player.Faction
      val factionChannel = s"$faction"
      continent.AvatarEvents ! Service.Join(continentId)
      continent.AvatarEvents ! Service.Join(factionChannel)
      continent.LocalEvents ! Service.Join(avatar.name)
      continent.LocalEvents ! Service.Join(continentId)
      continent.LocalEvents ! Service.Join(factionChannel)
      continent.VehicleEvents ! Service.Join(avatar.name)
      continent.VehicleEvents ! Service.Join(continentId)
      continent.VehicleEvents ! Service.Join(factionChannel)
      if(connectionState != 100) configZone(continent)
      sendResponse(TimeOfDayMessage(1191182336))
      //custom
      sendResponse(ReplicationStreamMessage(5, Some(6), Vector.empty)) //clear squad list
      sendResponse(PlanetsideAttributeMessage(PlanetSideGUID(0), 112, 0)) // disable festive backpacks

      //find and reclaim own deployables, if any
      val guid = player.GUID
      val foundDeployables = continent.DeployableList.filter(obj => obj.OwnerName.contains(player.Name) && obj.Health > 0)
      continent.LocalEvents ! LocalServiceMessage.Deployables(RemoverActor.ClearSpecific(foundDeployables, continent))
      foundDeployables.foreach(obj => {
        if(avatar.Deployables.Add(obj)) {
          obj.Owner = guid
          log.info(s"Found a ${obj.Definition.Name} of ours while loading the zone")
        }
      })
      //render deployable objects
      val (turrets, normal) = continent.DeployableList.partition(obj =>
        DeployableToolbox.UnifiedType(obj.Definition.Item) == DeployedItem.portable_manned_turret
      )
      normal.foreach(obj => {
        val definition = obj.Definition
        sendResponse(
          ObjectCreateMessage(
            definition.ObjectId,
            obj.GUID,
            definition.Packet.ConstructorData(obj).get
          )
        )
      })
      turrets.foreach(obj => {
        val objGUID = obj.GUID
        val definition = obj.Definition
        sendResponse(
          ObjectCreateMessage(
            definition.ObjectId,
            objGUID,
            definition.Packet.ConstructorData(obj).get
          )
        )
        //seated players
        obj.asInstanceOf[Mountable].Seats.values
          .map(_.Occupant)
          .collect {
            case Some(occupant) =>
              if(occupant.isAlive) {
                val tdefintion = occupant.Definition
                sendResponse(
                  ObjectCreateMessage(
                    tdefintion.ObjectId,
                    occupant.GUID,
                    ObjectCreateMessageParent(objGUID, 0),
                    tdefintion.Packet.ConstructorData(occupant).get
                  )
                )
              }
          }
      })
      //sensor animation
      normal
        .filter(obj =>
          obj.Definition.DeployCategory == DeployableCategory.Sensors &&
            !obj.Destroyed &&
            (obj match {
              case jObj : JammableUnit => !jObj.Jammed;
              case _ => true
            })
        )
        .foreach(obj => {
          sendResponse(TriggerEffectMessage(obj.GUID, "on", true, 1000))
        })
      //update the health of our faction's deployables (if necessary)
      //draw our faction's deployables on the map
      continent.DeployableList
        .filter(obj => obj.Faction == faction && !obj.Destroyed)
        .foreach(obj => {
          if(obj.Health != obj.DefaultHealth) {
            sendResponse(PlanetsideAttributeMessage(obj.GUID, 0, obj.Health))
          }
          val deployInfo = DeployableInfo(obj.GUID, Deployable.Icon(obj.Definition.Item), obj.Position, obj.Owner.getOrElse(PlanetSideGUID(0)))
          sendResponse(DeployableObjectsInfoMessage(DeploymentAction.Build, deployInfo))
        })
      //render Equipment that was dropped into zone before the player arrived
      continent.EquipmentOnGround.foreach(item => {
        val definition = item.Definition
        sendResponse(
          ObjectCreateMessage(
            definition.ObjectId,
            item.GUID,
            DroppedItemData(PlacementData(item.Position, item.Orientation), definition.Packet.ConstructorData(item).get)
          )
        )
      })
      //load active players in zone (excepting players who are seated or players who are us)
      val live = continent.LivePlayers
      live.filterNot(tplayer => {
        tplayer.GUID == player.GUID || tplayer.VehicleSeated.nonEmpty
      })
        .foreach(char => {
          val tdefintion = char.Definition
          sendResponse(ObjectCreateMessage(tdefintion.ObjectId, char.GUID, char.Definition.Packet.ConstructorData(char).get))
          if(char.UsingSpecial == SpecialExoSuitDefinition.Mode.Anchored) {
            sendResponse(PlanetsideAttributeMessage(char.GUID, 19, 1))
          }
        })
      //load corpses in zone
      continent.Corpses.foreach {
        TurnPlayerIntoCorpse
      }
      //load vehicles in zone (put separate the one we may be using)
      val (wreckages, (vehicles, usedVehicle)) = {
        val (a, b) = continent.Vehicles.partition(vehicle => {
          vehicle.Destroyed && vehicle.Definition.DestroyedModel.nonEmpty
        })
        (a, (continent.GUID(player.VehicleSeated) match {
          case Some(vehicle : Vehicle) if vehicle.PassengerInSeat(player).isDefined =>
            b.partition {
              _.GUID != vehicle.GUID
            }
          case Some(_) =>
            //vehicle, but we're not seated in it
            player.VehicleSeated = None
            (b, List.empty[Vehicle])
          case None =>
            //throw error since VehicleSeated didn't point to a vehicle?
            player.VehicleSeated = None
            (b, List.empty[Vehicle])
        }))
      }
      //active vehicles (and some wreckage)
      vehicles.foreach(vehicle => {
        val vguid = vehicle.GUID
        val vdefinition = vehicle.Definition
        sendResponse(ObjectCreateMessage(vdefinition.ObjectId, vguid, vdefinition.Packet.ConstructorData(vehicle).get))
        //occupants other than driver
        vehicle.Seats
          .filter({ case (index, seat) => seat.isOccupied && live.contains(seat.Occupant.get) && index > 0 })
          .foreach({ case (index, seat) =>
            val tplayer = seat.Occupant.get
            val tdefintion = tplayer.Definition
            sendResponse(
              ObjectCreateMessage(
                tdefintion.ObjectId,
                tplayer.GUID,
                ObjectCreateMessageParent(vguid, index),
                tdefintion.Packet.ConstructorData(tplayer).get
              )
            )
          })
      })
      vehicles.collect { case vehicle if vehicle.Faction == faction =>
        Vehicles.ReloadAccessPermissions(vehicle, player.Name)
      }
      //our vehicle would have already been loaded; see NewPlayerLoaded/AvatarCreate
      usedVehicle.headOption match {
        case Some(vehicle) =>
          //depict any other passengers already in this zone
          val vguid = vehicle.GUID
          vehicle.Seats
            .filter({ case (index, seat) => seat.isOccupied && !seat.Occupant.contains(player) && live.contains(seat.Occupant.get) && index > 0 })
            .foreach({ case (index, seat) =>
              val tplayer = seat.Occupant.get
              val tdefintion = tplayer.Definition
              sendResponse(
                ObjectCreateMessage(
                  tdefintion.ObjectId,
                  tplayer.GUID,
                  ObjectCreateMessageParent(vguid, index),
                  tdefintion.Packet.ConstructorData(tplayer).get
                )
              )
            })
          //since we would have only subscribed recently, we need to reload seat access states
          (0 to 3).foreach { group =>
            sendResponse(PlanetsideAttributeMessage(vguid, group + 10, vehicle.PermissionGroup(group).get.id))
          }
          //positive shield strength
          if(vehicle.Shields > 0) {
            sendResponse(PlanetsideAttributeMessage(vguid, 68, vehicle.Shields))
          }
        case _ => ; //no vehicle
      }
      //vehicle wreckages
      wreckages.foreach(vehicle => {
        sendResponse(
          ObjectCreateMessage(
            vehicle.Definition.DestroyedModel.get.id,
            vehicle.GUID,
            DestroyedVehicleConverter.converter.ConstructorData(vehicle).get
          )
        )
      })
      //cargo occupants (including our own vehicle as cargo)
      vehicles.collect { case vehicle if vehicle.CargoHolds.nonEmpty =>
        vehicle.CargoHolds.collect({ case (index, hold) if hold.isOccupied => {
          CargoBehavior.CargoMountBehaviorForAll(vehicle, hold.Occupant.get, index) //CargoMountBehaviorForUs can fail to attach the cargo vehicle on some clients
        }
        })
      }
      //special deploy states
      val deployedVehicles = vehicles.filter(_.DeploymentState == DriveState.Deployed)
      deployedVehicles.filter(_.Definition == GlobalDefinitions.ams).foreach(obj => {
        sendResponse(PlanetsideAttributeMessage(obj.GUID, 81, 1))
      })
      deployedVehicles.filter(_.Definition == GlobalDefinitions.router).foreach(obj => {
        sendResponse(DeployRequestMessage(player.GUID, obj.GUID, DriveState.Deploying, 0, false, Vector3.Zero))
        sendResponse(DeployRequestMessage(player.GUID, obj.GUID, DriveState.Deployed, 0, false, Vector3.Zero))
        ToggleTeleportSystem(obj, TelepadLike.AppraiseTeleportationSystem(obj, continent))
      })

      //implant terminals
      continent.Map.TerminalToInterface.foreach({ case ((terminal_guid, interface_guid)) =>
        val parent_guid = PlanetSideGUID(terminal_guid)
        continent.GUID(interface_guid) match {
          case Some(obj : Terminal) =>
            val objDef = obj.Definition
            sendResponse(
              ObjectCreateMessage(
                objDef.ObjectId,
                PlanetSideGUID(interface_guid),
                ObjectCreateMessageParent(parent_guid, 1),
                objDef.Packet.ConstructorData(obj).get
              )
            )
          case _ => ;
        }
        //seat terminal occupants
        continent.GUID(terminal_guid) match {
          case Some(obj : Mountable) =>
            obj.Seats(0).Occupant match {
              case Some(tplayer) =>
                val tdefintion = tplayer.Definition
                sendResponse(
                  ObjectCreateMessage(
                    tdefintion.ObjectId,
                    tplayer.GUID,
                    ObjectCreateMessageParent(parent_guid, 0),
                    tdefintion.Packet.ConstructorData(tplayer).get
                  )
                )
              case None => ;
            }
          case _ => ;
        }
      })

      //base turrets
      continent.Map.TurretToWeapon
        .map { case ((turret_guid, _)) => continent.GUID(turret_guid) }
        .collect { case Some(turret : FacilityTurret) =>
          val pguid = turret.GUID
          //attached weapon
          if(!turret.isUpgrading) {
            turret.ControlledWeapon(wepNumber = 1) match {
              case Some(obj : Tool) =>
                val objDef = obj.Definition
                sendResponse(
                  ObjectCreateMessage(
                    objDef.ObjectId,
                    obj.GUID,
                    ObjectCreateMessageParent(pguid, 1),
                    objDef.Packet.ConstructorData(obj).get
                  )
                )
              case _ => ;
            }
          }
          //reserved ammunition?
          //TODO need to register if it exists
          //seat turret occupant
          turret.Seats(0).Occupant match {
            case Some(tplayer) =>
              val tdefintion = tplayer.Definition
              sendResponse(
                ObjectCreateMessage(
                  tdefintion.ObjectId,
                  tplayer.GUID,
                  ObjectCreateMessageParent(pguid, 0),
                  tdefintion.Packet.ConstructorData(tplayer).get
                )
              )
            case None => ;
          }
        }
      continent.VehicleEvents ! VehicleServiceMessage(continent.Id, VehicleAction.UpdateAmsSpawnPoint(continent))
      beginZoningSetCurrentAvatarFunc(player)

    case msg @ PlayerStateMessageUpstream(avatar_guid, pos, vel, yaw, pitch, yaw_upper, seq_time, unk3, is_crouching, is_jumping, jump_thrust, is_cloaking, unk5, unk6) =>
      if (player.death_by == -1) {
        sendResponse(ChatMsg(ChatMessageType.UNK_71, true, "", "Your account has been logged out by a Customer Service Representative.", None))
        Thread.sleep(300)
        sendResponse(DropSession(sessionId, "kick by GM"))
      }
      playerStateMessageUpstreamCount += 1
      val isMoving = WorldEntity.isMoving(vel)
      val isMovingPlus = isMoving || is_jumping || jump_thrust
      if(isMovingPlus) {
        CancelZoningProcessWithDescriptiveReason("cancel_motion")
      }

      if(deadState == DeadState.Alive && playerStateMessageUpstreamCount % 2 == 0) { // Regen stamina roughly every 500ms
        if(player.skipStaminaRegenForTurns > 0) {
          //do not renew stamina for a while
          player.skipStaminaRegenForTurns -= 1
        }
        else if(!isMovingPlus && player.Stamina != player.MaxStamina) {
          player.Stamina += 1
        }
      }
      player.Position = pos
      player.Velocity = vel
      player.Orientation = Vector3(player.Orientation.x, pitch, yaw)
      player.FacingYawUpper = yaw_upper
      player.Crouching = is_crouching
      player.Jumping = is_jumping
      if(is_cloaking && !player.Cloaked) {
        CancelZoningProcessWithDescriptiveReason("cancel_cloak")
      }
      player.Cloaked = player.ExoSuit == ExoSuitType.Infiltration && is_cloaking
      CapacitorTick(jump_thrust)
      if(isMovingPlus && usingMedicalTerminal.isDefined) {
        continent.GUID(usingMedicalTerminal) match {
          case Some(term : Terminal with ProximityUnit) =>
            StopUsingProximityUnit(term)
          case _ => ;
        }
      }
      accessedContainer match {
        case Some(veh : Vehicle) =>
          if(isMoving || Vector3.DistanceSquared(player.Position, veh.Position) > 100) {
            val guid = player.GUID
            sendResponse(UnuseItemMessage(guid, veh.GUID))
            sendResponse(UnuseItemMessage(guid, guid))
            veh.AccessingTrunk = None
            UnAccessContents(veh)
            accessedContainer = None
          }
        case Some(container) => //just in case
          if(isMovingPlus) {
            val guid = player.GUID
            // If the container is a corpse and gets removed just as this runs it can cause a client disconnect, so we'll check the container has a GUID first.
            if(container.HasGUID) {
              sendResponse(UnuseItemMessage(guid, container.GUID))
            }
            sendResponse(UnuseItemMessage(guid, guid))
            accessedContainer = None
          }
        case None => ;
      }
      val wepInHand : Boolean = player.Slot(player.DrawnSlot).Equipment match {
        case Some(item) => item.Definition == GlobalDefinitions.bolt_driver
        case None => false
      }
      continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.PlayerState(avatar_guid, player.Position, player.Velocity, yaw, pitch, yaw_upper, seq_time, is_crouching, is_jumping, jump_thrust, is_cloaking, player.spectator, wepInHand))
      updateSquad()

    case msg@ChildObjectStateMessage(object_guid, pitch, yaw) =>
      //the majority of the following check retrieves information to determine if we are in control of the child
      FindContainedWeapon match {
        case (Some(_), Some(tool)) =>
          if(tool.GUID == object_guid) {
            //TODO set tool orientation?
            player.Orientation = Vector3(0f, pitch, yaw)
            continent.VehicleEvents ! VehicleServiceMessage(continent.Id, VehicleAction.ChildObjectState(player.GUID, object_guid, pitch, yaw))
          }
          else {
            log.warn(s"ChildObjectState: ${player.Name} is using a different controllable agent than #${object_guid.guid}")
          }
        case (Some(obj), None) =>
          log.warn(s"ChildObjectState: ${player.Name} can not find any controllable agent, let alone #${object_guid.guid}")
        case (None, _) => ;
        //TODO status condition of "playing getting out of vehicle to allow for late packets without warning
        //log.warn(s"ChildObjectState: player $player not related to anything with a controllable agent")
      }
      if (player.death_by == -1) {
        sendResponse(ChatMsg(ChatMessageType.UNK_71, true, "", "Your account has been logged out by a Customer Service Representative.", None))
        Thread.sleep(300)
        sendResponse(DropSession(sessionId, "kick by GM"))
      }

    case msg@VehicleStateMessage(vehicle_guid, unk1, pos, ang, vel, flying, unk6, unk7, wheels, is_decelerating, is_cloaked) =>
      if(deadState == DeadState.Alive) {
        GetVehicleAndSeat() match {
          case (Some(obj), Some(0)) =>
            val seat = obj.Seats(0)
            //we're driving the vehicle
            player.Position = pos //convenient
            if(seat.ControlledWeapon.isEmpty) {
              player.Orientation = Vector3.z(ang.z) //convenient
            }
            obj.Position = pos
            obj.Orientation = ang
            if(obj.MountedIn.isEmpty) {
              if(obj.DeploymentState != DriveState.Deployed) {
                obj.Velocity = vel
              } else {
                obj.Velocity = Some(Vector3.Zero)
              }
              if(obj.Definition.CanFly) {
                obj.Flying = flying.nonEmpty //usually Some(7)
              }
              obj.Cloaked = obj.Definition.CanCloak && is_cloaked
            }
            else {
              obj.Velocity = None
              obj.Flying = false
            }
            continent.VehicleEvents ! VehicleServiceMessage(continent.Id, VehicleAction.VehicleState(player.GUID, vehicle_guid, unk1, obj.Position, ang, obj.Velocity, if(obj.Flying) {
              flying
            }
            else {
              None
            }, unk6, unk7, wheels, is_decelerating, obj.Cloaked))
            updateSquad()
          case (None, _) =>
          //log.error(s"VehicleState: no vehicle $vehicle_guid found in zone")
          //TODO placing a "not driving" warning here may trigger as we are disembarking the vehicle
          case (_, Some(index)) =>
            log.error(s"VehicleState: player should not be dispatching this kind of packet from vehicle#$vehicle_guid  when not the driver ($index)")
          case _ => ;
        }
      }
    //log.info(s"VehicleState: $msg")
      if (player.death_by == -1) {
        sendResponse(ChatMsg(ChatMessageType.UNK_71, true, "", "Your account has been logged out by a Customer Service Representative.", None))
        Thread.sleep(300)
        sendResponse(DropSession(sessionId, "kick by GM"))
      }

    case msg@VehicleSubStateMessage(vehicle_guid, player_guid, vehicle_pos, vehicle_ang, vel, unk1, unk2) =>
    //log.info(s"VehicleSubState: $vehicle_guid, $player_guid, $vehicle_pos, $vehicle_ang, $vel, $unk1, $unk2")

    case msg@ProjectileStateMessage(projectile_guid, shot_pos, shot_vel, shot_orient, seq, end, target_guid) =>
      //log.trace(s"ProjectileState: $msg")
      val index = projectile_guid.guid - Projectile.BaseUID
      projectiles(index) match {
        case Some(projectile) if projectile.HasGUID =>
          val projectileGlobalUID = projectile.GUID
          projectile.Position = shot_pos
          projectile.Orientation = shot_orient
          projectile.Velocity = shot_vel
          continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.ProjectileState(player.GUID, projectileGlobalUID, shot_pos, shot_vel, shot_orient, seq, end, target_guid))
        case _ if seq == 0 =>
        /* missing the first packet in the sequence is permissible  */
        case _ =>
          log.warn(s"ProjectileState: constructed projectile ${projectile_guid.guid} can not be found")
      }

    case msg @ ReleaseAvatarRequestMessage() =>
      log.info(s"ReleaseAvatarRequest: ${player.GUID} on ${continent.Id} has released")
      reviveTimer.cancel
      GoToDeploymentMap()
      continent.Population ! Zone.Population.Release(avatar)
      player.VehicleSeated match {
        case None =>
          PrepareToTurnPlayerIntoCorpse(player, continent)

        case Some(_) =>
          val player_guid = player.GUID
          sendResponse(ObjectDeleteMessage(player_guid, 0))
          GetMountableAndSeat(None, player) match {
            case (Some(obj), Some(seatNum)) =>
              obj.Seats(seatNum).Occupant = None
              obj match {
                case v : Vehicle if seatNum == 0 && v.Flying =>
                  TotalDriverVehicleControl(v)
                  UnAccessContents(v)
                  v.Actor ! Vehicle.Deconstruct()
                case _ => ;
              }
            case _ => ; //found no vehicle where one was expected; since we're dead, let's not dwell on it
          }
          taskResolver ! GUIDTask.UnregisterPlayer(player)(continent.GUID)
      }

    case msg@SpawnRequestMessage(u1, spawn_type, u3, u4, zone_number) =>
      log.info(s"SpawnRequestMessage: $msg")
      if(deadState != DeadState.RespawnTime) {
        deadState = DeadState.RespawnTime
        cluster ! Zone.Lattice.RequestSpawnPoint(zone_number.toInt, player, spawn_type.id.toInt)
      }
      else {
        log.warn("SpawnRequestMessage: request consumed; already respawning ...")
      }

    case msg@SetChatFilterMessage(send_channel, origin, whitelist) =>
    //log.info("SetChatFilters: " + msg)

    case msg@ChatMsg(messagetype, has_wide_contents, recipient, contents, note_contents) =>
      var makeReply : Boolean = false
      var echoContents : String = contents
      val trimContents = contents.trim
      val trimRecipient = recipient.trim
      //TODO messy on/off strings may work
      if(messagetype == ChatMessageType.CMT_FLY && admin) {
        makeReply = false
        if(!flying) {
          flying = true
          sendResponse(ChatMsg(ChatMessageType.CMT_FLY, msg.wideContents, msg.recipient, "on", msg.note))
        }
        else {
          flying = false
          sendResponse(ChatMsg(ChatMessageType.CMT_FLY, msg.wideContents, msg.recipient, "off", msg.note))
        }
      }
      else if(messagetype == ChatMessageType.CMT_SPEED && admin) {
        makeReply = true
        speed = {
          try {
            trimContents.toFloat
          }
          catch {
            case _ : Exception =>
              echoContents = "1.000"
              1f
          }
        }
      }
      else if(messagetype == ChatMessageType.CMT_TOGGLESPECTATORMODE && admin) {
        makeReply = false
        if(!player.spectator) {
          player.spectator = true
          sendResponse(ChatMsg(ChatMessageType.CMT_TOGGLESPECTATORMODE, msg.wideContents, msg.recipient, "on", msg.note))
        }
        else {
          player.spectator = false
          sendResponse(ChatMsg(ChatMessageType.CMT_TOGGLESPECTATORMODE, msg.wideContents, msg.recipient, "off", msg.note))
        }
      }
      else if(messagetype == ChatMessageType.CMT_RECALL) {
        makeReply = false
        val sanctuary = Zones.SanctuaryZoneId(player.Faction)
        if(zoningType == Zoning.Method.InstantAction) {
          sendResponse(ChatMsg(ChatMessageType.CMT_QUIT, false, "", "@noinstantaction_instantactionting", None))
        }
        else if(zoningType == Zoning.Method.Recall) {
          sendResponse(ChatMsg(ChatMessageType.CMT_QUIT, false, "", "You already requested to recall to your sanctuary continent.", None))
        }
        else if(continent.Id.equals(sanctuary)) {
          //nonstandard message
          sendResponse(ChatMsg(ChatMessageType.CMT_QUIT, false, "", "You can't recall when you are already on your faction's sanctuary continent.", None))
        }
        else if(deadState != DeadState.Alive) {
          sendResponse(ChatMsg(ChatMessageType.CMT_QUIT, false, "", "@norecall_dead", None))
        }
        else if(player.VehicleSeated.nonEmpty) {
          sendResponse(ChatMsg(ChatMessageType.CMT_QUIT, false, "", "@norecall_invehicle", None))
        }
        else {
          zoningType = Zoning.Method.Recall
          zoningChatMessageType = messagetype
          zoningStatus = Zoning.Status.Request
          zoningReset = context.system.scheduler.scheduleOnce(10 seconds, self, ZoningReset())
          cluster ! Zoning.Recall.Request(player.Faction, sanctuary)
        }
      }
      else if(messagetype == ChatMessageType.CMT_INSTANTACTION) {
        makeReply = false
        if(zoningType == Zoning.Method.InstantAction) {
          sendResponse(ChatMsg(ChatMessageType.CMT_QUIT, false, "", "@noinstantaction_instantactionting", None))
        }
        else if(zoningType == Zoning.Method.Recall) {
          //nonstandard message
          sendResponse(ChatMsg(ChatMessageType.CMT_QUIT, false, "", "You already requested to recall to your sanctuary continent.", None))
        }
        else if(deadState != DeadState.Alive) {
          sendResponse(ChatMsg(ChatMessageType.CMT_QUIT, false, "", "@noinstantaction_dead", None))
        }
        else if(player.VehicleSeated.nonEmpty) {
          sendResponse(ChatMsg(ChatMessageType.CMT_QUIT, false, "", "@noinstantaction_invehicle", None))
        }
        else {
          zoningType = Zoning.Method.InstantAction
          zoningChatMessageType = messagetype
          zoningStatus = Zoning.Status.Request
          zoningReset = context.system.scheduler.scheduleOnce(10 seconds, self, ZoningReset())
          cluster ! Zoning.InstantAction.Request(player.Faction)
        }
      }
      CSRZone.read(traveler, msg) match {
        case (true, zone, pos) =>
          if (player.isAlive && zone != player.Continent && (admin || zone == "z8" || zone == "c1" || zone == "c2" || zone == "c3" || zone == "c4" || zone == "c5" || zone == "c6" ||
            zone == "tzshtr" || zone == "tzcotr" || zone == "tzdrtr" ||
            zone == "tzshnc" || zone == "tzconc" || zone == "tzdrnc" ||
            zone == "tzshvs" || zone == "tzcovs" || zone == "tzdrvs")) {
            deadState = DeadState.Release //cancel movement updates
            PlayerActionsToCancel()
            continent.GUID(player.VehicleSeated) match {
              case Some(vehicle : Vehicle) if vehicle.MountedIn.isEmpty =>
                vehicle.PassengerInSeat(player) match {
                  case Some(0) =>
                    vehicle.Position = pos
                    LoadZonePhysicalSpawnPoint(zone, pos, Vector3.Zero, 0)
                  case _ => //not seated as the driver, in which case we can't move
                    deadState = DeadState.Alive
                }
              case None =>
                player.Position = pos
                //continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.ObjectDelete(player.GUID, player.GUID))
                LoadZonePhysicalSpawnPoint(zone, pos, Vector3.Zero, 0)
              case _ => //seated in something that is not a vehicle or the vehicle is cargo, in which case we can't move
                deadState = DeadState.Alive
            }
          }
        case (_, _, _) => ;
      }

      CSRWarp.read(traveler, msg) match {
        case (true, pos) =>
          // continent.Id == "c1" || continent.Id == "c2" || continent.Id == "c3" || continent.Id == "c4" || continent.Id == "c5" || continent.Id == "c6" ||
          if (player.isAlive && (admin || continent.Id == "z8" ||
            continent.Id == "tzshtr" || continent.Id == "tzcotr" || continent.Id == "tzdrtr" ||
            continent.Id == "tzshnc" || continent.Id == "tzconc" || continent.Id == "tzdrnc" ||
            continent.Id == "tzshvs" || continent.Id == "tzcovs" || continent.Id == "tzdrvs")) {
            deadState = DeadState.Release //cancel movement updates
            PlayerActionsToCancel()
            continent.GUID(player.VehicleSeated) match {
              case Some(vehicle : Vehicle) if vehicle.MountedIn.isEmpty =>
                vehicle.PassengerInSeat(player) match {
                  case Some(0) =>
                    vehicle.Position = pos
                    LoadZonePhysicalSpawnPoint(continent.Id, pos, Vector3.z(vehicle.Orientation.z), 0)
                  case _ => //not seated as the driver, in which case we can't move
                    deadState = DeadState.Alive
                }
              case None =>
                player.Position = pos
                sendResponse(PlayerStateShiftMessage(ShiftState(0, pos, player.Orientation.z, None)))
                deadState = DeadState.Alive //must be set here
              case _ => //seated in something that is not a vehicle or the vehicle is cargo, in which case we can't move
                deadState = DeadState.Alive
            }
          }
        case (_, _) => ;
      }

      // TODO: Prevents log spam, but should be handled correctly
      if(messagetype != ChatMessageType.CMT_TOGGLE_GM) {
        log.info("Chat: " + msg)
      }
      else {
        log.info("Chat: " + msg)
        makeReply = false
      }
      if(messagetype == ChatMessageType.CMT_SUICIDE) {
        if(player.isAlive && deadState != DeadState.Release) {
          Suicide(player)
        }
      }
      else if(messagetype == ChatMessageType.CMT_CULLWATERMARK) {
        if(trimContents.contains("40 80")) connectionState = 100
        else if(trimContents.contains("120 200")) connectionState = 25
        else connectionState = 50
      }
      else if(messagetype == ChatMessageType.CMT_DESTROY) {
        makeReply = true
        val guid = contents.toInt
        continent.GUID(continent.Map.TerminalToSpawnPad.getOrElse(guid, guid)) match {
          case Some(pad : VehicleSpawnPad) =>
            pad.Actor ! VehicleSpawnControl.ProcessControl.Flush
          case Some(turret : FacilityTurret) if turret.isUpgrading =>
            WeaponTurrets.FinishUpgradingMannedTurret(turret, TurretUpgrade.None)
          case _ =>
            self ! PacketCoding.CreateGamePacket(0, RequestDestroyMessage(PlanetSideGUID(guid)))
        }
      } else if(messagetype == ChatMessageType.CMT_QUIT) { // TODO: handle this appropriately
        sendResponse(DropCryptoSession())
        sendResponse(DropSession(sessionId, "user quit"))
      }
      //dev hack; consider bang-commands to complement slash-commands in future
      if(trimContents.equals("!loc")) {
        makeReply = true
        echoContents = s"zone=${continent.Id} pos=${player.Position.x},${player.Position.y},${player.Position.z}; ori=${player.Orientation.x},${player.Orientation.y},${player.Orientation.z}"
        log.info(echoContents)
      }
      else if (trimContents.contains("!list") && admin) {
        //        StartBundlingPackets()
        val localString : String = contents.drop(contents.indexOf(" ") + 1)

        if(localString.equalsIgnoreCase("!list")) {
          sendResponse(ChatMsg(ChatMessageType.CMT_GMOPEN, has_wide_contents, "Server", "\\#8Name (Faction) [ID] at PosX PosY PosZ", note_contents))
          continent.LivePlayers.filterNot(_.GUID == player.GUID).sortBy(_.Name).foreach(char => {
            sendResponse(ChatMsg(ChatMessageType.CMT_GMOPEN, has_wide_contents, "Server",
              char.Name + " (" + char.Faction + ") [" + char.CharId + "] at " + char.Position.x.toInt + " " + char.Position.y.toInt + " " + char.Position.z.toInt, note_contents))
          })
          continent.Corpses.filterNot(_.GUID == player.GUID).sortBy(_.Name).foreach(char => {
            sendResponse(ChatMsg(ChatMessageType.CMT_GMOPEN, has_wide_contents, "Server",
              "\\#7" + char.Name + " (" + char.Faction + ") [" + char.CharId + "] at " + char.Position.x.toInt + " " + char.Position.y.toInt + " " + char.Position.z.toInt, note_contents))
          })
        }
        else if(localString.equalsIgnoreCase("z1")) {
          sendResponse(ChatMsg(ChatMessageType.CMT_GMOPEN, has_wide_contents, "Server", "\\#8Name (Faction) [ID]", note_contents))
          Zones.z1.Players.filterNot(_.CharId == player.CharId).sortBy(_.name).foreach(char => {
            sendResponse(ChatMsg(ChatMessageType.CMT_GMOPEN, has_wide_contents, "Server", char.name + " (" + char.faction + ") [" + char.CharId + "]", note_contents))
          })
        }
        else if(localString.equalsIgnoreCase("z2")) {
          sendResponse(ChatMsg(ChatMessageType.CMT_GMOPEN, has_wide_contents, "Server", "\\#8Name (Faction) [ID]", note_contents))
          Zones.z2.Players.filterNot(_.CharId == player.CharId).sortBy(_.name).foreach(char => {
            sendResponse(ChatMsg(ChatMessageType.CMT_GMOPEN, has_wide_contents, "Server", char.name + " (" + char.faction + ") [" + char.CharId + "]", note_contents))
          })
        }
        else if(localString.equalsIgnoreCase("z3")) {
          sendResponse(ChatMsg(ChatMessageType.CMT_GMOPEN, has_wide_contents, "Server", "\\#8Name (Faction) [ID]", note_contents))
          Zones.z3.Players.filterNot(_.CharId == player.CharId).sortBy(_.name).foreach(char => {
            sendResponse(ChatMsg(ChatMessageType.CMT_GMOPEN, has_wide_contents, "Server", char.name + " (" + char.faction + ") [" + char.CharId + "]", note_contents))
          })
        }
        else if(localString.equalsIgnoreCase("z4")) {
          sendResponse(ChatMsg(ChatMessageType.CMT_GMOPEN, has_wide_contents, "Server", "\\#8Name (Faction) [ID]", note_contents))
          Zones.z4.Players.filterNot(_.CharId == player.CharId).sortBy(_.name).foreach(char => {
            sendResponse(ChatMsg(ChatMessageType.CMT_GMOPEN, has_wide_contents, "Server", char.name + " (" + char.faction + ") [" + char.CharId + "]", note_contents))
          })
        }
        else if(localString.equalsIgnoreCase("z5")) {
          sendResponse(ChatMsg(ChatMessageType.CMT_GMOPEN, has_wide_contents, "Server", "\\#8Name (Faction) [ID]", note_contents))
          Zones.z5.Players.filterNot(_.CharId == player.CharId).sortBy(_.name).foreach(char => {
            sendResponse(ChatMsg(ChatMessageType.CMT_GMOPEN, has_wide_contents, "Server", char.name + " (" + char.faction + ") [" + char.CharId + "]", note_contents))
          })
        }
        else if(localString.equalsIgnoreCase("z6")) {
          sendResponse(ChatMsg(ChatMessageType.CMT_GMOPEN, has_wide_contents, "Server", "\\#8Name (Faction) [ID]", note_contents))
          Zones.z6.Players.filterNot(_.CharId == player.CharId).sortBy(_.name).foreach(char => {
            sendResponse(ChatMsg(ChatMessageType.CMT_GMOPEN, has_wide_contents, "Server", char.name + " (" + char.faction + ") [" + char.CharId + "]", note_contents))
          })
        }
        else if(localString.equalsIgnoreCase("z7")) {
          sendResponse(ChatMsg(ChatMessageType.CMT_GMOPEN, has_wide_contents, "Server", "\\#8Name (Faction) [ID]", note_contents))
          Zones.z7.Players.filterNot(_.CharId == player.CharId).sortBy(_.name).foreach(char => {
            sendResponse(ChatMsg(ChatMessageType.CMT_GMOPEN, has_wide_contents, "Server", char.name + " (" + char.faction + ") [" + char.CharId + "]", note_contents))
          })
        }
        else if(localString.equalsIgnoreCase("z8")) {
          sendResponse(ChatMsg(ChatMessageType.CMT_GMOPEN, has_wide_contents, "Server", "\\#8Name (Faction) [ID]", note_contents))
          Zones.z8.Players.filterNot(_.CharId == player.CharId).sortBy(_.name).foreach(char => {
            sendResponse(ChatMsg(ChatMessageType.CMT_GMOPEN, has_wide_contents, "Server", char.name + " (" + char.faction + ") [" + char.CharId + "]", note_contents))
          })
        }
        else if(localString.equalsIgnoreCase("z9")) {
          sendResponse(ChatMsg(ChatMessageType.CMT_GMOPEN, has_wide_contents, "Server", "\\#8Name (Faction) [ID]", note_contents))
          Zones.z9.Players.filterNot(_.CharId == player.CharId).sortBy(_.name).foreach(char => {
            sendResponse(ChatMsg(ChatMessageType.CMT_GMOPEN, has_wide_contents, "Server", char.name + " (" + char.faction + ") [" + char.CharId + "]", note_contents))
          })
        }
        else if(localString.equalsIgnoreCase("z10")) {
          sendResponse(ChatMsg(ChatMessageType.CMT_GMOPEN, has_wide_contents, "Server", "\\#8Name (Faction) [ID]", note_contents))
          Zones.z10.Players.filterNot(_.CharId == player.CharId).sortBy(_.name).foreach(char => {
            sendResponse(ChatMsg(ChatMessageType.CMT_GMOPEN, has_wide_contents, "Server", char.name + " (" + char.faction + ") [" + char.CharId + "]", note_contents))
          })
        }
        else if(localString.equalsIgnoreCase("home1")) {
          sendResponse(ChatMsg(ChatMessageType.CMT_GMOPEN, has_wide_contents, "Server", "\\#8Name (Faction) [ID]", note_contents))
          Zones.home1.Players.filterNot(_.CharId == player.CharId).sortBy(_.name).foreach(char => {
            sendResponse(ChatMsg(ChatMessageType.CMT_GMOPEN, has_wide_contents, "Server", char.name + " (" + char.faction + ") [" + char.CharId + "]", note_contents))
          })
        }
        else if(localString.equalsIgnoreCase("home2")) {
          sendResponse(ChatMsg(ChatMessageType.CMT_GMOPEN, has_wide_contents, "Server", "\\#8Name (Faction) [ID]", note_contents))
          Zones.home2.Players.filterNot(_.CharId == player.CharId).sortBy(_.name).foreach(char => {
            sendResponse(ChatMsg(ChatMessageType.CMT_GMOPEN, has_wide_contents, "Server", char.name + " (" + char.faction + ") [" + char.CharId + "]", note_contents))
          })
        }
        else if(localString.equalsIgnoreCase("home3")) {
          sendResponse(ChatMsg(ChatMessageType.CMT_GMOPEN, has_wide_contents, "Server", "\\#8Name (Faction) [ID]", note_contents))
          Zones.home3.Players.filterNot(_.CharId == player.CharId).sortBy(_.name).foreach(char => {
            sendResponse(ChatMsg(ChatMessageType.CMT_GMOPEN, has_wide_contents, "Server", char.name + " (" + char.faction + ") [" + char.CharId + "]", note_contents))
          })
        }
        else if(localString.equalsIgnoreCase("c1")) {
          sendResponse(ChatMsg(ChatMessageType.CMT_GMOPEN, has_wide_contents, "Server", "\\#8Name (Faction) [ID]", note_contents))
          Zones.c1.Players.filterNot(_.CharId == player.CharId).sortBy(_.name).foreach(char => {
            sendResponse(ChatMsg(ChatMessageType.CMT_GMOPEN, has_wide_contents, "Server", char.name + " (" + char.faction + ") [" + char.CharId + "]", note_contents))
          })
        }
        else if(localString.equalsIgnoreCase("c2")) {
          sendResponse(ChatMsg(ChatMessageType.CMT_GMOPEN, has_wide_contents, "Server", "\\#8Name (Faction) [ID]", note_contents))
          Zones.c2.Players.filterNot(_.CharId == player.CharId).sortBy(_.name).foreach(char => {
            sendResponse(ChatMsg(ChatMessageType.CMT_GMOPEN, has_wide_contents, "Server", char.name + " (" + char.faction + ") [" + char.CharId + "]", note_contents))
          })
        }
        else if(localString.equalsIgnoreCase("c3")) {
          sendResponse(ChatMsg(ChatMessageType.CMT_GMOPEN, has_wide_contents, "Server", "\\#8Name (Faction) [ID]", note_contents))
          Zones.c3.Players.filterNot(_.CharId == player.CharId).sortBy(_.name).foreach(char => {
            sendResponse(ChatMsg(ChatMessageType.CMT_GMOPEN, has_wide_contents, "Server", char.name + " (" + char.faction + ") [" + char.CharId + "]", note_contents))
          })
        }
        else if(localString.equalsIgnoreCase("c4")) {
          sendResponse(ChatMsg(ChatMessageType.CMT_GMOPEN, has_wide_contents, "Server", "\\#8Name (Faction) [ID]", note_contents))
          Zones.c4.Players.filterNot(_.CharId == player.CharId).sortBy(_.name).foreach(char => {
            sendResponse(ChatMsg(ChatMessageType.CMT_GMOPEN, has_wide_contents, "Server", char.name + " (" + char.faction + ") [" + char.CharId + "]", note_contents))
          })
        }
        else if(localString.equalsIgnoreCase("c5")) {
          sendResponse(ChatMsg(ChatMessageType.CMT_GMOPEN, has_wide_contents, "Server", "\\#8Name (Faction) [ID]", note_contents))
          Zones.c5.Players.filterNot(_.CharId == player.CharId).sortBy(_.name).foreach(char => {
            sendResponse(ChatMsg(ChatMessageType.CMT_GMOPEN, has_wide_contents, "Server", char.name + " (" + char.faction + ") [" + char.CharId + "]", note_contents))
          })
        }
        else if(localString.equalsIgnoreCase("c6")) {
          sendResponse(ChatMsg(ChatMessageType.CMT_GMOPEN, has_wide_contents, "Server", "\\#8Name (Faction) [ID]", note_contents))
          Zones.c6.Players.filterNot(_.CharId == player.CharId).sortBy(_.name).foreach(char => {
            sendResponse(ChatMsg(ChatMessageType.CMT_GMOPEN, has_wide_contents, "Server", char.name + " (" + char.faction + ") [" + char.CharId + "]", note_contents))
          })
        }
        else if(localString.equalsIgnoreCase("i1")) {
          sendResponse(ChatMsg(ChatMessageType.CMT_GMOPEN, has_wide_contents, "Server", "\\#8Name (Faction) [ID]", note_contents))
          Zones.i1.Players.filterNot(_.CharId == player.CharId).sortBy(_.name).foreach(char => {
            sendResponse(ChatMsg(ChatMessageType.CMT_GMOPEN, has_wide_contents, "Server", char.name + " (" + char.faction + ") [" + char.CharId + "]", note_contents))
          })
        }
        else if(localString.equalsIgnoreCase("i2")) {
          sendResponse(ChatMsg(ChatMessageType.CMT_GMOPEN, has_wide_contents, "Server", "\\#8Name (Faction) [ID]", note_contents))
          Zones.i2.Players.filterNot(_.CharId == player.CharId).sortBy(_.name).foreach(char => {
            sendResponse(ChatMsg(ChatMessageType.CMT_GMOPEN, has_wide_contents, "Server", char.name + " (" + char.faction + ") [" + char.CharId + "]", note_contents))
          })
        }
        else if(localString.equalsIgnoreCase("i3")) {
          sendResponse(ChatMsg(ChatMessageType.CMT_GMOPEN, has_wide_contents, "Server", "\\#8Name (Faction) [ID]", note_contents))
          Zones.i3.Players.filterNot(_.CharId == player.CharId).sortBy(_.name).foreach(char => {
            sendResponse(ChatMsg(ChatMessageType.CMT_GMOPEN, has_wide_contents, "Server", char.name + " (" + char.faction + ") [" + char.CharId + "]", note_contents))
          })
        }
        else if(localString.equalsIgnoreCase("i4")) {
          sendResponse(ChatMsg(ChatMessageType.CMT_GMOPEN, has_wide_contents, "Server", "\\#8Name (Faction) [ID]", note_contents))
          Zones.i4.Players.filterNot(_.CharId == player.CharId).sortBy(_.name).foreach(char => {
            sendResponse(ChatMsg(ChatMessageType.CMT_GMOPEN, has_wide_contents, "Server", char.name + " (" + char.faction + ") [" + char.CharId + "]", note_contents))
          })
        }
        else {
          sendResponse(ChatMsg(ChatMessageType.CMT_GMOPEN, has_wide_contents, "Server", "\\#8Name (Faction) [ID] in Zone at PosX PosY PosZ", note_contents))
          continent.LivePlayers.filter(_.Name.contains(localString)).sortBy(_.Name).foreach(char => {
            sendResponse(ChatMsg(ChatMessageType.CMT_GMOPEN, has_wide_contents, "Server",
              char.Name + " (" + char.Faction + ") [" + char.CharId + "] in " + char.Continent + " at " + char.Position.x.toInt + " " + char.Position.y.toInt + " " + char.Position.z.toInt, note_contents))
          })
          continent.Corpses.filter(_.Name.contains(localString)).sortBy(_.Name).foreach(char => {
            sendResponse(ChatMsg(ChatMessageType.CMT_GMOPEN, has_wide_contents, "Server",
              "\\#7" + char.Name + " (" + char.Faction + ") [" + char.CharId + "] in " + char.Continent + " at " + char.Position.x.toInt + " " + char.Position.y.toInt + " " + char.Position.z.toInt, note_contents))
          })
        }
        //        StopBundlingPackets()
      }
      else if (trimContents.contains("!kick") && admin) {
        val CharIDorName : String = contents.drop(contents.indexOf(" ") + 1)
        try {
          val charID : Long = CharIDorName.toLong
          if(charID != player.CharId) {
            var charToKick = continent.LivePlayers.filter(_.CharId == charID)
            if (charToKick.nonEmpty) {
              charToKick.head.death_by = -1
            }
            else {
              charToKick = continent.Corpses.filter(_.CharId == charID)
              if (charToKick.nonEmpty) charToKick.head.death_by = -1
            }
          }
        }
        catch {
          case _ : Throwable =>
          {
            val charToKick = continent.LivePlayers.filter(_.Name.equalsIgnoreCase(CharIDorName))
            if(charToKick.nonEmpty) charToKick.head.death_by = -1
          }
        }
      }
      else if(trimRecipient.equals("tr")) {
        sendResponse(ZonePopulationUpdateMessage(4, 414, 138, contents.toInt, 138, contents.toInt / 2, 138, 0, 138, 0))
      }
      else if(trimRecipient.equals("nc")) {
        sendResponse(ZonePopulationUpdateMessage(4, 414, 138, 0, 138, contents.toInt, 138, contents.toInt / 3, 138, 0))
      }
      else if(trimRecipient.equals("vs")) {
        sendResponse(ZonePopulationUpdateMessage(4, 414, 138, contents.toInt * 2, 138, 0, 138, contents.toInt, 138, 0))
      }
      else if(trimRecipient.equals("bo")){
        sendResponse(ZonePopulationUpdateMessage(4, 414, 138, 0, 138, 0, 138, 0, 138, contents.toInt))

        //          sendResponse(ZoneInfoMessage(contents.toInt, true, 25200000))
        //          sendResponse(ZoneInfoMessage(contents.toInt-1, false, 25200000))
        //          sendResponse(ChatMsg(ChatMessageType.UNK_229, false, "", "@cavern_switched^@c1~^@c5~", None))
      }
      else if(trimContents.startsWith("!ntu") && admin){
        continent.Buildings.values.foreach(building =>
          building.Amenities.foreach(amenity =>
            amenity.Definition match {
              case GlobalDefinitions.resource_silo =>
                val r = new scala.util.Random
                val silo = amenity.asInstanceOf[ResourceSilo]
                val ntu: Int = 900 + r.nextInt(100) - silo.ChargeLevel
                //                val ntu: Int = 0 + r.nextInt(100) - silo.ChargeLevel
                silo.Actor ! ResourceSilo.UpdateChargeLevel(ntu)

              case _ => ;
            }
          )
        )
      }
      else if(trimContents.startsWith("!hack") && admin){
        var hackFaction = PlanetSideEmpire.NEUTRAL
        val args = trimContents.split(" ")
        if (args.length == 3) {
          var bad : Boolean = false
          if(args(2).equalsIgnoreCase("tr")) hackFaction = PlanetSideEmpire.TR
          else if(args(2).equalsIgnoreCase("nc")) hackFaction = PlanetSideEmpire.NC
          else if(args(2).equalsIgnoreCase("vs")) hackFaction = PlanetSideEmpire.VS
          else if(args(2).equalsIgnoreCase("bo")) hackFaction = PlanetSideEmpire.NEUTRAL
          else bad = true
          if(bad) {
            sendResponse(ChatMsg(ChatMessageType.UNK_229, true, "", "USE !hack tr|vs|nc|bo OR !hack BaseName tr|vs|nc|bo", None))
          }
          else {
            continent.Buildings.foreach({
              case (id, building) =>
                if(!building.Name.isEmpty && args(1).equalsIgnoreCase(building.Name)) {
                  log.info(s"Hack Base Name : ${args(1)} to empire : ${args(2)}")
                  building.Faction = hackFaction
                  continent.LocalEvents ! LocalServiceMessage(continent.Id, LocalAction.SetEmpire(building.GUID, hackFaction))
                }
            })
          }
        } else if (args.length == 2) {
          var bad : Boolean = false
          if(args(1).equalsIgnoreCase("tr")) hackFaction = PlanetSideEmpire.TR
          else if(args(1).equalsIgnoreCase("nc")) hackFaction = PlanetSideEmpire.NC
          else if(args(1).equalsIgnoreCase("vs")) hackFaction = PlanetSideEmpire.VS
          else if(args(1).equalsIgnoreCase("bo")) hackFaction = PlanetSideEmpire.NEUTRAL
          else bad = true
          if(bad) {
            sendResponse(ChatMsg(ChatMessageType.UNK_229, true, "", "USE !hack tr|vs|nc|bo OR !hack BaseName tr|vs|nc|bo", None))
          }
          else {
            continent.Buildings.foreach({
              case (id, building) =>
                if (!building.Name.isEmpty && !bad
                  && building.BuildingType != StructureType.Bridge
                  && building.BuildingType != StructureType.Bunker
                  && building.BuildingType != StructureType.WarpGate
                ) {
                  log.info(s"Hack Bases to empire : ${args(1)}")
                  building.Faction = hackFaction
                  continent.LocalEvents ! LocalServiceMessage(continent.Id, LocalAction.SetEmpire(building.GUID, hackFaction))
                }
            })
          }
        }
        else {
          sendResponse(ChatMsg(ChatMessageType.UNK_229, true, "", "USE !hack tr|vs|nc|bo OR !hack BaseName tr|vs|nc|bo", None))
        }
      }
      // TODO: Depending on messagetype, may need to prepend sender's name to contents with proper spacing
      // TODO: Just replays the packet straight back to sender; actually needs to be routed to recipients!
      if(makeReply) {
        sendResponse(ChatMsg(messagetype, has_wide_contents, recipient, echoContents, note_contents))
      }
      if(messagetype == ChatMessageType.CMT_OPEN && !player.silenced) {
        chatService ! ChatServiceMessage("local", ChatAction.Local(player.GUID, player.Name, continent, player.Position, player.Faction, msg))
      }
      else if(messagetype == ChatMessageType.CMT_VOICE) {
        chatService ! ChatServiceMessage("voice", ChatAction.Voice(player.GUID, player.Name, continent, player.Position, player.Faction, msg))
      }
      else if(messagetype == ChatMessageType.CMT_TELL && !player.silenced) {
        chatService ! ChatServiceMessage("tell", ChatAction.Tell(player.GUID, player.Name, msg))
      }
      else if(messagetype == ChatMessageType.CMT_BROADCAST && !player.silenced) {
        chatService ! ChatServiceMessage("broadcast", ChatAction.Broadcast(player.GUID, player.Name, continent, player.Position, player.Faction, msg))
      }
      else if(messagetype == ChatMessageType.CMT_NOTE) {
        chatService ! ChatServiceMessage("note", ChatAction.Note(player.GUID, player.Name, msg))
      }
      else if(messagetype == ChatMessageType.CMT_SILENCE && admin) {
        chatService ! ChatServiceMessage("gm", ChatAction.GM(player.GUID, player.Name, msg))
      }
      else if (messagetype == ChatMessageType.CMT_SQUAD && !player.silenced) {
        if(squadChannel.nonEmpty) {
          chatService ! ChatServiceMessage(squadChannel.get, ChatAction.Squad(player.GUID, player.Name, continent, player.Position, player.Faction, msg))
        }
      }
      else if (messagetype == ChatMessageType.CMT_PLATOON && !player.silenced) {
        chatService ! ChatServiceMessage("platoon", ChatAction.Platoon(player.GUID, player.Name, continent, player.Position, player.Faction, msg))
      }
      else if (messagetype == ChatMessageType.CMT_COMMAND && admin) {
        chatService ! ChatServiceMessage("command", ChatAction.Command(player.GUID, player.Name, continent, player.Position, player.Faction, msg))
      }
      else if(messagetype == ChatMessageType.CMT_WHO || messagetype == ChatMessageType.CMT_WHO_CSR || messagetype == ChatMessageType.CMT_WHO_CR ||
        messagetype == ChatMessageType.CMT_WHO_PLATOONLEADERS || messagetype == ChatMessageType.CMT_WHO_SQUADLEADERS || messagetype == ChatMessageType.CMT_WHO_TEAMS) {
        val poplist = continent.Players
        val popTR = poplist.count(_.faction == PlanetSideEmpire.TR)
        val popNC = poplist.count(_.faction == PlanetSideEmpire.NC)
        val popVS = poplist.count(_.faction == PlanetSideEmpire.VS)
        val contName = continent.Map.Name
        StartBundlingPackets()
        sendResponse(ChatMsg(ChatMessageType.CMT_WHO, true, "", "That command doesn't work for now, but : ", None))
        sendResponse(ChatMsg(ChatMessageType.CMT_WHO, true, "", "NC online : " + popNC + " on " + contName, None))
        sendResponse(ChatMsg(ChatMessageType.CMT_WHO, true, "", "TR online : " + popTR + " on " + contName, None))
        sendResponse(ChatMsg(ChatMessageType.CMT_WHO, true, "", "VS online : " + popVS + " on " + contName, None))
        StopBundlingPackets()
      }

    case msg@VoiceHostRequest(unk, PlanetSideGUID(player_guid), data) =>
      log.info("Player " + player_guid + " requested in-game voice chat.")
      sendResponse(VoiceHostKill())

    case msg@VoiceHostInfo(player_guid, data) =>
      sendResponse(VoiceHostKill())

    case msg@ChangeAmmoMessage(item_guid, unk1) =>
      log.info("ChangeAmmo: " + msg)
      FindContainedEquipment match {
        case (Some(_), Some(obj : ConstructionItem)) =>
          PerformConstructionItemAmmoChange(obj, obj.AmmoTypeIndex)
        case (Some(obj), Some(tool : Tool)) =>
          PerformToolAmmoChange(tool, obj)
        case (_, Some(obj)) =>
          log.error(s"ChangeAmmo: the object ${obj.Definition.Name} is not a valid type")
        case (_, None) =>
          log.error(s"ChangeAmmo: can not find $item_guid")
      }

    case msg@ChangeFireModeMessage(item_guid, fire_mode) =>
      log.info("ChangeFireMode: " + msg)
      FindEquipment match {
        case Some(obj : PlanetSideGameObject with FireModeSwitch[_]) =>
          val originalModeIndex = obj.FireModeIndex
          obj match {
            case cItem : ConstructionItem =>
              NextConstructionItemFireMode(cItem, originalModeIndex)
            case _ =>
              obj.NextFireMode
          }
          val modeIndex = obj.FireModeIndex
          val tool_guid = obj.GUID
          if(originalModeIndex == modeIndex) {
            obj.FireModeIndex = originalModeIndex
            sendResponse(ChangeFireModeMessage(tool_guid, originalModeIndex)) //reinforcement
          }
          else {
            log.info(s"ChangeFireMode: changing $tool_guid to fire mode $modeIndex")
            sendResponse(ChangeFireModeMessage(tool_guid, modeIndex))
            continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.ChangeFireMode(player.GUID, tool_guid, modeIndex))
          }
        case Some(_) =>
          log.error(s"ChangeFireMode: the object that was found for $item_guid does not possess fire modes")
        case None =>
          log.error(s"ChangeFireMode: can not find $item_guid")
      }

    case msg@ChangeFireStateMessage_Start(item_guid) =>
      log.trace("ChangeFireState_Start: " + msg)
      if(shooting.isEmpty) {
        FindEquipment match {
          case Some(tool : Tool) =>
            if(tool.Magazine > 0 || prefire.contains(item_guid)) {
              prefire = None
              shooting = Some(item_guid)
              //special case - suppress the decimator's alternate fire mode, by projectile
              if(tool.Projectile != GlobalDefinitions.phoenix_missile_guided_projectile) {
                continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.ChangeFireState_Start(player.GUID, item_guid))
              }
            }
            else {
              log.warn(s"ChangeFireState_Start: ${tool.Definition.Name} magazine is empty before trying to shoot bullet")
              EmptyMagazine(item_guid, tool)
            }
          case Some(_) => //permissible, for now
            prefire = None
            shooting = Some(item_guid)
            continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.ChangeFireState_Start(player.GUID, item_guid))
          case None =>
            log.error(s"ChangeFireState_Start: can not find $item_guid")
        }
      }

    case msg@ChangeFireStateMessage_Stop(item_guid) =>
      log.trace("ChangeFireState_Stop: " + msg)
      prefire = None
      val weapon : Option[Equipment] = if(shooting.contains(item_guid)) {
        shooting = None
        continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.ChangeFireState_Stop(player.GUID, item_guid))
        FindEquipment
      }
      else {
        FindEquipment match {
          case Some(tool : Tool) =>
            //the decimator does not send a ChangeFireState_Start on the last shot
            if(tool.Definition == GlobalDefinitions.phoenix &&
              tool.Projectile != GlobalDefinitions.phoenix_missile_guided_projectile) {
              //suppress the decimator's alternate fire mode, however
              continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.ChangeFireState_Start(player.GUID, item_guid))
            }
            continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.ChangeFireState_Stop(player.GUID, item_guid))
            Some(tool)
          case Some(tool) => //permissible, for now
            continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.ChangeFireState_Stop(player.GUID, item_guid))
            Some(tool)
          case _ =>
            log.warn(s"ChangeFireState_Stop: received an unexpected message about $item_guid")
            None
        }
      }
      weapon match {
        case Some(tool : Tool) =>
          if(tool.Magazine == 0) {
            FireCycleCleanup(tool)
          }
        case Some(trigger : BoomerTrigger) =>
          val playerGUID = player.GUID
          continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.ChangeFireState_Start(playerGUID, item_guid))
          continent.GUID(trigger.Companion) match {
            case Some(boomer : BoomerDeployable) =>
              boomer.Destroyed = true
              continent.LocalEvents ! LocalServiceMessage(continent.Id, LocalAction.Detonate(boomer.GUID, boomer))
              Deployables.AnnounceDestroyDeployable(boomer, Some(500 milliseconds))
            case Some(_) | None => ;
          }
          FindEquipmentToDelete(item_guid, trigger)
          trigger.Companion = None
        case _ => ;
      }
      progressBarUpdate.cancel
      progressBarValue = None

    case msg@EmoteMsg(avatar_guid, emote) =>
      log.info("Emote: " + msg)
      sendResponse(EmoteMsg(avatar_guid, emote))

    case msg@DropItemMessage(item_guid) =>
      log.info(s"DropItem: $msg")
      ValidObject(item_guid) match {
        case Some(anItem : Equipment) =>
          player.FreeHand.Equipment match {
            case Some(item) =>
              if(item.GUID == item_guid) {
                continent.Ground ! Zone.Ground.DropItem(item, player.Position, player.Orientation)
              }
            case None =>
              log.warn(s"DropItem: $player wanted to drop a $anItem, but it wasn't at hand")
          }
        case Some(obj) => //TODO LLU
          log.warn(s"DropItem: $player wanted to drop a $obj, but that isn't possible")
        case None =>
          sendResponse(ObjectDeleteMessage(item_guid, 0)) //this is fine; item doesn't exist to the server anyway
          log.warn(s"DropItem: $player wanted to drop an item ($item_guid), but it was nowhere to be found")
      }

    case msg@PickupItemMessage(item_guid, player_guid, unk1, unk2) =>
      log.info(s"PickupItem: $msg")
      ValidObject(item_guid) match {
        case Some(item : Equipment) =>
          player.Fit(item) match {
            case Some(_) =>
              continent.Ground ! Zone.Ground.PickupItem(item_guid)
            case None => //skip
              sendResponse(ActionResultMessage.Fail(16)) //error code?
          }
        case _ =>
          log.warn(s"PickupItem: $player requested an item that doesn't exist in this zone; assume client-side garbage data")
          sendResponse(ObjectDeleteMessage(item_guid, 0))
      }

    case msg@ReloadMessage(item_guid, ammo_clip, unk1) =>
      log.info("Reload: " + msg)
      FindContainedWeapon match {
        case (Some(obj), Some(tool : Tool)) =>
          val currentMagazine : Int = tool.Magazine
          val magazineSize : Int = tool.MaxMagazine
          val reloadValue : Int = magazineSize - currentMagazine
          if(magazineSize > 0 && reloadValue > 0) {
            FindEquipmentStock(obj, FindAmmoBoxThatUses(tool.AmmoType), reloadValue, CountAmmunition).reverse match {
              case Nil =>
                log.warn(s"ReloadMessage: no ammunition could be found for $item_guid")
              case x :: xs =>
                val (deleteFunc, modifyFunc) : ((Int, AmmoBox) => Unit, (AmmoBox, Int) => Unit) = obj match {
                  case (veh : Vehicle) =>
                    (DeleteEquipmentFromVehicle(veh), ModifyAmmunitionInVehicle(veh))
                  case _ =>
                    (DeleteEquipment(obj), ModifyAmmunition(obj))
                }
                xs.foreach(item => {
                  deleteFunc(item.start, item.obj.asInstanceOf[AmmoBox])
                })
                val box = x.obj.asInstanceOf[AmmoBox]
                val tailReloadValue : Int = if(xs.isEmpty) {
                  0
                }
                else {
                  xs.map(_.obj.asInstanceOf[AmmoBox].Capacity).reduceLeft(_ + _)
                }
                val sumReloadValue : Int = box.Capacity + tailReloadValue
                val actualReloadValue = (if(sumReloadValue <= reloadValue) {
                  deleteFunc(x.start, box)
                  sumReloadValue
                }
                else {
                  modifyFunc(box, reloadValue - tailReloadValue)
                  reloadValue
                }) + currentMagazine
                log.info(s"ReloadMessage: success, $tool <- $actualReloadValue ${tool.AmmoType}")
                tool.Magazine = actualReloadValue
                sendResponse(ReloadMessage(item_guid, actualReloadValue, unk1))
                continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.Reload(player.GUID, item_guid))
            }
          }
          else {
            log.warn(s"ReloadMessage: item $item_guid can not reload (full=$magazineSize, want=$reloadValue)")
          }
        case (_, Some(_)) =>
          log.error(s"ReloadMessage: the object that was found for $item_guid was not a Tool")
        case (_, None) =>
          log.error(s"ReloadMessage: can not find $item_guid")
      }

    case msg@ObjectHeldMessage(avatar_guid, held_holsters, unk1) =>
      log.info(s"ObjectHeld: $msg")
      val before = player.DrawnSlot
      if(before != held_holsters) {
        if(player.ExoSuit == ExoSuitType.MAX && held_holsters != 0) {
          log.info(s"ObjectHeld: $player is denied changing hands to $held_holsters as a MAX")
          player.DrawnSlot = 0
          sendResponse(ObjectHeldMessage(avatar_guid, 0, true))
        }
        else if((player.DrawnSlot = held_holsters) != before) {
          continent.AvatarEvents ! AvatarServiceMessage(player.Continent, AvatarAction.ObjectHeld(player.GUID, player.LastDrawnSlot))

          // Ignore non-equipment holsters
          //todo: check current suit holster slots?
          if(held_holsters >= 0 && held_holsters < 5) {
            player.Holsters()(held_holsters).Equipment match {
              case Some(unholsteredItem : Equipment) =>
                if(unholsteredItem.Definition == GlobalDefinitions.remote_electronics_kit) {
                  // Player has unholstered a REK - we need to set an atttribute on the REK itself to change the beam/icon colour to the correct one for the player's hack level
                  continent.AvatarEvents ! AvatarServiceMessage(player.Continent, AvatarAction.PlanetsideAttribute(unholsteredItem.GUID, 116, Player.GetHackLevel(player)))
                }
              case None => ;
            }
          }

          // Stop using proximity terminals if player unholsters a weapon (which should re-trigger the proximity effect and re-holster the weapon)
          if(player.VisibleSlots.contains(held_holsters)) {
            continent.GUID(usingMedicalTerminal) match {
              case Some(term : Terminal with ProximityUnit) =>
                StopUsingProximityUnit(term)
              case _ => ;
            }
          }
        }
      }

    case msg@AvatarJumpMessage(state) =>
      //log.info("AvatarJump: " + msg)
      player.Stamina = player.Stamina - 10
      player.skipStaminaRegenForTurns = math.max(player.skipStaminaRegenForTurns, 5)

    case msg@ZipLineMessage(player_guid, forwards, action, path_id, pos) =>
      log.info("ZipLineMessage: " + msg)
      val (isTeleporter : Boolean, path : Option[ZipLinePath]) = continent.ZipLinePaths.find(x => x.PathId == path_id) match {
        case Some(x) => (x.IsTeleporter, Some(x))
        case _ =>
          log.warn(s"Couldn't find zipline path ${path_id} in zone ${continent.Number} / ${continent.Id}")
          (false, None)
      }
      if(isTeleporter) {
        CancelZoningProcessWithDescriptiveReason("cancel")
        val endPoint = path.get.ZipLinePoints.last
        sendResponse(ZipLineMessage(PlanetSideGUID(0), forwards, 0, path_id, pos)) // todo: send to zone to show teleport animation to all clients
        sendResponse(PlayerStateShiftMessage(ShiftState(0, endPoint, player.Orientation.z, None)))
      }
      else {
        CancelZoningProcessWithDescriptiveReason("cancel_motion")
        action match {
          case 0 =>
            // Travel along the zipline in the direction specified
            sendResponse(ZipLineMessage(player_guid, forwards, action, path_id, pos))
          case 1 =>
            //disembark from zipline at destination !
            sendResponse(ZipLineMessage(player_guid, forwards, action, 0, pos))
          case 2 =>
            //get off by force
            sendResponse(ZipLineMessage(player_guid, forwards, action, 0, pos))
          case _ =>
            log.warn(s"Tried to do something with a zipline but can't handle it. forwards: ${forwards} action: ${action} path_id: ${path_id} zone: ${continent.Number} / ${continent.Id}")
        }
      }

    case msg@RequestDestroyMessage(object_guid) =>
      // TODO: Make sure this is the correct response for all cases
      ValidObject(object_guid) match {
        case Some(vehicle : Vehicle) =>
          /* line 1a: player is admin (and overrules other access requirements) */
          /* line 1b: vehicle and player (as the owner) acknowledge each other */
          /* line 1c: vehicle is the same faction as player and either the owner is absent or the vehicle is destroyed */
          /* line 2: vehicle is not mounted in anything or, if it is, its seats are empty */
          if(
            (admin ||
              (player.VehicleOwned.contains(object_guid) && vehicle.Owner.contains(player.GUID)) ||
              (player.Faction == vehicle.Faction && ((vehicle.Owner.isEmpty || continent.GUID(vehicle.Owner.get).isEmpty) || vehicle.Destroyed))
              ) &&
              (vehicle.MountedIn.isEmpty || !vehicle.Seats.values.exists(_.isOccupied))
          ) {
            vehicle.Actor ! Vehicle.Deconstruct()
            log.info(s"RequestDestroy: vehicle $vehicle")
          }
          else {
            log.info(s"RequestDestroy: must own vehicle in order to deconstruct it")
          }

        case Some(obj : BoomerTrigger) =>
          if(FindEquipmentToDelete(object_guid, obj)) {
            continent.GUID(obj.Companion) match {
              case Some(boomer : BoomerDeployable) =>
                boomer.Trigger = None
                continent.LocalEvents ! LocalServiceMessage.Deployables(RemoverActor.AddTask(boomer, continent, Some(0 seconds)))
              //continent.Deployables ! Zone.Deployable.Dismiss(boomer)
              case Some(thing) =>
                log.info(s"RequestDestroy: BoomerTrigger object connected to wrong object - $thing")
              case None => ;
            }
          }

        case Some(obj : Equipment) =>
          FindEquipmentToDelete(object_guid, obj)

        case Some(_ : LocalProjectile) =>
          FindProjectileEntry(object_guid) match {
            case Some(projectile) =>
              if(projectile.isResolved) {
                log.warn(s"RequestDestroy: tried to clean up projectile ${object_guid.guid} but it was already resolved")
              }
              else {
                projectile.Miss()
                if(projectile.profile.ExistsOnRemoteClients && projectile.HasGUID) {
                  continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.ProjectileExplodes(player.GUID, projectile.GUID, projectile))
                  taskResolver ! UnregisterProjectile(projectile)
                }
              }
            case None =>
              log.warn(s"RequestDestroy: projectile ${object_guid.guid} has never been fired")
          }

        case Some(obj : BoomerDeployable) =>
          continent.LocalEvents ! LocalServiceMessage.Deployables(RemoverActor.AddTask(obj, continent, Some(0 seconds)))
          obj.Trigger match {
            case Some(trigger) =>
              obj.Trigger = None
              val guid = trigger.GUID
              Zone.EquipmentIs.Where(trigger, guid, continent) match {
                case Some(Zone.EquipmentIs.InContainer(container, index)) =>
                  container.Slot(index).Equipment = None
                case Some(Zone.EquipmentIs.OnGround()) =>
                  continent.Ground ! Zone.Ground.RemoveItem(guid)
                case Some(Zone.EquipmentIs.Orphaned()) =>
                  log.warn(s"RequestDestroy: boomer_trigger@$guid has been found but it seems to be orphaned")
                case _ => ;
              }
              continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.ObjectDelete(PlanetSideGUID(0), guid))
              GUIDTask.UnregisterObjectTask(trigger)(continent.GUID)

            case None => ;
          }

        case Some(obj : TelepadDeployable) =>
          continent.LocalEvents ! LocalServiceMessage.Telepads(SupportActor.ClearSpecific(List(obj), continent))
          continent.LocalEvents ! LocalServiceMessage.Deployables(RemoverActor.ClearSpecific(List(obj), continent))
          continent.LocalEvents ! LocalServiceMessage.Deployables(RemoverActor.AddTask(obj, continent, Some(0 seconds)))

        case Some(obj : PlanetSideGameObject with Deployable) =>
          continent.LocalEvents ! LocalServiceMessage.Deployables(RemoverActor.ClearSpecific(List(obj), continent))
          continent.LocalEvents ! LocalServiceMessage.Deployables(RemoverActor.AddTask(obj, continent, Some(0 seconds)))

        case Some(thing) =>
          log.warn(s"RequestDestroy: not allowed to delete object $thing")

        case None =>
          log.warn(s"RequestDestroy: object ${object_guid.guid} not found")
      }

    case msg@ObjectDeleteMessage(object_guid, unk1) =>
      sendResponse(ObjectDeleteMessage(object_guid, 0))
      log.info("ObjectDelete: " + msg)

    case msg@MoveItemMessage(item_guid, source_guid, destination_guid, dest, _) =>
      log.info(s"MoveItem: $msg")
      (continent.GUID(source_guid), continent.GUID(destination_guid), continent.GUID(item_guid)) match {
        case (Some(source : Container), Some(destination : Container), Some(item : Equipment)) =>
          source.Find(item_guid) match {
            case Some(index) =>
              val indexSlot = source.Slot(index)
              val tile = item.Definition.Tile
              val destinationCollisionTest = destination.Collisions(dest, tile.Width, tile.Height)
              val destItemEntry = destinationCollisionTest match {
                case Success(entry :: Nil) =>
                  Some(entry)
                case _ =>
                  None
              }
              if( {
                destinationCollisionTest match {
                  case Success(Nil) | Success(_ :: Nil) =>
                    true //no item or one item to swap
                  case _ =>
                    false //abort when too many items at destination or other failure case
                }
              } && indexSlot.Equipment.contains(item)) {
                if(PermitEquipmentStow(item, destination)) {
                  StartBundlingPackets()
                  PerformMoveItem(item, source, index, destination, dest, destItemEntry)
                  StopBundlingPackets()
                }
                else {
                  log.error(s"MoveItem: $item disallowed storage in $destination")
                }
              }
              else if(!indexSlot.Equipment.contains(item)) {
                log.error(s"MoveItem: wanted to move $item_guid, but found unexpected ${indexSlot.Equipment} at source location")
              }
              else {
                destinationCollisionTest match {
                  case Success(_) =>
                    log.error(s"MoveItem: wanted to move $item_guid, but multiple unexpected items at destination blocked progress")
                  case scala.util.Failure(err) =>
                    log.error(s"MoveItem: wanted to move $item_guid, but $err")
                }
              }
            case _ =>
              log.error(s"MoveItem: wanted to move $item_guid, but could not find it")
          }
        case (None, _, _) =>
          log.error(s"MoveItem: wanted to move $item_guid from $source_guid, but could not find source object")
        case (_, None, _) =>
          log.error(s"MoveItem: wanted to move $item_guid to $destination_guid, but could not find destination object")
        case (_, _, None) =>
          log.error(s"MoveItem: wanted to move $item_guid, but could not find it")
        case _ =>
          log.error(s"MoveItem: wanted to move $item_guid from $source_guid to $destination_guid, but multiple problems were encountered")
      }

    case msg@LootItemMessage(item_guid, target_guid) =>
      log.info(s"LootItem: $msg")
      (ValidObject(item_guid), ValidObject(target_guid)) match {
        case (Some(item : Equipment), Some(target : Container)) =>
          //figure out the source
          ( {
            val findFunc : PlanetSideGameObject with Container => Option[(PlanetSideGameObject with Container, Option[Int])] = FindInLocalContainer(item_guid)
            findFunc(player.Locker)
              .orElse(findFunc(player))
              .orElse(accessedContainer match {
                case Some(parent) =>
                  findFunc(parent)
                case None =>
                  None
              }
              )
          }, target.Fit(item)) match {
            case (Some((source, Some(index))), Some(dest)) =>
              if(PermitEquipmentStow(item, target)) {
                StartBundlingPackets()
                PerformMoveItem(item, source, index, target, dest, None)
                StopBundlingPackets()
              }
              else {
                log.error(s"LootItem: $item disallowed storage in $target")
              }
            case (None, _) =>
              log.error(s"LootItem: can not find where $item is put currently")
            case (_, None) =>
              log.error(s"LootItem: can not find somwhere to put $item in $target")
            case _ =>
              log.error(s"LootItem: wanted to move $item_guid to $target_guid, but multiple problems were encountered")
          }
        case (Some(obj), _) =>
          log.warn(s"LootItem: item $obj is (probably) not lootable")
        case (None, _) =>
          log.warn(s"LootItem: can not find $item_guid")
        case (_, None) =>
          log.warn(s"LootItem: can not find where to put $item_guid")
      }

    case msg @ AvatarImplantMessage(player_guid, action, slot, status) =>
      log.info("AvatarImplantMessage: " + msg)
      if(action == ImplantAction.Activation) {
        CancelZoningProcessWithDescriptiveReason("cancel_implant")
        player.Actor ! Player.ImplantActivation(slot, status)
      }

    case msg @ UseItemMessage(avatar_guid, item_used_guid, object_guid, unk2, unk3, unk4, unk5, unk6, unk7, unk8, itemType) =>
      //log.info("UseItem: " + msg)
      // TODO: Not all fields in the response are identical to source in real packet logs (but seems to be ok)
      // TODO: Not all incoming UseItemMessage's respond with another UseItemMessage (i.e. doors only send out GenericObjectStateMsg)
      val equipment = player.Slot(player.DrawnSlot).Equipment match {
        case out@Some(item) if item.GUID == item_used_guid => out
        case _ => None
      }
      ValidObject(object_guid) match {
        case Some(door : Door) =>
          if(player.Faction == door.Faction || (continent.Map.DoorToLock.get(object_guid.guid) match {
            case Some(lock_guid) =>
              val lock = continent.GUID(lock_guid).get.asInstanceOf[IFFLock]
              val owner = lock.Owner.asInstanceOf[Building]
              val playerIsOnInside = Vector3.ScalarProjection(lock.Outwards, player.Position - door.Position) < 0f

              // If an IFF lock exists and the IFF lock faction doesn't match the current player and one of the following conditions are met open the door:
              // The player is on the inside of the door, determined by the lock orientation
              // The lock is hacked
              // A base is hacked
              // A base is neutral
              // todo: A base is out of power (generator down)

              playerIsOnInside || lock.HackedBy.isDefined || owner.CaptureConsoleIsHacked || lock.Faction == PlanetSideEmpire.NEUTRAL
            case None => !door.isOpen // If there's no linked IFF lock just open the door if it's closed.
          })) {
            door.Actor ! Door.Use(player, msg)
          }
          else if(door.isOpen) {
            //the door is open globally ... except on our screen
            sendResponse(GenericObjectStateMsg(object_guid, 16))
          }

        case Some(resourceSilo : ResourceSilo) =>
          resourceSilo.Actor ! ResourceSilo.Use(player, msg)

        case Some(panel : IFFLock) =>
          equipment match {
            case Some(item) =>
              CancelZoningProcessWithDescriptiveReason("cancel_use")
              panel.Actor ! CommonMessages.Use(player, Some(item))
            case _ => ;
          }

        case Some(obj : Player) =>
          CancelZoningProcessWithDescriptiveReason("cancel_use")
          if(obj.isBackpack) {
            if(equipment.isEmpty) {
              log.info(s"UseItem: $player looting the corpse of $obj")
              sendResponse(UseItemMessage(avatar_guid, item_used_guid, object_guid, unk2, unk3, unk4, unk5, unk6, unk7, unk8, itemType))
              accessedContainer = Some(obj)
            }
          }
          else if(!unk3 && player.isAlive) { //potential kit use
            ValidObject(item_used_guid) match {
              case Some(kit : Kit) =>
                player.Find(kit) match {
                  case Some(index) =>
                    if(kit.Definition == GlobalDefinitions.medkit) {
                      if(player.Health == player.MaxHealth) {
                        sendResponse(ChatMsg(ChatMessageType.UNK_225, false, "", "@HealComplete", None))
                      }
                      else if(System.currentTimeMillis - whenUsedLastKit < 5000) {
                        sendResponse(ChatMsg(ChatMessageType.UNK_225, false, "", s"@TimeUntilNextUse^${5 - (System.currentTimeMillis - whenUsedLastKit) / 1000}~", None))
                      }
                      else {
                        whenUsedLastKit = System.currentTimeMillis
                        player.Slot(index).Equipment = None //remove from slot immediately; must exist on client for next packet
                        sendResponse(UseItemMessage(avatar_guid, item_used_guid, object_guid, 0, unk3, unk4, unk5, unk6, unk7, unk8, itemType))
                        sendResponse(ObjectDeleteMessage(kit.GUID, 0))
                        taskResolver ! GUIDTask.UnregisterEquipment(kit)(continent.GUID)
                        player.History(HealFromKit(PlayerSource(player), 25, kit.Definition))
                        player.Health = player.Health + 25
                        sendResponse(PlanetsideAttributeMessage(avatar_guid, 0, player.Health))
                        continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(avatar_guid, 0, player.Health))
                      }
                    }
                    else if(kit.Definition == GlobalDefinitions.super_medkit) {
                      if(player.Health == player.MaxHealth) {
                        sendResponse(ChatMsg(ChatMessageType.UNK_225, false, "", "@HealComplete", None))
                      }
                      else if(System.currentTimeMillis - whenUsedLastSMKit < 1200000) {
                        sendResponse(ChatMsg(ChatMessageType.UNK_225, false, "", s"@TimeUntilNextUse^${1200 - (System.currentTimeMillis - whenUsedLastSMKit) / 1000}~", None))
                      }
                      else {
                        whenUsedLastSMKit = System.currentTimeMillis
                        player.Slot(index).Equipment = None //remove from slot immediately; must exist on client for next packet
                        sendResponse(UseItemMessage(avatar_guid, item_used_guid, object_guid, 0, unk3, unk4, unk5, unk6, unk7, unk8, itemType))
                        sendResponse(ObjectDeleteMessage(kit.GUID, 0))
                        taskResolver ! GUIDTask.UnregisterEquipment(kit)(continent.GUID)
                        player.History(HealFromKit(PlayerSource(player), 100, kit.Definition))
                        player.Health = player.Health + 100
                        sendResponse(PlanetsideAttributeMessage(avatar_guid, 0, player.Health))
                        continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(avatar_guid, 0, player.Health))
                      }
                    }
                    else if(kit.Definition == GlobalDefinitions.super_armorkit) {
                      if(player.Armor == player.MaxArmor) {
                        sendResponse(ChatMsg(ChatMessageType.UNK_225, false, "", "Armor at maximum - No repairing required.", None))
                      }
                      else if(System.currentTimeMillis - whenUsedLastSAKit < 1200000) {
                        sendResponse(ChatMsg(ChatMessageType.UNK_225, false, "", s"@TimeUntilNextUse^${1200 - (System.currentTimeMillis - whenUsedLastSAKit) / 1000}~", None))
                      }
                      else {
                        whenUsedLastSAKit = System.currentTimeMillis
                        player.Slot(index).Equipment = None //remove from slot immediately; must exist on client for next packet
                        sendResponse(UseItemMessage(avatar_guid, item_used_guid, object_guid, 0, unk3, unk4, unk5, unk6, unk7, unk8, itemType))
                        sendResponse(ObjectDeleteMessage(kit.GUID, 0))
                        taskResolver ! GUIDTask.UnregisterEquipment(kit)(continent.GUID)
                        player.History(RepairFromKit(PlayerSource(player), 200, kit.Definition))
                        player.Armor = player.Armor + 200
                        sendResponse(PlanetsideAttributeMessage(avatar_guid, 4, player.Armor))
                        continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(avatar_guid, 4, player.Armor))
                      }
                    }
                    else if(kit.Definition == GlobalDefinitions.super_staminakit) {
                      if(player.Stamina == player.MaxStamina) {
                        sendResponse(ChatMsg(ChatMessageType.UNK_225, false, "", "Stamina at maximum - No recharge required.", None))
                      }
                      else if(System.currentTimeMillis - whenUsedLastSSKit < 1200000) {
                        sendResponse(ChatMsg(ChatMessageType.UNK_225, false, "", s"@TimeUntilNextUse^${300 - (System.currentTimeMillis - whenUsedLastSSKit) / 1200}~", None))
                      }
                      else {
                        whenUsedLastSSKit = System.currentTimeMillis
                        player.Slot(index).Equipment = None //remove from slot immediately; must exist on client for next packet
                        sendResponse(UseItemMessage(avatar_guid, item_used_guid, object_guid, 0, unk3, unk4, unk5, unk6, unk7, unk8, itemType))
                        sendResponse(ObjectDeleteMessage(kit.GUID, 0))
                        taskResolver ! GUIDTask.UnregisterEquipment(kit)(continent.GUID)
                        player.Stamina = player.Stamina + 100
                      }
                    }
                    else {
                      log.warn(s"UseItem: $kit behavior not supported")
                    }

                  case None =>
                    log.error(s"UseItem: anticipated a $kit, but can't find it")
                }
              case Some(item) =>
                log.warn(s"UseItem: looking for Kit to use, but found $item instead")
              case None =>
                log.warn(s"UseItem: anticipated a Kit $item_used_guid, but can't find it")
            }
          }
          else if(itemType == ObjectClass.avatar && unk3) {
            equipment match {
              case Some(tool : Tool) if tool.Definition == GlobalDefinitions.bank =>
                obj.Actor ! CommonMessages.Use(player, equipment)

              case Some(tool : Tool) if tool.Definition == GlobalDefinitions.medicalapplicator =>
                obj.Actor ! CommonMessages.Use(player, equipment)
              case _ => ;
            }
          }

        case Some(locker : Locker) =>
          equipment match {
            case Some(item) =>
              CancelZoningProcessWithDescriptiveReason("cancel_use")
              locker.Actor ! CommonMessages.Use(player, Some(item))
            case None if locker.Faction == player.Faction || !locker.HackedBy.isEmpty =>
              log.trace(s"UseItem: $player accessing a locker")
              CancelZoningProcessWithDescriptiveReason("cancel_use")
              val container = player.Locker
              accessedContainer = Some(container)
              sendResponse(UseItemMessage(avatar_guid, item_used_guid, container.GUID, unk2, unk3, unk4, unk5, unk6, unk7, unk8, 456))
            case _ => ;
          }

        case Some(gen : Generator) =>
          equipment match {
            case Some(item) =>
              CancelZoningProcessWithDescriptiveReason("cancel_use")
              gen.Actor ! CommonMessages.Use(player, Some(item))
            case None => ;
          }

        case Some(mech : ImplantTerminalMech) =>
          equipment match {
            case Some(item) =>
              CancelZoningProcessWithDescriptiveReason("cancel_use")
              mech.Actor ! CommonMessages.Use(player, Some(item))
            case None => ;
          }

        case Some(captureTerminal : CaptureTerminal) =>
          equipment match {
            case Some(item) =>
              CancelZoningProcessWithDescriptiveReason("cancel_use")
              captureTerminal.Actor ! CommonMessages.Use(player, Some(item))
            case _ => ;
          }

        case Some(obj : FacilityTurret) =>
          equipment match {
            case Some(item) =>
              CancelZoningProcessWithDescriptiveReason("cancel_use")
              obj.Actor ! CommonMessages.Use(player, Some(item)) //try generic
              obj.Actor ! CommonMessages.Use(player, Some((item, unk2.toInt))) //try upgrade path
            case _ => ;
          }

        case Some(obj : Vehicle) =>
          equipment match {
            case Some(item) =>
              CancelZoningProcessWithDescriptiveReason("cancel_use")
              obj.Actor ! CommonMessages.Use(player, Some(item))

            case None if player.Faction == obj.Faction =>
              //access to trunk
              if(obj.AccessingTrunk.isEmpty &&
                (!obj.PermissionGroup(AccessPermissionGroup.Trunk.id).contains(VehicleLockState.Locked) || obj.Owner.contains(player.GUID))) {
                CancelZoningProcessWithDescriptiveReason("cancel_use")
                obj.AccessingTrunk = player.GUID
                accessedContainer = Some(obj)
                AccessContents(obj)
                sendResponse(UseItemMessage(avatar_guid, item_used_guid, object_guid, unk2, unk3, unk4, unk5, unk6, unk7, unk8, itemType))
            }
            case _ => ;
          }

        case Some(terminal : Terminal) =>
          log.info(s"$msg")
          equipment match {
            case Some(item) =>
              CancelZoningProcessWithDescriptiveReason("cancel_use")
              terminal.Actor ! CommonMessages.Use(player, Some(item))

            case None if terminal.Faction == player.Faction || terminal.HackedBy.nonEmpty =>
              val tdef = terminal.Definition
              if(tdef.isInstanceOf[MatrixTerminalDefinition]) {
                //TODO matrix spawn point; for now, just blindly bind to show work (and hope nothing breaks)
                CancelZoningProcessWithDescriptiveReason("cancel_use")
                sendResponse(BindPlayerMessage(BindStatus.Bind, "", true, true, SpawnGroup.Sanctuary, 0, 0, terminal.Position))
              }
              else if(tdef == GlobalDefinitions.multivehicle_rearm_terminal || tdef == GlobalDefinitions.bfr_rearm_terminal ||
                tdef == GlobalDefinitions.air_rearm_terminal || tdef == GlobalDefinitions.ground_rearm_terminal) {
                FindLocalVehicle match {
                  case Some(vehicle) =>
                    sendResponse(UseItemMessage(avatar_guid, item_used_guid, object_guid, unk2, unk3, unk4, unk5, unk6, unk7, unk8, itemType))
                    sendResponse(UseItemMessage(avatar_guid, item_used_guid, vehicle.GUID, unk2, unk3, unk4, unk5, unk6, unk7, unk8, vehicle.Definition.ObjectId))
                  case None =>
                    log.error("UseItem: expected seated vehicle, but found none")
                }
              }
              else if(tdef == GlobalDefinitions.teleportpad_terminal) {
                //explicit request
                CancelZoningProcessWithDescriptiveReason("cancel_use")
                terminal.Actor ! Terminal.Request(
                  player,
                  ItemTransactionMessage(object_guid, TransactionType.Buy, 0, "router_telepad", 0, PlanetSideGUID(0))
                )
              }
              else {
                CancelZoningProcessWithDescriptiveReason("cancel_use")
                sendResponse(UseItemMessage(avatar_guid, item_used_guid, object_guid, unk2, unk3, unk4, unk5, unk6, unk7, unk8, itemType))
              }

            case _ => ;
          }

        case Some(obj : SpawnTube) =>
          equipment match {
            case Some(item) =>
              CancelZoningProcessWithDescriptiveReason("cancel_use")
              obj.Actor ! CommonMessages.Use(player, Some(item))
            case None if player.Faction == obj.Faction =>
              //deconstruction
              CancelZoningProcessWithDescriptiveReason("cancel_use")
              PlayerActionsToCancel()
              CancelAllProximityUnits()
              continent.Population ! Zone.Population.Release(avatar)
              GoToDeploymentMap()
            case _ => ;
          }

        case Some(obj : SensorDeployable) =>
          equipment match {
            case Some(item) =>
              CancelZoningProcessWithDescriptiveReason("cancel_use")
              obj.Actor ! CommonMessages.Use(player, Some(item))
            case _ => ;
          }

        case Some(obj : TurretDeployable) =>
          equipment match {
            case Some(item) =>
              CancelZoningProcessWithDescriptiveReason("cancel_use")
              obj.Actor ! CommonMessages.Use(player, Some(item))
            case _ => ;
          }

        case Some(obj : TrapDeployable) =>
          equipment match {
            case Some(item) =>
              CancelZoningProcessWithDescriptiveReason("cancel_use")
              obj.Actor ! CommonMessages.Use(player, Some(item))
            case _ => ;
          }

        case Some(obj : ShieldGeneratorDeployable) =>
          equipment match {
            case Some(item) =>
              CancelZoningProcessWithDescriptiveReason("cancel_use")
              obj.Actor ! CommonMessages.Use(player, Some(item))
            case _ => ;
          }

        case Some(obj : TelepadDeployable) =>
          if(equipment.isEmpty) {
            continent.GUID(obj.Router) match {
              case Some(vehicle : Vehicle) =>
                vehicle.Utility(UtilityType.internal_router_telepad_deployable) match {
                  case Some(util : Utility.InternalTelepad) =>
                    CancelZoningProcessWithDescriptiveReason("cancel")
                    UseRouterTelepadSystem(router = vehicle, internalTelepad = util, remoteTelepad = obj, src = obj, dest = util)
                  case _ =>
                    log.error(s"telepad@${object_guid.guid} is not linked to a router - ${vehicle.Definition.Name}, ${obj.Router}")
                }
              case Some(o) =>
                log.error(s"telepad@${object_guid.guid} is linked to wrong kind of object - ${o.Definition.Name}, ${obj.Router}")
              case None => ;
            }
          }

        case Some(obj : Utility.InternalTelepad) =>
          continent.GUID(obj.Telepad) match {
            case Some(pad : TelepadDeployable) =>
              CancelZoningProcessWithDescriptiveReason("cancel")
              UseRouterTelepadSystem(router = obj.Owner.asInstanceOf[Vehicle], internalTelepad = obj, remoteTelepad = pad, src = obj, dest = pad)
            case Some(o) =>
              log.error(s"internal telepad@${object_guid.guid} is not linked to a remote telepad - ${o.Definition.Name}@${o.GUID.guid}")
            case None => ;
          }

        case Some(obj) =>
          CancelZoningProcessWithDescriptiveReason("cancel_use")
          log.warn(s"UseItem: don't know how to handle $obj")

        case None =>
          log.error(s"UseItem: can not find object $object_guid")
      }

    case msg @ ProximityTerminalUseMessage(player_guid, object_guid, _) =>
      log.trace(s"ProximityTerminalUse: $msg")
      continent.GUID(object_guid) match {
        case Some(obj : Terminal with ProximityUnit) =>
          HandleProximityTerminalUse(obj)
        case Some(obj) => ;
          log.warn(s"ProximityTerminalUse: object does not have proximity effects - $obj")
        case None =>
          log.warn(s"ProximityTerminalUse: no object with guid $object_guid found")
      }

    case msg @ UnuseItemMessage(player_guid, object_guid) =>
      log.info(s"UnuseItem: $msg")
      //TODO check for existing accessedContainer value?
      ValidObject(object_guid) match {
        case Some(obj : Vehicle) =>
          if(obj.AccessingTrunk.contains(player.GUID)) {
            obj.AccessingTrunk = None
            UnAccessContents(obj)
          }
        case Some(obj : Player) =>
          TryDisposeOfLootedCorpse(obj)

        case _ =>;
      }
      accessedContainer = None

    case msg @ DeployObjectMessage(guid, unk1, pos, orient, unk2) =>
      log.info(s"DeployObject: $msg")
      //the hand with the construction item is no longer drawn
      //TODO consider player.Slot(player.LastDrawnSlot)
      (player.Holsters.find(slot => slot.Equipment.nonEmpty && slot.Equipment.get.GUID == guid) match {
        case Some(slot) =>
          slot.Equipment
        case None =>
          None
      }) match {
        case Some(obj : ConstructionItem) =>
          val ammoType = obj.AmmoType match {
            case DeployedItem.portable_manned_turret =>
              GlobalDefinitions.PortableMannedTurret(player.Faction).Item //faction-specific turret
            case turret =>
              turret
          }
          log.info(s"DeployObject: Constructing a ${ammoType}")
          CancelZoningProcessWithDescriptiveReason("cancel_use")
          val dObj : PlanetSideGameObject with Deployable = Deployables.Make(ammoType)()
          dObj.Position = pos
          dObj.Orientation = orient
          dObj.Faction = player.Faction
          dObj.AssignOwnership(player)
          val tasking : TaskResolver.GiveTask = dObj match {
            case turret : TurretDeployable =>
              GUIDTask.RegisterDeployableTurret(turret)(continent.GUID)
            case _ =>
              GUIDTask.RegisterObjectTask(dObj)(continent.GUID)
          }
          taskResolver ! CallBackForTask(tasking, continent.Deployables, Zone.Deployable.Build(dObj, obj))

        case Some(obj) =>
          log.warn(s"DeployObject: $obj is something?")
        case None =>
          log.warn("DeployObject: nothing?")
      }

    case msg @ GenericObjectStateMsg(object_guid, unk1) =>
      log.info("GenericObjectState: " + msg)

    case msg @ GenericActionMessage(action) =>
      log.info(s"GenericAction: $msg")
      val (toolOpt, definition) = player.Slot(0).Equipment match {
        case Some(tool : Tool) =>
          (Some(tool), tool.Definition)
        case _ =>
          (None, GlobalDefinitions.bullet_9mm)
      }
      if(action == 15) { //max deployment
        log.info(s"GenericObject: $player is anchored")
        player.UsingSpecial = SpecialExoSuitDefinition.Mode.Anchored
        continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(player.GUID, 19, 1))
        definition match {
          case GlobalDefinitions.trhev_dualcycler | GlobalDefinitions.trhev_burster =>
            val tool = toolOpt.get
            tool.ToFireMode = 1
            sendResponse(ChangeFireModeMessage(tool.GUID, 1))
          case GlobalDefinitions.trhev_pounder =>
            val tool = toolOpt.get
            val convertFireModeIndex = if(tool.FireModeIndex == 0) { 1 } else { 4 }
            tool.ToFireMode = convertFireModeIndex
            sendResponse(ChangeFireModeMessage(tool.GUID, convertFireModeIndex))
          case _ =>
            log.warn(s"GenericObject: $player is MAX with an unexpected weapon - ${definition.Name}")
        }
      }
      else if(action == 16) { //max deployment
        log.info(s"GenericObject: $player has released the anchors")
        player.UsingSpecial = SpecialExoSuitDefinition.Mode.Normal
        continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(player.GUID, 19, 0))
        definition match {
          case GlobalDefinitions.trhev_dualcycler | GlobalDefinitions.trhev_burster =>
            val tool = toolOpt.get
            tool.ToFireMode = 0
            sendResponse(ChangeFireModeMessage(tool.GUID, 0))
          case GlobalDefinitions.trhev_pounder =>
            val tool = toolOpt.get
            val convertFireModeIndex = if(tool.FireModeIndex == 1) { 0 } else { 3 }
            tool.ToFireMode = convertFireModeIndex
            sendResponse(ChangeFireModeMessage(tool.GUID, convertFireModeIndex))
          case _ =>
            log.warn(s"GenericObject: $player is MAX with an unexpected weapon - ${definition.Name}")
        }
      }
      else if (action == 20) {
        if(player.ExoSuit == ExoSuitType.MAX) {
          ToggleMaxSpecialState(enable = true)
        } else {
          log.warn("Got GenericActionMessage 20 but can't handle it")
        }
      }
      else if (action == 21) {
        if(player.ExoSuit == ExoSuitType.MAX) {
            player.Faction match {
              case PlanetSideEmpire.NC =>
                ToggleMaxSpecialState(enable = false)
              case _ => log.warn(s"Player ${player.Name} tried to cancel an uncancellable MAX special ability")
            }
        } else {
          log.warn("Got GenericActionMessage 21 but can't handle it")
        }
      }
      else if(action == 36) { //Looking For Squad ON
        if(squadUI.nonEmpty) {
          if(!lfsm && squadUI(player.CharId).index == 0) {
            lfsm = true
            continent.AvatarEvents ! AvatarServiceMessage(s"${player.Faction}", AvatarAction.PlanetsideAttribute(player.GUID, 53, 1))
          }
        }
        else if(!avatar.LFS) {
          avatar.LFS = true
          continent.AvatarEvents ! AvatarServiceMessage(s"${player.Faction}", AvatarAction.PlanetsideAttribute(player.GUID, 53, 1))
        }
      }
      else if(action == 37) { //Looking For Squad OFF
        if(squadUI.nonEmpty) {
          if(lfsm && squadUI(player.CharId).index == 0) {
            lfsm = false
            continent.AvatarEvents ! AvatarServiceMessage(s"${player.Faction}", AvatarAction.PlanetsideAttribute(player.GUID, 53, 0))
          }
        }
        else if(avatar.LFS) {
          avatar.LFS = false
          continent.AvatarEvents ! AvatarServiceMessage(s"${player.Faction}", AvatarAction.PlanetsideAttribute(player.GUID, 53, 0))
        }
      }

    case msg @ ItemTransactionMessage(terminal_guid, transaction_type, _, _, _, _) =>
      log.info("ItemTransaction: " + msg)
      continent.GUID(terminal_guid) match {
        case Some(term : Terminal) =>
          log.info(s"ItemTransaction: ${term.Definition.Name} found")
          if(lastTerminalOrderFulfillment) {
            lastTerminalOrderFulfillment = false
            CancelZoningProcessWithDescriptiveReason("cancel_use")
            term.Actor ! Terminal.Request(player, msg)
          }
        case Some(obj : PlanetSideGameObject) =>
          log.error(s"ItemTransaction: $obj is not a terminal")
        case _ =>
          log.error(s"ItemTransaction: $terminal_guid does not exist")
      }

    case msg @ FavoritesRequest(player_guid, list, action, line, label) =>
      log.info(s"FavoritesRequest: $msg")
      if(player.GUID == player_guid) {
        val lineno = if(list == LoadoutType.Vehicle) { line + 10 } else { line }
        val name = label.getOrElse(s"missing_loadout_${line+1}")
        action match {
          case FavoritesAction.Save =>
            (if(list == LoadoutType.Infantry) {
              Some(player)
            }
            else if(list == LoadoutType.Vehicle) {
              player.VehicleSeated match {
                case Some(vehicle_guid) =>
                  continent.GUID(vehicle_guid)
                case None =>
                  None
              }
            }
            else {
              None
            }) match {
              case Some(owner : Player) => //InfantryLoadout
                CancelZoningProcessWithDescriptiveReason("cancel_use")
                avatar.EquipmentLoadouts.SaveLoadout(owner, name, lineno)
                SaveLoadoutToDB(owner, name, lineno)
                import InfantryLoadout._
//                println(player_guid, line, name, DetermineSubtypeB(player.ExoSuit, DetermineSubtype(player)), player.ExoSuit, DetermineSubtype(player))
                sendResponse(FavoritesMessage(list, player_guid, line, name, DetermineSubtypeB(player.ExoSuit, DetermineSubtype(player))))
              case Some(owner : Vehicle) => //VehicleLoadout
                avatar.EquipmentLoadouts.SaveLoadout(owner, name, lineno)
                sendResponse(FavoritesMessage(list, player_guid, line, name))
              case Some(_) | None =>
                log.error("FavoritesRequest: unexpected owner for favorites")
            }

          case FavoritesAction.Delete =>
            CancelZoningProcessWithDescriptiveReason("cancel_use")
            avatar.EquipmentLoadouts.DeleteLoadout(lineno)
            sendResponse(FavoritesMessage(list, player_guid, line, ""))

          case FavoritesAction.Unknown =>
            log.warn("FavoritesRequest: unknown favorites action")
        }
      }

    case msg @ WeaponDelayFireMessage(seq_time, weapon_guid) =>
      log.info("WeaponDelayFire: " + msg)

    case msg @ WeaponDryFireMessage(weapon_guid) =>
      log.info("WeaponDryFireMessage: "+msg)
      FindWeapon match {
        case Some(tool : Tool) =>
          continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.WeaponDryFire(player.GUID, weapon_guid))
        case _ => ;
      }

    case msg @ WeaponFireMessage(seq_time, weapon_guid, projectile_guid, shot_origin, unk1, unk2, unk3, unk4, unk5, unk6, unk7) =>
      log.info(s"WeaponFire: $msg")
      CancelZoningProcessWithDescriptiveReason("cancel_fire")
      if(player.isShielded) {
        // Cancel NC MAX shield if it's active
        ToggleMaxSpecialState(enable = false)
      }
      FindContainedWeapon match {
        case (Some(obj), Some(tool : Tool)) =>
          if(tool.Magazine <= 0) { //safety: enforce ammunition depletion
            prefire = None
            EmptyMagazine(weapon_guid, tool)
          }
          else if(!player.isAlive) { //proper internal accounting, but no projectile
            prefire = shooting.orElse(Some(weapon_guid))
            tool.Discharge
            projectiles(projectile_guid.guid - Projectile.BaseUID) = None
            shotsWhileDead += 1
          }
          else { //shooting
            if (tool.FireModeIndex == 1 && (tool.Definition.Name == "anniversary_guna" || tool.Definition.Name == "anniversary_gun" || tool.Definition.Name == "anniversary_gunb")) {
              player.Stamina = 0
            }

            prefire = shooting.orElse(Some(weapon_guid))
            tool.Discharge //always
            val projectileIndex = projectile_guid.guid - Projectile.BaseUID
            val projectilePlace = projectiles(projectileIndex)
            if(projectilePlace match {
              case Some(projectile) => !projectile.isResolved
              case None => false
            }) {
              log.trace(s"WeaponFireMessage: overwriting unresolved projectile ${projectile_guid.guid}")
            }
            val (angle, attribution, acceptableDistanceToOwner) = obj match {
              case p : Player =>
                (SimpleWorldEntity.validateOrientationEntry(p.Orientation + Vector3.z(p.FacingYawUpper)), tool.Definition.ObjectId, 10f + (if(p.Velocity.nonEmpty) { 5f } else { 0f }))
              case v : Vehicle if v.Definition.CanFly =>
                (tool.Orientation, obj.Definition.ObjectId, 1000f) //TODO this is too simplistic to find proper angle
              case _ : Vehicle =>
                (tool.Orientation, obj.Definition.ObjectId, 225f) //TODO this is too simplistic to find proper angle
              case _ =>
                (obj.Orientation, obj.Definition.ObjectId, 300f)
            }
            val distanceToOwner = Vector3.DistanceSquared(shot_origin, player.Position)
            if(distanceToOwner <= acceptableDistanceToOwner) {
              val projectile_info = tool.Projectile
              val projectile = Projectile(projectile_info, tool.Definition, tool.FireMode, player, attribution, shot_origin, angle)
              projectiles(projectileIndex) = Some(projectile)
              if(projectile_info.ExistsOnRemoteClients) {
                log.trace(s"WeaponFireMessage: ${projectile_info.Name} is a remote projectile")
                taskResolver ! (if(projectile.HasGUID) {
                  continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.ProjectileExplodes(player.GUID, projectile.GUID, projectile))
                  ReregisterProjectile(projectile)
                }
                else {
                  RegisterProjectile(projectile)
                })
              }
              projectilesToCleanUp(projectileIndex) = false

              obj match {
                case turret : FacilityTurret if turret.Definition == GlobalDefinitions.vanu_sentry_turret =>
                  turret.Actor ! FacilityTurret.WeaponDischarged()
                case _ => ;
              }
            }
            else {
              log.warn(s"WeaponFireMessage: $player's ${tool.Definition.Name} projectile is too far from owner position at time of discharge ($distanceToOwner > $acceptableDistanceToOwner); suspect")
            }
          }
        case _ => ;
      }

    case msg @ WeaponLazeTargetPositionMessage(weapon, pos1, pos2) =>
      log.info("Lazing position: " + pos2.toString)

    case msg @ ObjectDetectedMessage(guid1, guid2, unk, targets) =>
      //log.info(s"Detection: $msg")
      FindWeapon match {
        case Some(weapon) if weapon.Projectile.AutoLock =>
          //projectile with auto-lock instigates a warning on the target
          val detectedTargets = FindDetectedProjectileTargets(targets)
          if(detectedTargets.nonEmpty) {
            val mode = 7 + (weapon.Projectile == GlobalDefinitions.wasp_rocket_projectile)
            detectedTargets.foreach { target =>
              continent.AvatarEvents ! AvatarServiceMessage(target, AvatarAction.ProjectileAutoLockAwareness(mode))
            }
          }
        case _ => ;
      }

    case msg @ HitMessage(seq_time, projectile_guid, unk1, hit_info, unk2, unk3, unk4) =>
      log.info(s"Hit: $msg")
      (hit_info match {
        case Some(hitInfo) =>
          ValidObject(hitInfo.hitobject_guid) match {
            case Some(target : PlanetSideGameObject with FactionAffinity with Vitality) =>
              Some((target, hitInfo.shot_origin, hitInfo.hit_pos))
            case _ =>
              None
          }
        case None => ;
          None
      }) match {
        case Some((target, shotOrigin, hitPos)) =>
          ResolveProjectileEntry(projectile_guid, ProjectileResolution.Hit, target, hitPos) match {
            case Some(projectile) =>
              HandleDealingDamage(target, projectile)
            case None => ;
          }
        case None => ;
      }

    case msg @ SplashHitMessage(seq_time, projectile_guid, explosion_pos, direct_victim_uid, unk3, projectile_vel, unk4, targets) =>
      log.info(s"Splash: $msg")
      FindProjectileEntry(projectile_guid) match {
        case Some(projectile) =>
          projectile.Position = explosion_pos
          projectile.Velocity = projectile_vel
          //direct_victim_uid
          ValidObject(direct_victim_uid) match {
            case Some(target : PlanetSideGameObject with FactionAffinity with Vitality) =>
              ResolveProjectileEntry(projectile, ProjectileResolution.Splash, target, target.Position) match {
                case Some(projectile) =>
                  HandleDealingDamage(target, projectile)
                case None => ;
              }
            case _ => ;
          }
          //other victims
          targets.foreach(elem => {
            ValidObject(elem.uid) match {
              case Some(target : PlanetSideGameObject with FactionAffinity with Vitality) =>
                ResolveProjectileEntry(projectile, ProjectileResolution.Splash, target, explosion_pos) match {
                  case Some(projectile) =>
                    HandleDealingDamage(target, projectile)
                  case None => ;
                }
              case _ => ;
            }
          })
          if(projectile.profile.ExistsOnRemoteClients && projectile.HasGUID) {
            //cleanup
            val localIndex = projectile_guid.guid - Projectile.BaseUID
            if(projectile.HasGUID) {
              CleanUpRemoteProjectile(projectile.GUID, projectile, localIndex)
            }
            else {
              projectilesToCleanUp(localIndex) = true
            }
          }
        case None => ;
      }

    case msg @ LashMessage(seq_time, killer_guid, victim_guid, projectile_guid, pos, unk1) =>
      log.info(s"Lash: $msg")
      ValidObject(victim_guid) match {
        case Some(target : PlanetSideGameObject with FactionAffinity with Vitality) =>
          ResolveProjectileEntry(projectile_guid, ProjectileResolution.Lash, target, pos) match {
            case Some(projectile) =>
              HandleDealingDamage(target, projectile)
            case None => ;
          }
        case _ => ;
      }

    case msg @ AvatarFirstTimeEventMessage(avatar_guid, object_guid, unk1, event_name) =>
      log.info(s"AvatarFirstTimeEvent: $event_name")
      avatar.FirstTimeEvents += event_name

    case msg @ WarpgateRequest(continent_guid, building_guid, dest_building_guid, dest_continent_guid, unk1, unk2) =>
      log.info(s"WarpgateRequest: $msg")
      CancelZoningProcessWithDescriptiveReason("cancel_use")
      if(deadState != DeadState.RespawnTime) {
        continent.Buildings.values.find(building => building.GUID == building_guid) match {
          case Some(wg : WarpGate) if (wg.Active && (GetKnownVehicleAndSeat() match {
            case (Some(vehicle), _) =>
              wg.Definition.VehicleAllowance && !wg.Definition.NoWarp.contains(vehicle.Definition)
            case _ =>
              true
          })) =>
            deadState = DeadState.RespawnTime
            cluster ! Zone.Lattice.RequestSpecificSpawnPoint(dest_continent_guid.guid, player, dest_building_guid)

        case Some(wg : WarpGate) if(!wg.Active) =>
          log.info(s"WarpgateRequest: inactive WarpGate")

        case _ =>
          deadState = DeadState.RespawnTime
          RequestSanctuaryZoneSpawn(player, continent.Number)
        }
      }
      else {
        log.warn("WarpgateRequest: request consumed; already respawning ...")
      }

    case msg @ MountVehicleMsg(player_guid, mountable_guid, entry_point) =>
      log.info("MountVehicleMsg: "+msg)
      ValidObject(mountable_guid) match {
        case Some(obj : Mountable) =>
          obj.GetSeatFromMountPoint(entry_point) match {
            case Some(seat_num) =>
              obj.Actor ! Mountable.TryMount(player, seat_num)
            case None =>
              log.warn(s"MountVehicleMsg: attempted to board mountable $mountable_guid's seat $entry_point, but no seat exists there")
          }
        case None | Some(_) =>
          log.warn(s"MountVehicleMsg: not a mountable thing")
      }

    case msg @ DismountVehicleMsg(player_guid, bailType, wasKickedByDriver) =>
      //TODO optimize this later
      log.info(s"DismountVehicleMsg: $msg")
      //common warning for this section
      def dismountWarning(msg : String) : Unit = {
        log.warn(s"$msg; some vehicle might not know that a player is no longer sitting in it")
      }
      if(player.GUID == player_guid) {
        //normally disembarking from a seat
        (interstellarFerry.orElse(continent.GUID(player.VehicleSeated)) match {
          case out @ Some(obj : Vehicle) =>
            if(obj.MountedIn.isEmpty) out else None
          case out @ Some(_ : Mountable) =>
            out
          case _ =>
            dismountWarning(s"DismountVehicleMsg: player $player_guid not considered seated in a mountable entity")
            None
        }) match {
          case Some(obj : Mountable) =>
            obj.PassengerInSeat(player) match {
              case Some(0) if controlled.nonEmpty =>
                log.warn(s"DismountVehicleMsg: can not dismount from vehicle as driver while server has asserted control; please wait ...")
              case Some(seat_num : Int) =>
                obj.Actor ! Mountable.TryDismount(player, seat_num)
                if(interstellarFerry.isDefined) {
                  //short-circuit the temporary channel for transferring between zones, the player is no longer doing that
                  //see above in VehicleResponse.TransferPassenger case
                  interstellarFerry = None
                }
                // Deconstruct the vehicle if the driver has bailed out and the vehicle is capable of flight
                //todo: implement auto landing procedure if the pilot bails but passengers are still present instead of deconstructing the vehicle
                //todo: continue flight path until aircraft crashes if no passengers present (or no passenger seats), then deconstruct.
                //todo: kick cargo passengers out. To be added after PR #216 is merged
                obj match {
                  case v : Vehicle if bailType == BailType.Bailed && seat_num == 0 && v.Flying =>
                    v.Actor ! Vehicle.Deconstruct(None) //immediate deconstruction
                  case _ => ;
                }

              case None =>
                dismountWarning(s"DismountVehicleMsg: can not find where player $player_guid is seated in mountable ${player.VehicleSeated}")
            }
          case _ =>
            dismountWarning(s"DismountVehicleMsg: can not find mountable entity ${player.VehicleSeated}")
        }
      }
      else {
        //kicking someone else out of a seat; need to own that seat/mountable
        player.VehicleOwned match {
          case Some(obj_guid) =>
            ((ValidObject(obj_guid), ValidObject(player_guid)) match {
              case (vehicle @ Some(obj : Vehicle), tplayer) =>
                if(obj.MountedIn.isEmpty) (vehicle, tplayer) else (None, None)
              case (mount @ Some(obj : Mountable), tplayer) =>
                (mount, tplayer)
              case _ =>
                (None, None)
            }) match {
              case (Some(obj : Mountable), Some(tplayer : Player)) =>
                obj.PassengerInSeat(tplayer) match {
                  case Some(seat_num : Int) =>
                    obj.Actor ! Mountable.TryDismount(tplayer, seat_num)
                  case None =>
                    dismountWarning(s"DismountVehicleMsg: can not find where other player $player_guid is seated in mountable $obj_guid")
                }
              case (None, _) => ;
                log.warn(s"DismountVehicleMsg: $player can not find his vehicle")
              case (_, None) => ;
                log.warn(s"DismountVehicleMsg: player $player_guid could not be found to kick")
              case _ =>
                log.warn(s"DismountVehicleMsg: object is either not a Mountable or not a Player")
            }
          case None =>
            log.warn(s"DismountVehicleMsg: $player does not own a vehicle")
        }
      }

    case msg @ DeployRequestMessage(player_guid, vehicle_guid, deploy_state, unk2, unk3, pos) =>
      log.info(s"DeployRequest: $msg")
      if(player.VehicleOwned.contains(vehicle_guid) && player.VehicleOwned == player.VehicleSeated) {
        continent.GUID(vehicle_guid) match {
          case Some(obj : Vehicle) =>
            obj.Actor ! Deployment.TryDeploymentChange(deploy_state)

          case _ =>
            log.error(s"DeployRequest: can not find $vehicle_guid in scope")
            player.VehicleOwned = None
        }
      }
      else {
        log.warn(s"DeployRequest: $player does not own the deploying $vehicle_guid object")
      }

    case msg @ AvatarGrenadeStateMessage(player_guid, state) =>
      log.info("AvatarGrenadeStateMessage: " + msg)

    case msg @ SquadDefinitionActionMessage(u1, u2, action) =>
      log.info(s"SquadDefinitionAction: $msg")
        squadService ! SquadServiceMessage(player, continent, SquadServiceAction.Definition(u1, u2, action))

    case msg @ SquadMembershipRequest(request_type, char_id, unk3, player_name, unk5) =>
      log.info(s"$msg")
      squadService ! SquadServiceMessage(player, continent, SquadServiceAction.Membership(request_type, char_id, unk3, player_name, unk5))

    case msg @ SquadWaypointRequest(request, _, wtype, unk, info) =>
      log.info(s"Waypoint Request: $msg")
      squadService ! SquadServiceMessage(player, continent, SquadServiceAction.Waypoint(request, wtype, unk, info))

    case msg @ GenericCollisionMsg(u1, p, t, php, thp, pv, tv, ppos, tpos, u2, u3, u4) =>
      log.info("Ouch! " + msg)

    case msg @ BugReportMessage(version_major,version_minor,version_date,bug_type,repeatable,location,zone,pos,summary,desc) =>
      log.info("BugReportMessage: " + msg)

    case msg @ BindPlayerMessage(action, bindDesc, unk1, logging, unk2, unk3, unk4, pos) =>
      log.info("BindPlayerMessage: " + msg)

    case msg @ PlanetsideAttributeMessage(object_guid, attribute_type, attribute_value) =>
      log.info("PlanetsideAttributeMessage: "+msg)
      ValidObject(object_guid) match {
        case Some(vehicle : Vehicle) =>
          if(player.VehicleOwned.contains(vehicle.GUID)) {
            if(9 < attribute_type && attribute_type < 14) {
              vehicle.PermissionGroup(attribute_type, attribute_value) match {
                case Some(allow) =>
                  val group = AccessPermissionGroup(attribute_type - 10)
                  log.info(s"Vehicle attributes: vehicle ${vehicle.GUID} access permission $group changed to $allow")
                  continent.VehicleEvents ! VehicleServiceMessage(continent.Id, VehicleAction.SeatPermissions(player.GUID, vehicle.GUID, attribute_type, attribute_value))
                  //kick players who should not be seated in the vehicle due to permission changes
                  if(allow == VehicleLockState.Locked) { //TODO only important permission atm
                    vehicle.Definition.MountPoints.values.foreach(mountpoint_num => {
                      vehicle.Seat(mountpoint_num) match {
                        case Some(seat) =>
                          seat.Occupant match {
                            case Some(tplayer) =>
                              if(vehicle.SeatPermissionGroup(mountpoint_num).contains(group) && tplayer != player) { //can not kick self
                                seat.Occupant = None
                                tplayer.VehicleSeated = None
                                continent.VehicleEvents ! VehicleServiceMessage(continent.Id, VehicleAction.KickPassenger(tplayer.GUID, 4, false, object_guid))
                              }
                            case None => ; // No player seated
                          }
                        case None => ; // Not a seat mounting point
                      }
                      vehicle.CargoHold(mountpoint_num) match {
                        case Some(cargo) =>
                          cargo.Occupant match {
                            case Some(vehicle) =>
                              if(vehicle.SeatPermissionGroup(mountpoint_num).contains(group)) {
                                //todo: this probably doesn't work for passengers within the cargo vehicle
                                // Instruct client to start bail dismount procedure
                                self ! DismountVehicleCargoMsg(player.GUID, vehicle.GUID, true, false, false)
                              }
                            case None => ; // No vehicle in cargo
                          }
                        case None => ; // Not a cargo mounting point
                      }

                    })
                  }
                case None => ;
              }
            }
            else {
              log.warn(s"Vehicle attributes: unsupported change on vehicle $object_guid - $attribute_type")
            }
          }
          else {
            log.warn(s"Vehicle attributes: $player does not own vehicle ${vehicle.GUID} and can not change it")
          }
        case _ =>
          log.warn(s"echo unknown attributes behavior")
          sendResponse(PlanetsideAttributeMessage(object_guid, attribute_type, attribute_value))
      }

    case msg @ FacilityBenefitShieldChargeRequestMessage(guid) =>
      player.VehicleSeated match {
        case Some(vehicleGUID) =>
          continent.GUID(vehicleGUID) match {
            case Some(obj : Vehicle) =>
              if(!obj.Destroyed) { //vehicle will try to charge even if destroyed
                obj.Actor ! Vehicle.ChargeShields(15)
              }
            case _ =>
              log.warn(s"FacilityBenefitShieldChargeRequest: can not find vehicle ${vehicleGUID.guid} in zone ${continent.Id}")
          }
        case None =>
          log.warn(s"FacilityBenefitShieldChargeRequest: player ${player.Name} is not seated in a vehicle")
      }

    case msg @ BattleplanMessage(char_id, player_name, zone_id, diagrams) =>
      log.info("Battleplan: "+msg)

    case msg @ CreateShortcutMessage(player_guid, slot, unk, add, shortcut) =>
      log.info("CreateShortcutMessage: "+msg)

    case msg @ FriendsRequest(action, friend) =>
      log.info("FriendsRequest: "+msg)

    case msg @ HitHint(source_guid, player_guid) =>
      log.trace(s"HitHint: $msg") //HitHint is manually distributed for proper operation

    case msg @ TargetingImplantRequest(list) =>
      log.info("TargetingImplantRequest: "+msg)
      val targetInfo: List[TargetInfo] = list.flatMap(x => {
        continent.GUID(x.target_guid) match {
          case Some(player: Player) =>
            val health = player.Health.toFloat / player.MaxHealth
            val armor = if (player.MaxArmor > 0) {
              player.Armor.toFloat / player.MaxArmor
            } else {
              0
            }

            Some(TargetInfo(player.GUID, health, armor))
          case _ =>
            log.warn(s"Target info requested for guid ${x.target_guid} but is not a player")
            None
        }
      })

      sendResponse(TargetingInfoMessage(targetInfo))
    case msg @ ActionCancelMessage(u1, u2, u3) =>
      log.info("Cancelled: "+msg)
      progressBarUpdate.cancel
      progressBarValue = None

    case default => log.error(s"Unhandled GamePacket $pkt")
  }

  /**
    * Iterate over a group of `EquipmentSlot`s, some of which may be occupied with an item.
    * Remove any encountered items and add them to an output `List`.
    * @param iter the `Iterator` of `EquipmentSlot`s
    * @param index a number that equals the "current" holster slot (`EquipmentSlot`)
    * @param list a persistent `List` of `Equipment` in the holster slots
    * @return a `List` of `Equipment` in the holster slots
    */
  @tailrec private def clearHolsters(iter : Iterator[EquipmentSlot], index : Int = 0, list : List[InventoryItem] = Nil) : List[InventoryItem] = {
    if(!iter.hasNext) {
      list
    }
    else {
      val slot = iter.next
      slot.Equipment match {
        case Some(equipment) =>
          slot.Equipment = None
          clearHolsters(iter, index + 1, InventoryItem(equipment, index) +: list)
        case None =>
          clearHolsters(iter, index + 1, list)
      }
    }
  }

  /**
    * Iterate over a group of `EquipmentSlot`s, some of which may be occupied with an item.
    * For any slots that are not yet occupied by an item, search through the `List` and find an item that fits in that slot.
    * Add that item to the slot and remove it from the list.
    * @param iter the `Iterator` of `EquipmentSlot`s
    * @param list a `List` of all `Equipment` that is not yet assigned to a holster slot or an inventory slot
    * @return the `List` of all `Equipment` not yet assigned to a holster slot or an inventory slot
    */
  @tailrec private def fillEmptyHolsters(iter : Iterator[EquipmentSlot], list : List[InventoryItem]) : List[InventoryItem] = {
    if(!iter.hasNext) {
      list
    }
    else {
      val slot = iter.next
      if(slot.Equipment.isEmpty) {
        list.find(item => item.obj.Size == slot.Size) match {
          case Some(obj) =>
            val index = list.indexOf(obj)
            slot.Equipment = obj.obj
            fillEmptyHolsters(iter, list.take(index) ++ list.drop(index + 1))
          case None =>
            fillEmptyHolsters(iter, list)
        }
      }
      else {
        fillEmptyHolsters(iter, list)
      }
    }
  }

  /**
    * Construct tasking that coordinates the following:<br>
    * 1) Accept a new piece of `Equipment` and register it with a globally unique identifier.<br>
    * 2) Once it is registered, give the `Equipment` to `target`.
    * @param target what object will accept the new `Equipment`
    * @param obj the new `Equipment`
    * @param index the slot where the new `Equipment` will be placed
    * @see `GUIDTask.RegisterEquipment`
    * @see `PutInSlot`
    * @return a `TaskResolver.GiveTask` message
    */
  private def PutEquipmentInSlot(target : PlanetSideGameObject with Container, obj : Equipment, index : Int) : TaskResolver.GiveTask = {
    val regTask = GUIDTask.RegisterEquipment(obj)(continent.GUID)
    obj match {
      case tool : Tool =>
        val linearToolTask = TaskResolver.GiveTask(regTask.task) +: regTask.subs
        TaskResolver.GiveTask(PutInSlot(target, tool, index).task, linearToolTask)
      case _ =>
        TaskResolver.GiveTask(PutInSlot(target, obj, index).task, List(regTask))
    }
  }

  /**
    * Construct tasking that coordinates the following:<br>
    * 1) Remove a new piece of `Equipment` from where it is currently stored.<br>
    * 2) Once it is removed, un-register the `Equipment`'s globally unique identifier.
    * @param target the object that currently possesses the `Equipment`
    * @param obj the `Equipment`
    * @param index the slot from where the `Equipment` will be removed
    * @see `GUIDTask.UnregisterEquipment`
    * @see `RemoveFromSlot`
    * @return a `TaskResolver.GiveTask` message
    */
  private def RemoveEquipmentFromSlot(target : PlanetSideGameObject with Container, obj : Equipment, index : Int) : TaskResolver.GiveTask = {
    val regTask = GUIDTask.UnregisterEquipment(obj)(continent.GUID)
    //to avoid an error from a GUID-less object from being searchable, it is removed from the inventory first
    obj match {
      case _ : Tool =>
        TaskResolver.GiveTask(regTask.task, RemoveFromSlot(target, obj, index) +: regTask.subs)
      case _ =>
        TaskResolver.GiveTask(regTask.task, List(RemoveFromSlot(target, obj, index)))
    }
  }

  /**
    * Construct tasking that gives the `Equipment` to `target`.
    * @param target what object will accept the new `Equipment`
    * @param obj the new `Equipment`
    * @param index the slot where the new `Equipment` will be placed
    * @return a `TaskResolver.GiveTask` message
    */
  private def PutInSlot(target : PlanetSideGameObject with Container, obj : Equipment, index : Int) : TaskResolver.GiveTask = {
    TaskResolver.GiveTask(
      new Task() {
        private val localTarget = target
        private val localIndex = index
        private val localObject = obj
        private val localAnnounce = self
        private val localService = continent.AvatarEvents

        override def isComplete : Task.Resolution.Value = {
          if(localTarget.Slot(localIndex).Equipment.contains(localObject)) {
            Task.Resolution.Success
          }
          else {
            Task.Resolution.Incomplete
          }
        }

        def Execute(resolver : ActorRef) : Unit = {
          localTarget.Slot(localIndex).Equipment = localObject
          resolver ! scala.util.Success(this)
        }

        override def onSuccess() : Unit = {
          val definition = localObject.Definition
          localAnnounce ! ResponseToSelf(
            ObjectCreateDetailedMessage(
              definition.ObjectId,
              localObject.GUID,
              ObjectCreateMessageParent(localTarget.GUID, localIndex),
              definition.Packet.DetailedConstructorData(localObject).get
            )
          )
          if(localTarget.VisibleSlots.contains(localIndex)) {
            localService ! AvatarServiceMessage(continent.Id, AvatarAction.EquipmentInHand(localTarget.GUID, localTarget.GUID, localIndex, localObject))
          }
        }
      })
  }

  /**
    * Construct tasking that registers all aspects of a `Player` avatar.
    * `Players` are complex objects that contain a variety of other register-able objects and each of these objects much be handled.
    * @param tplayer the avatar `Player`
    * @return a `TaskResolver.GiveTask` message
    */
  private def RegisterNewAvatar(tplayer : Player) : TaskResolver.GiveTask = {
    TaskResolver.GiveTask(
      new Task() {
        private val localPlayer = tplayer
        private val localAnnounce = self

        override def isComplete : Task.Resolution.Value = {
          if(localPlayer.HasGUID) {
            Task.Resolution.Success
          }
          else {
            Task.Resolution.Incomplete
          }
        }

        def Execute(resolver : ActorRef) : Unit = {
          log.info(s"Player $localPlayer is registered")
          resolver ! scala.util.Success(this)
          localAnnounce ! NewPlayerLoaded(localPlayer) //alerts WSA
        }

        override def onFailure(ex : Throwable) : Unit = {
          localAnnounce ! PlayerFailedToLoad(localPlayer) //alerts WSA
        }
      }, List(GUIDTask.RegisterAvatar(tplayer)(continent.GUID))
    )
  }

  /**
    * Construct tasking that registers all aspects of a `Player` avatar.
    * `Players` are complex objects that contain a variety of other register-able objects and each of these objects much be handled.
    * @param tplayer the avatar `Player`
    * @return a `TaskResolver.GiveTask` message
    */
  private def RegisterAvatar(tplayer : Player) : TaskResolver.GiveTask = {
    TaskResolver.GiveTask(
      new Task() {
        private val localPlayer = tplayer
        private val localAnnounce = self

        override def isComplete : Task.Resolution.Value = {
          if(localPlayer.HasGUID) {
            Task.Resolution.Success
          }
          else {
            Task.Resolution.Incomplete
          }
        }

        def Execute(resolver : ActorRef) : Unit = {
          log.info(s"Player $localPlayer is registered")
          resolver ! scala.util.Success(this)
          localAnnounce ! PlayerLoaded(localPlayer) //alerts WSA
        }

        override def onFailure(ex : Throwable) : Unit = {
          localAnnounce ! PlayerFailedToLoad(localPlayer) //alerts WSA
        }
      }, List(GUIDTask.RegisterPlayer(tplayer)(continent.GUID))
    )
  }

  /**
    * Construct tasking that adds a completed and registered vehicle into the scene.
    * Use this function to renew the globally unique identifiers on a vehicle that has already been added to the scene once.
    * @param vehicle the `Vehicle` object
    * @see `RegisterVehicleFromSpawnPad`
    * @return a `TaskResolver.GiveTask` message
    */
  def RegisterVehicle(vehicle : Vehicle) : TaskResolver.GiveTask = {
    TaskResolver.GiveTask(
      new Task() {
        private val localVehicle = vehicle
        private val localAnnounce = self

        override def isComplete : Task.Resolution.Value = {
          if(localVehicle.HasGUID) {
            Task.Resolution.Success
          }
          else {
            Task.Resolution.Incomplete
          }
        }

        def Execute(resolver : ActorRef) : Unit = {
          log.info(s"Vehicle $localVehicle is registered")
          resolver ! scala.util.Success(this)
          localAnnounce ! VehicleLoaded(localVehicle) //alerts WSA
        }
      }, List(GUIDTask.RegisterVehicle(vehicle)(continent.GUID))
    )
  }

  /**
    * Use this function to facilitate registering a droppod for a globally unique identifier
    * in the event that the user has instigated an instant action event to a destination within the current zone.<br>
    * <br>
    * If going to another zone instead,
    * this is uneccessary as the normal vehicle gating protocol is partially intersected for droppod operation,
    * and will properly register the droppod before introducing it into the new zone without additional concern.
    * The droppod should actually not be completely unregistered.
    * If inquired, it will act like a GUID had already been assigned to it, but it was invalidated.
    * This condition is artificial, but it necessary to pass certain operations related to vehicle gating.
    * Additionally, the driver is only partially associated with the vehicle at this time.
    * `interstellarFerry` is properly keeping track of the vehicle during the transition
    * and the user who is the driver (second param) is properly seated
    * but the said driver does not know about the vehicle through his usual convention - VehicleSeated` - yet.
    * @see `GlobalDefinitions.droppod`
    * @see `GUIDTask.RegisterObjectTask`
    * @see `interstellarFerry`
    * @see `Player.VehicleSeated`
    * @see `PlayerLoaded`
    * @see `TaskResolver.GiveTask`
    * @see `Vehicles.Own`
    * @param vehicle the unregistered droppod
    * @param tplayer the player using the droppod for instant action;
    *                should already be the driver of the droppod
    * @return a `TaskResolver.GiveTask` message
    */
  def RegisterDroppod(vehicle : Vehicle, tplayer : Player) : TaskResolver.GiveTask = {
    TaskResolver.GiveTask(
      new Task() {
        private val localDriver = tplayer
        private val localVehicle = vehicle
        private val localAnnounce = self

        override def isComplete : Task.Resolution.Value = {
          if(localVehicle.HasGUID) {
            Task.Resolution.Success
          }
          else {
            Task.Resolution.Incomplete
          }
        }

        def Execute(resolver : ActorRef) : Unit = {
          log.info(s"Vehicle $localVehicle is registered")
          localDriver.VehicleSeated = localVehicle.GUID
          Vehicles.Own(localVehicle, localDriver)
          localAnnounce ! PlayerLoaded(localDriver)
          resolver ! scala.util.Success(this)
        }
      }, List(GUIDTask.RegisterObjectTask(vehicle)(continent.GUID))
    )
  }

  /**
    * Construct tasking that adds a completed and registered vehicle into the scene.
    * The major difference between `RegisterVehicle` and `RegisterVehicleFromSpawnPad` is the assumption that this vehicle lacks an internal `Actor`.
    * Before being finished, that vehicle is supplied an `Actor` such that it may function properly.
    * This function wraps around `RegisterVehicle` and is used in case, prior to this event,
    * the vehicle is being brought into existence from scratch and was never a member of any `Zone`.
    * @param obj the `Vehicle` object
    * @see `RegisterVehicle`
    * @return a `TaskResolver.GiveTask` message
    */
  def RegisterVehicleFromSpawnPad(obj : Vehicle, pad : VehicleSpawnPad) : TaskResolver.GiveTask = {
    TaskResolver.GiveTask(
      new Task() {
        private val localVehicle = obj
        private val localPad = pad.Actor
        private val localSession : String = sessionId.toString
        private val localPlayer = player
        private val localVehicleService = continent.VehicleEvents
        private val localZone = continent

        override def isComplete : Task.Resolution.Value = {
          if(localVehicle.HasGUID) {
            Task.Resolution.Success
          }
          else {
            Task.Resolution.Incomplete
          }
        }

        def Execute(resolver : ActorRef) : Unit = {
          localPad ! VehicleSpawnPad.VehicleOrder(localPlayer, localVehicle)
          resolver ! scala.util.Success(this)
        }
      }, List(RegisterVehicle(obj)))
  }

  def RegisterDrivenVehicle(obj : Vehicle, driver : Player) : TaskResolver.GiveTask = {
    TaskResolver.GiveTask(
      new Task() {
        private val localVehicle = obj
        private val localDriver = driver
        private val localAnnounce = self

        override def isComplete : Task.Resolution.Value = {
          if(localVehicle.HasGUID && localDriver.HasGUID) {
            Task.Resolution.Success
          }
          else {
            Task.Resolution.Incomplete
          }
        }

        def Execute(resolver : ActorRef) : Unit = {
          localDriver.VehicleSeated = localVehicle.GUID
          Vehicles.Own(localVehicle, localDriver)
          localAnnounce ! NewPlayerLoaded(localDriver) //alerts WSA
          resolver ! scala.util.Success(this)
        }

        override def onFailure(ex : Throwable) : Unit = {
          localAnnounce ! PlayerFailedToLoad(localDriver) //alerts WSA
        }
      }, List(GUIDTask.RegisterAvatar(driver)(continent.GUID), GUIDTask.RegisterVehicle(obj)(continent.GUID)))
  }

  /**
    * Construct tasking that adds a completed but unregistered projectile into the scene.
    * After the projectile is registered to the curent zone's global unique identifier system,
    * all connected clients save for the one that registered it will be informed about the projectile's "creation."
    * @param obj the projectile to be registered
    * @return a `TaskResolver.GiveTask` message
    */
  def RegisterProjectile(obj : Projectile) : TaskResolver.GiveTask = {
    val definition = obj.Definition
    TaskResolver.GiveTask(
      new Task() {
        private val globalProjectile = obj
        private val localAnnounce = self

        override def isComplete : Task.Resolution.Value = {
          if(globalProjectile.HasGUID) {
            Task.Resolution.Success
          }
          else {
            Task.Resolution.Incomplete
          }
        }

        def Execute(resolver : ActorRef) : Unit = {
          localAnnounce ! LoadedRemoteProjectile(globalProjectile.GUID, Some(globalProjectile))
          resolver ! scala.util.Success(this)
        }
      }, List(GUIDTask.RegisterObjectTask(obj)(continent.GUID))
    )
  }

  def UnregisterDrivenVehicle(obj : Vehicle, driver : Player) : TaskResolver.GiveTask = {
    TaskResolver.GiveTask(
      new Task() {
        private val localVehicle = obj
        private val localDriver = driver

        override def isComplete : Task.Resolution.Value = {
          if(!localVehicle.HasGUID && !localDriver.HasGUID) {
            Task.Resolution.Success
          }
          else {
            Task.Resolution.Incomplete
          }
        }

        def Execute(resolver : ActorRef) : Unit = {
          resolver ! scala.util.Success(this)
        }
      }, List(GUIDTask.UnregisterAvatar(driver)(continent.GUID), GUIDTask.UnregisterVehicle(obj)(continent.GUID)))
  }

  /**
    * Construct tasking that removes a formerly complete and currently registered projectile from the scene.
    * After the projectile is unregistered from the curent zone's global unique identifier system,
    * all connected clients save for the one that registered it will be informed about the projectile's "destruction."
    * @param obj the projectile to be unregistered
    * @return a `TaskResolver.GiveTask` message
    */
  def UnregisterProjectile(obj : Projectile) : TaskResolver.GiveTask = {
    TaskResolver.GiveTask(
      new Task() {
        private val globalProjectile = obj
        private val localAnnounce = continent.AvatarEvents
        private val localMsg = AvatarServiceMessage(continent.Id, AvatarAction.ObjectDelete(player.GUID, obj.GUID, 2))

        override def isComplete : Task.Resolution.Value = {
          if(!globalProjectile.HasGUID) {
            Task.Resolution.Success
          }
          else {
            Task.Resolution.Incomplete
          }
        }

        def Execute(resolver : ActorRef) : Unit = {
          localAnnounce ! localMsg
          resolver ! scala.util.Success(this)
        }
      }, List(GUIDTask.UnregisterObjectTask(obj)(continent.GUID))
    )
  }

  /**
    * Construct tasking that removes the `Equipment` to `target`.
    * @param target what object that contains the `Equipment`
    * @param obj the `Equipment`
    * @param index the slot where the `Equipment` is stored
    * @return a `TaskResolver.GiveTask` message
    */
  private def RemoveFromSlot(target : PlanetSideGameObject with Container, obj : Equipment, index : Int) : TaskResolver.GiveTask = {
    TaskResolver.GiveTask(
      new Task() {
        private val localTarget = target
        private val localIndex = index
        private val localObject = obj
        private val localObjectGUID = obj.GUID
        private val localAnnounce = self //self may not be the same when it executes
        private val localService = continent.AvatarEvents
        private val localContinent = continent.Id

        override def isComplete : Task.Resolution.Value = {
          if(localTarget.Slot(localIndex).Equipment.contains(localObject)) {
            Task.Resolution.Incomplete
          }
          else {
            Task.Resolution.Success
          }
        }

        def Execute(resolver : ActorRef) : Unit = {
          localTarget.Slot(localIndex).Equipment = None
          resolver ! scala.util.Success(this)
        }

        override def onSuccess() : Unit = {
          localAnnounce ! ResponseToSelf( ObjectDeleteMessage(localObjectGUID, 0))
          if(localTarget.VisibleSlots.contains(localIndex)) {
            localService ! AvatarServiceMessage(localContinent, AvatarAction.ObjectDelete(localTarget.GUID, localObjectGUID))
          }
        }
      }
    )
  }

  /**
    * If the projectile object is unregistered, register it.
    * If the projectile object is already registered, unregister it and then register it again.
    * @see `RegisterProjectile(Projectile)`
    * @see `UnregisterProjectile(Projectile)`
    * @param obj the projectile to be registered (a second time?)
    * @return a `TaskResolver.GiveTask` message
    */
  def ReregisterProjectile(obj : Projectile) : TaskResolver.GiveTask = {
    val reg = RegisterProjectile(obj)
    if(obj.HasGUID) {
      TaskResolver.GiveTask(
        reg.task,
        List(TaskResolver.GiveTask(
          reg.subs(0).task,
          List(UnregisterProjectile(obj))
        ))
      )
    }
    else {
      reg
    }
  }

  /**
    * After some subtasking is completed, draw a particular slot, as if an `ObjectHeldMessage` packet was sent/received.<br>
    * <br>
    * The resulting `Task` is most useful for sequencing MAX weaponry when combined with the proper subtasks.
    * @param player the player
    * @param index the slot to be drawn
    * @param priorTasking subtasks that needs to be accomplished first
    * @return a `TaskResolver.GiveTask` message
    */
  private def DelayedObjectHeld(player : Player, index : Int, priorTasking : List[TaskResolver.GiveTask]) : TaskResolver.GiveTask = {
    TaskResolver.GiveTask(
      new Task() {
        private val localPlayer = player
        private val localSlot = index
        private val localAnnounce = self
        private val localService = continent.AvatarEvents

        override def isComplete : Task.Resolution.Value = {
          if(localPlayer.DrawnSlot == localSlot) {
            Task.Resolution.Success
          }
          else {
            Task.Resolution.Incomplete
          }
        }

        def Execute(resolver : ActorRef) : Unit = {
          localPlayer.DrawnSlot = localSlot
          resolver ! scala.util.Success(this)
        }

        override def onSuccess() : Unit = {
          localAnnounce ! ResponseToSelf( ObjectHeldMessage(localPlayer.GUID, localSlot, true))
          localService ! AvatarServiceMessage(localPlayer.Continent, AvatarAction.ObjectHeld(localPlayer.GUID, localSlot))
        }
      }, priorTasking
    )
  }

  /**
    * Before calling `Interstellar.GetWorld` to change zones, perform the following task (which can be a nesting of subtasks).
    * @param priorTask the tasks to perform
    * @param zoneId the zone to load afterwards
    * @return a `TaskResolver.GiveTask` message
    */
  def TaskBeforeZoneChange(priorTask : TaskResolver.GiveTask, zoneId : String) : TaskResolver.GiveTask = {
    TaskResolver.GiveTask(
      new Task() {
        private val localZone = continent
        private val localAvatarMsg = Zone.Population.Leave(avatar)
        private val localService = cluster
        private val localServiceMsg = InterstellarCluster.GetWorld(zoneId)

        override def isComplete : Task.Resolution.Value = priorTask.task.isComplete

        def Execute(resolver : ActorRef) : Unit = {
          localZone.Population ! localAvatarMsg
          localService ! localServiceMsg
          resolver ! scala.util.Success(this)
        }
      }, List(priorTask)
    )
  }

  def CallBackForTask(task : TaskResolver.GiveTask, sendTo : ActorRef, pass : Any) : TaskResolver.GiveTask = {
    TaskResolver.GiveTask(
      new Task() {
        private val destination = sendTo
        private val passMsg = pass

        def Execute(resolver : ActorRef) : Unit = {
          destination ! passMsg
          resolver ! scala.util.Success(this)
        }
      }, List(task)
    )
  }

  /**
    * After a client has connected to the server, their account is used to generate a list of characters.
    * On the character selection screen, each of these characters is made to exist temporarily when one is selected.
    * This "character select screen" is an isolated portion of the client, so it does not have any external constraints.
    * Temporary global unique identifiers are assigned to the underlying `Player` objects so that they can be turned into packets.
    * @param tplayer the `Player` object
    * @param gen a constant source of incremental unique numbers
    */
  private def SetCharacterSelectScreenGUID(tplayer : Player, gen : AtomicInteger) : Unit = {
    tplayer.Holsters().foreach(holster => {
      SetCharacterSelectScreenGUID_SelectEquipment(holster.Equipment, gen)
    })
    tplayer.GUID = PlanetSideGUID(gen.getAndIncrement)
  }

  /**
    * Assists in assigning temporary global unique identifiers.
    * If the item is a `Tool`, handle the embedded `AmmoBox` objects in each ammunition slot.
    * Whether or not, give the object itself a GUID as well.
    * @param item the piece of `Equipment`
    * @param gen a constant source of incremental unique numbers
    */
  private def SetCharacterSelectScreenGUID_SelectEquipment(item : Option[Equipment], gen : AtomicInteger) : Unit = {
    item match {
      case Some(tool : Tool) =>
        tool.AmmoSlots.foreach(slot => { slot.Box.GUID = PlanetSideGUID(gen.getAndIncrement) })
        tool.GUID = PlanetSideGUID(gen.getAndIncrement)
      case Some(item : Equipment) =>
        item.GUID = PlanetSideGUID(gen.getAndIncrement)
      case None => ;
    }
  }

  /**
    * 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.
    * @param tplayer the `Player` object
    */
  private def RemoveCharacterSelectScreenGUID(tplayer : Player) : Unit = {
    tplayer.Holsters().foreach(holster => {
      RemoveCharacterSelectScreenGUID_SelectEquipment(holster.Equipment)
    })
    tplayer.Invalidate()
  }

  /**
    * Assists in stripping temporary global unique identifiers.
    * If the item is a `Tool`, handle the embedded `AmmoBox` objects in each ammunition slot.
    * Whether or not, remove the GUID from the object itself.
    * @param item the piece of `Equipment`
    */
  private def RemoveCharacterSelectScreenGUID_SelectEquipment(item : Option[Equipment]) : Unit = {
    item match {
      case Some(item : Tool) =>
        item.AmmoSlots.foreach(slot => { slot.Box.Invalidate() })
        item.Invalidate()
      case Some(item : Equipment) =>
        item.Invalidate()
      case None => ;
    }
  }

  /**
    * Gives a target player positive battle experience points only.
    * If the player has access to more implant slots as a result of changing battle experience points, unlock those slots.
    * @param avatar the player
    * @param bep the change in experience points, positive by assertion
    * @return the player's current battle experience points
    */
  def AwardBattleExperiencePoints(avatar : Avatar, bep : Long) : Long = {
    val oldBep = avatar.BEP
    if(bep <= 0) {
      log.error(s"trying to set $bep battle experience points on $avatar; value can not be negative")
      oldBep
    }
    else {
      val oldSlots = DetailedCharacterData.numberOfImplantSlots(oldBep)
      val newBep = oldBep + bep
      val newSlots = DetailedCharacterData.numberOfImplantSlots(newBep)
      avatar.BEP = newBep
      if(newSlots > oldSlots) {
        (oldSlots until newSlots).foreach(slotNumber => {
          avatar.Implants(slotNumber).Unlocked = true
          log.info(s"unlocking implant slot $slotNumber for $avatar")
        })
      }
      newBep
    }
  }

  /**
    * Gives a target player positive battle experience points only.
    * This value gets set as the battle experience points
    * rather than be added to any previous total battle experience points.
    * The number of implant slots that are activated is equal to the allowances calculated from on this value.
    * We do this quietly.
    * @param avatar the player
    * @param bep the total amount of experience points, positive by assertion
    * @return the player's current battle experience points
    */
  def AwardCharacterSelectBattleExperiencePoints(avatar : Avatar, bep : Long) : Long = {
    if(bep <= 0) {
      log.error(s"trying to set $bep battle experience points on $avatar; value can not be negative")
    }
    else {
      avatar.BEP = bep
      val slots = DetailedCharacterData.numberOfImplantSlots(bep)
      (0 until slots).foreach(slotNumber =>
        avatar.Implants(slotNumber).Unlocked = true
      )
    }
    bep
  }

  /**
    * Common preparation for interfacing with a vehicle.
    * Join a vehicle-specific group for shared updates.
    * Construct every object in the vehicle's inventory for shared manipulation updates.
    * @param vehicle the vehicle
    */
  def AccessContents(vehicle : Vehicle) : Unit = {
    AccessContentsChannel(vehicle)
    val parent_guid = vehicle.GUID
    vehicle.Trunk.Items.foreach(entry => {
      val obj = entry.obj
      val objDef = obj.Definition
      sendResponse(
        ObjectCreateDetailedMessage(
          objDef.ObjectId,
          obj.GUID,
          ObjectCreateMessageParent(parent_guid, entry.start),
          objDef.Packet.DetailedConstructorData(obj).get
        )
      )
    })
  }

  def AccessContentsChannel(container : PlanetSideServerObject) : Unit = {
    continent.VehicleEvents ! Service.Join(s"${container.Actor}")
  }

  /**
    * Common preparation for disengaging from a vehicle.
    * Leave the vehicle-specific group that was used for shared updates.
    * Deconstruct every object in the vehicle's inventory.
    * @param vehicle the vehicle
    */
  def UnAccessContents(vehicle : Vehicle) : Unit = {
    continent.VehicleEvents ! Service.Leave(Some(s"${vehicle.Actor}"))
    vehicle.Trunk.Items.foreach(entry =>{
      sendResponse(ObjectDeleteMessage(entry.obj.GUID, 0))
    })
  }


  def UnAccessContentsChannel(container : PlanetSideServerObject) : Unit = {
    continent.VehicleEvents ! Service.Leave(Some(s"${container.Actor}"))
  }

  /**
    * Check two locations for a controlled piece of equipment that is associated with the `player`.<br>
    * <br>
    * The first location is dependent on whether the avatar is in a vehicle.
    * Some vehicle seats may have a "controlled weapon" which counts as the first location to be checked.
    * The second location is dependent on whether the avatar has a raised hand.
    * That is only possible if the player has something in their hand at the moment, hence the second location.
    * Players do have a concept called a "last drawn slot" (hand) but that former location is not eligible.<br>
    * <br>
    * Along with any discovered item, a containing object such that the statement:<br>
    *   `container.Find(object) = Some(slot)`<br>
    * ... will return a proper result.
    * For a seat controlled weapon, the vehicle is returned.
    * For the player's hand, the player is returned.
    * @return a `Tuple` of the returned values;
    *         the first value is a `Container` object;
    *         the second value is an `Equipment` object in the former
    */
  def FindContainedEquipment : (Option[PlanetSideGameObject with Container], Option[Equipment]) = {
    player.VehicleSeated match {
      case Some(vehicle_guid) => //weapon is vehicle turret?
        continent.GUID(vehicle_guid) match {
          case Some(vehicle : Mountable with MountedWeapons with Container) =>
            vehicle.PassengerInSeat(player) match {
              case Some(seat_num) =>
                (Some(vehicle), vehicle.WeaponControlledFromSeat(seat_num))
              case None => ;
                (None, None)
            }
          case _ => ;
            (None, None)
        }
      case None => //not in vehicle; weapon in hand?
        (Some(player), player.Slot(player.DrawnSlot).Equipment)
    }
  }

  /**
    * Runs `FindContainedEquipment` but ignores the `Container` object output.
    * @return an `Equipment` object
    */
  def FindEquipment : Option[Equipment] = FindContainedEquipment._2

  /**
    * Check two locations for a controlled piece of equipment that is associated with the `player`.
    * Filter for discovered `Tool`-type `Equipment`.
    * @return a `Tuple` of the returned values;
    *         the first value is a `Container` object;
    *         the second value is an `Tool` object in the former
    */
  def FindContainedWeapon : (Option[PlanetSideGameObject with Container], Option[Tool]) = {
    FindContainedEquipment match {
      case (container, Some(tool : Tool)) =>
        (container, Some(tool))
      case _ =>
        (None, None)
    }
  }

  /**
    * Runs `FindContainedWeapon` but ignores the `Container` object output.
    * @return a `Tool` object
    */
  def FindWeapon : Option[Tool] = FindContainedWeapon._2

  /**
    * Within a specified `Container`, find the smallest number of `Equipment` objects of a certain qualifying type
    * whose sum count is greater than, or equal to, a `desiredAmount` based on an accumulator method.<br>
    * <br>
    * In an occupied `List` of returned `Inventory` entries, all but the last entry is typically considered "emptied."
    * For objects with contained quantities, the last entry may require having that quantity be set to a non-zero number.
    * @param obj the `Container` to search
    * @param filterTest test used to determine inclusivity of `Equipment` collection
    * @param desiredAmount how much is requested
    * @param counting test used to determine value of found `Equipment`;
    *                 defaults to one per entry
    * @return a `List` of all discovered entries totaling approximately the amount requested
    */
  def FindEquipmentStock(obj : Container,
                         filterTest : (Equipment)=>Boolean,
                         desiredAmount : Int,
                         counting : (Equipment)=>Int = DefaultCount) : List[InventoryItem] = {
    var currentAmount : Int = 0
    obj.Inventory.Items
      .filter(item => filterTest(item.obj))
      .toList
      .sortBy(_.start)
      .takeWhile(entry => {
        val previousAmount = currentAmount
        currentAmount += counting(entry.obj)
        previousAmount < desiredAmount
      })
  }

  /**
    * The default counting function for an item.
    * Counts the number of item(s).
    * @param e the `Equipment` object
    * @return the quantity;
    *         always one
    */
  def DefaultCount(e : Equipment) : Int = 1

  /**
    * The counting function for an item of `AmmoBox`.
    * Counts the `Capacity` of the ammunition.
    * @param e the `Equipment` object
    * @return the quantity
    */
  def CountAmmunition(e : Equipment) : Int = {
    e match {
      case a : AmmoBox =>
        a.Capacity
      case _ =>
        0
    }
  }

  /**
    * The counting function for an item of `Tool` where the item is also a grenade.
    * Counts the number of grenades.
    * @see `GlobalDefinitions.isGrenade`
    * @param e the `Equipment` object
    * @return the quantity
    */
  def CountGrenades(e : Equipment) : Int = {
    e match {
      case t : Tool =>
        (GlobalDefinitions.isGrenade(t.Definition):Int) * t.Magazine
      case _ =>
        0
    }
  }

  /**
    * Flag an `AmmoBox` object that matches for the given ammunition type.
    * @param ammo the type of `Ammo` to check
    * @param e the `Equipment` object
    * @return `true`, if the object is an `AmmoBox` of the correct ammunition type; `false`, otherwise
    */
  def FindAmmoBoxThatUses(ammo : Ammo.Value)(e : Equipment) : Boolean = {
    e match {
      case t : AmmoBox =>
        t.AmmoType == ammo
      case _ =>
        false
    }
  }

  /**
    * Flag a `Tool` object that matches for loading the given ammunition type.
    * @param ammo the type of `Ammo` to check
    * @param e the `Equipment` object
    * @return `true`, if the object is a `Tool` that loads the correct ammunition type; `false`, otherwise
    */
  def FindToolThatUses(ammo : Ammo.Value)(e : Equipment) : Boolean = {
    e match {
      case t : Tool =>
        t.Definition.AmmoTypes.map { _.AmmoType }.contains(ammo)
      case _ =>
        false
    }
  }

  /**
    * Get the current `Vehicle` object that the player is riding/driving.
    * The vehicle must be found solely through use of `player.VehicleSeated`.
    * @return the vehicle
    */
  def FindLocalVehicle : Option[Vehicle] = {
    player.VehicleSeated match {
      case Some(vehicle_guid) =>
        continent.GUID(vehicle_guid) match {
          case Some(obj : Vehicle) =>
            Some(obj)
          case _ =>
            None
        }
      case None =>
        None
    }
  }

  /**
    * Given an object that contains an item (`Equipment`) in its `Inventory` at a certain location,
    * remove it permanently.
    * @param obj the `Container`
    * @param start where the item can be found
    * @param item an object to unregister;
    *             not explicitly checked
    */
  private def DeleteEquipment(obj : PlanetSideGameObject with Container)(start : Int, item : Equipment) : Unit = {
    val item_guid = item.GUID
    obj.Slot(start).Equipment = None
    //obj.Inventory -= start
    taskResolver ! GUIDTask.UnregisterEquipment(item)(continent.GUID)
    sendResponse(ObjectDeleteMessage(item_guid, 0))
  }

  /**
    * Given a vehicle that contains an item (`Equipment`) in its `Trunk` at a certain location,
    * remove it permanently.
    * @see `DeleteEquipment`
    * @param obj the `Vehicle`
    * @param start where the item can be found
    * @param item an object to unregister;
    *             not explicitly checked
    */
  private def DeleteEquipmentFromVehicle(obj : Vehicle)(start : Int, item : Equipment) : Unit = {
    val item_guid = item.GUID
    DeleteEquipment(obj)(start, item)
    continent.VehicleEvents ! VehicleServiceMessage(s"${obj.Actor}", VehicleAction.UnstowEquipment(player.GUID, item_guid))
  }

  /**
    * Given an object that contains a box of amunition in its `Inventory` at a certain location,
    * change the amount of ammunition within that box.
    * @param obj the `Container`
    * @param box an `AmmoBox` to modify
    * @param reloadValue the value to modify the `AmmoBox`;
    *                    subtracted from the current `Capacity` of `Box`
    */
  private def ModifyAmmunition(obj : PlanetSideGameObject with Container)(box : AmmoBox, reloadValue : Int) : Unit = {
    val capacity = box.Capacity - reloadValue
    box.Capacity = capacity
    sendResponse(InventoryStateMessage(box.GUID, obj.GUID, capacity))
  }

  /**
    * Given a vehicle that contains a box of amunition in its `Trunk` at a certain location,
    * change the amount of ammunition within that box.
    * @param obj the `Container`
    * @param box an `AmmoBox` to modify
    * @param reloadValue the value to modify the `AmmoBox`;
    *                    subtracted from the current `Capacity` of `Box`
    */
  private def ModifyAmmunitionInVehicle(obj : Vehicle)(box : AmmoBox, reloadValue : Int) : Unit = {
    val capacity = ModifyAmmunition(obj)(box, reloadValue)
    obj.Find(box) match {
      case Some(index) =>
        continent.VehicleEvents ! VehicleServiceMessage(s"${obj.Actor}", VehicleAction.InventoryState(player.GUID, box, obj.GUID, index, box.Definition.Packet.DetailedConstructorData(box).get))
      case None => ;
    }
  }

  /**
    * Announce that an already-registered `AmmoBox` object exists in a given position in some `Container` object's inventory.
    * @see `StowEquipmentInVehicles`
    * @see `ChangeAmmoMessage`
    * @param obj the `Container` object
    * @param index an index in `obj`'s inventory
    * @param item an `AmmoBox`
    */
  def StowEquipment(obj : PlanetSideGameObject with Container)(index : Int, item : AmmoBox) : Unit = {
    obj.Inventory += index -> item
    sendResponse(ObjectAttachMessage(obj.GUID, item.GUID, index))
  }

  /**
    * Announce that an already-registered `AmmoBox` object exists in a given position in some vehicle's inventory.
    * @see `StowEquipment`
    * @see `ChangeAmmoMessage`
    * @param obj the `Vehicle` object
    * @param index an index in `obj`'s inventory
    * @param item an `AmmoBox`
    */
  def StowEquipmentInVehicles(obj : Vehicle)(index : Int, item : AmmoBox) : Unit = {
    StowEquipment(obj)(index, item)
    continent.VehicleEvents ! VehicleServiceMessage(s"${obj.Actor}", VehicleAction.StowEquipment(player.GUID, obj.GUID, index, item))
  }

  /**
    * Prepare tasking that registers an `AmmoBox` object
    * and announces that it exists in a given position in some `Container` object's inventory.
    * `PutEquipmentInSlot` is the fastest way to achieve these goals.
    * @see `StowNewEquipmentInVehicle`
    * @see `ChangeAmmoMessage`
    * @param obj the `Container` object
    * @param index an index in `obj`'s inventory
    * @param item the `Equipment` item
    * @return a `TaskResolver.GiveTask` chain that executes the action
    */
  def StowNewEquipment(obj : PlanetSideGameObject with Container)(index : Int, item : Equipment) : TaskResolver.GiveTask = {
    PutEquipmentInSlot(obj, item, index)
  }

  /**
    * Prepare tasking that registers an `AmmoBox` object
    * and announces that it exists in a given position in some vehicle's inventory.
    * `PutEquipmentInSlot` is the fastest way to achieve these goals.
    * @see `StowNewEquipment`
    * @see `ChangeAmmoMessage`
    * @param obj the `Container` object
    * @param index an index in `obj`'s inventory
    * @param item the `Equipment` item
    * @return a `TaskResolver.GiveTask` chain that executes the action
    */
  def StowNewEquipmentInVehicle(obj : Vehicle)(index : Int, item : Equipment) : TaskResolver.GiveTask = {
    TaskResolver.GiveTask(
      new Task() {
        private val localService = continent.VehicleEvents
        private val localPlayer = player
        private val localVehicle = obj
        private val localIndex = index
        private val localItem = item

        override def isComplete : Task.Resolution.Value = Task.Resolution.Success

        def Execute(resolver : ActorRef) : Unit = {
          localService ! VehicleServiceMessage(
            s"${localVehicle.Actor}",
            VehicleAction.StowEquipment(localPlayer.GUID, localVehicle.GUID, localIndex, localItem)
          )
          resolver ! scala.util.Success(this)
        }
      },
      List(StowNewEquipment(obj)(index, item))
    )
  }

  /**
    * Given an item, and two places, one where the item currently is and one where the item will be moved,
    * perform a controlled transfer of the item.
    * If something exists at the `destination` side of the transfer in the position that `item` will occupy,
    * resolve its location as well by swapping it with where `item` originally was positioned.<br>
    * <br>
    * Parameter checks will not be performed.
    * Do perform checks before sending data to this function.
    * Do not call with incorrect or unverified data, e.g., `item` not actually being at `source` @ `index`.
    * @param item the item being moved
    * @param source the container in which `item` is currently located
    * @param index the index position in `source` where `item` is currently located
    * @param destination the container where `item` is being moved
    * @param dest the index position in `destination` where `item` is being moved
    * @param destinationCollisionEntry information about the contents in an area of `destination` starting at index `dest`
    */
  private def PerformMoveItem(item : Equipment,
                              source : PlanetSideGameObject with Container,
                              index : Int,
                              destination : PlanetSideGameObject with Container,
                              dest : Int,
                              destinationCollisionEntry : Option[InventoryItem]) : Unit = {
    val item_guid = item.GUID
    val source_guid = source.GUID
    val destination_guid = destination.GUID
    val player_guid = player.GUID
    val indexSlot = source.Slot(index)
    val sourceIsNotDestination : Boolean = source != destination //if source is destination, explicit OCDM is not required
    if(sourceIsNotDestination) {
      log.info(s"MoveItem: $item moved from $source @ $index to $destination @ $dest")
    }
    else {
      log.info(s"MoveItem: $item moved from $index to $dest in $source")
    }
    //remove item from source
    indexSlot.Equipment = None
    source match {
      case obj : Vehicle =>
        continent.VehicleEvents ! VehicleServiceMessage(s"${obj.Actor}", VehicleAction.UnstowEquipment(player_guid, item_guid))
      case obj : Player =>
        if(obj.isBackpack || source.VisibleSlots.contains(index)) { //corpse being looted, or item was in hands
          continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.ObjectDelete(player_guid, item_guid))
        }
      case _ => ;
    }

    destinationCollisionEntry match { //do we have a swap item in the destination slot?
      case Some(InventoryItem(item2, destIndex)) => //yes, swap
        //cleanly shuffle items around to avoid losing icons
        //the next ObjectDetachMessage is necessary to avoid icons being lost, but only as part of this swap
        sendResponse(ObjectDetachMessage(source_guid, item_guid, Vector3.Zero, 0f))
        val item2_guid = item2.GUID
        destination.Slot(destIndex).Equipment = None //remove the swap item from destination
        (indexSlot.Equipment = item2) match {
          case Some(_) => //item and item2 swapped places successfully
            log.info(s"MoveItem: $item2 swapped to $source @ $index")
            //remove item2 from destination
            sendResponse(ObjectDetachMessage(destination_guid, item2_guid, Vector3.Zero, 0f))
            destination match {
              case obj : Vehicle =>
                continent.VehicleEvents ! VehicleServiceMessage(s"${obj.Actor}", VehicleAction.UnstowEquipment(player_guid, item2_guid))
              case obj : Player =>
                if(obj.isBackpack || destination.VisibleSlots.contains(dest)) { //corpse being looted, or item was accessible
                  continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.ObjectDelete(player_guid, item2_guid))
                  //put hand down locally
                  if(dest == player.DrawnSlot) {
                    player.DrawnSlot = Player.HandsDownSlot
                  }
                }
              case _ => ;
            }
            //display item2 in source
            if(sourceIsNotDestination && player == source) {
              val objDef = item2.Definition
              sendResponse(
                ObjectCreateDetailedMessage(
                  objDef.ObjectId,
                  item2_guid,
                  ObjectCreateMessageParent(source_guid, index),
                  objDef.Packet.DetailedConstructorData(item2).get
                )
              )
            }
            else {
              sendResponse(ObjectAttachMessage(source_guid, item2_guid, index))
            }
            source match {
              case obj : Vehicle =>
                item2.Faction = PlanetSideEmpire.NEUTRAL
                continent.VehicleEvents ! VehicleServiceMessage(s"${obj.Actor}", VehicleAction.StowEquipment(player_guid, source_guid, index, item2))
              case obj : Player =>
                item2.Faction = obj.Faction
                if(source.VisibleSlots.contains(index)) { //item is put in hands
                  continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.EquipmentInHand(player_guid, source_guid, index, item2))
                }
                else if(obj.isBackpack) { //corpse being given item
                  continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.StowEquipment(player_guid, source_guid, index, item2))
                }
              case _ =>
                item2.Faction = PlanetSideEmpire.NEUTRAL
            }

          case None => //item2 does not fit; drop on ground
            log.info(s"MoveItem: $item2 can not fit in swap location; dropping on ground @ ${source.Position}")
            val pos = source.Position
            val sourceOrientZ = source.Orientation.z
            val orient : Vector3 = Vector3(0f, 0f, sourceOrientZ)
            continent.Ground ! Zone.Ground.DropItem(item2, pos, orient)
            sendResponse(ObjectDetachMessage(destination_guid, item2_guid, pos, sourceOrientZ)) //ground
            val objDef = item2.Definition
            destination match {
              case obj : Vehicle =>
                continent.VehicleEvents ! VehicleServiceMessage(s"${obj.Actor}", VehicleAction.UnstowEquipment(player_guid, item2_guid))
              case _ => ;
              //Player does not require special case; the act of dropping forces the item and icon to change
            }
            continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.DropItem(player_guid, item2, continent))
        }

      case None => ;
    }
    //move item into destination slot
    destination.Slot(dest).Equipment = item
    if(sourceIsNotDestination && player == destination) {
      val objDef = item.Definition
      sendResponse(
        ObjectCreateDetailedMessage(
          objDef.ObjectId,
          item_guid,
          ObjectCreateMessageParent(destination_guid, dest),
          objDef.Packet.DetailedConstructorData(item).get
        )
      )
    }
    else {
      sendResponse(ObjectAttachMessage(destination_guid, item_guid, dest))
    }
    destination match {
      case obj : Vehicle =>
        item.Faction = PlanetSideEmpire.NEUTRAL
        continent.VehicleEvents ! VehicleServiceMessage(s"${obj.Actor}", VehicleAction.StowEquipment(player_guid, destination_guid, dest, item))
      case obj : Player =>
        if(destination.VisibleSlots.contains(dest)) { //item is put in hands
          item.Faction = obj.Faction
          continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.EquipmentInHand(player_guid, destination_guid, dest, item))
        }
        else if(obj.isBackpack) { //corpse being given item
          item.Faction = PlanetSideEmpire.NEUTRAL
          continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.StowEquipment(player_guid, destination_guid, dest, item))
        }
      case _ =>
        item.Faction = PlanetSideEmpire.NEUTRAL
    }
  }

  /**
    * na
    * @param equipment na
    * @param obj na
    * @return `true`, if the object is allowed to contain the type of equipment object
    */
  def PermitEquipmentStow(equipment : Equipment, obj : PlanetSideGameObject with Container) : Boolean = {
    equipment match {
      case _ : BoomerTrigger =>
        obj.isInstanceOf[Player] //a BoomerTrigger can only be stowed in a player's holsters or inventory
      case _ =>
        true
    }
  }

  /**
    * na
    * @param tool na
    * @param obj na
    */
  def PerformToolAmmoChange(tool : Tool, obj : PlanetSideGameObject with Container) : Unit = {
    val originalAmmoType = tool.AmmoType
    do {
      val requestedAmmoType = tool.NextAmmoType
      val fullMagazine = tool.MaxMagazine
      if(requestedAmmoType != tool.AmmoSlot.Box.AmmoType) {
        FindEquipmentStock(obj, FindAmmoBoxThatUses(requestedAmmoType), fullMagazine, CountAmmunition).reverse match {
          case Nil => ;
          case x :: xs =>
            val (deleteFunc, modifyFunc) : ((Int, AmmoBox)=>Unit, (AmmoBox, Int)=>Unit) = obj match {
              case (veh : Vehicle) =>
                (DeleteEquipmentFromVehicle(veh), ModifyAmmunitionInVehicle(veh))
              case _ =>
                (DeleteEquipment(obj), ModifyAmmunition(obj))
            }
            val (stowFuncTask, stowFunc) : ((Int, AmmoBox)=>TaskResolver.GiveTask, (Int, AmmoBox)=>Unit) = obj match {
              case (veh : Vehicle) =>
                (StowNewEquipmentInVehicle(veh), StowEquipmentInVehicles(veh))
              case _ =>
                (StowNewEquipment(obj), StowEquipment(obj))
            }
            xs.foreach(item => {
              obj.Inventory -= x.start
              deleteFunc(item.start, item.obj.asInstanceOf[AmmoBox])
            })

            //box will be the replacement ammo; give it the discovered magazine and load it into the weapon @ 0
            val box = x.obj.asInstanceOf[AmmoBox]
            val originalBoxCapacity = box.Capacity
            val tailReloadValue : Int = if(xs.isEmpty) { 0 } else { xs.map(_.obj.asInstanceOf[AmmoBox].Capacity).reduceLeft(_ + _) }
            val sumReloadValue : Int = originalBoxCapacity + tailReloadValue
            val previousBox = tool.AmmoSlot.Box //current magazine in tool
            sendResponse(ObjectDetachMessage(tool.GUID, previousBox.GUID, Vector3.Zero, 0f))
            sendResponse(ObjectDetachMessage(player.GUID, box.GUID, Vector3.Zero, 0f))
            obj.Inventory -= x.start //remove replacement ammo from inventory
            val ammoSlotIndex = tool.FireMode.AmmoSlotIndex
            tool.AmmoSlots(ammoSlotIndex).Box = box //put replacement ammo in tool
            sendResponse(ObjectAttachMessage(tool.GUID, box.GUID, ammoSlotIndex))

            //announce swapped ammunition box in weapon
            val previous_box_guid = previousBox.GUID
            val boxDef = box.Definition
            val box_guid = box.GUID
            val tool_guid = tool.GUID
            sendResponse(ChangeAmmoMessage(tool_guid, box.Capacity))
            continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.ChangeAmmo(player.GUID, tool_guid, ammoSlotIndex,previous_box_guid, boxDef.ObjectId, box.GUID, boxDef.Packet.ConstructorData(box).get))

            //handle inventory contents
            box.Capacity = (if(sumReloadValue <= fullMagazine) {
              sumReloadValue
            }
            else {
              val splitReloadAmmo : Int = sumReloadValue - fullMagazine
              log.info(s"ChangeAmmo: taking ${originalBoxCapacity - splitReloadAmmo} from a box of ${originalBoxCapacity} $requestedAmmoType")
              val boxForInventory = AmmoBox(box.Definition, splitReloadAmmo)
              obj.Inventory += x.start -> boxForInventory //block early; assumption warning: swappable ammo types have the same icon size
              taskResolver ! stowFuncTask(x.start, boxForInventory)
              fullMagazine
            })
            sendResponse(InventoryStateMessage(box.GUID, tool.GUID, box.Capacity)) //should work for both players and vehicles
            log.info(s"ChangeAmmo: loading ${box.Capacity} $requestedAmmoType into ${tool.GUID} @ $ammoSlotIndex")
            if(previousBox.Capacity > 0) {
              //divide capacity across other existing and not full boxes of that ammo type
              var capacity = previousBox.Capacity
              val iter = obj.Inventory.Items
                .filter(entry => {
                  entry.obj match {
                    case (item : AmmoBox) =>
                      item.AmmoType == originalAmmoType && item.FullCapacity != item.Capacity
                    case _ =>
                      false
                  }
                })
                .toList
                .sortBy(_.start)
                .iterator
              while(capacity > 0 && iter.hasNext) {
                val entry = iter.next
                val item : AmmoBox = entry.obj.asInstanceOf[AmmoBox]
                val ammoAllocated = math.min(item.FullCapacity - item.Capacity, capacity)
                log.info(s"ChangeAmmo: putting $ammoAllocated back into a box of ${item.Capacity} $originalAmmoType")
                capacity -= ammoAllocated
                modifyFunc(item, -ammoAllocated)
              }
              previousBox.Capacity = capacity
            }

            if(previousBox.Capacity > 0) {
              //split previousBox into AmmoBox objects of appropriate max capacity, e.g., 100 9mm -> 2 x 50 9mm
              obj.Inventory.Fit(previousBox) match {
                case Some(index) =>
                  stowFunc(index, previousBox)
                case None =>
                  NormalItemDrop(player, continent, continent.AvatarEvents)(previousBox)
              }
              val dropFunc : (Equipment)=>TaskResolver.GiveTask = NewItemDrop(player, continent, continent.AvatarEvents)
              AmmoBox.Split(previousBox) match {
                case Nil  | _ :: Nil => ; //done (the former case is technically not possible)
                case _ :: xs =>
                  modifyFunc(previousBox, 0) //update to changed capacity value
                  xs.foreach(box => {
                    obj.Inventory.Fit(box) match {
                      case Some(index) =>
                        obj.Inventory += index -> box //block early, for purposes of Fit
                        taskResolver ! stowFuncTask(index, box)
                      case None =>
                        taskResolver ! dropFunc(box)
                    }
                  })
              }
            }
            else {
              taskResolver ! GUIDTask.UnregisterObjectTask(previousBox)(continent.GUID)
            }
        }
      }
    }
    while(tool.AmmoType != originalAmmoType && tool.AmmoType != tool.AmmoSlot.Box.AmmoType)
  }

  /**
    * Drop an `Equipment` item onto the ground.
    * Specifically, instruct the item where it will appear,
    * add it to the list of items that are visible to multiple users,
    * and then inform others that the item has been dropped.
    * @param obj a `Container` object that represents where the item will be dropped;
    *            curried for callback
    * @param zone the continent in which the item is being dropped;
    *             curried for callback
    * @param service a reference to the event system that announces that the item has been dropped on the ground;
    *                "AvatarService";
    *                curried for callback
    * @param item the item
    */
  def NormalItemDrop(obj : PlanetSideGameObject with Container, zone : Zone, service : ActorRef)(item : Equipment) : Unit = {
    continent.Ground ! Zone.Ground.DropItem(item, obj.Position, Vector3.z(obj.Orientation.z))
  }

  /**
    * Register an `Equipment` item and then drop it on the ground.
    * @see `NormalItemDrop`
    * @param obj a `Container` object that represents where the item will be dropped;
    *            curried for callback
    * @param zone the continent in which the item is being dropped;
    *             curried for callback
    * @param service a reference to the event system that announces that the item has been dropped on the ground;
    *                "AvatarService";
    *                curried for callback
    * @param item the item
    */
  def NewItemDrop(obj : PlanetSideGameObject with Container, zone : Zone, service : ActorRef)(item : Equipment) : TaskResolver.GiveTask = {
    TaskResolver.GiveTask(
      new Task() {
        private val localItem = item
        private val localFunc : (Equipment)=>Unit = NormalItemDrop(obj, zone, service)

        def Execute(resolver : ActorRef) : Unit = {
          localFunc(localItem)
          resolver ! scala.util.Success(this)
        }
      }, List(GUIDTask.RegisterEquipment(item)(zone.GUID))
    )
  }

  /**
    * After a weapon has finished shooting, determine if it needs to be sorted in a special way.
    * @param tool a weapon
    */
  def FireCycleCleanup(tool : Tool) : Unit = {
    //TODO this is temporary and will be replaced by more appropriate functionality in the future.
    val tdef = tool.Definition
    if(GlobalDefinitions.isGrenade(tdef)) {
      val ammoType = tool.AmmoType
      FindEquipmentStock(player, FindToolThatUses(ammoType), 3, CountGrenades).reverse match { //do not search sidearm holsters
        case Nil =>
          log.info(s"no more $ammoType grenades")
          taskResolver ! RemoveEquipmentFromSlot(player, tool, player.Find(tool).get)

        case x :: xs => //this is similar to ReloadMessage
          val box = x.obj.asInstanceOf[Tool]
          val tailReloadValue : Int = if(xs.isEmpty) { 0 } else { xs.map(_.obj.asInstanceOf[Tool].Magazine).reduce(_ + _) }
          val sumReloadValue : Int = box.Magazine + tailReloadValue
          val actualReloadValue = (if(sumReloadValue <= 3) {
            taskResolver ! RemoveEquipmentFromSlot(player, x.obj, x.start)
            sumReloadValue
          }
          else {
            ModifyAmmunition(player)(box.AmmoSlot.Box, 3 - tailReloadValue)
            3
          })
          log.info(s"found $actualReloadValue more $ammoType grenades to throw")
          ModifyAmmunition(player)(tool.AmmoSlot.Box, -actualReloadValue) //grenade item already in holster (negative because empty)
          xs.foreach(item => {
            taskResolver ! RemoveEquipmentFromSlot(player, item.obj, item.start)
          })
      }
    }
    else if(tdef == GlobalDefinitions.phoenix) {
      taskResolver ! RemoveEquipmentFromSlot(player, tool, player.Find(tool).get)
    }
  }

  /**
    * A predicate used to determine if an `InventoryItem` object contains `Equipment` that should be dropped.
    * Used to filter through lists of object data before it is placed into a player's inventory.
    * Drop the item if:<br>
    * - the item is cavern equipment<br>
    * - the item is a `BoomerTrigger` type object<br>
    * - the item is a `router_telepad` type object<br>
    * - the item is another faction's exclusive equipment
    * @param tplayer the player
    * @return true if the item is to be dropped; false, otherwise
    */
  def DropPredicate(tplayer : Player) : (InventoryItem => Boolean) = entry => {
    val objDef = entry.obj.Definition
    val faction = GlobalDefinitions.isFactionEquipment(objDef)
    GlobalDefinitions.isCavernEquipment(objDef) ||
      objDef == GlobalDefinitions.router_telepad ||
      entry.obj.isInstanceOf[BoomerTrigger] ||
      (faction != tplayer.Faction && faction != PlanetSideEmpire.NEUTRAL)
  }

  /**
    * Given an object globally unique identifier, search in a given location for it.
    * @param object_guid the object
    * @param parent a `Container` object wherein to search
    * @return an optional tuple that contains two values;
    *         the first value is the container that matched correctly with the object's GUID;
    *         the second value is the slot position of the object
    */
  def FindInLocalContainer(object_guid : PlanetSideGUID)(parent : PlanetSideGameObject with Container) : Option[(PlanetSideGameObject with Container, Option[Int])] = {
    val slot : Option[Int] = parent.Find(object_guid)
    slot match {
      case place @ Some(_) =>
        Some(parent, slot)
      case None =>
        None
    }
  }

  /**
    * Perform specific operations depending on the target of deployment.
    * @param obj the object that has had its deployment state changed
    */
  def DeploymentActivities(obj : Deployment.DeploymentObject) : Unit = {
    DeploymentActivities(obj, obj.DeploymentState)
  }

  /**
    * Perform specific operations depending on the target of deployment.
    * @param obj the object that has had its deployment state changed
    * @param state the new deployment state
    */
  def DeploymentActivities(obj : Deployment.DeploymentObject, state : DriveState.Value) : Unit = {
    obj match {
      case vehicle : Vehicle =>
        Vehicles.ReloadAccessPermissions(vehicle, player.Name) //TODO we should not have to do this imho
        //ams
        if(vehicle.Definition == GlobalDefinitions.ams) {
          state match {
            case DriveState.Deployed =>
              continent.VehicleEvents ! VehicleServiceMessage.AMSDeploymentChange(continent)
              sendResponse(PlanetsideAttributeMessage(vehicle.GUID, 81, 1))
            case DriveState.Undeploying =>
              continent.VehicleEvents ! VehicleServiceMessage.AMSDeploymentChange(continent)
              sendResponse(PlanetsideAttributeMessage(vehicle.GUID, 81, 0))
            case DriveState.Mobile | DriveState.State7 =>
            case _ => ;
          }
        }
        //ant
        else if(vehicle.Definition == GlobalDefinitions.ant) {
          state match {
            case DriveState.Deployed =>
              // We only want this WSA (not other player's WSA) to manage timers
              if(vehicle.Seats(0).Occupant.contains(player)){
                // Start ntu regeneration
                // If vehicle sends UseItemMessage with silo as target NTU regeneration will be disabled and orb particles will be disabled
                antChargingTick = context.system.scheduler.scheduleOnce(1000 milliseconds, self, NtuCharging(player, vehicle))
              }
            case DriveState.Undeploying =>
              // We only want this WSA (not other player's WSA) to manage timers
              if(vehicle.Seats(0).Occupant.contains(player)){
                antChargingTick.cancel() // Stop charging NTU if charging
              }

              continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(vehicle.GUID, 52, 0L)) // panel glow off
              continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(vehicle.GUID, 49, 0L)) // orb particles off
            case DriveState.Mobile | DriveState.State7 | DriveState.Deploying =>
            case _ => ;
          }
        }
        //router
        else if(vehicle.Definition == GlobalDefinitions.router) {
          state match {
            case DriveState.Deploying =>
              vehicle.Utility(UtilityType.internal_router_telepad_deployable) match {
                case Some(util : Utility.InternalTelepad) =>
                  util.Active = true
                case _ =>
                  log.warn(s"DeploymentActivities: could not find internal telepad in router@${vehicle.GUID.guid} while $state")
              }
            case DriveState.Deployed =>
              //let the timer do all the work
              continent.LocalEvents ! LocalServiceMessage(continent.Id, LocalAction.ToggleTeleportSystem(PlanetSideGUID(0), vehicle, TelepadLike.AppraiseTeleportationSystem(vehicle, continent)))
            case DriveState.Undeploying =>
              //deactivate internal router before trying to reset the system
              vehicle.Utility(UtilityType.internal_router_telepad_deployable) match {
                case Some(util : Utility.InternalTelepad) =>
                  //any telepads linked with internal mechanism must be deconstructed
                  continent.GUID(util.Telepad) match {
                    case Some(telepad : TelepadDeployable) =>
                      continent.LocalEvents ! LocalServiceMessage.Deployables(RemoverActor.ClearSpecific(List(telepad), continent))
                      continent.LocalEvents ! LocalServiceMessage.Deployables(RemoverActor.AddTask(telepad, continent, Some(0 milliseconds)))
                    case Some(_) | None => ;
                  }
                  util.Active = false
                  continent.LocalEvents ! LocalServiceMessage(continent.Id, LocalAction.ToggleTeleportSystem(PlanetSideGUID(0), vehicle, None))
                case _ =>
                  log.warn(s"DeploymentActivities: could not find internal telepad in router@${vehicle.GUID.guid} while $state")
              }
            case _ => ;
          }
        }
      case _ => ;
    }
  }

  /**
    * Common reporting behavior when a `Deployment` object fails to properly transition between states.
    * @param obj the game object that could not
    * @param state the `DriveState` that could not be promoted
    * @param reason a string explaining why the state can not or will not change
    */
  def CanNotChangeDeployment(obj : PlanetSideServerObject with Deployment, state : DriveState.Value, reason : String) : Unit = {
    val mobileShift : String = if(obj.DeploymentState != DriveState.Mobile) {
      obj.DeploymentState = DriveState.Mobile
      sendResponse(DeployRequestMessage(player.GUID, obj.GUID, DriveState.Mobile, 0, false, Vector3.Zero))
      continent.VehicleEvents ! VehicleServiceMessage(continent.Id, VehicleAction.DeployRequest(player.GUID, obj.GUID, DriveState.Mobile, 0, false, Vector3.Zero))
      "; enforcing Mobile deployment state"
    }
    else {
      ""
    }
    log.error(s"DeployRequest: $obj can not transition to $state - $reason$mobileShift")
  }

  /**
    * For a given continental structure, determine the method of generating server-join client configuration packets.
    * @param continentNumber the zone id
    * @param buildingNumber the building id
    * @param building the building object
    */
  def initBuilding(continentNumber : Int, buildingNumber : Int, building : Building) : Unit = {
    building.BuildingType match {
      case StructureType.WarpGate =>
        initGate(continentNumber, buildingNumber, building)
      case _ =>
        initFacility(continentNumber, buildingNumber, building)
    }
  }

  /**
    * For a given facility structure, configure a client by dispatching the appropriate packets.
    * Pay special attention to the details of `BuildingInfoUpdateMessage` when preparing this packet.<br>
    * <br>
    * 24 Janurtay 2019:<br>
    * Manual `BIUM` construction to alleviate player login.
    * @see `BuildingInfoUpdateMessage`
    * @see `DensityLevelUpdateMessage`
    * @param continentNumber the zone id
    * @param buildingNumber the building id
    * @param building the building object
    */
  def initFacility(continentNumber : Int, buildingNumber : Int, building : Building) : Unit = {
    val (
      ntuLevel,
      isHacked, empireHack, hackTimeRemaining, controllingEmpire,
      unk1, unk1x,
      generatorState, spawnTubesNormal, forceDomeActive,
      latticeBenefit, cavernBenefit,
      unk4, unk5, unk6,
      unk7, unk7x,
      boostSpawnPain, boostGeneratorPain
      ) = building.Info
    sendResponse(
      BuildingInfoUpdateMessage(
        building.Zone.Number,
        building.MapId,
        ntuLevel,
        isHacked, empireHack, hackTimeRemaining, controllingEmpire,
        unk1, unk1x,
        generatorState, spawnTubesNormal, forceDomeActive,
        latticeBenefit, cavernBenefit,
        unk4, unk5, unk6,
        unk7, unk7x,
        boostSpawnPain, boostGeneratorPain
      )
    )
    sendResponse(DensityLevelUpdateMessage(continentNumber, buildingNumber, List(0,0, 0,0, 0,0, 0,0)))
  }

  /**
    * For a given lattice warp gate structure, configure a client by dispatching the appropriate packets.
    * Unlike other facilities, gates do not have complicated `BuildingInfoUpdateMessage` packets.
    * Also unlike facilities, gates have an additional packet.
    * @see `BuildingInfoUpdateMessage`
    * @see `DensityLevelUpdateMessage`
    * @see `BroadcastWarpgateUpdateMessage`
    * @param continentNumber the zone id
    * @param buildingNumber the building id
    * @param building the building object
    */
  def initGate(continentNumber : Int, buildingNumber : Int, building : Building) : Unit = {
    building match {
      case wg : WarpGate =>
        sendResponse(
          BuildingInfoUpdateMessage(
            building.Zone.Number,
            building.MapId,
            ntu_level = 0,
            is_hacked = false,
            empire_hack = PlanetSideEmpire.NEUTRAL,
            hack_time_remaining = 0,
            building.Faction,
            unk1 = 0,
            unk1x = None,
            PlanetSideGeneratorState.Normal,
            spawn_tubes_normal = true,
            force_dome_active = false,
            lattice_benefit = 0,
            cavern_benefit = 0,
            unk4 = Nil,
            unk5 = 0,
            unk6 = false,
            unk7 = 8,
            unk7x = None,
            boost_spawn_pain = false,
            boost_generator_pain = false
          )
        )
        sendResponse(DensityLevelUpdateMessage(continentNumber, buildingNumber, List(0,0, 0,0, 0,0, 0,0)))
        //TODO one faction knows which gates are broadcast for another faction?
        sendResponse(
          BroadcastWarpgateUpdateMessage(
            continentNumber,
            buildingNumber,
            wg.Broadcast(PlanetSideEmpire.TR),
            wg.Broadcast(PlanetSideEmpire.NC),
            wg.Broadcast(PlanetSideEmpire.VS)
          )
        )
      case _ => ;
    }
  }

  /**
    * Configure the buildings and each specific amenity for that building in a given zone by sending the client packets.
    * These actions are performed during the loading of a zone.
    * @see `SetEmpireMessage`<br>
    *     `PlanetsideAttributeMessage`<br>
    *     `HackMessage`
    * @param zone the zone being loaded
    */
  def configZone(zone : Zone) : Unit = {
    zone.Buildings.values.foreach(building => {
      sendResponse(SetEmpireMessage(building.GUID, building.Faction))

      // Synchronise capitol force dome state
      if(building.IsCapitol && building.ForceDomeActive) {
        sendResponse(GenericObjectActionMessage(building.GUID, 13))
      }
      // Synchronise amenities
      building.Amenities.collect {
        case obj if obj.Destroyed => configAmenityAsDestroyed(obj)
        case obj => configAmenityAsWorking(obj)
      }
      Thread.sleep(connectionState)
    })
  }

  /**
    * Configure the specific working amenity by sending the client packets.
    * Amenities that are not `Damageable` are also included.
    * These actions are performed during the loading of a zone.
    * @see `Door`
    * @see `GenericObjectStateMsg`
    * @see `Hackable`
    * @see `HackCaptureTerminal`
    * @see `HackObject`
    * @see `PlanetsideAttributeMessage`
    * @see `ResourceSilo`
    * @see `SetEmpireMessage`
    * @see `VitalityDefinition.Damageable`
    * @param amenity the facility object
    */
  def configAmenityAsWorking(amenity : Amenity) : Unit = {
    val amenityId = amenity.GUID
    //sync model access state
    sendResponse(PlanetsideAttributeMessage(amenityId, 50, 0))
    sendResponse(PlanetsideAttributeMessage(amenityId, 51, 0))
    //sync damageable, if
    val health = amenity.Health
    if(amenity.Definition.Damageable && health < amenity.MaxHealth) {
      sendResponse(PlanetsideAttributeMessage(amenityId, 0, health))
    }
    //sync special object type cases
    amenity match {
      case silo : ResourceSilo =>
        //silo capacity
        sendResponse(PlanetsideAttributeMessage(amenityId, 45, silo.CapacitorDisplay))
        //warning lights
        sendResponse(PlanetsideAttributeMessage(silo.Owner.GUID, 47, if(silo.LowNtuWarningOn) 1 else 0))
        if(silo.ChargeLevel == 0) {
          sendResponse(PlanetsideAttributeMessage(silo.Owner.GUID, 48, 1))
        }
      case door : Door if door.isOpen =>
        sendResponse(GenericObjectStateMsg(amenityId, 16))

      case _ => ;
    }
    //sync hack state
    amenity match {
      case obj : Hackable if obj.HackedBy.nonEmpty =>
        amenity.Definition match {
          case GlobalDefinitions.capture_terminal =>
            HackCaptureTerminal(amenity.GUID, 0L, 0L, false)
          case _ =>
            HackObject(amenity.GUID, 1114636288L, 8L) //generic hackable object
        }
      case _ => ;
    }
  }

  /**
    * Configure the specific destroyed amenity by sending the client packets.
    * These actions are performed during the loading of a zone.
    * @see `Generator`
    * @see `ImplantTerminalMech`
    * @see `PlanetsideAttributeMessage`
    * @see `PlanetSideGameObject.Destroyed`
    * @param amenity the facility object
    */
  def configAmenityAsDestroyed(amenity : Amenity) : Unit = {
    val amenityId = amenity.GUID
    val configValue = amenity match {
      case _ : ImplantTerminalMech => 0
      case _ : Generator => 0
      case _ => 1
    }
    //sync model access state
    sendResponse(PlanetsideAttributeMessage(amenityId, 50, configValue))
    sendResponse(PlanetsideAttributeMessage(amenityId, 51, configValue))
    //sync damageable, if
    if(amenity.Definition.Damageable) {
      sendResponse(PlanetsideAttributeMessage(amenityId, 0, 0))
    }
  }

  /**
    * na
    * @param target_guid na
    * @param unk1 na
    * @param unk2 na
    */
  def HackObject(target_guid : PlanetSideGUID, unk1 : Long, unk2 : Long) : Unit = {
    sendResponse(HackMessage(0, target_guid, PlanetSideGUID(0), 100, unk1, HackState.Hacked, unk2))
  }

  /**
    * na
    * @param target_guid na
    * @param unk1 na
    * @param unk2 na
    * @param isResecured na
    */
  def HackCaptureTerminal(target_guid : PlanetSideGUID, unk1 : Long, unk2 : Long, isResecured : Boolean) : Unit = {
    var value = 0L
    if(isResecured) {
      value = 17039360L
      sendResponse(PlanetsideAttributeMessage(target_guid, 20, value))
    }
    else {
      continent.GUID(target_guid) match {
        case Some(capture_terminal : Amenity with Hackable) =>
          capture_terminal.HackedBy match {
            case Some(Hackable.HackInfo(_, _, hfaction, _, start, length)) =>
              val hack_time_remaining_ms = TimeUnit.MILLISECONDS.convert(math.max(0, start + length - System.nanoTime), TimeUnit.NANOSECONDS)
              val deciseconds_remaining = (hack_time_remaining_ms / 100)
              //See PlanetSideAttributeMessage #20 documentation for an explanation of how the timer is calculated
              val start_num = hfaction match {
                case PlanetSideEmpire.TR => 65536L
                case PlanetSideEmpire.NC => 131072L
                case PlanetSideEmpire.VS => 196608L
              }
              value = start_num + deciseconds_remaining
              sendResponse(PlanetsideAttributeMessage(target_guid, 20, value))
              continent.GUID(player.VehicleSeated) match {
                case Some(mountable : Amenity with Mountable) =>
                  if(mountable.Owner.GUID == capture_terminal.Owner.GUID) {
                    continent.VehicleEvents ! VehicleServiceMessage(continent.Id, VehicleAction.KickPassenger(player.GUID, mountable.Seats.head._1, true, mountable.GUID))
                  }
                case _ => ;
              }
            case _ => log.warn("HackCaptureTerminal: hack state monitor not defined")
          }
        case _ => log.warn(s"HackCaptureTerminal: couldn't find capture terminal with GUID ${target_guid} in zone ${continent.Id}")
      }
    }
  }

  /**
    * The player has lost the will to live and must be killed.
    * @see `Vitality`<br>
    *       `PlayerSuicide`
    * @param tplayer the player to be killed
    */
  def Suicide(tplayer : Player) : Unit = {
    tplayer.History(PlayerSuicide(PlayerSource(tplayer)))
    tplayer.Actor ! Player.Die()
  }

  /**
    * An event has occurred that would cause the player character to stop certain stateful activities.
    * These activities include shooting, the weapon being drawn, hacking, accessing (a container), flying, and running.
    * Other players in the same zone must be made aware that the player has stopped as well.<br>
    * <br>
    * Things whose configuration should not be changed:<br>
    * - if the player is seated<br>
    * - if the player is anchored<br>
    * This is not a complete list but, for the purpose of enforcement, some pointers will be documented here.
    */
  def PlayerActionsToCancel() : Unit = {
    progressBarUpdate.cancel
    progressBarValue = None
    lastTerminalOrderFulfillment = true
    player.skipStaminaRegenForTurns = 0
    accessedContainer match {
      case Some(obj : Vehicle) =>
        if(obj.AccessingTrunk.contains(player.GUID)) {
          obj.AccessingTrunk = None
          UnAccessContents(obj)
        }
        accessedContainer = None

      case Some(_) =>
        accessedContainer = None

      case None => ;
    }
    prefire.orElse(shooting) match {
      case Some(guid) =>
        sendResponse(ChangeFireStateMessage_Stop(guid))
        continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.ChangeFireState_Stop(player.GUID, guid))
        prefire = None
        shooting = None
      case None => ;
    }
    if(flying) {
      sendResponse(ChatMsg(ChatMessageType.CMT_FLY, false, "", "off", None))
      flying = false
    }
    if(speed > 1) {
      sendResponse(ChatMsg(ChatMessageType.CMT_SPEED, false, "", "1.000", None))
      speed = 1f
    }
  }

  /**
    * A part of the process of spawning the player into the game world.
    * The function should work regardless of whether the player is alive or dead - it will make them alive.
    * It adds the `WSA`-current `Player` to the current zone and sends out the expected packets.<br>
    * <br>
    * If that player is in a vehicle, it will construct that vehicle.
    * If the player is the driver of the vehicle,
    * they must temporarily be removed from the driver seat in order for the vehicle to be constructed properly.
    * These two previous statements operate through similar though distinct mechanisms and imply different conditions.
    * In reality, they produce the same output but enforce different relationships between the components.
    * The vehicle without a rendered player will always be created if that vehicle exists.
    * The vehicle should only be constructed once.
    * @see `AvatarCreateInVehicle`
    * @see `BeginZoningMessage`
    * @see `CargoBehavior.CargoMountBehaviorForOthers`
    * @see `GetKnownVehicleAndSeat`
    * @see `LoadZoneTransferPassengerMessages`
    * @see `Player.Spawn`
    * @see `ReloadUsedLastCoolDownTimes`
    * @see `Vehicles.Own`
    * @see `Vehicles.ReloadAccessPermissions`
    */
  def AvatarCreate() : Unit = {
    val health = player.Health
    val armor = player.Armor
    val stamina = player.Stamina
    player.Spawn
    if(health != 0) {
      player.Health = health
      player.Armor = armor
      player.Stamina = stamina
    }
    GetKnownVehicleAndSeat() match {
      case (Some(vehicle : Vehicle), Some(seat : Int)) =>
        //if the vehicle is the cargo of another vehicle in this zone
        val carrierInfo = continent.GUID(vehicle.MountedIn) match {
          case Some(carrier : Vehicle) =>
            (Some(carrier), carrier.CargoHolds.find({ case (index, hold) => hold.Occupant.contains(vehicle)}))
          case _ =>
            (None, None)
        }
        //vehicle and driver/passenger
        interstellarFerry = None
        val vdef = vehicle.Definition
        val vguid = vehicle.GUID
        val vdata = if(seat == 0) {
          //driver
          continent.Transport ! Zone.Vehicle.Spawn(vehicle)
          //as the driver, we must temporarily exclude ourselves from being in the vehicle during its creation
          val seat = vehicle.Seats(0)
          seat.Occupant = None
          val data = vdef.Packet.ConstructorData(vehicle).get
          sendResponse(ObjectCreateMessage(vehicle.Definition.ObjectId, vguid, data))
          seat.Occupant = player
          Vehicles.Own(vehicle, player)
          vehicle.CargoHolds.values
            .collect { case hold if hold.isOccupied => hold.Occupant.get }
            .foreach { _.MountedIn = vguid }
          continent.VehicleEvents ! VehicleServiceMessage(continent.Id, VehicleAction.LoadVehicle(player.GUID, vehicle, vdef.ObjectId, vguid, data))
          carrierInfo match {
            case (Some(carrier), Some((index, _))) =>
              CargoBehavior.CargoMountBehaviorForOthers(carrier, vehicle, index, player.GUID)
            case _ =>
              vehicle.MountedIn = None
          }
          data
        }
        else {
          //passenger
          //non-drivers are not rendered in the vehicle at this time
          val data = vdef.Packet.ConstructorData(vehicle).get
          sendResponse(ObjectCreateMessage(vehicle.Definition.ObjectId, vguid, data))
          carrierInfo match {
            case (Some(carrier), Some((index, _))) =>
              CargoMountBehaviorForUs(carrier, vehicle, index)
            case _ => ;
          }
          data
        }
        val originalSeated = player.VehicleSeated
        player.VehicleSeated = vguid
        if(Vehicles.AllGatedOccupantsInSameZone(vehicle)) {
          //do not dispatch delete action if any hierarchical occupant has not gotten this far through the summoning process
          val vehicleToDelete = interstellarFerryTopLevelGUID.orElse(originalSeated).getOrElse(PlanetSideGUID(0))
          val zone = vehicle.PreviousGatingManifest().get.origin
          zone.VehicleEvents ! VehicleServiceMessage(zone.Id, VehicleAction.UnloadVehicle(player.GUID, zone, vehicle, vehicleToDelete))
          log.info(s"AvatarCreate: cleaning up ghost of transitioning vehicle ${vehicle.Definition.Name}@${vehicleToDelete.guid} in zone ${zone.Id}")
        }
        Vehicles.ReloadAccessPermissions(vehicle, player.Name)
        //log.info(s"AvatarCreate (vehicle): $guid -> $data")
        AvatarCreateInVehicle(player, vehicle, seat)

      case _ =>
        player.VehicleSeated = None
        val packet = player.Definition.Packet
        val data = packet.DetailedConstructorData(player).get
        val guid = player.GUID
        sendResponse(ObjectCreateDetailedMessage(ObjectClass.avatar, guid, data))
        continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.LoadPlayer(guid, ObjectClass.avatar, guid, packet.ConstructorData(player).get, None))
        //log.info(s"AvatarCreate: $guid -> $data")
        log.trace(s"AvatarCreate: ${player.Name}")
    }
    continent.Population ! Zone.Population.Spawn(avatar, player)
    //cautious redundancy
    deadState = DeadState.Alive
    ReloadUsedLastCoolDownTimes()

    val lTime = System.currentTimeMillis // PTS v3
    for (i <- 0 to whenUsedLastItem.length-1) {
      if (lTime - whenUsedLastItem(i) < 300000) {
        sendResponse(AvatarVehicleTimerMessage(player.GUID, whenUsedLastItemName(i), 300 - ((lTime - whenUsedLastItem(i)) / 1000 toInt), true))
      }
    }
    for (i <- 1 to 3) {
      if (lTime - whenUsedLastMAX(i) < 300000) {
        sendResponse(AvatarVehicleTimerMessage(player.GUID, whenUsedLastMAXName(i), 300 - ((lTime - whenUsedLastMAX(i)) / 1000 toInt), true))
      }
    }
  }

  /**
    * If the player is mounted in some entity, find that entity and get the seat index number at which the player is sat.
    * The priority of object confirmation is `direct` then `occupant.VehicleSeated`.
    * Once an object is found, the remainder are ignored.
    * @param direct a game object in which the player may be sat
    * @param target the player who is sat and may have specified the game object in which mounted
    * @return a tuple consisting of a vehicle reference and a seat index
    *         if and only if the vehicle is known to this client and the `WorldSessioNActor`-global `player` occupies it;
    *         `(None, None)`, otherwise (even if the vehicle can be determined)
    */
  def GetMountableAndSeat(direct : Option[PlanetSideGameObject with Mountable], occupant : Player) : (Option[PlanetSideGameObject with Mountable], Option[Int]) =
    direct.orElse(continent.GUID(occupant.VehicleSeated)) match {
      case Some(obj : PlanetSideGameObject with Mountable) =>
        obj.PassengerInSeat(occupant) match {
          case index @ Some(_) =>
            (Some(obj), index)
          case None =>
            (None, None)
        }
      case _ =>
        (None, None)
    }

  /**
    * If the player is seated in a vehicle, find that vehicle and get the seat index number at which the player is sat.<br>
    * <br>
    * For special purposes involved in zone transfers,
    * where the vehicle may or may not exist in either of the zones (yet),
    * the value of `interstellarFerry` is also polled.
    * Making certain this field is blanked after the transfer is completed is important
    * to avoid inspecting the wrong vehicle and failing simple vehicle checks where this function may be employed.
    * @see `GetMountableAndSeat`
    * @see `interstellarFerry`
    * @return a tuple consisting of a vehicle reference and a seat index
    *         if and only if the vehicle is known to this client and the `WorldSessioNActor`-global `player` occupies it;
    *         `(None, None)`, otherwise (even if the vehicle can be determined)
    */
  def GetKnownVehicleAndSeat() : (Option[Vehicle], Option[Int]) = GetMountableAndSeat(interstellarFerry, player) match {
    case (Some(v : Vehicle), Some(seat)) => (Some(v), Some(seat))
    case _ => (None, None)
  }

  /**
    * If the player is seated in a vehicle, find that vehicle and get the seat index number at which the player is sat.
    * @see `GetMountableAndSeat`
    * @return a tuple consisting of a vehicle reference and a seat index
    *         if and only if the vehicle is known to this client and the `WorldSessioNActor`-global `player` occupies it;
    *         `(None, None)`, otherwise (even if the vehicle can be determined)
    */
  def GetVehicleAndSeat() : (Option[Vehicle], Option[Int]) = GetMountableAndSeat(None, player) match {
    case (Some(v : Vehicle), Some(seat)) => (Some(v), Some(seat))
    case _ => (None, None)
  }

  /**
    * Create an avatar character so that avatar's player is mounted in a vehicle's seat.
    * A part of the process of spawning the player into the game world.<br>
    * <br>
    * This is a very specific configuration of the player character that is not visited very often.
    * The value of `player.VehicleSeated` should be set to accommodate `Packet.DetailedConstructorData` and,
    * though not explicitly checked,
    * should be the same as the globally unique identifier that is assigned to the `vehicle` parameter for the current zone.
    * The priority of this function is consider "initial" so it introduces the avatar to the game world in this state
    * and is permitted to introduce the avatar to the vehicle's internal settings in a similar way.
    * Neither the player avatar nor the vehicle should be reconstructed before the next zone load operation
    * to avoid damaging the critical setup of this function.
    * @see `AccessContents`
    * @see `UpdateWeaponAtSeatPosition`
    * @param tplayer the player avatar seated in the vehicle's seat
    * @param vehicle the vehicle the player is riding
    * @param seat the seat index
    */
  def AvatarCreateInVehicle(tplayer : Player, vehicle : Vehicle, seat : Int) : Unit = {
    val pdef = tplayer.Definition
    val pguid = tplayer.GUID
    val vguid = vehicle.GUID
    tplayer.VehicleSeated = None
    val pdata = pdef.Packet.DetailedConstructorData(tplayer).get
    tplayer.VehicleSeated = vguid
    sendResponse(ObjectCreateDetailedMessage(pdef.ObjectId, pguid, pdata))
    sendResponse(ObjectAttachMessage(vguid, pguid, seat))
    AccessContents(vehicle)
    UpdateWeaponAtSeatPosition(vehicle, seat)
    continent.AvatarEvents ! AvatarServiceMessage(
      continent.Id,
      AvatarAction.LoadPlayer(
        pguid,
        pdef.ObjectId,
        pguid,
        pdef.Packet.ConstructorData(tplayer).get,
        Some(ObjectCreateMessageParent(vguid, seat))
      )
    )
    //log.info(s"AvatarCreateInVehicle: $pguid -> pdata")
  }

  /**
    * A part of the process of spawning the player into the game world
    * in the case of a restored game connection (relogging).<br>
    * <br>
    * A login protocol that substitutes the first call to `avatarSetupFunc` (replacing `AvatarCreate`)
    * in consideration of a user re-logging into the game
    * before the period of time where an avatar/player instance would decay and be cleaned-up.
    * Large portions of this function operate as a combination of the mechanics
    * for normal `AvatarCreate` and for `AvatarCreateInVehicle`.
    * Unlike either of the previous, this functionlality is disinterested in updating other clients
    * as the target player and potential vehicle already exist as far as other clients are concerned.<br>
    * <br>
    * If that player is in a vehicle, it will construct that vehicle.
    * If the player is the driver of the vehicle,
    * they must temporarily be removed from the driver seat in order for the vehicle to be constructed properly.
    * These two previous statements operate through similar though distinct mechanisms and imply different conditions.
    * In reality, they produce the same output but enforce different relationships between the components.
    * The vehicle without a rendered player will always be created if that vehicle exists.<br>
    * <br>
    * The value of `player.VehicleSeated` should be set to accommodate `Packet.DetailedConstructorData` and,
    * though not explicitly checked,
    * should be the same as the globally unique identifier that is assigned to the `vehicle` parameter for the current zone.
    * The priority of this function is consider "initial" so it introduces the avatar to the game world in this state
    * and is permitted to introduce the avatar to the vehicle's internal settings in a similar way.
    * Neither the player avatar nor the vehicle should be reconstructed before the next zone load operation
    * to avoid damaging the critical setup of this function.
    * @see `AccessContents`
    * @see `AccountPersistenceService`
    * @see `avatarSetupFunc`
    * @see `AvatarCreate`
    * @see `GetKnownVehicleAndSeat`
    * @see `ObjectAttachMessage`
    * @see `ObjectCreateMessage`
    * @see `PlayerInfo.LoginInfo`
    * @see `ReloadUsedLastCoolDownTimes`
    * @see `UpdateWeaponAtSeatPosition`
    * @see `Vehicles.ReloadAccessPermissions`
    */
  def AvatarRejoin() : Unit = {
    GetKnownVehicleAndSeat() match {
      case (Some(vehicle : Vehicle), Some(seat : Int)) =>
        //vehicle and driver/passenger
        val vdef = vehicle.Definition
        val vguid = vehicle.GUID
        if(seat == 0) {
          val seat = vehicle.Seats(0)
          seat.Occupant = None
          val vdata = vdef.Packet.ConstructorData(vehicle).get
          sendResponse(ObjectCreateMessage(vehicle.Definition.ObjectId, vguid, vdata))
          seat.Occupant = player
        }
        else {
          val vdata = vdef.Packet.ConstructorData(vehicle).get
          sendResponse(ObjectCreateMessage(vehicle.Definition.ObjectId, vguid, vdata))
        }
        Vehicles.ReloadAccessPermissions(vehicle, continent.Id)
        //log.info(s"AvatarCreate (vehicle): $vguid -> $vdata")
        val pdef = player.Definition
        val pguid = player.GUID
        val parent = ObjectCreateMessageParent(vguid, seat)
        player.VehicleSeated = None
        val pdata = pdef.Packet.DetailedConstructorData(player).get
        player.VehicleSeated = vguid
        sendResponse(ObjectCreateDetailedMessage(pdef.ObjectId, pguid, pdata))
        sendResponse(ObjectAttachMessage(vguid, pguid, seat))
        //log.info(s"AvatarCreateInVehicle: $pguid -> $pdata")
        AccessContents(vehicle)
        UpdateWeaponAtSeatPosition(vehicle, seat)
        //log.trace(s"AvatarCreateInVehicle: ${player.Name} in ${vehicle.Definition.Name}")

      case _ =>
        player.VehicleSeated = None
        val packet = player.Definition.Packet
        val data = packet.DetailedConstructorData(player).get
        val guid = player.GUID
        sendResponse(ObjectCreateDetailedMessage(ObjectClass.avatar, guid, data))
        //log.info(s"AvatarCreate: $guid -> $data")
        //log.trace(s"AvatarCreate: ${player.Name}")
    }
    //cautious redundancy
    deadState = DeadState.Alive
    ReloadUsedLastCoolDownTimes()
    setupAvatarFunc = AvatarCreate
  }

  /**
    * A part of the process of spawning the player into the game world
    * in the case of a restored game connection (relogging).
    * Rather than create any avatar here, the process has been skipped for now
    * and will be handled by a different operation
    * and this routine's normal operation when it revisits the same code.
    * @see `avatarSetupFunc`
    * @see `AvatarCreate`
    * @see `ReloadUsedLastCoolDownTimes`
    */
  def AvatarDeploymentPassOver() : Unit = {
    ReloadUsedLastCoolDownTimes()
    setupAvatarFunc = AvatarCreate
  }

  /**
    * Sometimes the game stops you from doing something a second time as soon as you would have liked to do it again.
    * This is called "skill".
    * @see `AvatarVehicleTimerMessage`
    */
  def ReloadUsedLastCoolDownTimes() : Unit = {
    val lTime = System.currentTimeMillis
    for (i <- 0 to whenUsedLastItem.length-1) {
      if (lTime - whenUsedLastItem(i) < 300000) {
        sendResponse(AvatarVehicleTimerMessage(player.GUID, whenUsedLastItemName(i), 300 - ((lTime - whenUsedLastItem(i)) / 1000 toInt), true))
      }
    }
    for (i <- 1 to 3) {
      if (lTime - whenUsedLastMAX(i) < 300000) {
        sendResponse(AvatarVehicleTimerMessage(player.GUID, whenUsedLastMAXName(i), 300 - ((lTime - whenUsedLastMAX(i)) / 1000 toInt), true))
      }
    }
  }

  /**
    * Produce a clone of the player that is equipped with the default infantry loadout.
    * The loadout is hardcoded.
    * The player is expected to be in a Standard Exo-Suit.
    * @param tplayer the original player
    * @return the duplication of the player, in Standard Exo-Suit and with default equipment loadout
    */
  def RespawnClone(tplayer : Player) : Player = {
    val faction = tplayer.Faction
    val obj = Player.Respawn(tplayer)
    obj.ResetAllImplants()
    LoadClassicDefault(obj)
    obj
  }

  /**
    * Remove items from a deceased player that are not expected to be found on a corpse.
    * Most all players have their melee slot knife (which can not be un-equipped normally) removed.
    * MAX's have their primary weapon in the designated slot removed.
    * @param obj the player to be turned into a corpse
    */
  def FriskDeadBody(obj : Player) : Unit = {
    if(obj.isBackpack) {
      obj.Slot(4).Equipment match {
        case None => ;
        case Some(knife) =>
          obj.Slot(4).Equipment = None
          taskResolver ! RemoveEquipmentFromSlot(obj, knife, 4)
      }
      obj.Slot(0).Equipment match {
        case Some(arms : Tool) =>
          if(GlobalDefinitions.isMaxArms(arms.Definition)) {
            obj.Slot(0).Equipment = None
            taskResolver ! RemoveEquipmentFromSlot(obj, arms, 0)
          }
        case _ => ;
      }
      //disown boomers and drop triggers
      val boomers = avatar.Deployables.ClearDeployable(DeployedItem.boomer)
      boomers.foreach(boomer => {
        continent.GUID(boomer) match {
          case Some(obj : BoomerDeployable) =>
            obj.OwnerName = None
            continent.LocalEvents ! LocalServiceMessage.Deployables(RemoverActor.AddTask(obj, continent))
          case Some(_) | None => ;
        }
      })
      val triggers = RemoveBoomerTriggersFromInventory()
      triggers.foreach(trigger => { NormalItemDrop(obj, continent, continent.AvatarEvents)(trigger) })
    }
  }

  /**
    * Creates a player that has the characteristics of a corpse
    * so long as the player has items in their knapsack or their holsters.
    * If the player has no items stored, the clean solution is to remove the player from the game.
    * To the game, that is a backpack (or some pastry, festive graphical modification allowing).
    * @see `AvatarAction.ObjectDelete`
    * @see `AvatarAction.Release`
    * @see `AvatarServiceMessage`
    * @see `FriskDeadBody`
    * @see `GUIDTask.UnregisterPlayer`
    * @see `ObjectDeleteMessage`
    * @see `WellLootedDeadBody`
    * @see `Zone.Corpse.Add`
    * @param tplayer the player
    */
  def PrepareToTurnPlayerIntoCorpse(tplayer : Player, zone : Zone) : Unit = {
    FriskDeadBody(tplayer)
    if(!WellLootedDeadBody(tplayer)) {
      TurnPlayerIntoCorpse(tplayer)
      zone.Population ! Zone.Corpse.Add(tplayer)
      zone.AvatarEvents ! AvatarServiceMessage(zone.Id, AvatarAction.Release(tplayer, zone))
    }
    else {
      //no items in inventory; leave no corpse
      val pguid = tplayer.GUID
      sendResponse(ObjectDeleteMessage(pguid, 0))
      zone.AvatarEvents ! AvatarServiceMessage(zone.Id, AvatarAction.ObjectDelete(pguid, pguid, 0))
      taskResolver ! GUIDTask.UnregisterPlayer(tplayer)(zone.GUID)
    }
  }

  /**
    * Creates a player that has the characteristics of a corpse.
    * To the game, that is a backpack (or some pastry, festive graphical modification allowing).
    * @see `CorpseConverter.converter`
    * @param tplayer the player
    */
  def TurnPlayerIntoCorpse(tplayer : Player) : Unit = {
    val guid = tplayer.GUID
    sendResponse(
      ObjectCreateDetailedMessage(ObjectClass.avatar, guid, CorpseConverter.converter.DetailedConstructorData(tplayer).get)
    )
  }

  /**
    * If the corpse has been well-lootedP, it has no items in its primary holsters nor any items in its inventory.
    * @param obj the corpse
    * @return `true`, if the `obj` is actually a corpse and has no objects in its holsters or backpack;
    *        `false`, otherwise
    */
  def WellLootedDeadBody(obj : Player) : Boolean = {
    obj.isBackpack && obj.Holsters().count(_.Equipment.nonEmpty) == 0 && obj.Inventory.Size == 0
  }

  /**
    * If the corpse has been well-looted, remove it from the ground.
    * @param obj the corpse
    * @return `true`, if the `obj` is actually a corpse and has no objects in its holsters or backpack;
    *        `false`, otherwise
    */
  def TryDisposeOfLootedCorpse(obj : Player) : Boolean = {
    if(WellLootedDeadBody(obj)) {
      continent.AvatarEvents ! AvatarServiceMessage.Corpse(RemoverActor.HurrySpecific(List(obj), continent))
      true
    }
    else {
      false
    }
  }

  /**
    * Attempt to tranfer to the player's faction-specific sanctuary continent.
    * If the server thinks the player is already on his sanctuary continent, and dead,
    * it will disconnect the player under the assumption that an error has occurred.
    * Eventually, this functionality should support better error-handling before it jumps to the conclusion:
    * "Disconnecting the client is the safest option."
    * @see `Zones.SanctuaryZoneNumber`
    * @param tplayer the player
    * @param currentZone the current zone number
    */
  def RequestSanctuaryZoneSpawn(tplayer : Player, currentZone : Int) : Unit = {
    val sanctNumber = Zones.SanctuaryZoneNumber(tplayer.Faction)
    if(currentZone == sanctNumber) {
      if(!player.isAlive) {
        sendResponse(DisconnectMessage("Player failed to load on faction's sanctuary continent.  Oh no."))
      }
      //we are already on sanctuary, alive; what more is there to do?
    }
    else {
      continent.GUID(player.VehicleSeated) match {
        case Some(obj : Vehicle) if !obj.Destroyed =>
          cluster ! Zone.Lattice.RequestSpawnPoint(sanctNumber, tplayer, 12) //warp gates for functioning vehicles
        case _ =>
          cluster ! Zone.Lattice.RequestSpawnPoint(sanctNumber, tplayer, 7) //player character spawns
      }
    }
  }

  /**
    * na
    * @param terminal na
    */
  def HandleProximityTerminalUse(terminal : Terminal with ProximityUnit) : Unit = {
    val term_guid = terminal.GUID
    val targets = FindProximityUnitTargetsInScope(terminal)
    val currentTargets = terminal.Targets
    targets.foreach(target => {
      if(!currentTargets.contains(target)) {
        StartUsingProximityUnit(terminal, target)
      }
      else if(targets.isEmpty) {
        log.warn(s"HandleProximityTerminalUse: ${player.Name} could not find valid targets to give to proximity unit ${terminal.Definition.Name}@${term_guid.guid}")
      }
    })
  }

  /**
    * na
    * @param terminal na
    * @return na
    */
  def FindProximityUnitTargetsInScope(terminal : Terminal with ProximityUnit) : Seq[PlanetSideGameObject] = {
    terminal.Definition.asInstanceOf[ProximityDefinition].TargetValidation.keySet collect {
      case EffectTarget.Category.Player => Some(player)
      case EffectTarget.Category.Vehicle | EffectTarget.Category.Aircraft => continent.GUID(player.VehicleSeated)
    } collect {
      case Some(a) => a
    } toSeq
  }

  /**
    * Queue a proximity-base service.
    * @param terminal the proximity-based unit
    * @param target the entity that is being considered for terminal operation
    */
  def StartUsingProximityUnit(terminal : Terminal with ProximityUnit, target : PlanetSideGameObject) : Unit = {
    val term_guid = terminal.GUID
    //log.trace(s"StartUsingProximityUnit: ${player.Name} wants to use ${terminal.Definition.Name}@${term_guid.guid} on $target")
    if(player.isAlive) {
      target match {
        case _ : Player =>
          terminal.Actor ! CommonMessages.Use(player, Some(target))
        case _ : Vehicle =>
          terminal.Actor ! CommonMessages.Use(player, Some((target, continent.VehicleEvents)))
        case _ =>
          log.error(s"StartUsingProximityUnit: can not deal with target $target")
      }
      terminal.Definition match {
        case GlobalDefinitions.adv_med_terminal | GlobalDefinitions.medical_terminal =>
          usingMedicalTerminal = Some(term_guid)
        case _ => ;
      }
    }
  }

  /**
    * Determine which functionality to pursue by a generic proximity-functional unit given the target for its activity.
    * @see `VehicleService:receive, ProximityUnit.Action`
    * @param terminal the proximity-based unit
    * @param target the object being affected by the unit
    */
  def SelectProximityUnitBehavior(terminal : Terminal with ProximityUnit, target : PlanetSideGameObject) : Unit = {
    target match {
      case o : Player =>
        HealthAndArmorTerminal(terminal, o)
      case _ => ;
    }
  }

  /**
    * Stop using a proximity-base service.
    * Special note is warranted when determining the identity of the proximity terminal.
    * Medical terminals of both varieties can be cancelled by movement.
    * Other sorts of proximity-based units are put on a timer.
    * @param terminal the proximity-based unit
    */
  def StopUsingProximityUnit(terminal : Terminal with ProximityUnit) : Unit = {
    val term_guid = terminal.GUID
    //log.trace(s"StopUsingProximityUnit: attempting to stop using proximity unit ${terminal.Definition.Name}@${term_guid.guid}")
    val targets = FindProximityUnitTargetsInScope(terminal)
    if(targets.nonEmpty) {
      if(usingMedicalTerminal.contains(term_guid)) {
        usingMedicalTerminal = None
      }
      targets.foreach(target =>
        terminal.Actor ! CommonMessages.Unuse(player, Some(target))
      )
    }
    else {
      log.warn(s"StopUsingProximityUnit: ${player.Name} could not find valid targets for proximity unit ${terminal.Definition.Name}@${term_guid.guid}")
    }
  }

  /**
    * na
    */
  def ForgetAllProximityTerminals(term_guid : PlanetSideGUID) : Unit = {
    if(usingMedicalTerminal.contains(term_guid)) {
      usingMedicalTerminal = None
    }
  }

  /**
    * Cease all current interactions with proximity-based units.
    * Pair with `PlayerActionsToCancel`, except when logging out (stopping).
    * This operations may invoke callback messages.
    * @see `postStop`
    */
  def CancelAllProximityUnits() : Unit = {
    continent.GUID(usingMedicalTerminal) match {
      case Some(terminal : Terminal with ProximityUnit) =>
        FindProximityUnitTargetsInScope(terminal).foreach(target =>
          terminal.Actor ! CommonMessages.Unuse(player, Some(target))
        )
        ForgetAllProximityTerminals(usingMedicalTerminal.get)
      case _ => ;
    }
  }

  /**
    * When standing on the platform of a(n advanced) medical terminal,
    * resotre the player's health and armor points (when they need their health and armor points restored).
    * If the player is both fully healed and fully repaired, stop using the terminal.
    * @param unit the medical terminal
    * @param target the player being healed
    */
  def HealthAndArmorTerminal(unit : Terminal with ProximityUnit, target : Player) : Unit = {
    val medDef = unit.Definition.asInstanceOf[MedicalTerminalDefinition]
    val healAmount = medDef.HealAmount
    val healthFull : Boolean = if(healAmount != 0 && target.Health < target.MaxHealth) {
      target.History(HealFromTerm(PlayerSource(target), healAmount, 0, medDef))
      HealAction(target, healAmount)
    }
    else {
      true
    }
    val repairAmount = medDef.ArmorAmount
    val armorFull : Boolean = if(repairAmount != 0 && target.Armor < target.MaxArmor) {
      target.History(HealFromTerm(PlayerSource(target), 0, repairAmount, medDef))
      ArmorRepairAction(target, repairAmount)
    }
    else {
      true
    }
    if(healthFull && armorFull) {
      StopUsingProximityUnit(unit)
    }
  }

  /**
    * Restore, at most, a specific amount of health points on a player.
    * Send messages to connected client and to events system.
    * @param tplayer the player
    * @param healValue the amount to heal;
    *                    10 by default
    * @return whether the player can be repaired for any more health points
    */
  def HealAction(tplayer : Player, healValue : Int = 10) : Boolean = {
    val player_guid = tplayer.GUID
    tplayer.Health = tplayer.Health + healValue
    sendResponse(PlanetsideAttributeMessage(player_guid, 0, tplayer.Health))
    continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(player_guid, 0, tplayer.Health))
    tplayer.Health == tplayer.MaxHealth
  }

  /**
    * Restore, at most, a specific amount of personal armor points on a player.
    * Send messages to connected client and to events system.
    * @param tplayer the player
    * @param repairValue the amount to repair;
    *                    10 by default
    * @return whether the player can be repaired for any more armor points
    */
  def ArmorRepairAction(tplayer : Player, repairValue : Int = 10) : Boolean = {
    val player_guid = tplayer.GUID
    tplayer.Armor = tplayer.Armor + repairValue
    sendResponse(PlanetsideAttributeMessage(player_guid, 4, tplayer.Armor))
    continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(player_guid, 4, tplayer.Armor))
    tplayer.Armor == tplayer.MaxArmor
  }

  /**
    * This function is applied to vehicles that are leaving a cargo vehicle's cargo hold to auto reverse them out
    * Lock all applicable controls of the current vehicle
    * Set the vehicle to move in reverse
    */
  def ServerVehicleLockReverse() : Unit = {
    controlled = Some(0)
    sendResponse(ServerVehicleOverrideMsg(lock_accelerator = true, lock_wheel = true, reverse = true, unk4 = true, lock_vthrust = 0, lock_strafe = 1, movement_speed = 2, unk8 = Some(0)))
  }

  /**
    * This function is applied to vehicles that are leaving a cargo vehicle's cargo hold to strafe right out of the cargo hold for vehicles that are mounted sideways e.g. router/BFR
    * Lock all applicable controls of the current vehicle
    * Set the vehicle to strafe right
    */
  def ServerVehicleLockStrafeRight() : Unit = {
    controlled = Some(0)
    sendResponse(ServerVehicleOverrideMsg(lock_accelerator = true, lock_wheel = true, reverse = false, unk4 = true, lock_vthrust = 0, lock_strafe = 3, movement_speed = 0, unk8 = Some(0)))
  }

  /**
    * This function is applied to vehicles that are leaving a cargo vehicle's cargo hold to strafe left out of the cargo hold for vehicles that are mounted sideways e.g. router/BFR
    * Lock all applicable controls of the current vehicle
    * Set the vehicle to strafe left
    */
  def ServerVehicleLockStrafeLeft() : Unit = {
    controlled = Some(0)
    sendResponse(ServerVehicleOverrideMsg(lock_accelerator = true, lock_wheel = true, reverse = false, unk4 = true, lock_vthrust = 0, lock_strafe = 2, movement_speed = 0, unk8 = Some(0)))
  }

  /**
    * Lock all applicable controls of the current vehicle.
    * This includes forward motion, turning, and, if applicable, strafing.
    * @param vehicle the vehicle being controlled
    */
  def ServerVehicleLock(vehicle : Vehicle) : Unit = {
    controlled = Some(0)
    sendResponse(ServerVehicleOverrideMsg(true, true, false, false, 0, 1, 0, Some(0)))
  }

  /**
    * Place the current vehicle under the control of the server's commands.
    * @param vehicle the vehicle
    * @param speed how fast the vehicle is moving forward
    * @param flight whether the vehicle is ascending or not, if the vehicle is an applicable type
    */
  def ServerVehicleOverride(vehicle : Vehicle, speed : Int = 0, flight : Int = 0) : Unit = {
    controlled = Some(speed)
    sendResponse(ServerVehicleOverrideMsg(true, true, false, false, flight, 0, speed, Some(0)))
  }

  /**
    * Place the current vehicle under the control of the driver's commands,
    * but leave it in a cancellable auto-drive.
    * @param vehicle the vehicle
    * @param speed how fast the vehicle is moving forward
    * @param flight whether the vehicle is ascending or not, if the vehicle is an applicable type
    */
  def DriverVehicleControl(vehicle : Vehicle, speed : Int = 0, flight : Int = 0) : Unit = {
    if(controlled.nonEmpty) {
      controlled = None
      sendResponse(ServerVehicleOverrideMsg(false, false, false, true, flight, 0, speed, None))
    }
  }

  /**
    * Place the current vehicle under the control of the driver's commands,
    * but leave it in a cancellable auto-drive.
    * Stop all movement entirely.
    * @param vehicle the vehicle
    */
  def TotalDriverVehicleControl(vehicle : Vehicle) : Unit = {
    if(controlled.nonEmpty) {
      controlled = None
      sendResponse(ServerVehicleOverrideMsg(false, false, false, false, 0, 0, 0, None))
    }
  }

  /**
    * Given a globally unique identifier in the 40100 to 40124 range
    * (with an optional 25 as buffer),
    * find a projectile.
    * @param projectile_guid the projectile's GUID
    * @return the discovered projectile
    */
  def FindProjectileEntry(projectile_guid : PlanetSideGUID) : Option[Projectile] = {
    val index = projectile_guid.guid - Projectile.BaseUID
    if(0 <= index && index < projectiles.length) {
      projectiles(index)
    }
    else {
      log.warn(s"ResolveProjectile: expected projectile, but ${projectile_guid.guid} not found")
      None
    }
  }

  /**
    * Find a projectile with the given globally unique identifier and mark it as a resolved shot.
    * A `Resolved` shot has either encountered an obstacle or is being cleaned up for not finding an obstacle.
    * @param projectile_guid the projectile GUID
    * @param resolution the resolution status to promote the projectile
    * @return the projectile
    */
  def ResolveProjectileEntry(projectile_guid : PlanetSideGUID, resolution : ProjectileResolution.Value, target : PlanetSideGameObject with FactionAffinity with Vitality, pos : Vector3) : Option[ResolvedProjectile] = {
    FindProjectileEntry(projectile_guid) match {
      case Some(projectile) =>
        ResolveProjectileEntry(projectile, resolution, target, pos)
      case None =>
        log.warn(s"ResolveProjectile: expected projectile, but ${projectile_guid.guid} not found")
        None
    }
  }

  /**
    * Find a projectile with the given globally unique identifier and mark it as a resolved shot.
    * @param projectile the projectile object
    * @param index where the projectile was found
    * @param resolution the resolution status to promote the projectile
    * @return a copy of the projectile
    */
  def ResolveProjectileEntry(projectile : Projectile, index : Int, resolution : ProjectileResolution.Value, target : PlanetSideGameObject with FactionAffinity with Vitality, pos : Vector3) : Option[ResolvedProjectile] = {
    if(!projectiles(index).contains(projectile)) {
      log.error(s"expected projectile could not be found at $index; can not resolve")
      None
    }
    else {
      ResolveProjectileEntry(projectile, resolution, target, pos)
    }
  }

  /**
    * na
    * @param projectile the projectile object
    * @param resolution the resolution status to promote the projectile
    * @return a copy of the projectile
    */
  def ResolveProjectileEntry(projectile : Projectile, resolution : ProjectileResolution.Value, target : PlanetSideGameObject with FactionAffinity with Vitality, pos : Vector3) : Option[ResolvedProjectile] = {
    if(projectile.isMiss) {
      log.error("expected projectile was already counted as a missed shot; can not resolve any further")
      None
    }
    else {
      projectile.Resolve()
      Some(ResolvedProjectile(resolution, projectile, SourceEntry(target), target.DamageModel, pos))
    }
  }

  /**
    * Common activities/procedure when a player mounts a valid object.
    * @param tplayer the player
    * @param obj the mountable object
    * @param seatNum the seat into which the player is mounting
    */
  def MountingAction(tplayer : Player, obj : PlanetSideGameObject with Mountable, seatNum : Int) : Unit = {
    val player_guid : PlanetSideGUID = tplayer.GUID
    val obj_guid : PlanetSideGUID = obj.GUID
    PlayerActionsToCancel()
    log.info(s"MountVehicleMsg: $player_guid mounts $obj @ $seatNum")
    sendResponse(ObjectAttachMessage(obj_guid, player_guid, seatNum))
    continent.VehicleEvents ! VehicleServiceMessage(continent.Id, VehicleAction.MountVehicle(player_guid, obj_guid, seatNum))
  }

  /**
    * Common activities/procedure when a player dismounts a valid object.
    * @param tplayer the player
    * @param obj the mountable object
    * @param seatNum the seat out of which which the player is disembarking
    */
  def DismountAction(tplayer : Player, obj : PlanetSideGameObject with Mountable, seatNum : Int) : Unit = {
    val player_guid : PlanetSideGUID = tplayer.GUID
    log.info(s"DismountVehicleMsg: ${tplayer.Name} dismounts $obj from $seatNum")
    sendResponse(DismountVehicleMsg(player_guid, BailType.Normal, false))
    continent.VehicleEvents ! VehicleServiceMessage(continent.Id, VehicleAction.DismountVehicle(player_guid, BailType.Normal, false))
  }

  /**
    * Calculate the amount of damage to be dealt to an active `target`
    * using the information reconstructed from a `Resolvedprojectile`
    * and affect the `target` in a synchronized manner.
    * The active `target` and the target of the `ResolvedProjectile` do not have be the same.
    * While the "tell" for being able to sustain damage is an entity of type `Vitality`,
    * only specific `Vitality` entity types are being screened for sustaining damage.
    * @see `DamageResistanceModel`
    * @see `Vitality`
    * @param target a valid game object that is known to the server
    * @param data a projectile that will affect the target
    */
  def HandleDealingDamage(target : PlanetSideGameObject with Vitality, data : ResolvedProjectile) : Unit = {
    val func = data.damage_model.Calculate(data)
    target match {
      case obj : Player if obj.CanDamage =>
        if(obj.spectator) {
          player.death_by = -1 // little thing for auto kick
        }
        else {
          obj.Actor ! Vitality.Damage(func)
        }
      case obj : Vehicle if obj.CanDamage => obj.Actor ! Vitality.Damage(func)
      case obj : Amenity if obj.CanDamage => obj.Actor ! Vitality.Damage(func)
      case obj : ComplexDeployable if obj.CanDamage => obj.Actor ! Vitality.Damage(func)

      case obj : SimpleDeployable if obj.CanDamage =>
        //damage is synchronized on `LSA` (results returned to and distributed from this `WSA`)
        continent.LocalEvents ! Vitality.DamageOn(obj, func)
      case _ => ;
    }
  }

  /**
    * Properly format a `DestroyDisplayMessage` packet
    * given sufficient information about a target (victim) and an actor (killer).
    * For the packet, the `charId` field is important for determining distinction between players.
    * @param killer the killer's entry
    * @param victim the victim's entry
    * @param method the manner of death
    * @param unk na;
    *            defaults to 121, the object id of `avatar`
    * @return a `DestroyDisplayMessage` packet that is properly formatted
    */
  def DestroyDisplayMessage(killer : SourceEntry, victim : SourceEntry, method : Int, unk : Int = 121) : DestroyDisplayMessage = {
    val killer_seated = killer match {
      case obj : PlayerSource => obj.Seated
      case _ => false
    }
    val victim_seated = victim match {
      case obj : PlayerSource => obj.Seated
      case _ => false
    }
    new DestroyDisplayMessage(
      killer.Name, killer.CharId, killer.Faction, killer_seated,
      unk, method,
      victim.Name, victim.CharId, victim.Faction, victim_seated
    )
  }

  /**
    * Initialize the deployables backend information.
    * @param avatar the player's core
    */
  def InitializeDeployableQuantities(avatar : Avatar) : Unit = {
    log.info("Setting up combat engineering ...")
    avatar.Deployables.Initialize(avatar.Certifications.toSet)
  }

  /**
    * Initialize the UI elements for deployables.
    * @param avatar the player's core
    */
  def InitializeDeployableUIElements(avatar : Avatar) : Unit = {
    log.info("Setting up combat engineering UI ...")
    UpdateDeployableUIElements(avatar.Deployables.UpdateUI())
  }

  /**
    * The player learned a new certification.
    * Update the deployables user interface elements if it was an "Engineering" certification.
    * The certification "Advanced Hacking" also relates to an element.
    * @param certification the certification that was added
    * @param certificationSet all applicable certifications
    */
  def AddToDeployableQuantities(certification : CertificationType.Value, certificationSet : Set[CertificationType.Value]) : Unit = {
    avatar.Deployables.AddToDeployableQuantities(certification, certificationSet)
    UpdateDeployableUIElements(avatar.Deployables.UpdateUI(certification))
  }

  /**
    * The player forgot a certification he previously knew.
    * Update the deployables user interface elements if it was an "Engineering" certification.
    * The certification "Advanced Hacking" also relates to an element.
    * @param certification the certification that was added
    * @param certificationSet all applicable certifications
    */
  def RemoveFromDeployablesQuantities(certification : CertificationType.Value, certificationSet : Set[CertificationType.Value]) : Unit = {
    avatar.Deployables.RemoveFromDeployableQuantities(certification, certificationSet)
    UpdateDeployableUIElements(avatar.Deployables.UpdateUI(certification))
  }

  /**
    * Initialize the deployables user interface elements.<br>
    * <br>
    * All element initializations require both the maximum deployable amount and the current deployables active counts.
    * Until initialized, all elements will be RED 0/0 as if the cooresponding certification were not `learn`ed.
    * The respective element will become a pair of numbers, the second always being non-zero, when properly initialized.
    * The numbers will appear GREEN when more deployables of that type can be placed.
    * The numbers will appear RED if the player can not place any more of that type of deployable.
    * The numbers will appear YELLOW if the current deployable count is greater than the maximum count of that type
    * such as may be the case when a player `forget`s a certification.
    * @param list a tuple of each UI element with four numbers;
    *             even numbers are attribute ids;
    *             odd numbers are quantities;
    *             first pair is current quantity;
    *             second pair is maximum quantity
    */
  def UpdateDeployableUIElements(list : List[(Int,Int,Int,Int)]) : Unit = {
    val guid = PlanetSideGUID(0)
    list.foreach({ case((currElem, curr, maxElem, max)) =>
      //fields must update in ordered pairs: max, curr
      sendResponse(PlanetsideAttributeMessage(guid, maxElem, max))
      sendResponse(PlanetsideAttributeMessage(guid, currElem, curr))
    })
  }

  /**
    * Draw the icon for this deployable object.<br>
    * <br>
    * When a client first joins a zone, all deployables are drawn on the continent map once.
    * Should the player place any deployables, those deployables belong to that player.
    * Ownership causes icon to be drawn in yellow to the player (as opposed to a white icon)
    * and that signifies a certain level of control over the deployable, at least the ability to quietly deconstruct it.
    * Under normal death/respawn cycles while the player is in a given zone,
    * the map icons for owned deployables ramin manipulable to that given user.
    * They do not havwe to be redrawn to stay accurate.
    * Upon leaving a zone, where the icons are erased, and returning back to the zone, where they are drawn again,
    * the deployables that a player owned should be restored in terms of their map icon visibility.
    * This control can not be recovered, however, until they are updated with the player's globally unique identifier.
    * Since the player does not need to redraw his own deployable icons each time he respawns,
    * but will not possess a valid GUID for that zone until he spawns in it at least once,
    * this function is swapped with another after the first spawn in any given zone.
    * This function is restored upon transferring zones.
    * @see `DontRedrawIcons`
    * @see `SetCurrentAvatar`
    * @param obj a `Deployable` object
    */
  def RedrawDeployableIcons(obj : PlanetSideGameObject with Deployable) : Unit = {
    val deployInfo = DeployableInfo(obj.GUID, Deployable.Icon(obj.Definition.Item), obj.Position, obj.Owner.getOrElse(PlanetSideGUID(0)))
    sendResponse(DeployableObjectsInfoMessage(DeploymentAction.Build, deployInfo))
  }

  /**
    * Do not draw any icon for this deployable object.<br>
    * <br>
    * When a client first joins a zone, all deployables are drawn on the continent map once.
    * Should the player place any deployables, those deployables belong to that player.
    * Ownership causes icon to be drawn in yellow to the player (as opposed to a white icon)
    * and that signifies a certain level of control over the deployable, at least the ability to quietly deconstruct it.
    * Under normal death/respawn cycles while the player is in a given zone,
    * the map icons for owned deployables remain manipulable by that given user.
    * They do not have to be redrawn to stay accurate.
    * Upon leaving a zone, where the icons are erased, and returning back to the zone, where they are drawn again,
    * the deployables that a player owned should be restored in terms of their map icon visibility.
    * This control can not be recovered, however, until they are updated with the player's globally unique identifier.
    * Since the player does not need to redraw his own deployable icons each time he respawns,
    * but will not possess a valid GUID for that zone until he spawns in it at least once,
    * this function swaps out with another after the first spawn in any given zone.
    * It stays swapped in until the player changes zones.
    * @see `RedrawDeployableIcons`
    * @see `SetCurrentAvatar`
    * @param obj a `Deployable` object
    */
  def DontRedrawIcons(obj : PlanetSideGameObject with Deployable) : Unit = { }

  /**
    * The custom behavior responding to the message `ChangeFireModeMessage` for `ConstructionItem` game objects.
    * Each fire mode has sub-modes corresponding to a type of "deployable" as ammunition
    * and each of these sub-modes have certification requirements that must be met before they can be used.
    * Additional effort is exerted to ensure that the requirements for the given mode and given sub-mode are satisfied.
    * If no satisfactory combination is achieved, the original state will be restored.
    * @see `FireModeSwitch.NextFireMode`
    * @see `PerformConstructionItemAmmoChange`
    * @param obj the `ConstructionItem` object
    * @param originalModeIndex the starting point fire mode index
    * @return the changed fire mode
    */
  def NextConstructionItemFireMode(obj : ConstructionItem, originalModeIndex : Int) : ConstructionFireMode = {
    val certifications = player.Certifications
    do {
      obj.NextFireMode
      if(!ConstructionItemPermissionComparison(certifications, obj.ModePermissions)) {
        PerformConstructionItemAmmoChange(obj, obj.AmmoTypeIndex)
      }
      sendResponse(ChangeFireModeMessage(obj.GUID, obj.FireModeIndex))
    }
    while(!ConstructionItemPermissionComparison(certifications, obj.ModePermissions) && originalModeIndex != obj.FireModeIndex)
    obj.FireMode
  }

  /**
    * The custom behavior responding to the message `ChangeAmmoMessage` for `ConstructionItem` game objects.
    * Iterate through sub-modes corresponding to a type of "deployable" as ammunition for this fire mode
    * and check each of these sub-modes for their certification requirements to be met before they can be used.
    * Additional effort is exerted to ensure that the requirements for the given ammunition are satisfied.
    * If no satisfactory combination is achieved, the original state will be restored.
    * @param obj the `ConstructionItem` object
    * @param originalAmmoIndex the starting point ammunition type mode index
    */
  def PerformConstructionItemAmmoChange(obj : ConstructionItem, originalAmmoIndex : Int) : Unit = {
    val certifications = player.Certifications
    do {
      obj.NextAmmoType
    }
    while(!ConstructionItemPermissionComparison(certifications, obj.ModePermissions) && originalAmmoIndex != obj.AmmoTypeIndex)
    log.info(s"ChangeFireMode: construction object ${obj.Definition.Name} changed to ${obj.AmmoType} (mode ${obj.FireModeIndex})")
    sendResponse(ChangeAmmoMessage(obj.GUID, obj.AmmoTypeIndex))
  }

  /**
    * Compare sets of certifications to determine if
    * the requested `Engineering`-like certification requirements of the one group can be found in a another group.
    * @see `CertificationType`
    * @param sample the certifications to be compared against
    * @param test the desired certifications
    * @return `true`, if the desired certification requirements are met; `false`, otherwise
    */
  def ConstructionItemPermissionComparison(sample : Set[CertificationType.Value], test : Set[CertificationType.Value]) : Boolean = {
    import CertificationType._
    val engineeringCerts : Set[CertificationType.Value] = Set(AssaultEngineering, FortificationEngineering)
    val testDiff : Set[CertificationType.Value] = test diff (engineeringCerts ++ Set(AdvancedEngineering))
    //substitute `AssaultEngineering` and `FortificationEngineering` for `AdvancedEngineering`
    val sampleIntersect = if(sample contains AdvancedEngineering) {
      engineeringCerts
    }
    else {
      sample intersect engineeringCerts
    }
    val testIntersect = if(test contains AdvancedEngineering) {
      engineeringCerts
    }
    else {
      test intersect engineeringCerts
    }
    (sample intersect testDiff equals testDiff) && (sampleIntersect intersect testIntersect equals testIntersect)
  }

  /**
    * Common actions related to constructing a new `Deployable` object in the game environment.<br>
    * <br>
    * Besides the standard `ObjectCreateMessage` packet that produces the model and game object on the client,
    * two messages are dispatched in accordance with enforced deployable limits.
    * The first limit of note is the actual number of a specific type of deployable can be placed.
    * The second limit of note is the actual number of a specific group (category) of deployables that can be placed.
    * For example, the player can place 25 mines but that count adds up all types of mines;
    * specific mines have individual limits such as 25 and 5 and only that many of that type can be placed at once.
    * Depending on which limit is encountered, an "oldest entry" is struck from the list to make space.
    * This generates the first message - "@*OldestDestroyed."
    * The other message is generated if the number of that specific type of deployable
    * or the number of deployables available in its category
    * matches against the maximum count allowed.
    * This generates the second message - "@*LimitReached."
    * These messages are mutually exclusive, with "@*OldestDestroyed" taking priority over "@*LimitReached."<br>
    * <br>
    * The map icon for the deployable just introduced is also created on the clients of all faction-affiliated players.
    * This icon is important as, short of destroying it,
    * the owner has no other means of controlling the created object that it is associated with.
    * @param obj the `Deployable` object to be built
    */
  def DeployableBuildActivity(obj : PlanetSideGameObject with Deployable) : Unit = {
    val guid = obj.GUID
    val definition = obj.Definition
    val item = definition.Item
    val deployables = avatar.Deployables
    val (curr, max) = deployables.CountDeployable(item)
    log.info(s"DeployableBuildActivity: ${definition.Name}")
    //two potential messages related to numerical limitations of deployables
    if(!avatar.Deployables.Available(obj)) {
      val (removed, msg) = {
        if(curr == max) { //too many of a specific type of deployable
          (deployables.DisplaceFirst(obj), max > 1)
        }
        else { //make room by eliminating a different type of deployable
          (deployables.DisplaceFirst(obj, { d => d.Definition.Item != item }), true)
        }
      }
      removed match {
        case Some(telepad : TelepadDeployable) =>
          telepad.AssignOwnership(None)
          continent.LocalEvents ! LocalServiceMessage.Deployables(RemoverActor.ClearSpecific(List(telepad), continent))
          continent.LocalEvents ! LocalServiceMessage.Deployables(RemoverActor.AddTask(telepad, continent, Some(0 seconds))) //normal decay
        case Some(old) =>
          old.AssignOwnership(None)
          continent.LocalEvents ! LocalServiceMessage.Deployables(RemoverActor.ClearSpecific(List(old), continent))
          continent.LocalEvents ! LocalServiceMessage.Deployables(RemoverActor.AddTask(old, continent, Some(0 seconds)))
          if(msg) { //max test
            sendResponse(ChatMsg(ChatMessageType.UNK_229, false, "", s"@${definition.Descriptor}OldestDestroyed", None))
          }
        case None => ; //should be an invalid case
          log.warn(s"DeployableBuildActivity: how awkward: we probably shouldn't be allowed to build this deployable right now")
      }
    }
    else if(obj.isInstanceOf[TelepadDeployable]) {
      //always treat the telepad we are putting down as the first and only one
      sendResponse(ObjectDeployedMessage.Success(definition.Name, 1, 1))
    }
    else {
      sendResponse(ObjectDeployedMessage.Success(definition.Name, curr + 1, max))
      val (catCurr, catMax) = deployables.CountCategory(item)
      if((max > 1 && curr + 1 == max) || (catMax > 1 && catCurr + 1 == catMax)) {
        sendResponse(ChatMsg(ChatMessageType.UNK_229, false, "", s"@${definition.Descriptor}LimitReached", None))
      }
    }
    avatar.Deployables.Add(obj)
    UpdateDeployableUIElements(avatar.Deployables.UpdateUIElement(item))
    sendResponse(GenericObjectActionMessage(guid, 21)) //reset build cooldown
    sendResponse(ObjectCreateMessage(definition.ObjectId, guid, definition.Packet.ConstructorData(obj).get))
    continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.DeployItem(player.GUID, obj))
    //map icon
    val deployInfo = DeployableInfo(guid, Deployable.Icon(item), obj.Position, obj.Owner.getOrElse(PlanetSideGUID(0)))
    sendResponse(DeployableObjectsInfoMessage(DeploymentAction.Build, deployInfo))
    continent.LocalEvents ! LocalServiceMessage(s"${player.Faction}", LocalAction.DeployableMapIcon(player.GUID, DeploymentAction.Build, deployInfo))
  }

  /**
    * If the tool is a form of field deployment unit (FDU, also called an `advanced_ace`),
    * completely remove the object from its current position and place it on the ground.
    * In the case of a botched deployable construction, dropping the FDU is visually consistent
    * as it should already be depicted as on the ground as a part of its animation cycle.
    * @param tool the `ConstructionItem` object currently in the slot (checked)
    * @param index the slot index
    * @param pos where to drop the object in the game world
    */
  def TryDropConstructionTool(tool : ConstructionItem, index : Int, pos : Vector3) : Unit = {
    if(tool.Definition == GlobalDefinitions.advanced_ace &&
      SafelyRemoveConstructionItemFromSlot(tool, index, "TryDropConstructionTool")) {
      continent.Ground ! Zone.Ground.DropItem(tool, pos, Vector3.Zero)
    }
  }

  /**
    * Destroy a `ConstructionItem` object that can be found in the indexed slot.
    * @see `Player.Find`
    * @param tool the `ConstructionItem` object currently in the slot (checked)
    * @param index the slot index
    */
  def CommonDestroyConstructionItem(tool : ConstructionItem, index : Int) : Unit = {
    if(SafelyRemoveConstructionItemFromSlot(tool, index, "CommonDestroyConstructionItem")) {
      taskResolver ! GUIDTask.UnregisterEquipment(tool)(continent.GUID)
    }
  }

  /**
    * Find the target `ConstructionTool` object, either at the suggested slot or wherever it is on the `player`,
    * and remove it from the game world visually.<br>
    * <br>
    * Not finding the target object at its intended slot is an entirely recoverable situation
    * as long as the target object is discovered to be somewhere else in the player's holsters or inventory space.
    * If found after a more thorough search, merely log the discrepancy as a warning.
    * If the discrepancy becomes common, the developer messed up the function call
    * or he should not be using this function.
    * @param tool the `ConstructionItem` object currently in the slot (checked)
    * @param index the slot index
    * @param logDecorator what kind of designation to give any log entires originating from this function;
    *                     defaults to its own function name
    * @return `true`, if the target object was found and removed;
    *        `false`, otherwise
    */
  def SafelyRemoveConstructionItemFromSlot(tool : ConstructionItem, index : Int, logDecorator : String = "SafelyRemoveConstructionItemFromSlot") : Boolean = {
    if({
      val holster = player.Slot(index)
      if(holster.Equipment.contains(tool)) {
        holster.Equipment = None
        true
      }
      else {
        player.Find(tool) match {
          case Some(newIndex) =>
            log.warn(s"$logDecorator: looking for item in $index, but item was found at $newIndex instead")
            player.Slot(newIndex).Equipment = None
            true
          case None =>
            log.error(s"$logDecorator: could not find the target ${tool.Definition.Name}")
            false
        }
      }
    }) {
      sendResponse(ObjectDeleteMessage(tool.GUID, 0))
      continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.ObjectDelete(player.GUID, tool.GUID))
      true
    }
    else {
      false
    }
  }

  /**
    * Find a `ConstructionItem` object in player's inventory
    * that is the same type as a target `ConstructionItem` object and
    * transfer it into the designated slot index, usually a holster.
    * Draw that holster.
    * After being transferred, the replacement should be reconfigured to match the fire mode of the original.
    * The primary use of this operation is following the successful manifestation of a deployable in the game world.<br>
    * <br>
    * As this function should be used in response to some other action such as actually placing a deployable,
    * do not instigate bundling from within the function's scope.
    * @see `WorldSessionActor.FinalizeDeployable`<br>
    *       `FindEquipmentStock`
    * @param tool the `ConstructionItem` object to match
    * @param index where to put the discovered replacement
    */
  def FindReplacementConstructionItem(tool : ConstructionItem, index : Int) : Unit = {
    val fireMode = tool.FireModeIndex
    val ammoType = tool.AmmoTypeIndex
    val definition = tool.Definition

    if(player.Slot(index).Equipment.isEmpty) {
      FindEquipmentStock(player, { (e) => e.Definition == definition }, 1) match {
        case x :: _ =>
          val guid = player.GUID
          val obj = x.obj.asInstanceOf[ConstructionItem]
          if((player.Slot(index).Equipment = obj).contains(obj)) {
            player.Inventory -= x.start
            sendResponse(ObjectAttachMessage(guid, obj.GUID, index))

            if(obj.FireModeIndex != fireMode) {
              obj.FireModeIndex = fireMode
              sendResponse(ChangeFireModeMessage(obj.GUID, fireMode))
            }
            if(obj.AmmoTypeIndex != ammoType) {
              obj.AmmoTypeIndex = ammoType
              sendResponse(ChangeAmmoMessage(obj.GUID, ammoType))
            }
            if(player.VisibleSlots.contains(index)) {
              continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.EquipmentInHand(guid, guid, index, obj))
              if(player.DrawnSlot == Player.HandsDownSlot) {
                player.DrawnSlot = index
                sendResponse(ObjectHeldMessage(guid, index, false))
                continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.ObjectHeld(guid, index))
              }
            }
          }
        case Nil => ; //no replacements found
      }
    }
    else {
      log.warn(s"FindReplacementConstructionItem: slot $index needs to be empty before a replacement ${definition.Name} can be installed")
    }
  }

  /**
    * A simple object searching algorithm that is limited to containers currently known and accessible by the player.
    * If all relatively local containers are checked and the object is not found,
    * the game environment (items on the ground) will be checked too.
    * If the target object is discovered, it is removed from its current location and is completely destroyed.
    * @see `RequestDestroyMessage`<br>
    *       `Zone.ItemIs.Where`
    * @param object_guid the target object's globally unique identifier;
    *                    it is not expected that the object will be unregistered, but it is also not gauranteed
    * @param obj the target object
    * @return `true`, if the target object was discovered and removed;
    *        `false`, otherwise
    */
  def FindEquipmentToDelete(object_guid : PlanetSideGUID, obj : Equipment) : Boolean = {
    val findFunc : PlanetSideGameObject with Container => Option[(PlanetSideGameObject with Container, Option[Int])] =
      FindInLocalContainer(object_guid)

    findFunc(player.Locker)
      .orElse(findFunc(player))
      .orElse(accessedContainer match {
        case Some(parent) =>
          findFunc(parent)
        case None =>
          None
      })
      .orElse(FindLocalVehicle match {
        case Some(parent) =>
          findFunc(parent)
        case None =>
          None
      })
    match {
      case Some((parent, Some(slot))) =>
        obj.Position = Vector3.Zero
        taskResolver ! RemoveEquipmentFromSlot(parent, obj, slot)
        log.info(s"RequestDestroy: equipment $obj")
        true

      case _ =>
        if(continent.EquipmentOnGround.contains(obj)) {
          obj.Position = Vector3.Zero
          continent.Ground ! Zone.Ground.RemoveItem(object_guid)
          continent.AvatarEvents ! AvatarServiceMessage.Ground(RemoverActor.ClearSpecific(List(obj), continent))
          continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.ObjectDelete(PlanetSideGUID(0), object_guid))
          log.info(s"RequestDestroy: equipment $obj on ground")
          true
        }
        else {
          log.warn(s"RequestDestroy: equipment $obj exists, but can not be reached")
          false
        }
    }
  }

  /**
    * Common behavior for deconstructing expended explosive deployables in the game environment.
    * @param obj the deployable
    * @param guid the globally unique identifier for the deployable
    * @param pos the previous position of the deployable
    */
  def DeconstructDeployable(obj : PlanetSideGameObject with Deployable, guid : PlanetSideGUID, pos : Vector3) : Unit = {
    StartBundlingPackets()
    sendResponse(SetEmpireMessage(guid, PlanetSideEmpire.NEUTRAL)) //for some, removes the green marker circle
    sendResponse(ObjectDeleteMessage(guid, 0))
    if(player.Faction == obj.Faction) {
      sendResponse(
        DeployableObjectsInfoMessage(
          DeploymentAction.Dismiss,
          DeployableInfo(guid, Deployable.Icon(obj.Definition.Item), pos, obj.Owner.getOrElse(PlanetSideGUID(0)))
        )
      )
    }
    StopBundlingPackets()
  }

  /**
    * Common behavior for deconstructing deployables in the game environment.
    * @param obj the deployable
    * @param guid the globally unique identifier for the deployable
    * @param pos the previous position of the deployable
    * @param orient the previous orientation of the deployable
    * @param deletionType the value passed to `ObjectDeleteMessage` concerning the deconstruction animation
    */
  def DeconstructDeployable(obj : PlanetSideGameObject with Deployable, guid : PlanetSideGUID, pos : Vector3, orient : Vector3, deletionType : Int) : Unit = {
    StartBundlingPackets()
    sendResponse(SetEmpireMessage(guid, PlanetSideEmpire.NEUTRAL)) //for some, removes the green marker circle
    sendResponse(TriggerEffectMessage("spawn_object_failed_effect", pos, orient))
    sendResponse(PlanetsideAttributeMessage(guid, 29, 1)) //make deployable vanish
    sendResponse(ObjectDeleteMessage(guid, deletionType))
    if(player.Faction == obj.Faction) {
      sendResponse(
        DeployableObjectsInfoMessage(
          DeploymentAction.Dismiss,
          DeployableInfo(guid, Deployable.Icon(obj.Definition.Item), pos, obj.Owner.getOrElse(PlanetSideGUID(0)))
        )
      )
    }
    StopBundlingPackets()
  }

  /**
    * Search through the player's holsters and their inventory space
    * and remove all `BoomerTrigger` objects, both functionally and visually.
    * @return all discovered `BoomTrigger` objects
    */
  def RemoveBoomerTriggersFromInventory() : List[BoomerTrigger] = {
    val holstersWithIndex = player.Holsters().zipWithIndex
    ((player.Inventory.Items.collect({ case InventoryItem(obj : BoomerTrigger, index) => (obj, index) })) ++
      (holstersWithIndex
        .map({ case ((slot, index)) => (slot.Equipment, index) })
        .collect { case ((Some(obj : BoomerTrigger), index)) => (obj, index) }
        )
      )
      .map({ case ((obj, index)) =>
        player.Slot(index).Equipment = None
        sendResponse(ObjectDeleteMessage(obj.GUID, 0))
        if(player.VisibleSlots.contains(index) && player.HasGUID) {
          continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.ObjectDelete(player.GUID, obj.GUID))
        }
        obj
      })
  }

  /**
    * The starting point of behavior for a player who:
    * is dead and is respawning;
    * is deconstructing at a spawn tube and is respawning;
    * is using a warp gate; or,
    * any or none of the previous conditions, but the final result involves changing what zone the player occupies.
    * This route is not taken when first spawning in the game world, unless special conditions need to be satisfied.
    * The visible result will be apparent by the respawn timer being displayed to the client over the deployment map.<br>
    * <br>
    * Two choices must be independently made to complete this part of the process.
    * The first choice ivolves the state of the player who is spawning
    * as the known entry state involve either being alive or being dead.
    * A dead player (technically, a "corpse" that can no longer be revived) is embodied
    * in a completely new player with a new globally unique identifier and a whole new inventory.
    * A player who is transferring continents also satisfies the requirements
    * for obtaining a completely new globally unique identifier,
    * though the new identifier belongs to the new zone rather than the previous (still current) one.
    * The second choice is satisfied by respawning in the same zone while still in a state of still being alive.
    * In this singular case, the player retains his previous globally unique identifier.
    * In all other cases, as indicated, a new globally unique identifier is selected.<br>
    * <br>
    * If the player is alive and mounted in a vehicle, a different can of worms is produced.
    * The ramifications of these conditions are not fully satisfied until the player loads into the new zone.
    * Even then, the conclusion becomes delayed while a slightly lagged mechanism hoists players between zones.
    * @see `AvatarDeadStateMessage`
    * @see `interstellarFerry`
    * @see `LoadZoneAsPlayer`
    * @see `LoadZoneInVehicle`
    * @param zone_id the zone in which the player will be placed
    * @param pos the game world coordinates where the player will be positioned
    * @param ori the direction in which the player will be oriented
    * @param respawnTime the character downtime spent respawning, as clocked on the redeployment screen;
    *                    does not factor in any time required for loading zone or game objects
    */
  def LoadZonePhysicalSpawnPoint(zone_id : String, pos : Vector3, ori : Vector3, respawnTime : Long) : Unit = {
    log.info(s"Load in zone $zone_id at position $pos in $respawnTime seconds")
    respawnTimer.cancel
    reviveTimer.cancel
    val backpack = player.isBackpack
    val respawnTimeMillis = respawnTime * 1000 //ms
    deadState = DeadState.RespawnTime
    sendResponse(AvatarDeadStateMessage(DeadState.RespawnTime, respawnTimeMillis, respawnTimeMillis, Vector3.Zero, player.Faction, true))
    shiftPosition = Some(pos)
    shiftOrientation = Some(ori)
    val (target, msg) = if(backpack) { //if the player is dead, he is handled as dead infantry, even if he died in a vehicle
      //new player is spawning
      val newPlayer = RespawnClone(player)
      newPlayer.Position = pos
      newPlayer.Orientation = ori
      LoadZoneAsPlayer(newPlayer, zone_id)
    }
    else {
      interstellarFerry.orElse(continent.GUID(player.VehicleSeated)) match {
        case Some(vehicle : Vehicle) => //driver or passenger in vehicle using a warp gate, or a droppod
          LoadZoneInVehicle(vehicle, pos, ori, zone_id)

        case _ if player.HasGUID => //player is deconstructing self
          val player_guid = player.GUID
          sendResponse(ObjectDeleteMessage(player_guid, 4))
          continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.ObjectDelete(player_guid, player_guid, 4))
          player.Position = pos
          player.Orientation = ori
          LoadZoneAsPlayer(player, zone_id)

        case _ => //player is logging in
          player.Position = pos
          player.Orientation = ori
          LoadZoneAsPlayer(player, zone_id)
      }
    }
    import scala.concurrent.ExecutionContext.Implicits.global
    respawnTimer = context.system.scheduler.scheduleOnce(respawnTime seconds, target, msg)
  }

  /**
    * Deal with a target player as free-standing infantry in the course of a redeployment action to a target continent
    * whether that action is the result of a deconstruction (reconstruction), a death (respawning),
    * or other position shifting action handled directly by the server.<br>
    * <br>
    * The two important vectors are still whether the zone being transported to is the same or is different
    * and whether the target player is alive or released (note: not just "dead" ...).
    * @see `LoadZoneCommonTransferActivity`
    * @see `GUIDTask.UnregisterAvatar`
    * @see `GUIDTask.UnregisterLocker`
    * @see `PlayerLoaded`
    * @see `Player.isBackpack`
    * @see `RegisterAvatar`
    * @see `TaskBeforeZoneChange`
    * @param tplayer the target player being moved around;
    *                not necessarily the same player as the `WorldSessionActor`-global `player`
    * @param zone_id the zone in which the player will be placed
    * @return a tuple composed of an `ActorRef` destination and a message to send to that destination
    */
  def LoadZoneAsPlayer(tplayer : Player, zone_id : String) : (ActorRef, Any) = {
    if(zone_id == continent.Id) {
      if(player.isBackpack) { //important! test the actor-wide player ref, not the parameter
        //respawning from unregistered player
        (taskResolver, RegisterAvatar(tplayer))
      }
      else {
        //move existing player; this is the one case where the original GUID is retained by the player
        (self, PlayerLoaded(tplayer))
      }
    }
    else {
      LoadZoneCommonTransferActivity()
      val original = player
      if(player.isBackpack) {
        //unregister avatar locker + GiveWorld
        player = tplayer
        (taskResolver, TaskBeforeZoneChange(GUIDTask.UnregisterLocker(original.Locker)(continent.GUID), zone_id))
      }
      else if(player.HasGUID) {
        //unregister avatar whole + GiveWorld
        (taskResolver, TaskBeforeZoneChange(GUIDTask.UnregisterAvatar(original)(continent.GUID), zone_id))
      }
      else {
        //not currently registered; so we'll just GiveWorld
        (cluster, InterstellarCluster.GetWorld(zone_id))
      }
    }
  }

  /**
    * Deal with a target player as a vehicle occupant in the course of a redeployment action to a target continent
    * whether that action is the result of a deconstruction (reconstruction)
    * or other position shifting action handled directly by the server.<br>
    * <br>
    * The original target player must be alive and the only consideration is in what position the player is mounted in the vehicle.
    * Any seated position that isn't the driver is a passenger.
    * The most important role performed in this function is to declare a reference to the vehicle itsself
    * since no other connection from the player to the vehicle is guaranteed to persist in a meaningful way during the transfer.
    * @see `interstellarFerry`
    * @see `LoadZoneInVehicleAsDriver`
    * @see `LoadZoneInVehicleAsPassenger`
    * @see `Vehicle.PassengerInSeat`
    * @param vehicle the target vehicle being moved around;
    *                WILL necessarily be the same vehicles as is controlled by the `WorldSessionActor`-global `player`
    * @param pos     the game world coordinates where the vehicle will be positioned
    * @param ori     the direction in which the vehicle will be oriented
    * @param zone_id the zone in which the vehicle and driver will be placed,
    *                or in which the vehicle has already been placed
    * @return a tuple composed of an ActorRef` destination and a message to send to that destination
    **/
  def LoadZoneInVehicle(vehicle : Vehicle, pos : Vector3, ori : Vector3, zone_id : String) : (ActorRef, Any) = {
    interstellarFerry = Some(vehicle)
    if(vehicle.PassengerInSeat(player).contains(0)) {
      vehicle.Position = pos
      vehicle.Orientation = ori
      LoadZoneInVehicleAsDriver(vehicle, zone_id)
    }
    else {
      LoadZoneInVehicleAsPassenger(vehicle, zone_id)
    }
  }

  /**
    * Deal with a target player as a vehicle driver in the course of a redeployment action to a target continent
    * whether that action is the result of a deconstruction (reconstruction)
    * or other position shifting action handled directly by the server.<br>
    * <br>
    * During a vehicle transfer, whether to the same zone or to a different zone,
    * the driver has the important task of ensuring the certain safety of his passengers during transport.
    * The driver must modify the conditions of the vehicle's passengers common communication channel
    * originally determined entirely by the vehicle's soon-to-be blanked internal `Actor` object.
    * Any cargo vehicles under the control of the target vehicle must also be made aware of the current state of the process.
    * In the case of a series of ferrying vehicles and cargo vehicles,
    * the vehicle to be deleted might not be the one immediately mounted.
    * A reference to the top-level ferrying vehicle's former globally unique identifier has been retained for this purpose.
    * This vehicle can be deleted for everyone if no more work can be detected.
    * @see `interstellarFerryTopLevelGUID`
    * @see `LoadZoneCommonTransferActivity`
    * @see `PlayerLoaded`
    * @see `TaskBeforeZoneChange`
    * @see `UnAccessContents`
    * @see `UnregisterDrivenVehicle`
    * @param vehicle the target vehicle being moved around;
    *                WILL necessarily be the same vehicles as is controlled by the `WorldSessionActor`-global `player`
    * @param zone_id the zone in which the vehicle and driver will be placed,
    *                or in which the vehicle has already been placed
    * @return a tuple composed of an `ActorRef` destination and a message to send to that destination
    **/
  def LoadZoneInVehicleAsDriver(vehicle : Vehicle, zone_id : String) : (ActorRef, Any) = {
    log.info(s"LoadZoneInVehicleAsDriver: ${player.Name} is driving a ${vehicle.Definition.Name}")
    val manifest = vehicle.PrepareGatingManifest()
    log.info(s"$manifest")
    val pguid = player.GUID
    val toChannel = manifest.file
    val topLevel = interstellarFerryTopLevelGUID.getOrElse(vehicle.GUID)
    continent.VehicleEvents ! VehicleServiceMessage(
      s"${vehicle.Actor}",
      VehicleAction.TransferPassengerChannel(pguid, s"${vehicle.Actor}", toChannel, vehicle, topLevel)
    )
    manifest.cargo.foreach {
      case ("MISSING_DRIVER", index) =>
        val cargo = vehicle.CargoHolds(index).Occupant.get
        log.error(s"LoadZoneInVehicleAsDriver: eject cargo in hold $index; vehicle missing driver")
        CargoBehavior.HandleVehicleCargoDismount(cargo.GUID, cargo, vehicle.GUID, vehicle, false, false, true)
      case (name, index) =>
        val cargo = vehicle.CargoHolds(index).Occupant.get
        continent.VehicleEvents ! VehicleServiceMessage(name, VehicleAction.TransferPassengerChannel(pguid, s"${cargo.Actor}", toChannel, cargo, topLevel))
    }
    //
    if(zone_id == continent.Id) {
      if(vehicle.Definition == GlobalDefinitions.droppod) {
        //instant action droppod in the same zone
        (taskResolver, RegisterDroppod(vehicle, player))
      }
      else {
        //transferring a vehicle between spawn points (warp gates) in the same zone
        (self, PlayerLoaded(player))
      }
    }
    else if(vehicle.Definition == GlobalDefinitions.droppod) {
      LoadZoneCommonTransferActivity()
      player.Continent = zone_id //forward-set the continent id to perform a test
      (taskResolver, TaskBeforeZoneChange(GUIDTask.UnregisterAvatar(player)(continent.GUID), zone_id))
    }
    else {
      UnAccessContents(vehicle)
      LoadZoneCommonTransferActivity()
      player.VehicleSeated = vehicle.GUID
      player.Continent = zone_id //forward-set the continent id to perform a test
      interstellarFerryTopLevelGUID = (if(manifest.passengers.isEmpty && manifest.cargo.count { case (name, _) => !name.equals("MISSING_DRIVER") } == 0) {
        //do not delete if vehicle has passengers or cargo
        continent.VehicleEvents ! VehicleServiceMessage(continent.Id, VehicleAction.UnloadVehicle(pguid, continent, vehicle, topLevel))
        None
      }
      else {
        Some(topLevel)
      })
      //unregister vehicle and driver whole + GiveWorld
      continent.Transport ! Zone.Vehicle.Despawn(vehicle)
      (taskResolver, TaskBeforeZoneChange(UnregisterDrivenVehicle(vehicle, player), zone_id))
    }
  }

  /**
    * Deal with a target player as a vehicle passenger in the course of a redeployment action to a target continent
    * whether that action is the result of a deconstruction (reconstruction)
    * or other position shifting action handled directly by the server.<br>
    * <br>
    * The way a vehicle is handled in reference to being a passenger
    * is very similar to how an infantry player is handled in the same process.
    * If this player is the last person who requires a zone change
    * which is the concluding zone transfer of what might have been a long chain of vehicle and passengers
    * then that player is responsible for deleting the vehicle for other players of the previous zone.
    * In the case of a series of ferrying vehicles and cargo vehicles,
    * the vehicle to be deleted might not be the one immediately mounted.
    * A reference to the top-level ferrying vehicle's former globally unique identifier has been retained for this purpose.
    * This vehicle can be deleted for everyone if no more work can be detected.
    * @see `GUIDTask.UnregisterAvatar`
    * @see `LoadZoneCommonTransferActivity`
    * @see `Vehicles.AllGatedOccupantsInSameZone`
    * @see `PlayerLoaded`
    * @see `TaskBeforeZoneChange`
    * @see `UnAccessContents`
    * @param vehicle the target vehicle being moved around
    * @param zone_id the zone in which the vehicle and driver will be placed
    * @return a tuple composed of an `ActorRef` destination and a message to send to that destination
    **/
  def LoadZoneInVehicleAsPassenger(vehicle : Vehicle, zone_id : String) : (ActorRef, Any) = {
    log.info(s"LoadZoneInVehicleAsPassenger: ${player.Name} is the passenger of a ${vehicle.Definition.Name}")
    if(zone_id == continent.Id) {
      //transferring a vehicle between spawn points (warp gates) in the same zone
      (self, PlayerLoaded(player))
    }
    else {
      LoadZoneCommonTransferActivity()
      player.VehicleSeated = vehicle.GUID
      player.Continent = zone_id //forward-set the continent id to perform a test
      val continentId = continent.Id
      interstellarFerryTopLevelGUID = None
      //unregister avatar + GiveWorld
      (taskResolver, TaskBeforeZoneChange(GUIDTask.UnregisterAvatar(player)(continent.GUID), zone_id))
    }
  }

  /**
    * Dispatch messages to all target players in immediate passenger and gunner seats
    * and to the driver of all vehicles in cargo holds
    * that their current ferrying vehicle is being transported from one zone to the next
    * and that they should follow after it.
    * The messages address the avatar of their recipient `WorldSessionActor` objects.
    * @param player_guid the driver of the target vehicle
    * @param toZoneId the zone where the target vehicle will be moved
    * @param toChannel the vehicle-specific channel with which all passengers are coordinated to the vehicle
    * @param vehicle the vehicle (object)
    */
  def LoadZoneTransferPassengerMessages(player_guid : PlanetSideGUID, toZoneId : String, vehicle : Vehicle) : Unit = {
    vehicle.PublishGatingManifest() match {
      case Some(manifest) =>
        val toChannel = manifest.file
        val topLevel = interstellarFerryTopLevelGUID.getOrElse(vehicle.GUID)
        galaxyService ! GalaxyServiceMessage(toChannel, GalaxyAction.TransferPassenger(player_guid, toChannel, vehicle, topLevel, manifest))
        vehicle.CargoHolds.values
          .collect {
            case hold if hold.isOccupied =>
              val cargo = hold.Occupant.get
              cargo.Continent = toZoneId
              //point to the cargo vehicle to instigate cargo vehicle driver transportation
              galaxyService ! GalaxyServiceMessage(toChannel, GalaxyAction.TransferPassenger(player_guid, toChannel, vehicle, topLevel, manifest))
          }
      case None =>
        log.error("LoadZoneTransferPassengerMessages: expected a manifest for zone transfer; got nothing")
    }
  }

  /**
    * Common behavior when transferring between zones
    * encompassing actions that disassociate the player with entities they left (will leave) in the previous zone.
    * It also sets up actions for the new zone loading process.
    */
  def LoadZoneCommonTransferActivity() : Unit = {
    RemoveBoomerTriggersFromInventory().foreach(obj => {
      taskResolver ! GUIDTask.UnregisterObjectTask(obj)(continent.GUID)
    })
    Deployables.Disown(continent, avatar, self)
    drawDeloyableIcon = RedrawDeployableIcons //important for when SetCurrentAvatar initializes the UI next zone
    squadSetup = ZoneChangeSquadSetup
  }

  /**
    * Primary functionality for tranferring a piece of equipment from a player's hands or his inventory to the ground.
    * Items are always dropped at player's feet because for simplicity's sake
    * because, by virtue of already standing there, the stability of the surface has been proven.
    * The only exception to this is dropping items while falling.
    * @see `Player.Find`<br>
    *       `ObjectDetachMessage`
    * @param item the `Equipment` object in the player's hand
    * @param pos the game world coordinates where the object will be dropped
    * @param orient a suggested orientation in which the object will settle when on the ground;
    *               as indicated, the simulation is only concerned with certain angles
    */
  def PutItemOnGround(item : Equipment, pos : Vector3, orient : Vector3) : Unit = {
    CancelZoningProcessWithDescriptiveReason("cancel_use")
    //TODO delay or reverse dropping item when player is falling down
    item.Position = pos
    item.Orientation = Vector3.z(orient.z)
    item.Faction = PlanetSideEmpire.NEUTRAL
    //dropped items rotate towards the user's standing direction
    val exclusionId = player.Find(item) match {
      //if the item is in our hands ...
      case Some(slotNum) =>
        player.Slot(slotNum).Equipment = None
        sendResponse(ObjectDetachMessage(player.GUID, item.GUID, pos, orient.z))
        sendResponse(ActionResultMessage.Pass)
        player.GUID //we're dropping the item; don't need to see it dropped again
      case None =>
        PlanetSideGUID(0) //item is being introduced into the world upon drop
    }
    continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.DropItem(exclusionId, item, continent))
  }

  /**
    * Primary functionality for tranferring a piece of equipment from the ground in a player's hands or his inventory.
    * The final destination of the item in terms of slot position is not determined until the attempt is made.
    * If it can not be placed in a slot correctly, the item will be returned to the ground in the same place.
    * @see `Player.Fit`
    * @param item the `Equipment` object on the ground
    * @return `true`, if the object was properly picked up;
    *        `false` if it was returned to the ground
    */
  def PutItemInHand(item : Equipment) : Boolean = {
    player.Fit(item) match {
      case Some(slotNum) =>
        CancelZoningProcessWithDescriptiveReason("cancel_use")
        item.Faction = player.Faction
        val item_guid = item.GUID
        val player_guid = player.GUID
        player.Slot(slotNum).Equipment = item
        val definition = item.Definition
        sendResponse(
          ObjectCreateDetailedMessage(
            definition.ObjectId,
            item_guid,
            ObjectCreateMessageParent(player_guid, slotNum),
            definition.Packet.DetailedConstructorData(item).get
          )
        )
        continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.PickupItem(player_guid, continent, player, slotNum, item))
        true
      case None =>
        continent.Ground ! Zone.Ground.DropItem(item, item.Position, item.Orientation) //restore previous state
        false
    }
  }

  /**
    * Attempt to link the router teleport system using the provided terminal information.
    * Although additional states are necessary to properly use the teleportation system,
    * e.g., deployment state, active state of the endpoints, etc.,
    * this decision is not made factoring those other conditions.
    * @param router the vehicle that houses one end of the teleportation system (the `InternalTelepad` object)
    * @param systemPlan specific object identification of the two endpoints of the teleportation system;
    *                   if absent, the knowable endpoint is deleted from the client reflexively
    */
  def ToggleTeleportSystem(router : Vehicle, systemPlan : Option[(Utility.InternalTelepad, TelepadDeployable)]) : Unit = {
    StartBundlingPackets()
    systemPlan match {
      case Some((internalTelepad, remoteTelepad)) =>
        LinkRouterToRemoteTelepad(router, internalTelepad, remoteTelepad)
      case _ =>
        router.Utility(UtilityType.internal_router_telepad_deployable) match {
          case Some(util : Utility.InternalTelepad) =>
            sendResponse(ObjectDeleteMessage(util.GUID, 0))
          case _ => ;
        }
    }
    StopBundlingPackets()
  }

  /**
    * Link the router teleport system using the provided terminal information.
    * The internal telepad is made known of the remote telepad, creating the link.
    * @param router the vehicle that houses one end of the teleportation system (the `internalTelepad`)
    * @param internalTelepad the endpoint of the teleportation system housed by the router
    * @param remoteTelepad the endpoint of the teleportation system that exists in the environment
    */
  def LinkRouterToRemoteTelepad(router : Vehicle, internalTelepad : Utility.InternalTelepad, remoteTelepad : TelepadDeployable) : Unit = {
    internalTelepad.Telepad = remoteTelepad.GUID //necessary; backwards link to the (new) telepad
    CreateRouterInternalTelepad(router, internalTelepad)
    LinkRemoteTelepad(remoteTelepad.GUID)
  }

  /**
    * Create the mechanism that serves as one endpoint of the linked router teleportation system.<br>
    * <br>
    * Technically, the mechanism - an `InternalTelepad` object - is always made to exist
    * due to how the Router vehicle object is encoded into an `ObjectCreateMessage` packet.
    * Regardless, that internal mechanism is created anew each time the system links a new remote telepad.
    * @param router the vehicle that houses one end of the teleportation system (the `internalTelepad`)
    * @param internalTelepad the endpoint of the teleportation system housed by the router
    */
  def CreateRouterInternalTelepad(router : Vehicle, internalTelepad : PlanetSideGameObject with TelepadLike) : Unit = {
    //create the interal telepad each time the link is made
    val rguid = router.GUID
    val uguid = internalTelepad.GUID
    val udef = internalTelepad.Definition
    /*
    the following instantiation and configuration creates the internal Router component
    normally dispatched while the Router is transitioned into its Deploying state
    it is safe, however, to perform these actions at any time during and after the Deploying state
     */
    sendResponse(
      ObjectCreateMessage(
        udef.ObjectId,
        uguid,
        ObjectCreateMessageParent(rguid, 2), //TODO stop assuming slot number
        udef.Packet.ConstructorData(internalTelepad).get
      )
    )
    sendResponse(GenericObjectActionMessage(uguid, 27))
    sendResponse(GenericObjectActionMessage(uguid, 30))
    /*
    the following configurations create the interactive beam underneath the Deployed Router
    normally dispatched after the warm-up timer has completed
     */
    sendResponse(GenericObjectActionMessage(uguid, 27))
    sendResponse(GenericObjectActionMessage(uguid, 28))
  }

  /**
    * na
    * @param telepadGUID na
    */
  def LinkRemoteTelepad(telepadGUID: PlanetSideGUID) : Unit = {
    sendResponse(GenericObjectActionMessage(telepadGUID, 27))
    sendResponse(GenericObjectActionMessage(telepadGUID, 28))
  }

  /**
    * A player uses a fully-linked Router teleportation system.
    * @param router the Router vehicle
    * @param internalTelepad the internal telepad within the Router vehicle
    * @param remoteTelepad the remote telepad that is currently associated with this Router
    * @param src the origin of the teleportation (where the player starts)
    * @param dest the destination of the teleportation (where the player is going)
    */
  def UseRouterTelepadSystem(router: Vehicle, internalTelepad: InternalTelepad, remoteTelepad: TelepadDeployable, src: PlanetSideGameObject with TelepadLike, dest: PlanetSideGameObject with TelepadLike) = {
    val time = System.nanoTime
    if(time - recentTeleportAttempt > (2 seconds).toNanos && router.DeploymentState == DriveState.Deployed && internalTelepad.Active && remoteTelepad.Active) {
      val pguid = player.GUID
      val sguid = src.GUID
      val dguid = dest.GUID
      StartBundlingPackets()
      sendResponse(PlayerStateShiftMessage(ShiftState(0, dest.Position, player.Orientation.z)))
      UseRouterTelepadEffect(pguid, sguid, dguid)
      StopBundlingPackets()
      continent.LocalEvents ! LocalServiceMessage(continent.Id, LocalAction.RouterTelepadTransport(pguid, pguid, sguid, dguid))
    }
    else {
      log.warn(s"UseRouterTelepadSystem: can not teleport")
    }
    recentTeleportAttempt = time
  }

  /**
    * Animate(?) a player using a fully-linked Router teleportation system.
    * In reality, this seems to do nothing visually?
    * @param playerGUID the player being teleported
    * @param srcGUID the origin of the teleportation
    * @param destGUID the destination of the teleportation
    */
  def UseRouterTelepadEffect(playerGUID : PlanetSideGUID, srcGUID : PlanetSideGUID, destGUID : PlanetSideGUID) : Unit = {
    sendResponse(PlanetsideAttributeMessage(playerGUID, 64, 1)) //what does this do?
    sendResponse(GenericObjectActionMessage(srcGUID, 31))
    sendResponse(GenericObjectActionMessage(destGUID, 32))
  }

  /**
    * Before a vehicle is removed from the game world, the following actions must be performed.
    * @param vehicle the vehicle
    */
  def BeforeUnloadVehicle(vehicle : Vehicle) : Unit = {
    vehicle.Definition match {
      case GlobalDefinitions.ams if vehicle.Faction == player.Faction =>
        log.info("BeforeUnload: cleaning up after a mobile spawn vehicle ...")
        continent.VehicleEvents ! VehicleServiceMessage(continent.Id, VehicleAction.UpdateAmsSpawnPoint(continent))
      case GlobalDefinitions.router =>
        //this may repeat for multiple players on the same continent but that's okay(?)
        log.info("BeforeUnload: cleaning up after a router ...")
        Deployables.RemoveTelepad(vehicle)
      case _ => ;
    }
  }

  /**
    * For a certain weapon that cna load ammunition, enforce that its magazine is empty.
    * @param weapon_guid the weapon
    */
  def EmptyMagazine(weapon_guid : PlanetSideGUID) : Unit = {
    continent.GUID(weapon_guid) match {
      case Some(tool : Tool) =>
        EmptyMagazine(weapon_guid, tool)
      case _ => ;
    }
  }

  /**
    * For a certain weapon that can load ammunition, enforce that its magazine is empty.
    * Punctuate that emptiness with a ceasation of weapons fire and a dry fire sound effect.
    * @param weapon_guid the weapon (GUID)
    * @param tool the weapon (object)
    */
  def EmptyMagazine(weapon_guid : PlanetSideGUID, tool : Tool) : Unit = {
    tool.Magazine = 0
    sendResponse(InventoryStateMessage(tool.AmmoSlot.Box.GUID, weapon_guid, 0))
    sendResponse(ChangeFireStateMessage_Stop(weapon_guid))
    continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.ChangeFireState_Stop(player.GUID, weapon_guid))
    sendResponse(WeaponDryFireMessage(weapon_guid))
    continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.WeaponDryFire(player.GUID, weapon_guid))
  }

  def SaveLoadoutToDB(owner : Player, label : String, line : Int) = {
    val charId = owner.CharId
    val exosuitId = owner.ExoSuit.id
    var clob : String = {
      val clobber : StringBuilder = new StringBuilder()
      //encode holsters
      owner.Holsters()
        .zipWithIndex
        .collect { case (slot, index) if slot.Equipment.nonEmpty =>
          clobber.append(EncodeLoadoutCLOBFragment(slot.Equipment.get, index))
        }
      //encode inventory
      owner.Inventory.Items.foreach { case InventoryItem(obj, index) =>
        clobber.append(EncodeLoadoutCLOBFragment(obj, index))
      }
      clobber.mkString
    }
    //database
    Database.getConnection.connect.onComplete {
      case scala.util.Success(connection) =>
        Database.query(connection.sendPreparedStatement(
          "SELECT id, exosuit_id, name, items FROM loadouts where characters_id = ? AND loadout_number = ?", Array(charId, line)
        )).onComplete {
          case scala.util.Success(queryResult) =>
            queryResult match {
              case row: ArrayRowData => // Update
                connection.sendPreparedStatement(
                  "UPDATE loadouts SET exosuit_id=?, name=?, items=? where id=?", Array(exosuitId, label, clob.drop(1), row(0))
                ).onComplete {
                  case _ =>
                    if(connection.isConnected) connection.disconnect
                }
                Thread.sleep(50)
              case _ => // Save
                connection.sendPreparedStatement(
                  "INSERT INTO loadouts (characters_id, loadout_number, exosuit_id, name, items) VALUES(?,?,?,?,?) RETURNING id",
                  Array(charId, line, exosuitId, label, clob.drop(1))
                ).onComplete {
                  case _ =>
                    if(connection.isConnected) connection.disconnect
                }
                Thread.sleep(50)
            }
          case scala.util.Failure(e) =>
            if(connection.isConnected) connection.disconnect
            log.error(s"SaveLoadoutToDB: query failed - ${e.getMessage}")
        }
      case scala.util.Failure(e) =>
        log.error(s"SaveLoadoutToDB: no connection ${e.getMessage}")
    }
  }

  /**
    * na
    * @param equipment
    * @param index
    * @return
    */
  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"
  }

  /**
    * A selection of up to ten customized equipment loadouts that are saved externally.
    * The loadouts are encoded through number and text and procedural assembly is required.
    * When loaded properly, these loadouts will become available through an equipment terminal entity
    * and will influence the equipment terminal to open to the equipment loadout selection tab called "Favorites."<br>
    * <br>
    * The operation requires a database connection and completion of a database transaction,
    * both of which must completed independently of any subsequent tasking,
    * especially if that future tasking may require database use.
    * @see `ClearHolstersAndInventory`
    * @see `Connection.sendPreparedStatement`
    * @see `Database.getConnection`
    * @see `ExoSuitType`
    * @see `Future`
    * @see `GetToolDefFromObjectID`
    * @see `Loadout`
    * @see `Player.EquipmentLoadouts`
    * @see `Player.EquipmentLoadouts.SaveLoadout`
    * @see `Promise`
    * @see `QueryResult`
    * @param owner the player who will be stipped of equipment
    * @return a `Future` predicated by the "promise" of the task being completed
    */
  def LoadDataBaseLoadouts(owner : Player) : Future[Any] = {
    val result : Promise[Any] = Promise[Any]()
    Database.getConnection.connect.onComplete {
      case scala.util.Success(connection) =>
        connection.sendPreparedStatement(
          "SELECT id, loadout_number, exosuit_id, name, items FROM loadouts where characters_id = ?", Array(owner.CharId)
        ).onComplete {
          case Success(queryResult) =>
            if(connection.isConnected) connection.disconnect
            queryResult match {
              case result: QueryResult =>
                if (result.rows.nonEmpty) {
                  val doll = new Player(Avatar("doll", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)) //play dress up
                  log.debug(s"LoadDataBaseLoadouts: ${result.rows.size} saved loadout(s) for character with id ${owner.CharId}")
                  result.rows foreach{ row  =>
                    row.zipWithIndex.foreach{ case (value,i) =>
                      val lLoadoutNumber : Int = value(1).asInstanceOf[Int]
                      val lExosuitId : Int = value(2).asInstanceOf[Int]
                      val lName : String = value(3).asInstanceOf[String]
                      val lItems : String = value(4).asInstanceOf[String]

                      doll.ExoSuit = ExoSuitType(lExosuitId)

                      val args = lItems.split("/")
                      args.indices.foreach(i => {
                        val args2 = args(i).split(",")
                        val lType = args2(0)
                        val lIndex : Int = args2(1).toInt
                        val lObjectId : Int = args2(2).toInt

                        lType match {
                          case "Tool" =>
                            doll.Slot(lIndex).Equipment = Tool(GetToolDefFromObjectID(lObjectId).asInstanceOf[ToolDefinition])
                          case "AmmoBox" =>
                            doll.Slot(lIndex).Equipment = AmmoBox(GetToolDefFromObjectID(lObjectId).asInstanceOf[AmmoBoxDefinition])
                          case "ConstructionItem" =>
                            doll.Slot(lIndex).Equipment = ConstructionItem(GetToolDefFromObjectID(lObjectId).asInstanceOf[ConstructionItemDefinition])
                          case "SimpleItem" =>
                            doll.Slot(lIndex).Equipment = SimpleItem(GetToolDefFromObjectID(lObjectId).asInstanceOf[SimpleItemDefinition])
                          case "Kit" =>
                            doll.Slot(lIndex).Equipment = Kit(GetToolDefFromObjectID(lObjectId).asInstanceOf[KitDefinition])
                          case thing =>
                            log.warn(s"LoadDataBaseLoadouts: what's that $thing doing in the loadout?")
                        }
                        if (args2.length == 4) { //tool ammo info
                          val args3 = args2(3).split("_")
                          (1 until args3.length).foreach(j => {
                            val args4 = args3(j).split("-")
                            val lAmmoSlots = args4(0).toInt
                            val lAmmoTypeIndex = args4(1).toInt
                            val lAmmoBoxDefinition = args4(2).toInt
                            doll.Slot(lIndex).Equipment.get.asInstanceOf[Tool].AmmoSlots(lAmmoSlots).AmmoTypeIndex = lAmmoTypeIndex
                            doll.Slot(lIndex).Equipment.get.asInstanceOf[Tool].AmmoSlots(lAmmoSlots).Box = AmmoBox(AmmoBoxDefinition(lAmmoBoxDefinition))
                          })
                        }
                      })
                      owner.EquipmentLoadouts.SaveLoadout(doll, lName, lLoadoutNumber)
                      ClearHolstersAndInventory(doll)
                    }
                    // something to do at end of loading ?
                  }
                }
              case _ =>
                log.debug(s"LoadDataBaseLoadouts: no saved loadout(s) for character with id ${owner.CharId}")
            }
            result success queryResult
          case scala.util.Failure(e) =>
            if(connection.isConnected) connection.disconnect
            val msg = s"LoadDataBaseLoadouts: unexpected query result - ${e.getMessage}"
            log.error(msg)
            result failure new Throwable(msg)
        }
      case scala.util.Failure(e) =>
        val msg = s"LoadDataBaseLoadouts: no connection - ${e.getMessage}"
        log.error(msg)
        result failure new Throwable(msg)
    }
    result.future
  }

  /**
    * Remove the equipment from all holsters and from out of the player inventory,
    * no matter how spacious it is.
    * @param target the player who will be stipped of equipment
    */
  def ClearHolstersAndInventory(target : Player) : Unit = {
    (0 until 4).foreach( index => {
      target.Slot(index).Equipment = None
    })
    target.Inventory.Clear()
  }

  /**
    * The "default loadout."
    * The selection of equipment that a player respawns in possession of after dying.
    * Excepting the pistol (slot 0) and its ammo and the melee weapon, which are all faction-associated,
    * all equipment is generic.
    * All equipment belongs to the implicit `Standard` certification.
    * @param target the player who will be assigned this selection of equipment
    */
  def LoadClassicDefault(target : Player) : Unit = {
    val faction = target.Faction
    target.ExoSuit = ExoSuitType.Standard
    target.Slot(0).Equipment = Tool(GlobalDefinitions.StandardPistol(faction))
    target.Slot(2).Equipment = Tool(GlobalDefinitions.suppressor)
    target.Slot(4).Equipment = Tool(GlobalDefinitions.StandardMelee(faction))
    target.Slot(6).Equipment = AmmoBox(GlobalDefinitions.bullet_9mm)
    target.Slot(9).Equipment = AmmoBox(GlobalDefinitions.bullet_9mm)
    target.Slot(12).Equipment = AmmoBox(GlobalDefinitions.bullet_9mm)
    target.Slot(33).Equipment = AmmoBox(GlobalDefinitions.bullet_9mm_AP)
    target.Slot(36).Equipment = AmmoBox(GlobalDefinitions.StandardPistolAmmo(faction))
    target.Slot(39).Equipment = SimpleItem(GlobalDefinitions.remote_electronics_kit)
    target.Inventory.Items.foreach { _.obj.Faction = faction }
  }

  /**
    * A selection of ten customized equipment loadouts.
    * Currently, unused.
    * @see `ClearHolstersAndInventory`
    * @see `GlobalDefinitions`
    * @see `Player.EquipmentLoadouts`
    * @see `Player.EquipmentLoadouts.SaveLoadout`
    * @see `Player.ExoSuit`
    * @see `Player.Slot`
    * @param target the player who will be assigned these loadouts
    */
  def LoadDefaultLoadouts(target : Player) : Unit = {
    //cached defaults
    val faction = target.Faction
    val aiMaxAmmo = AmmoBox(GlobalDefinitions.AI_MAXAmmo(faction))
    val avMaxAmmo = AmmoBox(GlobalDefinitions.AV_MAXAmmo(faction))
    val antiVehicularAmmo = AmmoBox(GlobalDefinitions.AntiVehicularAmmo(faction))
    val armorCanister = AmmoBox(GlobalDefinitions.armor_canister)
    val bank = Tool(GlobalDefinitions.bank)
    val bolt = AmmoBox(GlobalDefinitions.bolt)
    val decimator = Tool(GlobalDefinitions.phoenix)
    val fragGrenade = Tool(GlobalDefinitions.frag_grenade)
    val fragCartridge = AmmoBox(GlobalDefinitions.frag_cartridge)
    val healthCanister = AmmoBox(GlobalDefinitions.health_canister)
    val heavyRifle = Tool(GlobalDefinitions.HeavyRifle(faction))
    val heavyRifleAmmo= AmmoBox(GlobalDefinitions.HeavyRifleAmmo(faction))
    val heavyRifleAPAmmo= AmmoBox(GlobalDefinitions.HeavyRifleAPAmmo(faction))
    val jammerGrenade = Tool(GlobalDefinitions.jammer_grenade)
    val medicalApplicator = Tool(GlobalDefinitions.medicalapplicator)
    val mediumRifle = Tool(GlobalDefinitions.MediumRifle(faction))
    val mediumRifleAmmo = AmmoBox(GlobalDefinitions.MediumRifleAmmo(faction))
    val medkit = Kit(GlobalDefinitions.medkit)
    val rek = SimpleItem(GlobalDefinitions.remote_electronics_kit)
    val rocket = AmmoBox(GlobalDefinitions.rocket)
    val shotgunAmmo = AmmoBox(GlobalDefinitions.shotgun_shell)
    //
    val doll = new Player(Avatar("doll", faction, CharacterGender.Male, 0, CharacterVoice.Mute)) //play dress up
    doll.Slot(4).Equipment = Tool(GlobalDefinitions.StandardMelee(faction)) //will not be cleared
    // 1
    doll.ExoSuit = ExoSuitType.Agile
    doll.Slot(0).Equipment = fragGrenade
    doll.Slot(1).Equipment = bank
    doll.Slot(2).Equipment = heavyRifle
    doll.Slot(6).Equipment = medicalApplicator
    doll.Slot(9).Equipment = heavyRifleAmmo
    doll.Slot(12).Equipment = heavyRifleAmmo
    doll.Slot(33).Equipment = decimator
    doll.Slot(60).Equipment = rek
    doll.Slot(72).Equipment = jammerGrenade
    doll.Slot(74).Equipment = medkit
    target.EquipmentLoadouts.SaveLoadout(doll, "Agile HA/Deci", 0)
    ClearHolstersAndInventory(doll)
    // 2
    doll.ExoSuit = ExoSuitType.Agile
    doll.Slot(0).Equipment = fragGrenade
    doll.Slot(1).Equipment = fragGrenade
    doll.Slot(2).Equipment = heavyRifle
    doll.Slot(6).Equipment = heavyRifleAmmo
    doll.Slot(9).Equipment = heavyRifleAmmo
    doll.Slot(12).Equipment = rek
    doll.Slot(33).Equipment = medicalApplicator
    doll.Slot(36).Equipment = fragGrenade
    doll.Slot(38).Equipment = medkit
    doll.Slot(54).Equipment = fragGrenade
    doll.Slot(56).Equipment = medkit
    doll.Slot(60).Equipment = bank
    doll.Slot(72).Equipment = jammerGrenade
    doll.Slot(74).Equipment = medkit
    target.EquipmentLoadouts.SaveLoadout(doll, "Agile HA", 1)
    ClearHolstersAndInventory(doll)
    // 3
    doll.ExoSuit = ExoSuitType.Reinforced
    doll.Slot(0).Equipment = medicalApplicator
    doll.Slot(1).Equipment = rek
    doll.Slot(2).Equipment = heavyRifle
    doll.Slot(3).Equipment = decimator
    doll.Slot(6).Equipment = heavyRifleAmmo
    doll.Slot(9).Equipment = heavyRifleAmmo
    doll.Slot(12).Equipment = medkit
    doll.Slot(16).Equipment = fragGrenade
    doll.Slot(36).Equipment = medkit
    doll.Slot(40).Equipment = fragGrenade
    doll.Slot(42).Equipment = heavyRifleAmmo
    doll.Slot(45).Equipment = heavyRifleAPAmmo
    doll.Slot(60).Equipment = medkit
    doll.Slot(64).Equipment = jammerGrenade
    doll.Slot(78).Equipment = decimator
    doll.Slot(87).Equipment = bank
    target.EquipmentLoadouts.SaveLoadout(doll, "Rexo HA/Deci", 2)
    ClearHolstersAndInventory(doll)
    // 4
    doll.ExoSuit = ExoSuitType.Reinforced
    doll.Slot(0).Equipment = medicalApplicator
    doll.Slot(1).Equipment = rek
    doll.Slot(2).Equipment = mediumRifle
    doll.Slot(3).Equipment = Tool(GlobalDefinitions.AntiVehicularLauncher(faction))
    doll.Slot(6).Equipment = mediumRifleAmmo
    doll.Slot(9).Equipment = mediumRifleAmmo
    doll.Slot(12).Equipment = mediumRifleAmmo
    doll.Slot(15).Equipment = bank
    doll.Slot(42).Equipment = fragGrenade
    doll.Slot(44).Equipment = jammerGrenade
    doll.Slot(46).Equipment = medkit
    doll.Slot(50).Equipment = medkit
    doll.Slot(66).Equipment = antiVehicularAmmo
    doll.Slot(70).Equipment = antiVehicularAmmo
    doll.Slot(74).Equipment = antiVehicularAmmo
    target.EquipmentLoadouts.SaveLoadout(doll, "Rexo MA/AV", 3)
    ClearHolstersAndInventory(doll)
    // 5
    doll.ExoSuit = ExoSuitType.Reinforced
    doll.Slot(0).Equipment = medicalApplicator
    doll.Slot(1).Equipment = rek
    doll.Slot(2).Equipment = heavyRifle
    doll.Slot(3).Equipment = Tool(GlobalDefinitions.thumper)
    doll.Slot(6).Equipment = heavyRifleAmmo
    doll.Slot(9).Equipment = heavyRifleAmmo
    doll.Slot(12).Equipment = medkit
    doll.Slot(16).Equipment = fragGrenade
    doll.Slot(36).Equipment = medkit
    doll.Slot(40).Equipment = fragGrenade
    doll.Slot(42).Equipment = heavyRifleAmmo
    doll.Slot(45).Equipment = heavyRifleAPAmmo
    doll.Slot(60).Equipment = medkit
    doll.Slot(64).Equipment = jammerGrenade
    doll.Slot(78).Equipment = bank
    doll.Slot(81).Equipment = fragCartridge
    doll.Slot(84).Equipment = fragCartridge
    doll.Slot(87).Equipment = fragCartridge
    target.EquipmentLoadouts.SaveLoadout(doll, "Rexo HA/Thumper", 4)
    ClearHolstersAndInventory(doll)
    // 6
    doll.ExoSuit = ExoSuitType.Reinforced
    doll.Slot(0).Equipment = medicalApplicator
    doll.Slot(1).Equipment = rek
    doll.Slot(2).Equipment = heavyRifle
    doll.Slot(3).Equipment = Tool(GlobalDefinitions.rocklet)
    doll.Slot(6).Equipment = heavyRifleAmmo
    doll.Slot(9).Equipment = heavyRifleAmmo
    doll.Slot(12).Equipment = medkit
    doll.Slot(16).Equipment = fragGrenade
    doll.Slot(36).Equipment = medkit
    doll.Slot(40).Equipment = fragGrenade
    doll.Slot(42).Equipment = heavyRifleAmmo
    doll.Slot(45).Equipment = heavyRifleAPAmmo
    doll.Slot(60).Equipment = medkit
    doll.Slot(64).Equipment = jammerGrenade
    doll.Slot(78).Equipment = bank
    doll.Slot(81).Equipment = rocket
    doll.Slot(84).Equipment = rocket
    doll.Slot(87).Equipment = fragCartridge
    target.EquipmentLoadouts.SaveLoadout(doll, "Rexo HA/Rocklet", 5)
    ClearHolstersAndInventory(doll)
    // 7
    doll.ExoSuit = ExoSuitType.Reinforced
    doll.Slot(0).Equipment = medicalApplicator
    doll.Slot(1).Equipment = rek
    doll.Slot(2).Equipment = mediumRifle
    doll.Slot(3).Equipment = Tool(GlobalDefinitions.bolt_driver)
    doll.Slot(6).Equipment = mediumRifleAmmo
    doll.Slot(9).Equipment = mediumRifleAmmo
    doll.Slot(12).Equipment = medkit
    doll.Slot(16).Equipment = fragGrenade
    doll.Slot(36).Equipment = medkit
    doll.Slot(40).Equipment = fragGrenade
    doll.Slot(42).Equipment = mediumRifleAmmo
    doll.Slot(45).Equipment = mediumRifleAmmo
    doll.Slot(60).Equipment = medkit
    doll.Slot(64).Equipment = jammerGrenade
    doll.Slot(78).Equipment = bank
    doll.Slot(81).Equipment = bolt
    doll.Slot(84).Equipment = bolt
    doll.Slot(87).Equipment = bolt
    target.EquipmentLoadouts.SaveLoadout(doll, "Rexo MA/Sniper", 6)
    ClearHolstersAndInventory(doll)
    // 8
    doll.ExoSuit = ExoSuitType.Reinforced
    doll.Slot(0).Equipment = medicalApplicator
    doll.Slot(1).Equipment = rek
    doll.Slot(2).Equipment = Tool(GlobalDefinitions.flechette)
    doll.Slot(3).Equipment = decimator
    doll.Slot(6).Equipment = shotgunAmmo
    doll.Slot(9).Equipment = shotgunAmmo
    doll.Slot(12).Equipment = medkit
    doll.Slot(16).Equipment = fragGrenade
    doll.Slot(36).Equipment = medkit
    doll.Slot(40).Equipment = fragGrenade
    doll.Slot(42).Equipment = shotgunAmmo
    doll.Slot(45).Equipment = AmmoBox(GlobalDefinitions.shotgun_shell_AP)
    doll.Slot(60).Equipment = medkit
    doll.Slot(64).Equipment = jammerGrenade
    doll.Slot(78).Equipment = decimator
    doll.Slot(87).Equipment = bank
    target.EquipmentLoadouts.SaveLoadout(doll, "Rexo Sweeper/Deci", 7)
    ClearHolstersAndInventory(doll)
    // 9
    doll.ExoSuit = ExoSuitType.MAX
    doll.Slot(0).Equipment = Tool(GlobalDefinitions.AI_MAX(faction))
    doll.Slot(6).Equipment = aiMaxAmmo
    doll.Slot(10).Equipment = aiMaxAmmo
    doll.Slot(14).Equipment = aiMaxAmmo
    doll.Slot(18).Equipment = aiMaxAmmo
    doll.Slot(70).Equipment = aiMaxAmmo
    doll.Slot(74).Equipment = aiMaxAmmo
    doll.Slot(78).Equipment = medkit
    doll.Slot(98).Equipment = healthCanister
    doll.Slot(100).Equipment = armorCanister
    doll.Slot(110).Equipment = medkit
    doll.Slot(134).Equipment = medkit
    doll.Slot(138).Equipment = medkit
    doll.Slot(142).Equipment = medkit
    doll.Slot(146).Equipment = medkit
    doll.Slot(166).Equipment = medkit
    doll.Slot(170).Equipment = medkit
    doll.Slot(174).Equipment = medkit
    doll.Slot(178).Equipment = medkit
    target.EquipmentLoadouts.SaveLoadout(doll, "AI MAX", 8)
    ClearHolstersAndInventory(doll)
    // 10
    doll.ExoSuit = ExoSuitType.MAX
    doll.Slot(0).Equipment = Tool(GlobalDefinitions.AV_MAX(faction))
    doll.Slot(6).Equipment = avMaxAmmo
    doll.Slot(10).Equipment = avMaxAmmo
    doll.Slot(14).Equipment = avMaxAmmo
    doll.Slot(18).Equipment = avMaxAmmo
    doll.Slot(70).Equipment = avMaxAmmo
    doll.Slot(74).Equipment = avMaxAmmo
    doll.Slot(78).Equipment = medkit
    doll.Slot(98).Equipment = healthCanister
    doll.Slot(100).Equipment = armorCanister
    doll.Slot(110).Equipment = medkit
    doll.Slot(134).Equipment = medkit
    doll.Slot(138).Equipment = medkit
    doll.Slot(142).Equipment = medkit
    doll.Slot(146).Equipment = medkit
    doll.Slot(166).Equipment = medkit
    doll.Slot(170).Equipment = medkit
    doll.Slot(174).Equipment = medkit
    doll.Slot(178).Equipment = medkit
    target.EquipmentLoadouts.SaveLoadout(doll, "AV MAX", 9)
  }

  def GetToolDefFromObjectID(objectID : Int) : Any = {
    import net.psforever.objects.GlobalDefinitions._
    objectID match {
      //ammunition
      case 0 => bullet_105mm
      case 3 => bullet_12mm
      case 6 => bullet_150mm
      case 9 => bullet_15mm
      case 16 => bullet_20mm
      case 19 => bullet_25mm
      case 21 => bullet_35mm
      case 25 => bullet_75mm
      case 28 => bullet_9mm
      case 29 => bullet_9mm_AP
      case 50 => ancient_ammo_combo
      case 51 => ancient_ammo_vehicle
      case 54 => anniversary_ammo
      case 86 => aphelion_immolation_cannon_ammo
      case 89 => aphelion_laser_ammo
      case 97 => aphelion_plasma_rocket_ammo
      case 101 => aphelion_ppa_ammo
      case 106 => aphelion_starfire_ammo
      case 111 => armor_canister
      case 145 => bolt
      case 154 => burster_ammo
      case 180 => colossus_100mm_cannon_ammo
      case 186 => colossus_burster_ammo
      case 191 => colossus_chaingun_ammo
      case 195 => colossus_cluster_bomb_ammo
      case 205 => colossus_tank_cannon_ammo
      case 209 => comet_ammo
      case 265 => dualcycler_ammo
      case 272 => energy_cell
      case 275 => energy_gun_ammo
      case 285 => falcon_ammo
      case 287 => firebird_missile
      case 300 => flamethrower_ammo
      case 307 => flux_cannon_thresher_battery
      case 310 => fluxpod_ammo
      case 327 => frag_cartridge
      case 331 => frag_grenade_ammo
      case 347 => gauss_cannon_ammo
      case 389 => health_canister
      case 391 => heavy_grenade_mortar
      case 393 => heavy_rail_beam_battery
      case 399 => hellfire_ammo
      case 403 => hunter_seeker_missile
      case 413 => jammer_cartridge
      case 417 => jammer_grenade_ammo
      case 426 => lancer_cartridge
      case 434 => liberator_bomb
      case 463 => maelstrom_ammo
      case 540 => melee_ammo
      case 600 => oicw_ammo
      case 630 => pellet_gun_ammo
      case 637 => peregrine_dual_machine_gun_ammo
      case 645 => peregrine_mechhammer_ammo
      case 653 => peregrine_particle_cannon_ammo
      case 656 => peregrine_rocket_pod_ammo
      case 659 => peregrine_sparrow_ammo
      case 664 => phalanx_ammo
      case 674 => phoenix_missile
      case 677 => plasma_cartridge
      case 681 => plasma_grenade_ammo
      case 693 => pounder_ammo
      case 704 => pulse_battery
      case 712 => quasar_ammo
      case 722 => reaver_rocket
      case 734 => rocket
      case 745 => scattercannon_ammo
      case 755 => shotgun_shell
      case 756 => shotgun_shell_AP
      case 762 => six_shooter_ammo
      case 786 => skyguard_flak_cannon_ammo
      case 791 => sparrow_ammo
      case 820 => spitfire_aa_ammo
      case 823 => spitfire_ammo
      case 830 => starfire_ammo
      case 839 => striker_missile_ammo
      case 877 => trek_ammo
      case 922 => upgrade_canister
      case 998 => wasp_gun_ammo
      case 1000 => wasp_rocket_ammo
      case 1004 => winchester_ammo
      //weapons
      case 14 => cannon_dropship_20mm
      case 40 => advanced_missile_launcher_t
      case 55 => anniversary_gun
      case 56 => anniversary_guna
      case 57 => anniversary_gunb
      case 63 => apc_ballgun_l
      case 64 => apc_ballgun_r
      case 69 => apc_weapon_systema
      case 70 => apc_weapon_systemb
      case 72 => apc_weapon_systemc_nc
      case 73 => apc_weapon_systemc_tr
      case 74 => apc_weapon_systemc_vs
      case 76 => apc_weapon_systemd_nc
      case 77 => apc_weapon_systemd_tr
      case 78 => apc_weapon_systemd_vs
      case 119 => aurora_weapon_systema
      case 120 => aurora_weapon_systemb
      case 136 => battlewagon_weapon_systema
      case 137 => battlewagon_weapon_systemb
      case 138 => battlewagon_weapon_systemc
      case 139 => battlewagon_weapon_systemd
      case 140 => beamer
      case 146 => bolt_driver
      case 175 => chainblade
      case 177 => chaingun_p
      case 233 => cycler
      case 262 => dropship_rear_turret
      case 274 => energy_gun
      case 276 => energy_gun_nc
      case 278 => energy_gun_tr
      case 280 => energy_gun_vs
      case 298 => flail_weapon
      case 299 => flamethrower
      case 304 => flechette
      case 306 => flux_cannon_thresher
      case 324 => forceblade
      case 336 => fury_weapon_systema
      case 339 => galaxy_gunship_cannon
      case 340 => galaxy_gunship_gun
      case 342 => galaxy_gunship_tailgun
      case 345 => gauss
      case 371 => grenade_launcher_marauder
      case 394 => heavy_rail_beam_magrider
      case 396 => heavy_sniper
      case 406 => hunterseeker
      case 407 => ilc9
      case 411 => isp
      case 421 => katana
      case 425 => lancer
      case 429 => lasher
      case 433 => liberator_25mm_cannon
      case 435 => liberator_bomb_bay
      case 440 => liberator_weapon_system
      case 445 => lightgunship_weapon_system
      case 448 => lightning_weapon_system
      case 462 => maelstrom
      case 468 => magcutter
      case 534 => mediumtransport_weapon_systemA
      case 535 => mediumtransport_weapon_systemB
      case 556 => mini_chaingun
      case 587 => nchev_falcon
      case 588 => nchev_scattercannon
      case 589 => nchev_sparrow
      case 599 => oicw
      case 628 => particle_beam_magrider
      case 629 => pellet_gun
      case 666 => phalanx_avcombo
      case 668 => phalanx_flakcombo
      case 670 => phalanx_sgl_hevgatcan
      case 673 => phoenix
      case 699 => prowler_weapon_systemA
      case 700 => prowler_weapon_systemB
      case 701 => pulsar
      case 706 => punisher
      case 709 => quadassault_weapon_system
      case 714 => r_shotgun
      case 716 => radiator
      case 730 => repeater
      case 737 => rocklet
      case 740 => rotarychaingun_mosquito
      case 747 => scythe
      case 761 => six_shooter
      case 788 => skyguard_weapon_system
      case 817 => spiker
      case 822 => spitfire_aa_weapon
      case 827 => spitfire_weapon
      case 838 => striker
      case 845 => suppressor
      case 864 => thumper
      case 866 => thunderer_weapon_systema
      case 867 => thunderer_weapon_systemb
      case 888 => trhev_burster
      case 889 => trhev_dualcycler
      case 890 => trhev_pounder
      case 927 => vanguard_weapon_system
      case 968 => vshev_comet
      case 969 => vshev_quasar
      case 970 => vshev_starfire
      case 987 => vulture_bomb_bay
      case 990 => vulture_nose_weapon_system
      case 992 => vulture_tail_cannon
      case 1002 => wasp_weapon_system
      case 1003 => winchester
      case 267 => dynomite
      case 330 => frag_grenade
      case 416 => jammer_grenade
      case 680 => plasma_grenade
      //medkits
      case 536 => medkit
      case 842 => super_armorkit
      case 843 => super_medkit
      case 844 => super_staminakit
      //tools
      case 728 => remote_electronics_kit
      case 876 => trek
      case 531 => medicalapplicator
      case 132 => bank
      case 577 => nano_dispenser
      case 213 => command_detonater
      case 297 => flail_targeting_laser
      //deployables
      case 32 => ace
      case 39 => advanced_ace
      case 148 => boomer
      case 149 => boomer_trigger
      case _ => frag_grenade
    }
  }
  /**
    * Make this client display the deployment map, and all its available destination spawn points.
    * @see `AvatarDeadStateMessage`
    * @see `DeadState.Release`
    * @see `Player.Release`
    */
  def GoToDeploymentMap() : Unit = {
    player.Release
    deadState = DeadState.Release
    sendResponse(AvatarDeadStateMessage(DeadState.Release, 0, 0, player.Position, player.Faction, true))
    DrawCurrentAmsSpawnPoint()
  }

  /**
    * From a seat, find the weapon controlled from it, and update the ammunition counts for that weapon's magazines.
    * @param objWithSeat the object that owns seats (and weaponry)
    * @param seatNum the seat
    */
  def UpdateWeaponAtSeatPosition(objWithSeat : MountedWeapons, seatNum : Int) : Unit = {
    objWithSeat.WeaponControlledFromSeat(seatNum) match {
      case Some(weapon : Tool) =>
        //update mounted weapon belonging to seat
        weapon.AmmoSlots.foreach(slot => {
          //update the magazine(s) in the weapon, specifically
          val magazine = slot.Box
          sendResponse(InventoryStateMessage(magazine.GUID, weapon.GUID, magazine.Capacity.toLong))
        })
      case _ => ; //no weapons to update
    }
  }

  /**
    * Given an origin and a destination, determine how long the process of traveling should take in reconstruction time.
    * For most destinations, the unit of receiving ("spawn point") determines the reconstruction time.
    * In a special consideration, travel to any sanctuary or sanctuary-special zone should be as immediate as zone loading.
    * @param toZoneId the zone where the target is headed
    * @param toSpawnPoint the unit the target is using as a destination
    * @param fromZoneId the zone where the target current is located
    * @return how long in seconds the spawning process will take
    */
  def CountSpawnDelay(toZoneId : String, toSpawnPoint : SpawnPoint, fromZoneId : String) : Long = {
    val sanctuaryZoneId = Zones.SanctuaryZoneId(player.Faction)
    if(fromZoneId.equals("Nowhere") || sanctuaryZoneId.equals(toZoneId)) { //to sanctuary
      0L
    }
    else if(!player.isAlive) {
      toSpawnPoint.Definition.Delay //TODO +cumulative death penalty
    }
    else {
      toSpawnPoint.Definition.Delay
    }
  }

  /**
    * In the background, a list of advanced mobile spawn vehicles that are deployed in the zone is being updated constantly.
    * Select, from this list, the AMS that is closest to the player's current or last position
    * and draw its spawn selection icon onto the deployment map.
    * @see `BindPlayerMessage`
    * @see `DeadState.Release`
    */
  def DrawCurrentAmsSpawnPoint() : Unit = {
    if(deadState == DeadState.Release) {
      amsSpawnPoints
        .sortBy(tube => Vector3.DistanceSquared(tube.Position, player.Position))
        .headOption match {
        case Some(tube) =>
          log.info("DrawCurrentAmsSpawnPoint - new @ams spawn point drawn")
          sendResponse(BindPlayerMessage(BindStatus.Available, "@ams", true, false, SpawnGroup.AMS, continent.Number, 5, tube.Position))
        case None =>
          log.info("DrawCurrentAmsSpawnPoint - no @ams spawn point drawn")
          sendResponse(BindPlayerMessage(BindStatus.Unavailable, "@ams", false, false, SpawnGroup.AMS, continent.Number, 0, Vector3.Zero))
      }
    }
  }

  def SwapSquadUIElements(squad : Squad, fromIndex : Int, toIndex : Int) : Unit = {
    if(squadUI.nonEmpty) {
      val fromMember = squad.Membership(toIndex) //the players have already been swapped in the backend object
      val fromCharId = fromMember.CharId
      val toMember = squad.Membership(fromIndex) //the players have already been swapped in the backend object
      val toCharId = toMember.CharId
      val id = 11
      if(toCharId > 0) {
        //toMember and fromMember have swapped places
        val fromElem = squadUI(fromCharId)
        val toElem = squadUI(toCharId)
        squadUI(toCharId) = SquadUIElement(fromElem.name, toIndex, fromElem.zone, fromElem.health, fromElem.armor, fromElem.position)
        squadUI(fromCharId) = SquadUIElement(toElem.name, fromIndex, toElem.zone, toElem.health, toElem.armor, toElem.position)
        sendResponse(SquadMemberEvent.Add(id, toCharId, toIndex, fromElem.name, fromElem.zone, unk7 = 0))
        sendResponse(SquadMemberEvent.Add(id, fromCharId, fromIndex, toElem.name, toElem.zone, unk7 = 0))
        sendResponse(
          SquadState(
            PlanetSideGUID(id),
            List(
              SquadStateInfo(fromCharId, toElem.health, toElem.armor, toElem.position, 2, 2, false, 429, None, None),
              SquadStateInfo(toCharId, fromElem.health, fromElem.armor, fromElem.position, 2, 2, false, 429, None, None)
            )
          )
        )
      }
      else {
        //previous fromMember has moved toMember
        val elem = squadUI(fromCharId)
        squadUI(fromCharId) = SquadUIElement(elem.name, toIndex, elem.zone, elem.health, elem.armor, elem.position)
        sendResponse(SquadMemberEvent.Remove(id, fromCharId, fromIndex))
        sendResponse(SquadMemberEvent.Add(id, fromCharId, toIndex, elem.name, elem.zone, unk7 = 0))
        sendResponse(
          SquadState(
            PlanetSideGUID(id),
            List(SquadStateInfo(fromCharId, elem.health, elem.armor, elem.position, 2, 2, false, 429, None, None))
          )
        )
      }
      val charId = avatar.CharId
      if(toCharId == charId) {
        sendResponse(PlanetsideAttributeMessage(player.GUID, 32, toIndex))
      }
      else if(fromCharId == charId) {
        sendResponse(PlanetsideAttributeMessage(player.GUID, 32, fromIndex))
      }
    }
  }

  def NoSquadUpdates() : Unit = { }

  def SquadUpdates() : Unit = {
    squadService ! SquadServiceMessage(
      player,
      continent,
      continent.GUID(player.VehicleSeated) match {
        case Some(vehicle : Vehicle) =>
          SquadServiceAction.Update(player.CharId, vehicle.Health, vehicle.MaxHealth, vehicle.Shields, vehicle.MaxShields, vehicle.Position, continent.Number)
        case Some(obj : PlanetSideGameObject with WeaponTurret) =>
          SquadServiceAction.Update(player.CharId, obj.Health, obj.MaxHealth, 0, 0, obj.Position, continent.Number)
        case _ =>
          SquadServiceAction.Update(player.CharId, player.Health, player.MaxHealth, player.Armor, player.MaxArmor, player.Position, continent.Number)
      }
    )
  }

  def PeriodicUpdatesWhenEnrolledInSquad() : Unit = {
    queuedSquadActions(squadUpdateCounter)()
    squadUpdateCounter = (squadUpdateCounter + 1) % queuedSquadActions.length
  }

  def CapacitorTick(jump_thrust : Boolean): Unit = {
    if(player.ExoSuit == ExoSuitType.MAX) {
      //Discharge
      if(jump_thrust || player.isOverdrived || player.isShielded) {
        if(player.CapacitorState == CapacitorStateType.Discharging) {
          // Previous tick was already discharging, calculate how much energy to drain from time between the two ticks
          val timeDiff = (System.currentTimeMillis() - player.CapacitorLastUsedMillis).toFloat / 1000
          val drainAmount = player.ExoSuitDef.CapacitorDrainPerSecond.toFloat * timeDiff
          player.Capacitor -= drainAmount
          sendResponse(PlanetsideAttributeMessage(player.GUID, 7, player.Capacitor.toInt))
        } else {
          // Start discharging
          player.CapacitorState = CapacitorStateType.Discharging
        }
      }
      // Charge
      else if(player.Capacitor < player.ExoSuitDef.MaxCapacitor
        && (player.CapacitorState == CapacitorStateType.Idle || player.CapacitorState == CapacitorStateType.Charging || (player.CapacitorState == CapacitorStateType.ChargeDelay && System.currentTimeMillis() - player.CapacitorLastUsedMillis > player.ExoSuitDef.CapacitorRechargeDelayMillis)))
      {
        if(player.CapacitorState == CapacitorStateType.Charging) {
          val timeDiff = (System.currentTimeMillis() - player.CapacitorLastChargedMillis).toFloat / 1000
          val chargeAmount = player.ExoSuitDef.CapacitorRechargePerSecond * timeDiff
          player.Capacitor += chargeAmount
          sendResponse(PlanetsideAttributeMessage(player.GUID, 7, player.Capacitor.toInt))
        } else {
          player.CapacitorState = CapacitorStateType.Charging
        }
      }

      if(player.Faction == PlanetSideEmpire.VS) {
        // Start charge delay for VS when not boosting
        if(!jump_thrust && player.CapacitorState == CapacitorStateType.Discharging ) {
          player.CapacitorState = CapacitorStateType.ChargeDelay
        }
      }
      else {
        // Start charge delay for other factions if capacitor is empty or special ability is off
        if(player.CapacitorState == CapacitorStateType.Discharging && (player.Capacitor == 0 || (!player.isOverdrived && !player.isShielded))) {
          player.CapacitorState = CapacitorStateType.ChargeDelay
          ToggleMaxSpecialState(enable = false)
        }
      }
    }
    else {
      if(player.CapacitorState != CapacitorStateType.Idle) {
        player.CapacitorState = CapacitorStateType.Idle
      }
    }
  }

  def ToggleMaxSpecialState(enable : Boolean): Unit = {
    if(player.ExoSuit == ExoSuitType.MAX) {
      if(enable) {
        player.Faction match {
          case PlanetSideEmpire.TR => if(player.Capacitor == player.ExoSuitDef.MaxCapacitor) player.UsingSpecial = SpecialExoSuitDefinition.Mode.Overdrive
          case PlanetSideEmpire.NC => if(player.Capacitor > 0) player.UsingSpecial = SpecialExoSuitDefinition.Mode.Shielded
          case _ => log.warn(s"Player ${player.Name} tried to use a MAX special ability but their faction doesn't have one")
        }
        if(player.UsingSpecial == SpecialExoSuitDefinition.Mode.Overdrive || player.UsingSpecial == SpecialExoSuitDefinition.Mode.Shielded) {
          continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttributeToAll(player.GUID, 8, 1))
        }
      }
      else {
        player.UsingSpecial = SpecialExoSuitDefinition.Mode.Normal
        continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttributeToAll(player.GUID, 8, 0))
      }
    }
  }

  /**
    * The main purpose of this method is to determine which targets will receive "locked on" warnings from remote projectiles.
    * For a given series of globally unique identifiers, indicating targets,
    * and that may include mounted elements (players),
    * estimate a series of channel names for communication with the vulnerable targets.
    * @param targets the globally unique identifiers of the immediate detected targets
    * @return channels names that allow direct communication to specific realized targets
    */
  def FindDetectedProjectileTargets(targets : Iterable[PlanetSideGUID]) : Iterable[String] = {
    targets
      .map { ValidObject }
      .flatMap {
        case Some(obj : Vehicle) if !obj.Cloaked =>
          //TODO hint: vehicleService ! VehicleServiceMessage(s"${obj.Actor}", VehicleAction.ProjectileAutoLockAwareness(mode))
          obj.Seats.values.collect { case seat if seat.isOccupied => seat.Occupant.get.Name }
        case Some(obj : Mountable) =>
          obj.Seats.values.collect { case seat if seat.isOccupied => seat.Occupant.get.Name }
        case Some(obj : Player) if obj.ExoSuit == ExoSuitType.MAX =>
          Seq(obj.Name)
        case _ =>
          Seq.empty[String]
      }
  }

  /**
    * For a given registered remote projectile, perform all the actions necessary to properly dispose of it.
    * Those actions involve:
    * informing that the projectile should explode,
    * unregistering the projectile's globally unique identifier,
    * and managing the projectiles's local status information.
    * @see `CleanUpRemoteProjectile(PlanetSideGUID, Projectile, Int)`
    * @param projectile_guid the globally unique identifier of the projectile
    * @param projectile the projectile
    */
  def CleanUpRemoteProjectile(projectile_guid : PlanetSideGUID, projectile : Projectile) : Unit = {
    projectiles.indexWhere({
      case Some(p) => p eq projectile
      case None => false
    }) match {
      case -1 => ; //required catch
      case index if projectilesToCleanUp(index) =>
        CleanUpRemoteProjectile(projectile_guid, projectile, index)
      case _ => ;
    }
  }

  /**
    * For a given registered remote projectile, perform all the actions necessary to properly dispose of it.
    * Those actions involve:
    * informing that the projectile should explode,
    * unregistering the projectile's globally unique identifier,
    * and managing the projectiles's local status information.
    * @param projectile_guid the globally unique identifier of the projectile
    * @param projectile the projectile
    * @param local_index an index of the absolute sequence of the projectile, for internal lists
    */
  def CleanUpRemoteProjectile(projectile_guid : PlanetSideGUID, projectile : Projectile, local_index : Int) : Unit = {
    continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.ProjectileExplodes(player.GUID, projectile_guid, projectile))
    taskResolver ! UnregisterProjectile(projectile)
    projectiles(local_index) match {
      case Some(obj) if !obj.isResolved => obj.Miss
      case _ => ;
    }
    projectilesToCleanUp(local_index) = false
  }

  def DeactivateImplants() : Unit = {
    for(slot <- 0 to player.Implants.length - 1) {
      player.Actor ! Player.ImplantActivation(slot, 0)
    }
  }

  def failWithError(error : String) = {
    log.error(error)
    sendResponse(ConnectionClose())
  }

  /**
    * Persistent collector that intercepts `GamePacket` and `ControlPacket` messages that are being sent towards the network.
    */
  private val packetBundlingCollector : MultiPacketCollector = new MultiPacketCollector()
  /**
    * Re-assigned function used to direct/intercept packets being sent towards the network.
    * Defaults to directing the packets.
    */
  private var packetBundlingFunc : (PlanetSidePacket)=>Option[PlanetSidePacket] = NoBundlingAction

  /**
    * Start packet bundling by assigning the appropriate function.
    * @see `sendResponse(PlanetSidePacket) : Unit`
    */
  def StartBundlingPackets() : Unit = {
    log.trace("WORLD SEND: STARTED BUNDLING PACKETS")
    packetBundlingFunc = PerformBundlingAction
  }

  /**
    * Stop packet bundling by assigning the appropriate function.
    * If any bundles are in the collector's buffer, push that bundle out towards the network.
    * @see `sendResponse(PlanetSidePacket) : Unit`
    */
  def StopBundlingPackets() : Unit = {
    log.trace("WORLD SEND: PACKET BUNDLING SUSPENDED")
    packetBundlingFunc = NoBundlingAction
    packetBundlingCollector.BundleOption match {
      case Some(bundle) =>
        sendResponse(bundle)
      case None => ;
    }
  }

  /**
    * Transform the packet into either a `PlanetSideGamePacket` or a `PlanetSideControlPacket` and push it towards the network.
    * @param cont the packet
    * @return the same packet, to indicate it was sent
    */
  private def NoBundlingAction(cont : PlanetSidePacket) : Option[PlanetSidePacket] = {
    cont match {
      case game : PlanetSideGamePacket =>
        sendResponse(PacketCoding.CreateGamePacket(0, game))
      case control : PlanetSideControlPacket =>
        sendResponse(PacketCoding.CreateControlPacket(control))
      case _ => ;
    }
    Some(cont)
  }

  /**
    * Intercept the packet being sent towards the network and
    * add it to a bundle that will eventually be sent to the network itself.
    * @param cont the packet
    * @return always `None`, to indicate the packet was not sent
    */
  private def PerformBundlingAction(cont : PlanetSidePacket) : Option[PlanetSidePacket] = {
    log.trace("WORLD SEND, BUNDLED: " + cont)
    packetBundlingCollector.Add(cont)
    None
  }

  /**
    * Common entry point for transmitting packets to the network.
    * Alternately, catch those packets and retain them to send out a bundled message.
    * @param cont the packet
    */
  def sendResponse(cont : PlanetSidePacket) : Unit = packetBundlingFunc(cont)

  /**
    * `KeepAliveMessage` is a special `PlanetSideGamePacket` that is excluded from being bundled when it is sent to the network.<br>
    * <br>
    * The risk of the server getting caught in a state where the packets dispatched to the client are always bundled is posible.
    * Starting the bundling functionality but forgetting to transition into a state where it is deactivated can lead to this problem.
    * No packets except for `KeepAliveMessage` will ever be sent until the ever-accumulating packets overflow.
    * To avoid this state, whenever a `KeepAliveMessage` is sent, the packet collector empties its current contents to the network.
    * @see `StartBundlingPackets`
    * @see `StopBundlingPackets`
    * @see `clientKeepAlive`
    * @param cont a `KeepAliveMessage` packet
    */
  def sendResponse(cont : KeepAliveMessage) : Unit = {
    sendResponse(PacketCoding.CreateGamePacket(0, cont))
    packetBundlingCollector.BundleOption match {
      case Some(bundle) =>
        log.trace("WORLD SEND: INTERMITTENT PACKET BUNDLE")
        sendResponse(bundle)
      case None => ;
    }
  }

  def sendResponse(cont : PlanetSidePacketContainer) : Unit = {
    log.trace("WORLD SEND: " + cont)
    sendResponse(cont.asInstanceOf[Any])
  }

  def sendResponse(cont : MultiPacketBundle) : Unit = {
    sendResponse(cont.asInstanceOf[Any])
  }

  def sendResponse(msg : Any) : Unit = {
    MDC("sessionId") = sessionId.toString
    rightRef !> msg
  }

  def sendRawResponse(pkt : ByteVector) = {
    log.trace("WORLD SEND RAW: " + pkt)
    sendResponse(RawPacket(pkt))
  }
}

object WorldSessionActor {
  final case class ResponseToSelf(pkt : PlanetSideGamePacket)

  private final case class PokeClient()
  private final case class ServerLoaded()
  private final case class NewPlayerLoaded(tplayer : Player)
  private final case class PlayerLoaded(tplayer : Player)
  private final case class PlayerFailedToLoad(tplayer : Player)
  private final case class CreateCharacter(name : String, head : Int, voice : CharacterVoice.Value, gender : CharacterGender.Value, empire : PlanetSideEmpire.Value)
  private final case class ListAccountCharacters()
  private final case class SetCurrentAvatar(tplayer : Player)
  private final case class VehicleLoaded(vehicle : Vehicle)
  private final case class ZoningReset()

  final val ftes = (
    FirstTimeEvents.Maps ++ FirstTimeEvents.Monoliths ++
      FirstTimeEvents.Standard.All ++ FirstTimeEvents.Cavern.All ++
      FirstTimeEvents.TR.All ++ FirstTimeEvents.NC.All ++ FirstTimeEvents.VS.All ++
      FirstTimeEvents.Generic
    ).toList

  /**
    * The message that progresses some form of user-driven activity with a certain eventual outcome
    * and potential feedback per cycle.
    * @param delta how much the progress value changes each tick, which will be treated as a percentage;
    *              must be a positive value
    * @param completionAction a finalizing action performed once the progress reaches 100(%)
    * @param tickAction an action that is performed for each increase of progress
    */
  final case class ProgressEvent(delta : Float, completionAction : ()=>Unit, tickAction : Float=>Boolean)

  private final val zoningCountdownMessages : Seq[Int] = Seq(5,10,20)

  protected final case class SquadUIElement(name : String, index : Int, zone : Int, health : Int, armor : Int, position : Vector3)

  private final case class NtuCharging(tplayer: Player, vehicle: Vehicle)

  private final case class NtuDischarging(tplayer: Player, vehicle: Vehicle, silo_guid: PlanetSideGUID)

  private final case class FinalizeDeployable(obj : PlanetSideGameObject with Deployable, tool : ConstructionItem, index : Int)

  private final case class LoadedRemoteProjectile(projectile_guid : PlanetSideGUID, projectile : Option[Projectile])
}
