PSF-BotServer/pslogin/src/main/scala/WorldSessionActor.scala
FateJH 0fe46311ad Deployment:
Class and Actor mixins for Deployment state.  The logic is surprisingly self-contained, mostly.

DriveState:

This is not the former DriveState Enumeration of /packet/game/objectcreate/.  This is now a /types/ Enumeration shared across /common/ objects and serves the functionality of both, at least to the extent that it is understood.  Functions are includes that define the logic order or state changes and divides states into (two) groups.

VehicleService:

The directory /pslogin/src/test has been created and tests have been migrated there.  Originally, the tests were located in the wrong place and were being skipped when not executed manually.  They should now appear in coverage reports and be run as a part of continuous integration.
2018-02-17 17:37:53 -05:00

3081 lines
144 KiB
Scala

// Copyright (c) 2017 PSForever
import java.util.concurrent.atomic.AtomicInteger
import akka.actor.{Actor, ActorRef, Cancellable, MDCContextAware}
import net.psforever.packet._
import net.psforever.packet.control._
import net.psforever.packet.game._
import scodec.Attempt.{Failure, Successful}
import scodec.bits._
import org.log4s.MDC
import MDCContextAware.Implicits._
import services.ServiceManager.Lookup
import net.psforever.objects._
import net.psforever.objects.equipment._
import net.psforever.objects.guid.{GUIDTask, Task, TaskResolver}
import net.psforever.objects.inventory.{Container, GridInventory, InventoryItem}
import net.psforever.objects.serverobject.mount.Mountable
import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.serverobject.deploy.Deployment
import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject}
import net.psforever.objects.serverobject.doors.Door
import net.psforever.objects.serverobject.implantmech.ImplantTerminalMech
import net.psforever.objects.serverobject.locks.IFFLock
import net.psforever.objects.serverobject.mblocker.Locker
import net.psforever.objects.serverobject.pad.VehicleSpawnPad
import net.psforever.objects.serverobject.terminals.Terminal
import net.psforever.objects.vehicles.{AccessPermissionGroup, VehicleLockState}
import net.psforever.objects.zones.{InterstellarCluster, Zone}
import net.psforever.packet.game.objectcreate._
import net.psforever.types._
import services._
import services.avatar.{AvatarAction, AvatarResponse, AvatarServiceMessage, AvatarServiceResponse}
import services.local.{LocalAction, LocalResponse, LocalServiceMessage, LocalServiceResponse}
import services.vehicle.{VehicleAction, VehicleResponse, VehicleServiceMessage, VehicleServiceResponse}
import scala.annotation.tailrec
import scala.util.Success
class WorldSessionActor extends Actor with MDCContextAware {
import WorldSessionActor._
private[this] val log = org.log4s.getLogger
var sessionId : Long = 0
var leftRef : ActorRef = ActorRef.noSender
var rightRef : ActorRef = ActorRef.noSender
var avatarService : ActorRef = ActorRef.noSender
var localService : ActorRef = ActorRef.noSender
var vehicleService : ActorRef = ActorRef.noSender
var taskResolver : ActorRef = Actor.noSender
var galaxy : ActorRef = Actor.noSender
var continent : Zone = null
var progressBarValue : Option[Float] = None
var shooting : Option[PlanetSideGUID] = None
var accessedContainer : Option[PlanetSideGameObject with Container] = None
var clientKeepAlive : Cancellable = DefaultCancellable.obj
var progressBarUpdate : Cancellable = DefaultCancellable.obj
override def postStop() = {
if(clientKeepAlive != null)
clientKeepAlive.cancel()
localService ! Service.Leave()
vehicleService ! Service.Leave()
avatarService ! Service.Leave()
LivePlayerList.Remove(sessionId) match {
case Some(tplayer) =>
tplayer.VehicleSeated match {
case Some(vehicle_guid) =>
//TODO do this at some other time
vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.KickPassenger(tplayer.GUID, 0, true, vehicle_guid))
case None => ;
}
tplayer.VehicleOwned match {
case Some(vehicle_guid) =>
continent.GUID(vehicle_guid) match {
case Some(vehicle : Vehicle) =>
vehicle.Owner = None
//TODO temporary solution; to un-own, permit driver seat to Empire access level
vehicle.PermissionGroup(10, VehicleLockState.Empire.id)
vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.SeatPermissions(tplayer.GUID, vehicle_guid, 10, VehicleLockState.Empire.id))
case _ => ;
}
case None => ;
}
if(tplayer.HasGUID) {
val guid = tplayer.GUID
avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.ObjectDelete(guid, guid))
taskResolver ! GUIDTask.UnregisterAvatar(tplayer)(continent.GUID)
//TODO normally, the actual player avatar persists a minute or so after the user disconnects
}
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)
ServiceManager.serviceManager ! Lookup("avatar")
ServiceManager.serviceManager ! Lookup("local")
ServiceManager.serviceManager ! Lookup("vehicle")
ServiceManager.serviceManager ! Lookup("taskResolver")
ServiceManager.serviceManager ! Lookup("galaxy")
case _ =>
log.error("Unknown message")
context.stop(self)
}
def Started : Receive = {
case ServiceManager.LookupResult("avatar", endpoint) =>
avatarService = endpoint
log.info("ID: " + sessionId + " Got avatar service " + endpoint)
case ServiceManager.LookupResult("local", endpoint) =>
localService = endpoint
log.info("ID: " + sessionId + " Got local service " + endpoint)
case ServiceManager.LookupResult("vehicle", endpoint) =>
vehicleService = endpoint
log.info("ID: " + sessionId + " Got vehicle service " + endpoint)
case ServiceManager.LookupResult("taskResolver", endpoint) =>
taskResolver = endpoint
log.info("ID: " + sessionId + " Got task resolver service " + endpoint)
case ServiceManager.LookupResult("galaxy", endpoint) =>
galaxy = endpoint
log.info("ID: " + sessionId + " Got galaxy service " + endpoint)
case ctrl @ ControlPacket(_, _) =>
handlePktContainer(ctrl)
case game @ GamePacket(_, _, _) =>
handlePktContainer(game)
// temporary hack to keep the client from disconnecting
case PokeClient() =>
sendResponse(PacketCoding.CreateGamePacket(0, KeepAliveMessage()))
case AvatarServiceResponse(_, guid, reply) =>
reply match {
case AvatarResponse.ArmorChanged(suit, subtype) =>
if(player.GUID != guid) {
sendResponse(PacketCoding.CreateGamePacket(0, ArmorChangedMessage(guid, suit, subtype)))
}
case AvatarResponse.ChangeAmmo(weapon_guid, weapon_slot, previous_guid, ammo_id, ammo_guid, ammo_data) =>
if(player.GUID != guid) {
sendResponse(PacketCoding.CreateGamePacket(0,
ObjectDetachMessage(weapon_guid, previous_guid, Vector3(0,0,0), 0f, 0f, 0f)
))
sendResponse(PacketCoding.CreateGamePacket(0,
ObjectCreateMessage(
ammo_id,
ammo_guid,
ObjectCreateMessageParent(weapon_guid, weapon_slot),
ammo_data
))
)
sendResponse(PacketCoding.CreateGamePacket(0, ChangeAmmoMessage(weapon_guid, 1)))
}
case AvatarResponse.ChangeFireMode(item_guid, mode) =>
if(player.GUID != guid) {
sendResponse(PacketCoding.CreateGamePacket(0, ChangeFireModeMessage(item_guid, mode)))
}
case AvatarResponse.ChangeFireState_Start(weapon_guid) =>
if(player.GUID != guid) {
sendResponse(PacketCoding.CreateGamePacket(0, ChangeFireStateMessage_Start(weapon_guid)))
}
case AvatarResponse.ChangeFireState_Stop(weapon_guid) =>
if(player.GUID != guid) {
sendResponse(PacketCoding.CreateGamePacket(0, ChangeFireStateMessage_Stop(weapon_guid)))
}
case AvatarResponse.ConcealPlayer() =>
if(player.GUID != guid) {
sendResponse(PacketCoding.CreateGamePacket(0, GenericObjectActionMessage(guid, 36)))
}
case AvatarResponse.EquipmentInHand(slot, item) =>
if(player.GUID != guid) {
val definition = item.Definition
sendResponse(
PacketCoding.CreateGamePacket(0,
ObjectCreateMessage(
definition.ObjectId,
item.GUID,
ObjectCreateMessageParent(guid, slot),
definition.Packet.ConstructorData(item).get
)
)
)
}
case AvatarResponse.EquipmentOnGround(pos, orient, item_id, item_guid, item_data) =>
if(player.GUID != guid) {
sendResponse(
PacketCoding.CreateGamePacket(0,
ObjectCreateMessage(
item_id,
item_guid,
DroppedItemData(PlacementData(pos, Vector3(0f, 0f, orient.z)), item_data)
)
)
)
}
case AvatarResponse.LoadPlayer(pdata) =>
if(player.GUID != guid) {
sendResponse(PacketCoding.CreateGamePacket(0, ObjectCreateMessage(ObjectClass.avatar, guid, pdata)))
}
case AvatarResponse.ObjectDelete(item_guid, unk) =>
if(player.GUID != guid) {
sendResponse(PacketCoding.CreateGamePacket(0, ObjectDeleteMessage(item_guid, unk)))
}
case AvatarResponse.ObjectHeld(slot) =>
if(player.GUID != guid) {
sendResponse(PacketCoding.CreateGamePacket(0, ObjectHeldMessage(guid, slot, false)))
}
case AvatarResponse.PlanetsideAttribute(attribute_type, attribute_value) =>
if(player.GUID != guid) {
sendResponse(PacketCoding.CreateGamePacket(0, PlanetsideAttributeMessage(guid, attribute_type, attribute_value)))
}
case AvatarResponse.PlayerState(msg, spectating, weaponInHand) =>
if(player.GUID != guid) {
val now = System.currentTimeMillis()
val (location, time, distanceSq) : (Vector3, Long, Float) = if(spectating) {
(Vector3(2, 2, 2), 0L, 0f)
}
else {
val before = player.lastSeenStreamMessage(guid.guid)
val dist = Vector3.DistanceSquared(player.Position, msg.pos)
(msg.pos, now - before, dist)
}
if(spectating ||
((distanceSq < 900 || weaponInHand) && time > 200) ||
(distanceSq < 10000 && time > 500) ||
(distanceSq < 160000 && (
(msg.is_jumping || time < 200)) ||
((msg.vel.isEmpty || Vector3.MagnitudeSquared(msg.vel.get).toInt == 0) && time > 2000) ||
(time > 1000)) ||
(distanceSq > 160000 && time > 5000))
{
sendResponse(
PacketCoding.CreateGamePacket(0,
PlayerStateMessage(
guid,
location,
msg.vel,
msg.facingYaw,
msg.facingPitch,
msg.facingYawUpper,
0,
msg.is_crouching,
msg.is_jumping,
msg.jump_thrust,
msg.is_cloaked
)
)
)
player.lastSeenStreamMessage(guid.guid) = now
}
}
case AvatarResponse.Reload(item_guid) =>
if(player.GUID != guid) {
sendResponse(PacketCoding.CreateGamePacket(0, ReloadMessage(item_guid, 1, 0)))
}
case AvatarResponse.WeaponDryFire(weapon_guid) =>
if(player.GUID != guid) {
sendResponse(PacketCoding.CreateGamePacket(0, WeaponDryFireMessage(weapon_guid)))
}
case _ => ;
}
case LocalServiceResponse(_, guid, reply) =>
reply match {
case LocalResponse.DoorOpens(door_guid) =>
if(player.GUID != guid) {
sendResponse(PacketCoding.CreateGamePacket(0, GenericObjectStateMsg(door_guid, 16)))
}
case LocalResponse.DoorCloses(door_guid) => //door closes for everyone
sendResponse(PacketCoding.CreateGamePacket(0, GenericObjectStateMsg(door_guid, 17)))
case LocalResponse.HackClear(target_guid, unk1, unk2) =>
sendResponse(PacketCoding.CreateGamePacket(0, HackMessage(0, target_guid, guid, 0, unk1, HackState.HackCleared, unk2)))
case LocalResponse.HackObject(target_guid, unk1, unk2) =>
if(player.GUID != guid) {
sendResponse(PacketCoding.CreateGamePacket(0, HackMessage(0, target_guid, guid, 100, unk1, HackState.Hacked, unk2)))
}
case LocalResponse.TriggerSound(sound, pos, unk, volume) =>
sendResponse(PacketCoding.CreateGamePacket(0, TriggerSoundMessage(sound, pos, unk, volume)))
case _ => ;
}
case VehicleServiceResponse(_, guid, reply) =>
reply match {
case VehicleResponse.Awareness(vehicle_guid) =>
//resets exclamation point fte marker (once)
sendResponse(PacketCoding.CreateGamePacket(0, PlanetsideAttributeMessage(guid, 21, vehicle_guid.guid.toLong)))
case VehicleResponse.ChildObjectState(object_guid, pitch, yaw) =>
if(player.GUID != guid) {
sendResponse(PacketCoding.CreateGamePacket(0, ChildObjectStateMessage(object_guid, pitch, yaw)))
}
case VehicleResponse.DismountVehicle(unk1, unk2) =>
if(player.GUID != guid) {
sendResponse(PacketCoding.CreateGamePacket(0, DismountVehicleMsg(guid, unk1, unk2)))
}
case VehicleResponse.DeployRequest(object_guid, state, unk1, unk2, pos) =>
if(player.GUID != guid) {
sendResponse(PacketCoding.CreateGamePacket(0, DeployRequestMessage(guid, object_guid, state, unk1, unk2, pos)))
}
case VehicleResponse.InventoryState(obj, parent_guid, start, con_data) =>
if(player.GUID != guid) {
//TODO prefer ObjectDetachMessage, but how to force ammo pools to update properly?
val obj_guid = obj.GUID
sendResponse(PacketCoding.CreateGamePacket(0, ObjectDeleteMessage(obj_guid, 0)))
sendResponse(PacketCoding.CreateGamePacket(0,
ObjectCreateDetailedMessage(
obj.Definition.ObjectId,
obj_guid,
ObjectCreateMessageParent(parent_guid, start),
con_data)
))
}
case VehicleResponse.KickPassenger(unk1, unk2, vehicle_guid) =>
sendResponse(PacketCoding.CreateGamePacket(0, DismountVehicleMsg(guid, unk1, unk2)))
if(guid == player.GUID) {
continent.GUID(vehicle_guid) match {
case Some(obj : Vehicle) =>
UnAccessContents(obj)
case _ => ;
}
}
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(player.GUID != guid) {
sendResponse(PacketCoding.CreateGamePacket(0, ObjectCreateMessage(vtype, vguid, vdata)))
ReloadVehicleAccessPermissions(vehicle)
}
case VehicleResponse.MountVehicle(vehicle_guid, seat) =>
if(player.GUID != guid) {
sendResponse(PacketCoding.CreateGamePacket(0, ObjectAttachMessage(vehicle_guid, guid, seat)))
}
case VehicleResponse.SeatPermissions(vehicle_guid, seat_group, permission) =>
if(player.GUID != guid) {
sendResponse(PacketCoding.CreateGamePacket(0, PlanetsideAttributeMessage(vehicle_guid, seat_group, permission)))
}
case VehicleResponse.StowEquipment(vehicle_guid, slot, item_type, item_guid, item_data) =>
if(player.GUID != guid) {
//TODO prefer ObjectAttachMessage, but how to force ammo pools to update properly?
sendResponse(PacketCoding.CreateGamePacket(0,
ObjectCreateDetailedMessage(item_type, item_guid, ObjectCreateMessageParent(vehicle_guid, slot), item_data)
))
}
case VehicleResponse.UnloadVehicle(vehicle_guid) =>
sendResponse(PacketCoding.CreateGamePacket(0, ObjectDeleteMessage(vehicle_guid, 0)))
case VehicleResponse.UnstowEquipment(item_guid) =>
if(player.GUID != guid) {
//TODO prefer ObjectDetachMessage, but how to force ammo pools to update properly?
sendResponse(PacketCoding.CreateGamePacket(0, ObjectDeleteMessage(item_guid, 0)))
}
case VehicleResponse.VehicleState(vehicle_guid, unk1, pos, ang, vel, unk2, unk3, unk4, wheel_direction, unk5, unk6) =>
if(player.GUID != guid) {
sendResponse(PacketCoding.CreateGamePacket(0, VehicleStateMessage(vehicle_guid, unk1, pos, ang, vel, unk2, unk3, unk4, wheel_direction, unk5, unk6)))
if(player.VehicleSeated.contains(vehicle_guid)) {
player.Position = pos
}
}
case _ => ;
}
case Deployment.CanDeploy(obj, state) =>
val vehicle_guid = obj.GUID
if(state == DriveState.Deploying) {
log.info(s"DeployRequest: $obj transitioning to deploy state")
sendResponse(PacketCoding.CreateGamePacket(0, DeployRequestMessage(player.GUID, vehicle_guid, state, 0, false, obj.Position)))
vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.DeployRequest(player.GUID, vehicle_guid, state, 0, false, obj.Position))
import scala.concurrent.duration._
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")
sendResponse(PacketCoding.CreateGamePacket(0, DeployRequestMessage(player.GUID, vehicle_guid, state, 0, false, obj.Position)))
vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.DeployRequest(player.GUID, vehicle_guid, state, 0, false, obj.Position))
//...
}
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(PacketCoding.CreateGamePacket(0, DeployRequestMessage(player.GUID, vehicle_guid, state, 0, false, Vector3.Zero)))
vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.DeployRequest(player.GUID, vehicle_guid, state, 0, false, Vector3.Zero))
import scala.concurrent.duration._
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(PacketCoding.CreateGamePacket(0, DeployRequestMessage(player.GUID, vehicle_guid, state, 0, false, Vector3.Zero)))
vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.DeployRequest(player.GUID, vehicle_guid, state, 0, false, Vector3.Zero))
//...
}
else {
CanNotChangeDeployment(obj, state, "incorrect undeploy state")
}
case Deployment.CanNotChangeDeployment(obj, state, reason) =>
CanNotChangeDeployment(obj, state, reason)
case Door.DoorMessage(tplayer, msg, order) =>
val door_guid = msg.object_guid
order match {
case Door.OpenEvent() =>
continent.GUID(door_guid) match {
case Some(door : Door) =>
sendResponse(PacketCoding.CreateGamePacket(0, GenericObjectStateMsg(door_guid, 16)))
localService ! 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(PacketCoding.CreateGamePacket(0, GenericObjectStateMsg(door_guid, 17)))
localService ! LocalServiceMessage(continent.Id, LocalAction.DoorCloses(tplayer.GUID, door_guid))
case Door.NoEvent() => ;
}
case Mountable.MountMessages(tplayer, reply) =>
reply match {
case Mountable.CanMount(obj : ImplantTerminalMech, seat_num) =>
val player_guid : PlanetSideGUID = tplayer.GUID
val obj_guid : PlanetSideGUID = obj.GUID
log.info(s"MountVehicleMsg: $player_guid mounts $obj @ $seat_num")
//tplayer.VehicleSeated = Some(obj_guid)
sendResponse(PacketCoding.CreateGamePacket(0, PlanetsideAttributeMessage(obj_guid, 0, 1000L))) //health of mech
sendResponse(PacketCoding.CreateGamePacket(0, ObjectAttachMessage(obj_guid, player_guid, seat_num)))
vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.MountVehicle(player_guid, obj_guid, seat_num))
case Mountable.CanMount(obj : Vehicle, seat_num) =>
val obj_guid : PlanetSideGUID = obj.GUID
val player_guid : PlanetSideGUID = tplayer.GUID
log.info(s"MountVehicleMsg: $player_guid mounts $obj_guid @ $seat_num")
vehicleService ! VehicleServiceMessage.UnscheduleDeconstruction(obj_guid) //clear all deconstruction timers
//tplayer.VehicleSeated = Some(obj_guid)
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 => ;
}
tplayer.VehicleOwned = Some(obj_guid)
obj.Owner = Some(player_guid)
}
obj.WeaponControlledFromSeat(seat_num) 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(PacketCoding.CreateGamePacket(0, InventoryStateMessage(magazine.GUID, weapon.GUID, magazine.Capacity.toLong)))
})
case _ => ; //no weapons to update
}
sendResponse(PacketCoding.CreateGamePacket(0, ObjectAttachMessage(obj_guid, player_guid, seat_num)))
AccessContents(obj)
vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.MountVehicle(player_guid, obj_guid, 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) =>
val obj_guid : PlanetSideGUID = obj.GUID
val player_guid : PlanetSideGUID = tplayer.GUID
log.info(s"DismountVehicleMsg: $player_guid dismounts $obj @ $seat_num")
sendResponse(PacketCoding.CreateGamePacket(0, DismountVehicleMsg(player_guid, seat_num, false)))
vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.DismountVehicle(player_guid, seat_num, false))
case Mountable.CanDismount(obj : Vehicle, seat_num) =>
val player_guid : PlanetSideGUID = tplayer.GUID
if(player_guid == player.GUID) {
//disembarking self
log.info(s"DismountVehicleMsg: $player_guid dismounts $obj @ $seat_num")
sendResponse(PacketCoding.CreateGamePacket(0, DismountVehicleMsg(player_guid, seat_num, false)))
vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.DismountVehicle(player_guid, seat_num, false))
UnAccessContents(obj)
}
else {
vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.KickPassenger(player_guid, seat_num, true, obj.GUID))
}
if(obj.Seats.values.count(seat => seat.isOccupied) == 0) {
vehicleService ! VehicleServiceMessage.DelayedVehicleDeconstruction(obj, continent, 600L) //start vehicle decay (10m)
}
case Mountable.CanDismount(obj : Mountable, _) =>
log.warn(s"DismountVehicleMsg: $obj is some generic mountable object and nothing will happen")
case Mountable.CanNotMount(obj, 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")
}
case Terminal.TerminalMessage(tplayer, msg, order) =>
order match {
case Terminal.BuyExosuit(exosuit, subtype) => //refresh armor points
if(tplayer.ExoSuit == exosuit) {
if(Loadout.DetermineSubtype(tplayer) != subtype) {
//special case: MAX suit switching to a different MAX suit; we need to change the main weapon
sendResponse(PacketCoding.CreateGamePacket(0, ArmorChangedMessage(tplayer.GUID, exosuit, subtype)))
avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.ArmorChanged(tplayer.GUID, exosuit, subtype))
val arms = tplayer.Slot(0).Equipment.get
val putTask = PutEquipmentInSlot(tplayer, Tool(GlobalDefinitions.MAXArms(subtype, tplayer.Faction)), 0)
taskResolver ! DelayedObjectHeld(tplayer, 0, List(TaskResolver.GiveTask(putTask.task, putTask.subs :+ RemoveEquipmentFromSlot(tplayer, arms, 0))))
}
//outside of the MAX condition above, we should seldom reach this point through conventional methods
tplayer.Armor = tplayer.MaxArmor
sendResponse(PacketCoding.CreateGamePacket(0, PlanetsideAttributeMessage(tplayer.GUID, 4, tplayer.Armor)))
avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.PlanetsideAttribute(tplayer.GUID, 4, tplayer.Armor))
sendResponse(PacketCoding.CreateGamePacket(0, ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, true)))
}
else { //load a complete new exo-suit and shuffle the inventory around
val originalSuit = tplayer.ExoSuit
//save inventory before it gets cleared (empty holsters)
val dropPred = DropPredicate(tplayer)
val (dropHolsters, beforeHolsters) = clearHolsters(tplayer.Holsters().iterator).partition(dropPred)
val (dropInventory, beforeInventory) = tplayer.Inventory.Clear().partition(dropPred)
//change suit (clear inventory and change holster sizes; note: holsters must be empty before this point)
Player.SuitSetup(tplayer, exosuit)
tplayer.Armor = tplayer.MaxArmor
//delete everything not dropped
(beforeHolsters ++ beforeInventory).foreach({ elem =>
sendResponse(PacketCoding.CreateGamePacket(0, ObjectDeleteMessage(elem.obj.GUID, 0)))
})
beforeHolsters.foreach({ elem =>
avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.ObjectDelete(tplayer.GUID, elem.obj.GUID))
})
//report change
sendResponse(PacketCoding.CreateGamePacket(0, ArmorChangedMessage(tplayer.GUID, exosuit, subtype)))
avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.ArmorChanged(tplayer.GUID, exosuit, subtype))
sendResponse(PacketCoding.CreateGamePacket(0, PlanetsideAttributeMessage(tplayer.GUID, 4, tplayer.Armor)))
avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.PlanetsideAttribute(tplayer.GUID, 4, tplayer.Armor))
val finalInventory = if(exosuit == ExoSuitType.MAX) {
//MAX weapon to be placed in first pistol slot; slot to be drawn
taskResolver ! DelayedObjectHeld(tplayer, 0, List(PutEquipmentInSlot(tplayer, Tool(GlobalDefinitions.MAXArms(subtype, tplayer.Faction)), 0)))
//fill melee slot
fillEmptyHolsters(List(tplayer.Slot(4)).iterator, beforeHolsters) ++ beforeInventory
}
else {
//remove potential MAX weapon
val normalWeapons = 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 {
avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.ObjectHeld(tplayer.GUID, Player.HandsDownSlot))
beforeHolsters
}
//fill holsters
val (afterHolsters, toInventory) = normalWeapons.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(
PacketCoding.CreateGamePacket(0,
ObjectCreateDetailedMessage(
definition.ObjectId,
obj.GUID,
ObjectCreateMessageParent(tplayer.GUID, index),
definition.Packet.DetailedConstructorData(obj).get
)
)
)
avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.EquipmentInHand(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(
PacketCoding.CreateGamePacket(0,
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) = GridInventory.recoverInventory(finalInventory, tplayer.Inventory)
stow.foreach(elem => {
tplayer.Inventory.Insert(elem.start, elem.obj)
val obj = elem.obj
val definition = obj.Definition
sendResponse(
PacketCoding.CreateGamePacket(0,
ObjectCreateDetailedMessage(
definition.ObjectId,
obj.GUID,
ObjectCreateMessageParent(tplayer.GUID, elem.start),
definition.Packet.DetailedConstructorData(obj).get
)
)
)
})
//drop items on ground
val pos = tplayer.Position
val orient = tplayer.Orientation
((dropHolsters ++ dropInventory).map(_.obj) ++ drop).foreach(obj => {
continent.Ground ! Zone.DropItemOnGround(obj, pos, Vector3(0f, 0f, orient.z))
// val definition = obj.Definition
sendResponse(
PacketCoding.CreateGamePacket(0,
ObjectDetachMessage(tplayer.GUID, obj.GUID, pos, 0f, 0f, orient.z)
// ObjectCreateMessage(
// definition.ObjectId,
// obj.GUID,
// DroppedItemData(PlacementData(pos, Vector3(0f, 0f, orient.z)), definition.Packet.ConstructorData(obj).get)
// )
)
)
val objDef = obj.Definition
avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.EquipmentOnGround(tplayer.GUID, pos, orient, objDef.ObjectId, obj.GUID, objDef.Packet.ConstructorData(obj).get))
})
sendResponse(PacketCoding.CreateGamePacket(0, ItemTransactionResultMessage (msg.terminal_guid, TransactionType.Buy, true)))
}
case Terminal.BuyEquipment(item) => ;
tplayer.Fit(item) match {
case Some(index) =>
sendResponse(PacketCoding.CreateGamePacket(0, ItemTransactionResultMessage (msg.terminal_guid, TransactionType.Buy, true)))
taskResolver ! PutEquipmentInSlot(tplayer, item, index)
case None =>
sendResponse(PacketCoding.CreateGamePacket(0, ItemTransactionResultMessage (msg.terminal_guid, TransactionType.Buy, false)))
}
case Terminal.SellEquipment() =>
tplayer.FreeHand.Equipment match {
case Some(item) =>
if(item.GUID == msg.item_guid) {
sendResponse(PacketCoding.CreateGamePacket(0, ItemTransactionResultMessage (msg.terminal_guid, TransactionType.Sell, true)))
taskResolver ! RemoveEquipmentFromSlot(tplayer, item, Player.FreeHandSlot)
}
case None =>
sendResponse(PacketCoding.CreateGamePacket(0, ItemTransactionResultMessage (msg.terminal_guid, TransactionType.Sell, false)))
}
case Terminal.InfantryLoadout(exosuit, subtype, holsters, inventory) =>
//TODO optimizations against replacing Equipment with the exact same Equipment and potentially for recycling existing Equipment
log.info(s"$tplayer wants to change equipment loadout to their option #${msg.unk1 + 1}")
sendResponse(PacketCoding.CreateGamePacket(0, ItemTransactionResultMessage (msg.terminal_guid, TransactionType.InfantryLoadout, true)))
val dropPred = DropPredicate(tplayer)
val (dropHolsters, beforeHolsters) = clearHolsters(tplayer.Holsters().iterator).partition(dropPred)
val (dropInventory, beforeInventory) = tplayer.Inventory.Clear().partition(dropPred)
val (_, afterHolsters) = holsters.partition(dropPred) //dropped items are lost
val (_, afterInventory) = inventory.partition(dropPred) //dropped items are lost
val beforeFreeHand = tplayer.FreeHand.Equipment
//change suit (clear inventory and change holster sizes; note: holsters must be empty before this point)
Player.SuitSetup(tplayer, exosuit)
tplayer.Armor = tplayer.MaxArmor
//delete everything (not dropped)
beforeHolsters.foreach({ elem =>
avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.ObjectDelete(tplayer.GUID, elem.obj.GUID))
})
(beforeHolsters ++ beforeInventory).foreach({ elem =>
sendResponse(PacketCoding.CreateGamePacket(0, ObjectDeleteMessage(elem.obj.GUID, 0)))
taskResolver ! GUIDTask.UnregisterEquipment(elem.obj)(continent.GUID)
})
//report change
sendResponse(PacketCoding.CreateGamePacket(0, ArmorChangedMessage(tplayer.GUID, exosuit, 0)))
avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.ArmorChanged(tplayer.GUID, exosuit, subtype))
sendResponse(PacketCoding.CreateGamePacket(0, PlanetsideAttributeMessage(tplayer.GUID, 4, tplayer.Armor)))
avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.PlanetsideAttribute(tplayer.GUID, 4, tplayer.Armor))
//re-draw equipment held in free hand
beforeFreeHand match {
case Some(item) =>
tplayer.FreeHand.Equipment = beforeFreeHand
val definition = item.Definition
sendResponse(
PacketCoding.CreateGamePacket(0,
ObjectCreateDetailedMessage(
definition.ObjectId,
item.GUID,
ObjectCreateMessageParent(tplayer.GUID, Player.FreeHandSlot),
definition.Packet.DetailedConstructorData(item).get
)
)
)
case None => ;
}
//draw holsters
if(exosuit == ExoSuitType.MAX) {
tplayer.DrawnSlot = 0
val (maxWeapons, otherWeapons) = afterHolsters.partition(entry => { entry.obj.Size == EquipmentSize.Max })
taskResolver ! DelayedObjectHeld(tplayer, 0, List(PutEquipmentInSlot(tplayer, maxWeapons.head.obj, 0)))
otherWeapons
}
else {
afterHolsters
}.foreach(entry => {
taskResolver ! PutEquipmentInSlot(tplayer, entry.obj, entry.start)
})
//put items into inventory
afterInventory.foreach(entry => {
taskResolver ! PutEquipmentInSlot(tplayer, entry.obj, entry.start)
})
//drop stuff on ground
val pos = tplayer.Position
val orient = tplayer.Orientation
((dropHolsters ++ dropInventory).map(_.obj)).foreach(obj => {
continent.Ground ! Zone.DropItemOnGround(obj, pos, Vector3(0f, 0f, orient.z))
sendResponse(
PacketCoding.CreateGamePacket(0,
ObjectDetachMessage(tplayer.GUID, obj.GUID, pos, 0f, 0f, orient.z)
)
)
val objDef = obj.Definition
avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.EquipmentOnGround(tplayer.GUID, pos, orient, objDef.ObjectId, obj.GUID, objDef.Packet.ConstructorData(obj).get))
})
sendResponse(PacketCoding.CreateGamePacket(0, ItemTransactionResultMessage (msg.terminal_guid, TransactionType.InfantryLoadout, true)))
case Terminal.LearnCertification(cert, cost) =>
if(!player.Certifications.contains(cert)) {
log.info(s"$tplayer is learning the $cert certification for $cost points")
tplayer.Certifications += cert
sendResponse(PacketCoding.CreateGamePacket(0, PlanetsideAttributeMessage(tplayer.GUID, 24, cert.id.toLong)))
sendResponse(PacketCoding.CreateGamePacket(0, ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Learn, true)))
}
else {
log.warn(s"$tplayer already knows the $cert certification, so he can't learn it")
sendResponse(PacketCoding.CreateGamePacket(0, ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Learn, false)))
}
case Terminal.SellCertification(cert, cost) =>
if(player.Certifications.contains(cert)) {
log.info(s"$tplayer is forgetting the $cert certification for $cost points")
tplayer.Certifications -= cert
sendResponse(PacketCoding.CreateGamePacket(0, PlanetsideAttributeMessage(tplayer.GUID, 25, cert.id.toLong)))
sendResponse(PacketCoding.CreateGamePacket(0, ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Sell, true)))
}
else {
log.warn(s"$tplayer doesn't know what a $cert certification is, so he can't forget it")
sendResponse(PacketCoding.CreateGamePacket(0, ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Learn, false)))
}
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(!tplayer.Implants.exists({slot => slot.Implant == implant_type})) { //no duplicates
tplayer.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(PacketCoding.CreateGamePacket(0, AvatarImplantMessage(tplayer.GUID, 0, slot, implant_type.id)))
sendResponse(PacketCoding.CreateGamePacket(0, ItemTransactionResultMessage(terminal_guid, TransactionType.Learn, true)))
}
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(PacketCoding.CreateGamePacket(0, ItemTransactionResultMessage(terminal_guid, TransactionType.Learn, false)))
}
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),
tplayer.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")
sendResponse(PacketCoding.CreateGamePacket(0, AvatarImplantMessage(tplayer.GUID, 1, slot, 0)))
sendResponse(PacketCoding.CreateGamePacket(0, 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(PacketCoding.CreateGamePacket(0, ItemTransactionResultMessage(terminal_guid, TransactionType.Sell, false)))
}
case Terminal.BuyVehicle(vehicle, weapons, trunk) =>
continent.Map.TerminalToSpawnPad.get(msg.terminal_guid.guid) match {
case Some(pad_guid) =>
val pad = continent.GUID(pad_guid).get.asInstanceOf[VehicleSpawnPad]
vehicle.Faction = tplayer.Faction
vehicle.Position = pad.Position
vehicle.Orientation = pad.Orientation
//default loadout, weapons
log.info(s"default weapons: ${weapons.size}")
val vWeapons = vehicle.Weapons
weapons.foreach(entry => {
val index = entry.start
vWeapons.get(index) match {
case Some(slot) =>
slot.Equipment = None
slot.Equipment = entry.obj
case None =>
log.warn(s"applying default loadout to $vehicle, can not find a mounted weapon @ $index")
}
})
//default loadout, trunk
log.info(s"default trunk: ${trunk.size}")
val vTrunk = vehicle.Trunk
vTrunk.Clear()
trunk.foreach(entry => { vTrunk += entry.start -> entry.obj })
taskResolver ! RegisterNewVehicle(vehicle, pad)
sendResponse(PacketCoding.CreateGamePacket(0, ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, true)))
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")
}
case Terminal.NoDeal() =>
log.warn(s"$tplayer made a request but the terminal rejected the order $msg")
sendResponse(PacketCoding.CreateGamePacket(0, ItemTransactionResultMessage(msg.terminal_guid, msg.transaction_type, false)))
}
case VehicleSpawnPad.ConcealPlayer =>
sendResponse(PacketCoding.CreateGamePacket(0, GenericObjectActionMessage(player.GUID, 36)))
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ConcealPlayer(player.GUID))
case VehicleSpawnPad.LoadVehicle(vehicle, _/*pad*/) =>
val player_guid = player.GUID
val definition = vehicle.Definition
val objedtId = definition.ObjectId
val vehicle_guid = vehicle.GUID
val vdata = definition.Packet.ConstructorData(vehicle).get
sendResponse(PacketCoding.CreateGamePacket(0, ObjectCreateMessage(objedtId, vehicle_guid, vdata)))
continent.Transport ! Zone.SpawnVehicle(vehicle)
vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.LoadVehicle(player_guid, vehicle, objedtId, vehicle_guid, vdata))
sendResponse(PacketCoding.CreateGamePacket(0, PlanetsideAttributeMessage(vehicle_guid, 22, 1L))) //mount points off?
//sendResponse(PacketCoding.CreateGamePacket(0, PlanetsideAttributeMessage(vehicle_guid, 21, player_guid.guid))) //fte and ownership?
//sendResponse(PacketCoding.CreateGamePacket(0, ObjectAttachMessage(vehicle_guid, player_guid, 0)))
vehicleService ! VehicleServiceMessage.UnscheduleDeconstruction(vehicle_guid) //cancel queue timeout delay
vehicleService ! VehicleServiceMessage.DelayedVehicleDeconstruction(vehicle, continent, 21L) //temporary drive away from pad delay
vehicle.Actor ! Mountable.TryMount(player, 0)
case VehicleSpawnPad.PlayerSeatedInVehicle(vehicle) =>
vehicleService ! VehicleServiceMessage.DelayedVehicleDeconstruction(vehicle, continent, 21L) //sitting in the vehicle clears the drive away delay
val vehicle_guid = vehicle.GUID
sendResponse(PacketCoding.CreateGamePacket(0, PlanetsideAttributeMessage(vehicle_guid, 22, 0L))) //mount points on?
//sendResponse(PacketCoding.CreateGamePacket(0, PlanetsideAttributeMessage(vehicle_guid, 0, vehicle.Definition.MaxHealth)))
sendResponse(PacketCoding.CreateGamePacket(0, PlanetsideAttributeMessage(vehicle_guid, 68, 0L))) //???
sendResponse(PacketCoding.CreateGamePacket(0, PlanetsideAttributeMessage(vehicle_guid, 113, 0L))) //???
ReloadVehicleAccessPermissions(vehicle)
case VehicleSpawnPad.SpawnPadBlockedWarning(vehicle, warning_count) =>
if(warning_count > 2) {
sendResponse(PacketCoding.CreateGamePacket(0, TriggerSoundMessage(TriggeredSound.Unknown14, vehicle.Position, 20, 1f)))
sendResponse(PacketCoding.CreateGamePacket(0,
ChatMsg(ChatMessageType.CMT_TELL, true, "", "\\#FYour vehicle is blocking the spawn pad, and will be deconstructed if not moved.", None))
)
}
case VehicleSpawnPad.SpawnPadUnblocked(vehicle_guid) =>
//vehicle has moved away from spawn pad after initial spawn
vehicleService ! VehicleServiceMessage.UnscheduleDeconstruction(vehicle_guid) //cancel temporary drive away from pad delay
case ListAccountCharacters =>
import net.psforever.objects.definition.converter.CharacterSelectConverter
val gen : AtomicInteger = new AtomicInteger(1)
val converter : CharacterSelectConverter = new CharacterSelectConverter
//load characters
SetCharacterSelectScreenGUID(player, gen)
val health = player.Health
val stamina = player.Stamina
val armor = player.Armor
player.Spawn
sendResponse(PacketCoding.CreateGamePacket(0,
ObjectCreateDetailedMessage(ObjectClass.avatar, player.GUID, converter.DetailedConstructorData(player).get)
))
if(health > 0) { //player can not be dead; stay spawned as alive
player.Health = health
player.Stamina = stamina
player.Armor = armor
}
sendResponse(PacketCoding.CreateGamePacket(0, CharacterInfoMessage(15,PlanetSideZoneID(10000), 41605313, player.GUID, false, 6404428)))
RemoveCharacterSelectScreenGUID(player)
sendResponse(PacketCoding.CreateGamePacket(0, CharacterInfoMessage(0, PlanetSideZoneID(1), 0, PlanetSideGUID(0), true, 0)))
case InterstellarCluster.GiveWorld(zoneId, zone) =>
log.info(s"Zone $zoneId has been loaded")
player.Continent = zoneId
continent = zone
taskResolver ! RegisterAvatar(player)
case PlayerLoaded(tplayer) =>
log.info(s"Player $tplayer has been loaded")
//init for whole server
galaxy ! InterstellarCluster.RequestClientInitialization(tplayer)
case PlayerFailedToLoad(tplayer) =>
player.Continent match {
case _ =>
failWithError(s"$tplayer failed to load anywhere")
}
case VehicleLoaded(_/*vehicle*/) => ;
//currently being handled by VehicleSpawnPad.LoadVehicle during testing phase
case Zone.ClientInitialization(/*initList*/_) =>
//TODO iterate over initList; for now, just do this
sendResponse(
PacketCoding.CreateGamePacket(0,
BuildingInfoUpdateMessage(
PlanetSideGUID(6), //Ceryshen
PlanetSideGUID(2), //Anguta
8, //80% NTU
true, //Base hacked
PlanetSideEmpire.NC, //Base hacked by NC
600000, //10 minutes remaining for hack
PlanetSideEmpire.VS, //Base owned by VS
0, //!! Field != 0 will cause malformed packet. See class def.
None,
PlanetSideGeneratorState.Critical, //Generator critical
true, //Respawn tubes destroyed
true, //Force dome active
16, //Tech plant lattice benefit
0,
Nil, //!! Field > 0 will cause malformed packet. See class def.
0,
false,
8, //!! Field != 8 will cause malformed packet. See class def.
None,
true, //Boosted spawn room pain field
true //Boosted generator room pain field
)
)
)
sendResponse(PacketCoding.CreateGamePacket(0, ContinentalLockUpdateMessage(PlanetSideGUID(13), PlanetSideEmpire.VS))) // "The VS have captured the VS Sanctuary."
sendResponse(PacketCoding.CreateGamePacket(0, BroadcastWarpgateUpdateMessage(PlanetSideGUID(13), PlanetSideGUID(1), false, false, true))) // VS Sanctuary: Inactive Warpgate -> Broadcast Warpgate
sendResponse(PacketCoding.CreateGamePacket(0, ZonePopulationUpdateMessage(PlanetSideGUID(13), 414, 138, 0, 138, 0, 138, 0, 138, 0)))
case InterstellarCluster.ClientInitializationComplete(tplayer)=>
//this will cause the client to send back a BeginZoningMessage packet (see below)
sendResponse(PacketCoding.CreateGamePacket(0, LoadMapMessage(continent.Map.Name, continent.Id, 40100,25,true,3770441820L))) //VS Sanctuary
log.info("Load the now-registered player")
//load the now-registered player
tplayer.Spawn
val dcdata = tplayer.Definition.Packet.DetailedConstructorData(tplayer).get
sendResponse(PacketCoding.CreateGamePacket(0, ObjectCreateDetailedMessage(ObjectClass.avatar, tplayer.GUID, dcdata)))
avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.LoadPlayer(tplayer.GUID, tplayer.Definition.Packet.ConstructorData(tplayer).get))
log.debug(s"ObjectCreateDetailedMessage: $dcdata")
case SetCurrentAvatar(tplayer) =>
val guid = tplayer.GUID
LivePlayerList.Assign(continent.Number, sessionId, guid)
sendResponse(PacketCoding.CreateGamePacket(0, SetCurrentAvatarMessage(guid,0,0)))
sendResponse(PacketCoding.CreateGamePacket(0, CreateShortcutMessage(guid, 1, 0, true, Shortcut.MEDKIT)))
sendResponse(PacketCoding.CreateGamePacket(0, ChatMsg(ChatMessageType.CMT_EXPANSIONS, true, "", "1 on", None))) //CC on
case Zone.ItemFromGround(tplayer, item) =>
val obj_guid = item.GUID
val player_guid = tplayer.GUID
tplayer.Fit(item) match {
case Some(slot) =>
tplayer.Slot(slot).Equipment = item
avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.ObjectDelete(player_guid, obj_guid))
val definition = item.Definition
sendResponse(
PacketCoding.CreateGamePacket(0,
ObjectCreateDetailedMessage(
definition.ObjectId,
obj_guid,
ObjectCreateMessageParent(player_guid, slot),
definition.Packet.DetailedConstructorData(item).get
)
)
)
if(tplayer.VisibleSlots.contains(slot)) {
avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.EquipmentInHand(player_guid, slot, item))
}
case None =>
continent.Ground ! Zone.DropItemOnGround(item, item.Position, item.Orientation) //restore
}
case ItemHacking(tplayer, target, tool_guid, delta, completeAction, tickAction) =>
progressBarUpdate.cancel
if(progressBarValue.isDefined) {
val progressBarVal : Float = progressBarValue.get + delta
val vis = if(progressBarVal == 0L) { //hack state for progress bar visibility
HackState.Start
}
else if(progressBarVal > 100L) {
HackState.Finished
}
else {
HackState.Ongoing
}
sendResponse(PacketCoding.CreateGamePacket(0, HackMessage(1, target.GUID, player.GUID, progressBarVal.toInt, 0L, vis, 8L)))
if(progressBarVal > 100) { //done
progressBarValue = None
log.info(s"Hacked a $target")
sendResponse(PacketCoding.CreateGamePacket(0, HackMessage(0, target.GUID, player.GUID, 100, 1114636288L, HackState.Hacked, 8L)))
completeAction()
}
else { //continue next tick
tickAction.getOrElse(() => Unit)()
progressBarValue = Some(progressBarVal)
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global
progressBarUpdate = context.system.scheduler.scheduleOnce(250 milliseconds, self, ItemHacking(tplayer, target, tool_guid, delta, completeAction))
}
}
case ResponseToSelf(pkt) =>
log.info(s"Received a direct message: $pkt")
sendResponse(pkt)
case default =>
log.warn(s"Invalid packet class received: $default")
}
def handlePkt(pkt : PlanetSidePacket) : Unit = pkt match {
case ctrl : PlanetSideControlPacket =>
handleControlPkt(ctrl)
case game : PlanetSideGamePacket =>
handleGamePkt(game)
case default => log.error(s"Invalid packet class received: $default")
}
def handlePktContainer(pkt : PlanetSidePacketContainer) : Unit = pkt match {
case ctrl @ ControlPacket(opcode, ctrlPkt) =>
handleControlPkt(ctrlPkt)
case game @ GamePacket(opcode, seq, gamePkt) =>
handleGamePkt(gamePkt)
case default => log.warn(s"Invalid packet container class received: $default")
}
def handleControlPkt(pkt : PlanetSideControlPacket) = {
pkt match {
case SlottedMetaPacket(slot, subslot, innerPacket) =>
sendResponse(PacketCoding.CreateControlPacket(SlottedMetaAck(slot, subslot)))
PacketCoding.DecodePacket(innerPacket) match {
case Failure(e) =>
log.error(s"Failed to decode inner packet of SlottedMetaPacket: $e")
case Successful(v) =>
handlePkt(v)
}
case sync @ ControlSync(diff, unk, f1, f2, f3, f4, fa, fb) =>
log.debug(s"SYNC: $sync")
val serverTick = Math.abs(System.nanoTime().toInt) // limit the size to prevent encoding error
sendResponse(PacketCoding.CreateControlPacket(ControlSyncResp(diff, serverTick,
fa, fb, fb, fa)))
case MultiPacket(packets) =>
packets.foreach { pkt =>
PacketCoding.DecodePacket(pkt) match {
case Failure(e) =>
log.error(s"Failed to decode inner packet of MultiPacket: $e")
case Successful(v) =>
handlePkt(v)
}
}
case MultiPacketEx(packets) =>
packets.foreach { pkt =>
PacketCoding.DecodePacket(pkt) match {
case Failure(e) =>
log.error(s"Failed to decode inner packet of MultiPacketEx: $e")
case Successful(v) =>
handlePkt(v)
}
}
case RelatedA0(subslot) =>
log.error(s"Client not ready for last control packet with subslot $subslot; potential system disarray")
case RelatedB0(subslot) =>
log.trace(s"Good control packet received $subslot")
case TeardownConnection(_) =>
log.info("Good bye")
case default =>
log.warn(s"Unhandled ControlPacket $default")
}
}
var player : Player = null
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")
//TODO begin temp player character auto-loading; remove later
import net.psforever.objects.GlobalDefinitions._
player = Player("TestCharacter"+sessionId.toString, PlanetSideEmpire.VS, CharacterGender.Female, 41, 1)
//player.Position = Vector3(3674.8438f, 2726.789f, 91.15625f)
//player.Position = Vector3(3523.039f, 2855.5078f, 90.859375f)
player.Position = Vector3(3561.0f, 2854.0f, 90.859375f)
player.Orientation = Vector3(0f, 0f, 90f)
player.Certifications += CertificationType.StandardAssault
player.Certifications += CertificationType.MediumAssault
player.Certifications += CertificationType.StandardExoSuit
player.Certifications += CertificationType.AgileExoSuit
player.Certifications += CertificationType.ReinforcedExoSuit
player.Certifications += CertificationType.ATV
player.Certifications += CertificationType.Harasser
//
player.Certifications += CertificationType.InfiltrationSuit
player.Certifications += CertificationType.Sniping
player.Certifications += CertificationType.AntiVehicular
player.Certifications += CertificationType.HeavyAssault
player.Certifications += CertificationType.SpecialAssault
player.Certifications += CertificationType.EliteAssault
player.Certifications += CertificationType.GroundSupport
player.Certifications += CertificationType.GroundTransport
player.Certifications += CertificationType.Flail
player.Certifications += CertificationType.Switchblade
player.Certifications += CertificationType.AssaultBuggy
player.Certifications += CertificationType.ArmoredAssault1
player.Certifications += CertificationType.ArmoredAssault2
player.Certifications += CertificationType.AirCavalryScout
player.Certifications += CertificationType.AirCavalryAssault
player.Certifications += CertificationType.AirCavalryInterceptor
player.Certifications += CertificationType.AirSupport
player.Certifications += CertificationType.GalaxyGunship
player.Certifications += CertificationType.Phantasm
player.Certifications += CertificationType.UniMAX
AwardBattleExperiencePoints(player, 1000000L)
// player.ExoSuit = ExoSuitType.MAX //TODO strange issue; divide number above by 10 when uncommenting
player.Slot(0).Equipment = SimpleItem(remote_electronics_kit) //Tool(GlobalDefinitions.StandardPistol(player.Faction))
player.Slot(2).Equipment = Tool(punisher) //suppressor
player.Slot(4).Equipment = Tool(GlobalDefinitions.StandardMelee(player.Faction))
player.Slot(6).Equipment = AmmoBox(bullet_9mm, 20) //bullet_9mm
player.Slot(9).Equipment = AmmoBox(rocket, 11) //bullet_9mm
player.Slot(12).Equipment = AmmoBox(frag_cartridge) //bullet_9mm
player.Slot(33).Equipment = AmmoBox(bullet_9mm_AP)
player.Slot(36).Equipment = AmmoBox(GlobalDefinitions.StandardPistolAmmo(player.Faction))
player.Slot(39).Equipment = AmmoBox(plasma_cartridge) //SimpleItem(remote_electronics_kit)
player.Slot(5).Equipment.get.asInstanceOf[LockerContainer].Inventory += 0 -> SimpleItem(remote_electronics_kit)
//TODO end temp player character auto-loading
self ! ListAccountCharacters
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global
clientKeepAlive.cancel
clientKeepAlive = context.system.scheduler.schedule(0 seconds, 500 milliseconds, self, PokeClient())
case msg @ CharacterCreateRequestMessage(name, head, voice, gender, empire) =>
log.info("Handling " + msg)
sendResponse(PacketCoding.CreateGamePacket(0, ActionResultMessage(true, None)))
self ! ListAccountCharacters
case msg @ CharacterRequestMessage(charId, action) =>
log.info("Handling " + msg)
action match {
case CharacterRequestAction.Delete =>
sendResponse(PacketCoding.CreateGamePacket(0, ActionResultMessage(false, Some(1))))
case CharacterRequestAction.Select =>
LivePlayerList.Add(sessionId, player)
//TODO check if can spawn on last continent/location from player?
//TODO if yes, get continent guid accessors
//TODO if no, get sanctuary guid accessors and reset the player's expectations
galaxy ! InterstellarCluster.GetWorld("home3")
case default =>
log.error("Unsupported " + default + " in " + msg)
}
case KeepAliveMessage(code) =>
sendResponse(PacketCoding.CreateGamePacket(0, KeepAliveMessage()))
case msg @ BeginZoningMessage() =>
log.info("Reticulating splines ...")
//map-specific initializations
//TODO continent.ClientConfiguration()
sendResponse(PacketCoding.CreateGamePacket(0, SetEmpireMessage(PlanetSideGUID(2), PlanetSideEmpire.VS))) //HART building C
sendResponse(PacketCoding.CreateGamePacket(0, SetEmpireMessage(PlanetSideGUID(29), PlanetSideEmpire.NC))) //South Villa Gun Tower
sendResponse(PacketCoding.CreateGamePacket(0, TimeOfDayMessage(1191182336)))
sendResponse(PacketCoding.CreateGamePacket(0, ReplicationStreamMessage(5, Some(6), Vector(SquadListing())))) //clear squad list
//render Equipment that was dropped into zone before the player arrived
continent.EquipmentOnGround.foreach(item => {
val definition = item.Definition
sendResponse(
PacketCoding.CreateGamePacket(0,
ObjectCreateMessage(
definition.ObjectId,
item.GUID,
DroppedItemData(PlacementData(item.Position, item.Orientation), definition.Packet.ConstructorData(item).get)
)
)
)
})
//load active players in zone
LivePlayerList.ZonePopulation(continent.Number, _ => true).foreach(char => {
sendResponse(
PacketCoding.CreateGamePacket(0,
ObjectCreateMessage(ObjectClass.avatar, char.GUID, char.Definition.Packet.ConstructorData(char).get)
)
)
})
//load active vehicles in zone
continent.Vehicles.foreach(vehicle => {
val definition = vehicle.Definition
sendResponse(PacketCoding.CreateGamePacket(0,
ObjectCreateMessage(
definition.ObjectId,
vehicle.GUID,
definition.Packet.ConstructorData(vehicle).get
)
))
//seat vehicle occupants
vehicle.Definition.MountPoints.values.foreach(seat_num => {
vehicle.Seat(seat_num).get.Occupant match {
case Some(tplayer) =>
if(tplayer.HasGUID) {
sendResponse(PacketCoding.CreateGamePacket(0, ObjectAttachMessage(vehicle.GUID, tplayer.GUID, seat_num)))
}
case None => ;
}
})
ReloadVehicleAccessPermissions(vehicle)
})
//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
val obj_uid = objDef.ObjectId
val obj_data = objDef.Packet.ConstructorData(obj).get
sendResponse(PacketCoding.CreateGamePacket(0,
ObjectCreateMessage(
obj_uid,
PlanetSideGUID(interface_guid),
ObjectCreateMessageParent(parent_guid, 1),
obj_data
)
))
case _ => ;
}
//seat terminal occupants
continent.GUID(terminal_guid) match {
case Some(obj : Mountable) =>
obj.MountPoints.foreach({ case((_, seat_num)) =>
obj.Seat(seat_num).get.Occupant match {
case Some(tplayer) =>
if(tplayer.HasGUID) {
sendResponse(PacketCoding.CreateGamePacket(0, ObjectAttachMessage(parent_guid, tplayer.GUID, seat_num)))
}
case None => ;
}
})
case _ => ;
}
})
avatarService ! Service.Join(player.Continent)
localService ! Service.Join(player.Continent)
vehicleService ! Service.Join(player.Continent)
self ! SetCurrentAvatar(player)
case msg @ PlayerStateMessageUpstream(avatar_guid, pos, vel, yaw, pitch, yaw_upper, seq_time, unk3, is_crouching, is_jumping, unk4, is_cloaking, unk5, unk6) =>
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
val wepInHand : Boolean = player.Slot(player.DrawnSlot).Equipment match {
case Some(item) => item.Definition == GlobalDefinitions.bolt_driver
case None => false
}
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlayerState(avatar_guid, msg, player.Spectator, wepInHand))
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
player.VehicleSeated match {
case Some(vehicle_guid) =>
continent.GUID(vehicle_guid) match {
case Some(obj : Vehicle) =>
obj.PassengerInSeat(player) match {
case Some(seat_num) =>
obj.WeaponControlledFromSeat(seat_num) match {
case Some(tool) =>
if(tool.GUID == object_guid) {
//TODO set tool orientation?
player.Orientation = Vector3(0f, pitch, yaw)
vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.ChildObjectState(player.GUID, object_guid, pitch, yaw))
}
case None =>
log.warn(s"ChildObjectState: player $player is not using stated controllable agent")
}
case None =>
log.warn(s"ChildObjectState: player ${player.GUID} is not in a position to use controllable agent")
}
case _ =>
log.warn(s"ChildObjectState: player $player's controllable agent not available in scope")
}
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")
}
//log.info("ChildObjectState: " + msg)
case msg @ VehicleStateMessage(vehicle_guid, unk1, pos, ang, vel, unk5, unk6, unk7, wheels, unk9, unkA) =>
continent.GUID(vehicle_guid) match {
case Some(obj : Vehicle) =>
val seat = obj.Seat(0).get
if(seat.Occupant.contains(player)) { //we're driving the vehicle
player.Position = pos //convenient
if(seat.ControlledWeapon.isEmpty) {
player.Orientation = Vector3(0f, 0f, ang.z) //convenient
}
obj.Position = pos
obj.Orientation = ang
obj.Velocity = vel
vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.VehicleState(player.GUID, vehicle_guid, unk1, pos, ang, vel, unk5, unk6, unk7, wheels, unk9, unkA))
}
//TODO placing a "not driving" warning here may trigger as we are disembarking the vehicle
case _ =>
log.warn(s"VehicleState: no vehicle $vehicle_guid found in zone")
}
//log.info("VehicleState: " + msg)
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_vector, unk1, unk2, unk3, unk4, time_alive) =>
//log.info("ProjectileState: " + msg)
case msg @ ChatMsg(messagetype, has_wide_contents, recipient, contents, note_contents) =>
// TODO: Prevents log spam, but should be handled correctly
if (messagetype != ChatMessageType.CMT_TOGGLE_GM) {
log.info("Chat: " + msg)
}
if (messagetype == ChatMessageType.CMT_VOICE) {
sendResponse(PacketCoding.CreateGamePacket(0, ChatMsg(ChatMessageType.CMT_VOICE, false, "IlllIIIlllIlIllIlllIllI", contents, None)))
}
// TODO: handle this appropriately
if(messagetype == ChatMessageType.CMT_QUIT) {
sendResponse(DropCryptoSession())
sendResponse(DropSession(sessionId, "user quit"))
}
// 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!
sendResponse(PacketCoding.CreateGamePacket(0, ChatMsg(messagetype, has_wide_contents, recipient, contents, note_contents)))
case msg @ VoiceHostRequest(unk, PlanetSideGUID(player_guid), data) =>
log.info("Player "+player_guid+" requested in-game voice chat.")
sendResponse(PacketCoding.CreateGamePacket(0, VoiceHostKill()))
case msg @ VoiceHostInfo(player_guid, data) =>
sendResponse(PacketCoding.CreateGamePacket(0, VoiceHostKill()))
case msg @ ChangeAmmoMessage(item_guid, unk1) =>
log.info("ChangeAmmo: " + msg)
FindContainedWeapon match {
case (Some(obj), Some(tool : Tool)) =>
val originalAmmoType = tool.AmmoType
val fullMagazine = tool.MaxMagazine
do {
val requestedAmmoType = tool.NextAmmoType
if(requestedAmmoType != tool.AmmoSlot.Box.AmmoType) {
FindReloadAmmunition(obj, requestedAmmoType, fullMagazine).reverse match {
case Nil => ;
case x :: xs =>
val (deleteFunc, modifyFunc) : ((Int, AmmoBox)=>Unit, (AmmoBox, Int)=>Unit) = obj match {
case (veh : Vehicle) =>
(DeleteAmmunitionInVehicle(veh), ModifyAmmunitionInVehicle(veh))
case _ =>
(DeleteAmmunition(obj), ModifyAmmunition(obj))
}
val (stowFuncTask, stowFunc) : ((Int, AmmoBox)=>TaskResolver.GiveTask, (Int, AmmoBox)=>Unit) = obj match {
case (veh : Vehicle) =>
(StowNewAmmunitionInVehicles(veh), StowAmmunitionInVehicles(veh))
case _ =>
(StowNewAmmunition(obj), StowAmmunition(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(PacketCoding.CreateGamePacket(0, ObjectDetachMessage(tool.GUID, previousBox.GUID, Vector3(0f, 0f, 0f), 0f, 0f, 0f)))
sendResponse(PacketCoding.CreateGamePacket(0, ObjectDetachMessage(player.GUID, box.GUID, Vector3(0f, 0f, 0f), 0f, 0f, 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(PacketCoding.CreateGamePacket(0, 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(PacketCoding.CreateGamePacket(0, ChangeAmmoMessage(tool_guid, box.Capacity)))
avatarService ! 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(PacketCoding.CreateGamePacket(0, 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
.map({case(_, entry) => entry })
.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) {
//TODO split previousBox into AmmoBox objects of appropriate max capacity, e.g., 100 9mm -> 2 x 50 9mm
obj.Inventory.Fit(previousBox.Definition.Tile) match {
case Some(index) => //put retained magazine in inventory
stowFunc(index, previousBox)
case None => //drop
log.info(s"ChangeAmmo: dropping ammo box $previousBox")
val pos = player.Position
val orient = player.Orientation
sendResponse(PacketCoding.CreateGamePacket(0, ObjectDetachMessage(Service.defaultPlayerGUID, previous_box_guid, pos, 0f, 0f, orient.z)))
val orient2 = Vector3(0f, 0f, orient.z)
continent.Ground ! Zone.DropItemOnGround(previousBox, pos, orient2)
val objDef = previousBox.Definition
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.EquipmentOnGround(player.GUID, pos, orient2, objDef.ObjectId, previousBox.GUID, objDef.Packet.ConstructorData(previousBox).get))
}
}
else {
taskResolver ! GUIDTask.UnregisterObjectTask(previousBox)(continent.GUID)
}
}
}
}
while(tool.AmmoType != originalAmmoType && tool.AmmoType != tool.AmmoSlot.Box.AmmoType)
case (_, Some(_)) =>
log.error(s"ChangeAmmo: the object that was found for $item_guid was not a Tool")
case (_, None) =>
log.error(s"ChangeAmmo: can not find $item_guid")
}
case msg @ ChangeFireModeMessage(item_guid, fire_mode) =>
log.info("ChangeFireMode: " + msg)
FindWeapon match {
case Some(tool : Tool) =>
val originalModeIndex = tool.FireModeIndex
tool.NextFireMode
val modeIndex = tool.FireModeIndex
val tool_guid = tool.GUID
if(originalModeIndex != modeIndex) {
log.info(s"ChangeFireMode: changing $tool_guid to fire mode $modeIndex")
sendResponse(PacketCoding.CreateGamePacket(0, ChangeFireModeMessage(tool_guid, modeIndex)))
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ChangeFireMode(player.GUID, tool_guid, modeIndex))
}
else {
tool.FireModeIndex = originalModeIndex
sendResponse(PacketCoding.CreateGamePacket(0, ChangeFireModeMessage(tool_guid, originalModeIndex)))
}
case Some(_) =>
log.error(s"ChangeFireMode: the object that was found for $item_guid was not a Tool")
case None =>
log.error(s"ChangeFireMode: can not find $item_guid")
}
case msg @ ChangeFireStateMessage_Start(item_guid) =>
log.info("ChangeFireState_Start: " + msg)
if(shooting.isEmpty) {
FindEquipment match {
case Some(tool : Tool) =>
if(tool.GUID == item_guid && tool.Magazine > 0) {
shooting = Some(item_guid)
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ChangeFireState_Start(player.GUID, item_guid))
}
case Some(_) => //permissible, for now
shooting = Some(item_guid)
avatarService ! 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.info("ChangeFireState_Stop: " + msg)
val weapon : Option[Equipment] = if(shooting.contains(item_guid)) {
shooting = None
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ChangeFireState_Stop(player.GUID, item_guid))
FindEquipment
}
else {
//some weapons, e.g., the decimator, do not send a ChangeFireState_Start on the last shot
FindEquipment match {
case Some(tool) =>
if(tool.Definition == GlobalDefinitions.phoenix) {
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ChangeFireState_Start(player.GUID, item_guid))
avatarService ! 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 _ => ;
}
progressBarUpdate.cancel //TODO independent action?
case msg @ EmoteMsg(avatar_guid, emote) =>
log.info("Emote: " + msg)
sendResponse(PacketCoding.CreateGamePacket(0, EmoteMsg(avatar_guid, emote)))
case msg @ DropItemMessage(item_guid) =>
log.info("DropItem: " + msg)
player.FreeHand.Equipment match {
case Some(item) =>
if(item.GUID == item_guid) {
val orient : Vector3 = Vector3(0f, 0f, player.Orientation.z)
player.FreeHand.Equipment = None
continent.Ground ! Zone.DropItemOnGround(item, player.Position, orient)
sendResponse(PacketCoding.CreateGamePacket(0, ObjectDetachMessage(player.GUID, item.GUID, player.Position, 0f, 0f, player.Orientation.z)))
val objDef = item.Definition
avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.EquipmentOnGround(player.GUID, player.Position, orient, objDef.ObjectId, item.GUID, objDef.Packet.ConstructorData(item).get))
}
else {
log.warn(s"item in hand was ${item.GUID} but trying to drop $item_guid; nothing will be dropped")
}
case None =>
log.error(s"$player wanted to drop an item, but it was not in hand")
}
case msg @ PickupItemMessage(item_guid, player_guid, unk1, unk2) =>
log.info("PickupItem: " + msg)
continent.Ground ! Zone.GetItemOnGround(player, item_guid)
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) {
FindReloadAmmunition(obj, tool.AmmoType, reloadValue).reverse match {
case Nil =>
log.warn(s"ReloadMessage: no ammunition could be found for $item_guid")
case list @ x :: xs =>
val (deleteFunc, modifyFunc) : ((Int, AmmoBox)=>Unit, (AmmoBox, Int)=>Unit) = obj match {
case (veh : Vehicle) =>
(DeleteAmmunitionInVehicle(veh), ModifyAmmunitionInVehicle(veh))
case _ =>
(DeleteAmmunition(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(PacketCoding.CreateGamePacket(0, ReloadMessage(item_guid, actualReloadValue, unk1)))
avatarService ! 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("ObjectHeld: " + msg)
val before = player.DrawnSlot
//TODO remove this kludge; explore how to stop BuyExoSuit(Max) sending a tardy ObjectHeldMessage(me, 255)
if(player.ExoSuit != ExoSuitType.MAX && (player.DrawnSlot = held_holsters) != before) {
avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.ObjectHeld(player.GUID, player.LastDrawnSlot))
}
case msg @ AvatarJumpMessage(state) =>
//log.info("AvatarJump: " + msg)
case msg @ ZipLineMessage(player_guid,origin_side,action,id,pos) =>
log.info("ZipLineMessage: " + msg)
if (!origin_side && action == 0) {
//doing this lets you use the zip line in one direction, cant come back
sendResponse(PacketCoding.CreateGamePacket(0, ZipLineMessage(player_guid, origin_side, action, id, pos)))
}
else if (!origin_side && action == 1) {
//disembark from zipline at destination !
sendResponse(PacketCoding.CreateGamePacket(0, ZipLineMessage(player_guid, origin_side, action, 0, pos)))
}
else if (!origin_side && action == 2) {
//get off by force
sendResponse(PacketCoding.CreateGamePacket(0, ZipLineMessage(player_guid, origin_side, action, 0, pos)))
}
else if (origin_side && action == 0) {
// for teleporters & the other zipline direction
}
case msg @ RequestDestroyMessage(object_guid) =>
// TODO: Make sure this is the correct response for all cases
continent.GUID(object_guid) match {
case Some(vehicle : Vehicle) =>
if(player.VehicleOwned.contains(object_guid) && vehicle.Owner.contains(player.GUID)) {
vehicleService ! VehicleServiceMessage.UnscheduleDeconstruction(object_guid)
vehicleService ! VehicleServiceMessage.RequestDeleteVehicle(vehicle, continent)
log.info(s"RequestDestroy: vehicle $object_guid")
}
else {
log.info(s"RequestDestroy: must own vehicle $object_guid in order to deconstruct it")
}
case Some(obj : Equipment) =>
val findFunc : PlanetSideGameObject with Container => Option[(PlanetSideGameObject with Container, Option[Int])] = FindInLocalContainer(object_guid)
findFunc(player)
.orElse(findFunc(player.Locker))
.orElse(accessedContainer match {
case Some(parent) =>
findFunc(parent)
case None =>
None
}) match {
case Some((parent, Some(slot))) =>
taskResolver ! RemoveEquipmentFromSlot(parent, obj, slot)
log.info(s"RequestDestroy: equipment $object_guid")
case _ =>
//TODO search for item on ground
sendResponse(PacketCoding.CreateGamePacket(0, ObjectDeleteMessage(object_guid, 0)))
log.warn(s"RequestDestroy: object $object_guid not found")
}
case None =>
log.warn(s"RequestDestroy: object $object_guid not found")
case _ =>
log.warn(s"RequestDestroy: not allowed to delete object $object_guid")
}
case msg @ ObjectDeleteMessage(object_guid, unk1) =>
sendResponse(PacketCoding.CreateGamePacket(0, ObjectDeleteMessage(object_guid, 0)))
log.info("ObjectDelete: " + msg)
case msg @ MoveItemMessage(item_guid, source_guid, destination_guid, dest, unk1) =>
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 destSlot = destination.Slot(dest)
val destItem = destSlot.Equipment
if( {
val tile = item.Definition.Tile
destination.Collisions(dest, tile.Width, tile.Height) match {
case Success(Nil) =>
destItem.isEmpty //no item swap; abort if encountering an unexpected item
case Success(entry :: Nil) =>
destItem.contains(entry.obj) //one item to swap; abort if destination item is missing or is wrong
case Success(_) | scala.util.Failure(_) =>
false //abort when too many items at destination or other failure case
}
} && indexSlot.Equipment.contains(item)) {
log.info(s"MoveItem: $item_guid moved from $source_guid @ $index to $destination_guid @ $dest")
indexSlot.Equipment = None
destItem match { //do we have a swap item?
case Some(item2) => //yes, swap
destSlot.Equipment = None //remove item2 to make room for item
destSlot.Equipment = item
(indexSlot.Equipment = item2) match {
case Some(_) => //item and item2 swapped places successfully
log.info(s"MoveItem: ${item2.GUID} swapped to $source_guid @ $index")
//cleanly shuffle items around to avoid losing icons
sendResponse(PacketCoding.CreateGamePacket(0, ObjectDetachMessage(source_guid, item_guid, Vector3(0f, 0f, 0f), 0f, 0f, 0f))) //ground; A -> C
sendResponse(PacketCoding.CreateGamePacket(0, ObjectAttachMessage(source_guid, item2.GUID, index))) //B -> A
source match {
case (obj : Vehicle) =>
val player_guid = player.GUID
vehicleService ! VehicleServiceMessage(s"${obj.Actor}", VehicleAction.UnstowEquipment(player_guid, item_guid))
vehicleService ! VehicleServiceMessage(s"${obj.Actor}", VehicleAction.StowEquipment(player_guid, source_guid, index, item2))
//TODO visible slot verification, in the case of BFR arms
case (_ : Player) =>
if(source.VisibleSlots.contains(index)) {
avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.EquipmentInHand(source_guid, index, item2))
}
case _ => ;
//TODO something?
}
case None => //item2 does not fit; drop on ground
val pos = source.Position
val sourceOrientZ = source.Orientation.z
val orient : Vector3 = Vector3(0f, 0f, sourceOrientZ)
continent.Actor ! Zone.DropItemOnGround(item2, pos, orient)
sendResponse(PacketCoding.CreateGamePacket(0, ObjectDetachMessage(source_guid, item2.GUID, pos, 0f, 0f, sourceOrientZ))) //ground
val objDef = item2.Definition
avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.EquipmentOnGround(player.GUID, pos, orient, objDef.ObjectId, item2.GUID, objDef.Packet.ConstructorData(item2).get))
}
case None => //just move item over
destSlot.Equipment = item
source match {
case (obj : Vehicle) =>
vehicleService ! VehicleServiceMessage(s"${obj.Actor}", VehicleAction.UnstowEquipment(player.GUID, item_guid))
//TODO visible slot verification, in the case of BFR arms
case _ => ;
//TODO something?
}
}
sendResponse(PacketCoding.CreateGamePacket(0, ObjectAttachMessage(destination_guid, item_guid, dest)))
destination match {
case (obj : Vehicle) =>
vehicleService ! VehicleServiceMessage(s"${obj.Actor}", VehicleAction.StowEquipment(player.GUID, destination_guid, dest, item))
//TODO visible slot verification, in the case of BFR arms
case (_ : Player) =>
if(destination.VisibleSlots.contains(dest)) {
avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.EquipmentInHand(destination_guid, dest, item))
}
case _ => ;
//TODO something?
}
}
else if(indexSlot.Equipment.nonEmpty) {
log.error(s"MoveItem: wanted to move $item_guid, but unexpected item ${indexSlot.Equipment.get} at origin")
}
else {
log.error(s"MoveItem: wanted to move $item_guid, but unexpected item(s) at destination")
}
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")
case (_, None, _) =>
log.error(s"MoveItem: wanted to move $item_guid from $source_guid to $destination_guid, but could not find destination")
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("LootItem: " + msg)
case msg @ AvatarImplantMessage(_, _, _, _) => //(player_guid, unk1, unk2, implant) =>
log.info("AvatarImplantMessage: " + msg)
case msg @ UseItemMessage(avatar_guid, unk1, 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)
continent.GUID(object_guid) match {
case Some(door : Door) =>
if(player.Faction == door.Faction || ((continent.Map.DoorToLock.get(object_guid.guid) match {
case Some(lock_guid) => continent.GUID(lock_guid).get.asInstanceOf[IFFLock].HackedBy.isDefined
case None => !door.isOpen
}) || Vector3.ScalarProjection(door.Outwards, player.Position - door.Position) < 0f)) {
door.Actor ! Door.Use(player, msg)
}
else if(door.isOpen) {
//the door is open globally ... except on our screen
sendResponse(PacketCoding.CreateGamePacket(0, GenericObjectStateMsg(object_guid, 16)))
}
case Some(panel : IFFLock) =>
if(panel.Faction != player.Faction && panel.HackedBy.isEmpty) {
player.Slot(player.DrawnSlot).Equipment match {
case Some(tool : SimpleItem) =>
if(tool.Definition == GlobalDefinitions.remote_electronics_kit) {
//TODO get player hack level (for now, presume 15s in intervals of 4/s)
progressBarValue = Some(-2.66f)
self ! WorldSessionActor.ItemHacking(player, panel, tool.GUID, 2.66f, FinishHackingDoor(panel, 1114636288L))
log.info("Hacking a door~")
}
case _ => ;
}
}
case Some(obj : Locker) =>
if(player.Faction == obj.Faction) {
log.info(s"UseItem: $player accessing a locker")
val container = player.Locker
accessedContainer = Some(container)
sendResponse(PacketCoding.CreateGamePacket(0, UseItemMessage(avatar_guid, unk1, container.GUID, unk2, unk3, unk4, unk5, unk6, unk7, unk8, 456)))
}
else {
log.info(s"UseItem: not $player's locker")
}
case Some(obj : Vehicle) =>
val equipment = player.Slot(player.DrawnSlot).Equipment
if(player.Faction == obj.Faction) {
if(equipment match {
case Some(tool : Tool) =>
tool.Definition match {
case GlobalDefinitions.nano_dispenser => false
case _ => true
}
case _ => true
}) {
//access to trunk
if(obj.AccessingTrunk.isEmpty) {
obj.AccessingTrunk = player.GUID
accessedContainer = Some(obj)
AccessContents(obj)
sendResponse(PacketCoding.CreateGamePacket(0, UseItemMessage(avatar_guid, unk1, object_guid, unk2, unk3, unk4, unk5, unk6, unk7, unk8, itemType)))
}
else {
log.info(s"UseItem: $player can not cut in line while player ${obj.AccessingTrunk.get} is using $obj's trunk")
}
}
else if(equipment.isDefined) {
equipment.get.Definition match {
case GlobalDefinitions.nano_dispenser =>
//TODO repairing behavior
case _ => ;
}
}
}
//enemy player interactions
else if(equipment.isDefined) {
equipment.get.Definition match {
case GlobalDefinitions.remote_electronics_kit =>
//TODO hacking behavior
case _ => ;
}
}
case Some(obj : PlanetSideGameObject) =>
if(itemType != 121) {
sendResponse(PacketCoding.CreateGamePacket(0, UseItemMessage(avatar_guid, unk1, object_guid, unk2, unk3, unk4, unk5, unk6, unk7, unk8, itemType)))
}
else if(itemType == 121 && !unk3) { // TODO : medkit use ?!
sendResponse(PacketCoding.CreateGamePacket(0, UseItemMessage(avatar_guid, unk1, object_guid, 0, unk3, unk4, unk5, unk6, unk7, unk8, itemType)))
sendResponse(PacketCoding.CreateGamePacket(0, PlanetsideAttributeMessage(avatar_guid, 0, 100))) // avatar with 100 hp
sendResponse(PacketCoding.CreateGamePacket(0, ObjectDeleteMessage(PlanetSideGUID(unk1), 2)))
}
case None => ;
}
case msg @ UnuseItemMessage(player_guid, object_guid) =>
log.info("UnuseItem: " + msg)
continent.GUID(object_guid) match {
case Some(obj : Vehicle) =>
if(obj.AccessingTrunk.contains(player.GUID)) {
obj.AccessingTrunk = None
UnAccessContents(obj)
}
case _ =>;
}
accessedContainer = None
case msg @ DeployObjectMessage(guid, unk1, pos, roll, pitch, yaw, unk2) =>
log.info("DeployObject: " + msg)
case msg @ GenericObjectStateMsg(object_guid, unk1) =>
log.info("GenericObjectState: " + msg)
case msg @ ItemTransactionMessage(terminal_guid, _, _, _, _, _) =>
log.info("ItemTransaction: " + msg)
continent.GUID(terminal_guid) match {
case Some(term : Terminal) =>
if(player.Faction == term.Faction) {
term.Actor ! Terminal.Request(player, msg)
}
case Some(obj : PlanetSideGameObject) => ;
case None => ;
}
case msg @ FavoritesRequest(player_guid, unk, action, line, label) =>
if(player.GUID == player_guid) {
val name = label.getOrElse("missing_loadout_name")
action match {
case FavoritesAction.Unknown => ;
case FavoritesAction.Save =>
player.SaveLoadout(name, line)
sendResponse(PacketCoding.CreateGamePacket(0, FavoritesMessage(0, player_guid, line, name)))
case FavoritesAction.Delete =>
player.DeleteLoadout(line)
sendResponse(PacketCoding.CreateGamePacket(0, FavoritesMessage(0, player_guid, line, "")))
}
}
log.info("FavoritesRequest: " + msg)
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) =>
avatarService ! 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("WeaponFire: " + msg)
FindWeapon match {
case Some(tool : Tool) =>
if(tool.Magazine <= 0) { //safety: enforce ammunition depletion
tool.Magazine = 0
sendResponse(PacketCoding.CreateGamePacket(0, InventoryStateMessage(tool.AmmoSlot.Box.GUID, weapon_guid, 0)))
sendResponse(PacketCoding.CreateGamePacket(0, ChangeFireStateMessage_Stop(weapon_guid)))
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ChangeFireState_Stop(player.GUID, weapon_guid))
sendResponse(PacketCoding.CreateGamePacket(0, WeaponDryFireMessage(weapon_guid)))
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.WeaponDryFire(player.GUID, weapon_guid))
}
else { //shooting
tool.Discharge
//TODO other stuff?
}
case _ => ;
}
case msg @ WeaponLazeTargetPositionMessage(weapon, pos1, pos2) =>
log.info("Lazing position: " + pos2.toString)
case msg @ HitMessage(seq_time, projectile_guid, unk1, hit_info, unk2, unk3, unk4) =>
log.info("Hit: " + msg)
case msg @ SplashHitMessage(unk1, unk2, unk3, unk4, unk5, unk6, unk7, unk8) =>
log.info("SplashHitMessage: " + msg)
case msg @ AvatarFirstTimeEventMessage(avatar_guid, object_guid, unk1, event_name) =>
log.info("AvatarFirstTimeEvent: " + msg)
case msg @ WarpgateRequest(continent_guid, building_guid, dest_building_guid, dest_continent_guid, unk1, unk2) =>
log.info("WarpgateRequest: " + msg)
case msg @ MountVehicleMsg(player_guid, mountable_guid, unk) =>
log.info("MountVehicleMsg: "+msg)
continent.GUID(mountable_guid) match {
case Some(obj : Mountable) =>
obj.GetSeatFromMountPoint(unk) 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 $unk, but no seat exists there")
}
case None | Some(_) =>
log.warn(s"MountVehicleMsg: not a mountable thing")
}
case msg @ DismountVehicleMsg(player_guid, unk1, unk2) =>
//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
player.VehicleSeated match {
case Some(obj_guid) =>
continent.GUID(obj_guid) match {
case Some(obj : Mountable) =>
obj.PassengerInSeat(player) match {
case Some(seat_num : Int) =>
obj.Actor ! Mountable.TryDismount(player, seat_num)
case None =>
dismountWarning(s"DismountVehicleMsg: can not find where player $player_guid is seated in mountable $obj_guid")
}
case _ =>
dismountWarning(s"DismountVehicleMsg: can not find mountable entity $obj_guid")
}
case None =>
dismountWarning(s"DismountVehicleMsg: player $player_guid not considered seated in a mountable entity")
}
}
else {
//kicking someone else out of a seat; need to own that seat/mountable
player.VehicleOwned match {
case Some(obj_guid) =>
(continent.GUID(obj_guid), continent.GUID(player_guid)) 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 == Some(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; removing ownership to mitigate confusion")
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(a, b, c, d, e, f, g, h, i) =>
log.info("SquadDefinitionAction: " + msg)
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)
continent.GUID(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")
vehicleService ! 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(seat_num => {
val seat = vehicle.Seat(seat_num).get
seat.Occupant match {
case Some(tplayer) =>
if(vehicle.SeatPermissionGroup(seat_num).contains(group) && tplayer != player) {
seat.Occupant = None
tplayer.VehicleSeated = None
vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.KickPassenger(tplayer.GUID, 4, false, object_guid))
}
case None => ;
}
})
}
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(PacketCoding.CreateGamePacket(0,PlanetsideAttributeMessage(object_guid, attribute_type, attribute_value)))
}
case msg @ BattleplanMessage(char_id, player_name, zonr_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, player_guid) =>
log.info("HitHint: "+msg)
case msg @ TargetingImplantRequest(list) =>
log.info("TargetingImplantRequest: "+msg)
case msg @ ActionCancelMessage(u1, u2, u3) =>
log.info("Cancelled: "+msg)
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 = avatarService
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(
PacketCoding.CreateGamePacket(0,
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, 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 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.RegisterAvatar(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 `RegisterNewVehicle`
* @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))
)
}
/**
* Construct tasking that adds a completed and registered vehicle into the scene.
* The major difference between `RegisterVehicle` and `RegisterNewVehicle` 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 RegisterNewVehicle(obj : Vehicle, pad : VehicleSpawnPad) : TaskResolver.GiveTask = {
TaskResolver.GiveTask(
new Task() {
private val localVehicle = obj
private val localPad = pad.Actor
private val localAnnounce = vehicleService
private val localSession : String = sessionId.toString
private val localPlayer = player
private val localVehicleService = vehicleService
private val localZone = continent
override def isComplete : Task.Resolution.Value = {
if(localVehicle.Actor != ActorRef.noSender) {
Task.Resolution.Success
}
else {
Task.Resolution.Incomplete
}
}
def Execute(resolver : ActorRef) : Unit = {
localAnnounce ! VehicleServiceMessage.GiveActorControl(obj, localSession)
localPad ! VehicleSpawnPad.VehicleOrder(localPlayer, localVehicle)
localVehicleService ! VehicleServiceMessage.DelayedVehicleDeconstruction(localVehicle, localZone, 60L)
resolver ! scala.util.Success(this)
}
}, List(RegisterVehicle(obj)))
}
/**
* 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 = avatarService
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(PacketCoding.CreateGamePacket(0, ObjectDeleteMessage(localObjectGUID, 0)))
if(localTarget.VisibleSlots.contains(localIndex)) {
localService ! AvatarServiceMessage(localContinent, AvatarAction.ObjectDelete(localTarget.GUID, localObjectGUID))
}
}
}
)
}
/**
* 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 = avatarService
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(PacketCoding.CreateGamePacket(0, ObjectHeldMessage(localPlayer.GUID, localSlot, true)))
localService ! AvatarServiceMessage(localPlayer.Continent, AvatarAction.ObjectHeld(localPlayer.GUID, localSlot))
}
}, priorTasking
)
}
/**
* 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 => ;
}
}
/**
* The process of hacking the `Door` `IFFLock` is completed.
* Pass the message onto the lock and onto the local events system.
* @param target the `IFFLock` belonging to the door that is being hacked
* @param unk na;
* used by `HackingMessage` as `unk5`
* @see `HackMessage`
*/
//TODO add params here depending on which params in HackMessage are important
//TODO sound should be centered on IFFLock, not on player
private def FinishHackingDoor(target : IFFLock, unk : Long)() : Unit = {
target.Actor ! CommonMessages.Hack(player)
localService ! LocalServiceMessage(continent.Id, LocalAction.TriggerSound(player.GUID, TriggeredSound.HackDoor, player.Position, 30, 0.49803925f))
localService ! LocalServiceMessage(continent.Id, LocalAction.HackTemporarily(player.GUID, continent, target, unk))
}
/**
* Temporary function that iterates over vehicle permissions and turns them into `PlanetsideAttributeMessage` packets.<br>
* <br>
* 2 November 2017
* Unexpected behavior causes seat mount points to become blocked when a new driver claims the vehicle.
* For the purposes of ensuring that other players are always aware of the proper permission state of the trunk and seats,
* packets are intentionally dispatched to the current client to update the states.
* Perform this action just after any instance where the client would initially gain awareness of the vehicle.
* The most important examples include either the player or the vehicle itself spawning in for the first time.
* @param vehicle the `Vehicle`
*/
def ReloadVehicleAccessPermissions(vehicle : Vehicle) : Unit = {
val vehicle_guid = vehicle.GUID
(0 to 3).foreach(group => {
sendResponse(PacketCoding.CreateGamePacket(0,
PlanetsideAttributeMessage(vehicle_guid, group + 10, vehicle.PermissionGroup(group).get.id.toLong)
))
})
}
/**
* 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 tplayer the player
* @param bep the change in experience points, positive by assertion
* @return the player's current battle experience points
*/
def AwardBattleExperiencePoints(tplayer : Player, bep : Long) : Long = {
val oldBep = tplayer.BEP
if(bep <= 0) {
log.error(s"trying to set $bep battle experience points on $tplayer; value can not be negative")
oldBep
}
else {
val oldSlots = DetailedCharacterData.numberOfImplantSlots(oldBep)
val newBep = oldBep + bep
val newSlots = DetailedCharacterData.numberOfImplantSlots(newBep)
tplayer.BEP = newBep
if(newSlots > oldSlots) {
(oldSlots until newSlots).foreach(slotNumber => {
tplayer.Implants(slotNumber).Unlocked = true
log.info(s"unlocking implant slot $slotNumber for $tplayer")
})
}
newBep
}
}
/**
* Common preparation for interfacing with a vehicle.
* Join a vehicle-specific group for shared updates.
* Construct every object in the vehicle's inventory fpr shared manipulation updates.
* @param vehicle the vehicle
*/
def AccessContents(vehicle : Vehicle) : Unit = {
vehicleService ! Service.Join(s"${vehicle.Actor}")
val parent_guid = vehicle.GUID
vehicle.Trunk.Items.foreach({
case ((_, entry)) =>
val obj = entry.obj
val objDef = obj.Definition
sendResponse(PacketCoding.CreateGamePacket(0,
ObjectCreateDetailedMessage(
objDef.ObjectId,
obj.GUID,
ObjectCreateMessageParent(parent_guid, entry.start),
objDef.Packet.DetailedConstructorData(obj).get
)
))
})
}
/**
* 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 = {
vehicleService ! Service.Leave(Some(s"${vehicle.Actor}"))
vehicle.Trunk.Items.foreach({
case ((_, entry)) =>
sendResponse(PacketCoding.CreateGamePacket(0, ObjectDeleteMessage(entry.obj.GUID, 0)))
})
}
/**
* 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 : Vehicle) =>
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 `AmmoBox` objects of a certain type of `Ammo`
* whose sum capacities is greater than, or equal to, a `desiredAmount`.<br>
* <br>
* In an occupied `List` of returned `Inventory` entries, all but the last entry is considered emptied.
* The last entry may require having its `Capacity` be set to a non-zero number.
* @param obj the `Container` to search
* @param ammoType the type of `Ammo` to search for
* @param desiredAmount how much ammunition is requested to be found
* @return a `List` of all discovered entries totaling approximately the amount of the requested `Ammo`
*/
def FindReloadAmmunition(obj : Container, ammoType : Ammo.Value, desiredAmount : Int) : List[InventoryItem] = {
var currentAmount : Int = 0
obj.Inventory.Items
.map({ case ((_, item)) => item })
.filter(obj => {
obj.obj match {
case (box : AmmoBox) =>
box.AmmoType == ammoType
case _ =>
false
}
})
.toList
.sortBy(_.start)
.takeWhile(entry => {
val previousAmount = currentAmount
currentAmount += entry.obj.asInstanceOf[AmmoBox].Capacity
previousAmount < desiredAmount
})
}
/**
* Given an object that contains a box of amunition in its `Inventory` at a certain location,
* remove it permanently.
* @param obj the `Container`
* @param start where the ammunition can be found
* @param item an object to unregister (should have been the ammunition that was removed);
* not explicitly checked
*/
private def DeleteAmmunition(obj : PlanetSideGameObject with Container)(start : Int, item : AmmoBox) : Unit = {
val item_guid = item.GUID
obj.Inventory -= start
taskResolver ! GUIDTask.UnregisterEquipment(item)(continent.GUID)
sendResponse(PacketCoding.CreateGamePacket(0, ObjectDeleteMessage(item_guid, 0)))
}
/**
* Given a vehicle that contains a box of amunition in its `Trunk` at a certain location,
* remove it permanently.
* @see `DeleteAmmunition`
* @param obj the `Vehicle`
* @param start where the ammunition can be found
* @param item an object to unregister (should have been the ammunition that was removed);
* not explicitly checked
*/
private def DeleteAmmunitionInVehicle(obj : Vehicle)(start : Int, item : AmmoBox) : Unit = {
val item_guid = item.GUID
DeleteAmmunition(obj)(start, item)
vehicleService ! VehicleServiceMessage(s"${obj.Actor}", VehicleAction.UnstowEquipment(player.GUID, item_guid))
}
/**
* Given an object that contains a box of amunition in its `Inventry` 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(PacketCoding.CreateGamePacket(0, 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)
vehicleService ! VehicleServiceMessage(s"${obj.Actor}", VehicleAction.InventoryState(player.GUID, box, obj.GUID, obj.Find(box).get, box.Definition.Packet.DetailedConstructorData(box).get))
}
/**
* Announce that an already-registered `AmmoBox` object exists in a given position in some `Container` object's inventory.
* @see `StowAmmunitionInVehicles`
* @see `ChangeAmmoMessage`
* @param obj the `Container` object
* @param index an index in `obj`'s inventory
* @param item an `AmmoBox`
*/
def StowAmmunition(obj : PlanetSideGameObject with Container)(index : Int, item : AmmoBox) : Unit = {
obj.Inventory += index -> item
sendResponse(PacketCoding.CreateGamePacket(0, ObjectAttachMessage(obj.GUID, item.GUID, index)))
}
/**
* Announce that an already-registered `AmmoBox` object exists in a given position in some vehicle's inventory.
* @see `StowAmmunition`
* @see `ChangeAmmoMessage`
* @param obj the `Vehicle` object
* @param index an index in `obj`'s inventory
* @param item an `AmmoBox`
*/
def StowAmmunitionInVehicles(obj : Vehicle)(index : Int, item : AmmoBox) : Unit = {
StowAmmunition(obj)(index, item)
vehicleService ! 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 `StowNewAmmunitionInVehicles`
* @see `ChangeAmmoMessage`
* @param obj the `Container` object
* @param index an index in `obj`'s inventory
* @param item an `AmmoBox`
* @return a `TaskResolver.GiveTask` chain that executes the action
*/
def StowNewAmmunition(obj : PlanetSideGameObject with Container)(index : Int, item : AmmoBox) : 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 `StowNewAmmunition`
* @see `ChangeAmmoMessage`
* @param obj the `Container` object
* @param index an index in `obj`'s inventory
* @param item an `AmmoBox`
* @return a `TaskResolver.GiveTask` chain that executes the action
*/
def StowNewAmmunitionInVehicles(obj : Vehicle)(index : Int, item : AmmoBox) : TaskResolver.GiveTask = {
TaskResolver.GiveTask(
new Task() {
private val localService = vehicleService
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(StowNewAmmunition(obj)(index, item))
)
}
/**
* 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)) {
taskResolver ! RemoveEquipmentFromSlot(player, tool, player.Find(tool).get)
}
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.
* @param tplayer the player
* @return true if the item is to be dropped; false, otherwise
*/
def DropPredicate(tplayer : Player) : (InventoryItem => Boolean) = entry => { //drop if Cavern equipment, or is another faction's exclusive equipment
val objDef = entry.obj.Definition
val faction = GlobalDefinitions.isFactionEquipment(objDef)
GlobalDefinitions.isCavernEquipment(objDef) || (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
}
}
/**
* 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(PacketCoding.CreateGamePacket(0, DeployRequestMessage(player.GUID, obj.GUID, DriveState.Mobile, 0, false, Vector3.Zero)))
vehicleService ! 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")
}
def failWithError(error : String) = {
log.error(error)
sendResponse(PacketCoding.CreateControlPacket(ConnectionClose()))
}
def sendResponse(cont : PlanetSidePacketContainer) : Unit = {
log.trace("WORLD SEND: " + cont)
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 : GamePacket)
private final case class PokeClient()
private final case class ServerLoaded()
private final case class PlayerLoaded(tplayer : Player)
private final case class PlayerFailedToLoad(tplayer : Player)
private final case class ListAccountCharacters()
private final case class SetCurrentAvatar(tplayer : Player)
private final case class VehicleLoaded(vehicle : Vehicle)
/**
* A message that indicates the user is using a remote electronics kit to hack some server object.
* Each time this message is sent for a given hack attempt counts as a single "tick" of progress.
* The process of "making progress" with a hack involves sending this message repeatedly until the progress is 100 or more.
* @param tplayer the player
* @param target the object being hacked
* @param tool_guid the REK
* @param delta how much the progress bar value changes each tick
* @param completeAction a custom action performed once the hack is completed
* @param tickAction an optional action is is performed for each tick of progress
*/
private final case class ItemHacking(tplayer : Player,
target : PlanetSideServerObject,
tool_guid : PlanetSideGUID,
delta : Float,
completeAction : () => Unit,
tickAction : Option[() => Unit] = None)
}