mirror of
https://github.com/psforever/PSF-LoginServer.git
synced 2026-04-29 08:15:29 +00:00
adjusted sample Reload code and added insertion and removal functions for inventory more work on player classes; moving PacketResolution to another branch decoupling GUIDs from objects; introduced Ammo enum; minor adjustments to inventory system; different object/class hierarchy transferring basic files from another branch converted from get/set to accessor/mutator; resolved conflict from name changes refactored basic components such as GUID and location/orientation utilities kludge; more fields are given accessor and mutators; create package for vehicle-specific classes GUID assurance, now with less object creation test files; changes to how AmmoBox initializes sorry, a little bit of everything, so much I forgot to write it all down switched to a unified fire mode object internal to a Tool importing a heavily modified version of my GUID manager objects from the laternate branch; not finished or tested yet created a Trait to make Key private, sources and selectors to allow NumberPools to exist independent of a NumberSource; placed Ascending into a misc folder swapped the Return methods for selectors so that the more primitive logic is the one that needs to be overriden; renamed a selector to be more common; had to update some copyright messages fixed major logic issue with NumberPool; added comments to NumberSource files NumberSource tests simplified and made more consistent the method naming convention of NumberSources comments for NumberSelectors starting on NumberSelector tests modifications that should better support number pools; added a pool hub that acts on a predefined source adjustment to how Tools and FireModeDefintion keep track of ammunition and ammunition slots; I don't think this is sufficient small additions to Tools; filled out simple tests for other three Selectors added object lookup notation for the pool hub added more NumberSelector tests; removed the word 'Number' from subclass names re-named classes, re-packaged classes, re-named packages; created new Selector, split pools to create a fallback for the NumberPoolHub changes to NumberPool classes; tests on NumberPool classes changes to NumberPool classes; tests on NumberPool classes2 some robust testing for NumberPoolHub, plus necessary modifications to other files register and unregister functions now use Success and Failure conditions, save for one true thrown Exception reduced the flow of certain algorithm chains, mainly by adding match statements and removing Exceptions error message text the same thing as the last commit, but with NumberPools rather than NumberPoolHub various types of freeform registration added sorting functions to Selectors to allow for hotswapping for number pools, especially to and from SpecificSelector; tests for NumberPoolHub get numbers from an Array of indices, not the list of Numbers, in SimplePool added a class to represent the four types of recovery kits comments on Kit files created package for supporting equipment classes; renamed /definition/ package adding class for items that construct deployables, the router telepad included added SimpleItem, classes for game Equipment that do not have internal state; re-organized ObjectDefinition files and the game objects they create to more naturally move around EquipmentSize and InventoryTile (size) added SimpleItem tests (what they are...); removed example code that has hogging an import from AmmoBox auto-sort for loading and fitting former inventory content back into the inventory method of finding first available position to fit an certain size block in the inventory changed CheckCollision return type to provide Try[List[Int]; fixed all existing references and tests wrote comments for GridInventory methods; changed insertion param to be of form 'key -> element' adding features to Player; created definitions for Player class; re-grouped ConstructionItem enumerations initial work on implants; shuffled classes to better accommodate the new implant system, I think wrote some tests for Implants; fixing Implant logic wrote tests for Player class and made adjustments where necessary basic initialization during Player creation based on exo-suit type three wrapper Actors for the normal classes comments on code modified tests to improve accountability; added Resolver class to deal with multiple tasks that contribute to a larger task changed Tools to an internal AmmoBox; don't have to def -= symbol if I def _= symbol LivePlayerList -> MasterPlayerList, and added a Fit def for Player that checks holsters as well as inventory example of packet conversion can be found with AmmoBoxDefinition added conversion for ToolDefinition added all Equipment packet conversion functionality; started working on Avatar-related conversions continued effort towards a working Player packet conversion test subclasses of Equipment apparently do not need to overide the type of the PacketConverter for generics the logical conclusion: it doesn't matter what generics Packet returns so long as it returns an ObjectCreateConverter[] type separated converters from definitions into files changed some configuration information to final; added a bunch of converters, not fully tested though changed function names in converters replaced WSA packet-driven OCDM with Player object OCDM; upgrade to Float angular data added partial support for LockerContainer; changed Equipment defaults to a common value changes to AvatarConverter to include 5th slot; changes to VehicleConverter to make work; implementation of Fury in Vehicle->packet example in WSA added a seat definition and renovated how the weapon controlled from a seat can be found comments to files mainly; non-essential functionality to some classes, mostly access determination moved converter tests to their own test file write more of this test added ServiceManager, as it is useful pool range changes added AvatarService, as it is useful straightened out the GUID actors; added the static method for adding AmmoBoxes (to be converted later) chnages to task resolution operation complicated Task and TaskResolver logic is now possible; for example, you can schedule giving an AmmoBox a GUID, before giving a Tool a GUID, before placing the Tool in a player's hand; see Suppressor example in WSA separated the Task trait and the TaskResolver actor into their own classes, moving the former RegistrationTaskResolver class into the /misc/ folder; deleted old backup copy of HubActor; modifications for PoC and supported tests added better support and protection against putting things in the wrong hand when using inventories and the Player.Slot(n) function GlobalDefinitions file; added laze pointer as an SItem, and gave it the command detonater management code; additionally fixed spelling of 'detonat[o]r' in Codec; early Terminal class work updated tests to GlobalDefinitions entries; Terminal works but I don't like it played with GUID pooling workflow, though to little avail; modifications to Terminal purchasing workflow, but still very volatile modified NumberPoolActor and NumberPoolAccessor to make them more straightforward and not use akka ask as a go-between fixed recovery options so that they do not cause more messages trailing newline InventoryItem (packet data) renamed InventoryItemData to remove ambiguity; Terminal functionality improved, allowing for swapping of exo-suits and the restoration of equipment positions remove yet-unsupported Terminal messaging made Terminal message more specific; can now put equipment into empty slot on exo-suit change; should report changes better re-organized function calls to preserved items removed from holster slots on exo-suit change moved predicate to the end of the list of params for recoverInventory so that repetition can be eliminated and a default value can be assigned issues with making Tool; committing changes before revert of NumberPoolActor and NumberPoolAccessorActor to see if those broke it a necessary evil, the reverting of these two Actors; subtask resolution does not work unless I do so, for now restored the registration portion of tasking back to where it previously was (and better?) NumberPoolActor and the ...AccessorActor are back to a comfortable place (and better?) re-draw object in hand when switching exo-suits; build AmmoBoxes for Tool during Terminal-controlled creation, not Tool-controlled creation order of task cleanup reversed to avoid index mismatch; added itsm to TerminalDefinition common 5x5 AmmoBox size; added vehicle weapon ammo boxes to terminal added error catching messages; stopped odd double-registering issue early resolved issue where multiple subtasks started their main task multiple times; added checks that an object does not register a new GUID when it already has one wrote unregistration code for Selling items back through the Terminal, repairing logic along the way; also, wrote a top-level GUID find for the Player for use of MoveItem added framework for starting on Loadouts; managed issue with parent tasks starting before being summoned by child subtasks, often resulting in the complete skip of the execution phase of the parent; refactored registration tasks in WSA modified Tool structure, exposing the AmmoSlot list a bit more stuff stuff Tool ammo slot changes to default and comments basic loadout framework for Infantry; need to integrate initial work on FavoritesRequest packet tests for FavoritesRequest packet increased size of number pool for testing; wrote an algorithm that translates to and from the simplified version of objects stored in loadouts refactored the tasking for adding Equipment and removing Equipment updated the inventory so the Map of items does not have to rely on the GUID of an item being set before the item is inserted untested routine for registering a player character; pushing all changes before making significant changes to the client init code structure added to comments of BeginZoningMessage; transitioned player through and initial step of a more proper login GUID association the current avatar is properly registered and there is something of a workflow with the messages and packets corrected another bit of logic where inventories used to be indexed by object GUID in AvatarConverter; reversed unregister-remove task sequence such that GUID-less object is not allowed to exist in a stable object hierarchy working Loadout loading added identification functions to GlobalDefinitions; echo ObjectDelete back to client accidentally got rid of something in WSA, but now restored; adding extra details to Terminal operations separated Terminal into separate files and moved files into their own package under \objects\ for now; can delete loadouts now in WSA better handling of ReloadMessage and MoveItemMessage framework for better support involving dropping and picking up items code comments and small modifications, such as the location and structure of the Terminal Equipment definitions wrote comments in GlobalDefinitions; modified code so that a primitive form of player synchronization now occurs for future testing added code to display already-dropped Equipment on the ground; limitations are explained; moved TaskResolver to more a global location, though I don't know if that helps modified avatar unregister logic to ensure vacating player is deleted from other clients 'properly' more comments; improved checks for MoveItemMessage; squared distances as necessary subtle changes to login scripting so that test character is always offered re-organizing the functions in WSA so that only the local objects separate the two message processing blocks
1565 lines
67 KiB
Scala
1565 lines
67 KiB
Scala
// Copyright (c) 2017 PSForever
|
|
import java.util.concurrent.atomic.AtomicInteger
|
|
|
|
import akka.actor.{Actor, ActorRef, Cancellable, MDCContextAware}
|
|
import net.psforever.packet.{PlanetSideGamePacket, _}
|
|
import net.psforever.packet.control._
|
|
import net.psforever.packet.game.{ObjectCreateDetailedMessage, _}
|
|
import scodec.Attempt.{Failure, Successful}
|
|
import scodec.bits._
|
|
import org.log4s.MDC
|
|
import MDCContextAware.Implicits._
|
|
import ServiceManager.Lookup
|
|
import net.psforever.objects._
|
|
import net.psforever.objects.entity.IdentifiableEntity
|
|
import net.psforever.objects.equipment._
|
|
import net.psforever.objects.guid.{Task, TaskResolver}
|
|
import net.psforever.objects.guid.actor.{Register, Unregister}
|
|
import net.psforever.objects.inventory.{GridInventory, InventoryItem}
|
|
import net.psforever.objects.terminals.{OrderTerminalDefinition, Terminal}
|
|
import net.psforever.packet.game.objectcreate._
|
|
import net.psforever.types._
|
|
|
|
import scala.annotation.tailrec
|
|
|
|
class WorldSessionActor extends Actor with MDCContextAware {
|
|
private[this] val log = org.log4s.getLogger
|
|
|
|
private final case class PokeClient()
|
|
private final case class ServerLoaded()
|
|
private final case class PlayerLoaded(tplayer : Player)
|
|
private final case class ListAccountCharacters()
|
|
private final case class SetCurrentAvatar(tplayer : Player)
|
|
private final case class Continent_GiveItemFromGround(tplyaer : Player, item : Option[Equipment]) //TODO wrong place, move later
|
|
|
|
var sessionId : Long = 0
|
|
var leftRef : ActorRef = ActorRef.noSender
|
|
var rightRef : ActorRef = ActorRef.noSender
|
|
var avatarService = Actor.noSender
|
|
var accessor = Actor.noSender
|
|
var taskResolver = Actor.noSender
|
|
|
|
var clientKeepAlive : Cancellable = WorldSessionActor.DefaultCancellable
|
|
|
|
override def postStop() = {
|
|
if(clientKeepAlive != null)
|
|
clientKeepAlive.cancel()
|
|
|
|
avatarService ! Leave()
|
|
LivePlayerList.Remove(sessionId) match {
|
|
case Some(tplayer) =>
|
|
val guid = tplayer.GUID
|
|
avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.ObjectDelete(guid, guid))
|
|
taskResolver ! UnregisterAvatar(tplayer)
|
|
//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, right) =>
|
|
this.sessionId = inSessionId
|
|
leftRef = sender()
|
|
rightRef = right.asInstanceOf[ActorRef]
|
|
|
|
ServiceManager.serviceManager ! Lookup("avatar")
|
|
ServiceManager.serviceManager ! Lookup("accessor1")
|
|
ServiceManager.serviceManager ! Lookup("taskResolver")
|
|
|
|
context.become(Started)
|
|
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("accessor1", endpoint) =>
|
|
accessor = endpoint
|
|
log.info("ID: " + sessionId + " Got guid service " + endpoint)
|
|
case ServiceManager.LookupResult("taskResolver", endpoint) =>
|
|
taskResolver = endpoint
|
|
log.info("ID: " + sessionId + " Got task resolver 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 AvatarServiceResponse.ArmorChanged(suit, subtype) =>
|
|
if(player.GUID != guid) {
|
|
sendResponse(PacketCoding.CreateGamePacket(0, ArmorChangedMessage(guid, suit, subtype)))
|
|
}
|
|
|
|
case AvatarServiceResponse.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 AvatarServiceResponse.EquipmentOnGround(pos, orient, item) =>
|
|
if(player.GUID != guid) {
|
|
val definition = item.Definition
|
|
sendResponse(
|
|
PacketCoding.CreateGamePacket(0,
|
|
ObjectCreateMessage(
|
|
definition.ObjectId,
|
|
item.GUID,
|
|
DroppedItemData(PlacementData(pos, Vector3(0f, 0f, orient.z)), definition.Packet.ConstructorData(item).get)
|
|
)
|
|
)
|
|
)
|
|
}
|
|
|
|
case AvatarServiceResponse.LoadPlayer(pdata) =>
|
|
if(player.GUID != guid) {
|
|
sendResponse(
|
|
PacketCoding.CreateGamePacket(
|
|
0,
|
|
ObjectCreateMessage(ObjectClass.avatar, guid, pdata)
|
|
)
|
|
)
|
|
}
|
|
|
|
case AvatarServiceResponse.ObjectDelete(item_guid, unk) =>
|
|
if(player.GUID != guid) {
|
|
sendResponse(PacketCoding.CreateGamePacket(0, ObjectDeleteMessage(item_guid, unk)))
|
|
}
|
|
|
|
case AvatarServiceResponse.ObjectHeld(slot) =>
|
|
if(player.GUID != guid) {
|
|
sendResponse(PacketCoding.CreateGamePacket(0, ObjectHeldMessage(guid, slot, true)))
|
|
}
|
|
|
|
case AvatarServiceResponse.PlanetSideAttribute(attribute_type, attribute_value) =>
|
|
if(player.GUID != guid) {
|
|
sendResponse(PacketCoding.CreateGamePacket(0, PlanetsideAttributeMessage(guid, attribute_type, attribute_value)))
|
|
}
|
|
|
|
case AvatarServiceResponse.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 = WorldSessionActor.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)) ||
|
|
(distanceSq < 160000 && msg.vel.isEmpty && time > 2000) ||
|
|
(distanceSq < 160000 && 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 AvatarServiceResponse.Reload(mag) =>
|
|
if(player.GUID != guid) {
|
|
sendResponse(PacketCoding.CreateGamePacket(0, ReloadMessage(guid, mag, 0)))
|
|
}
|
|
|
|
case _ => ;
|
|
}
|
|
|
|
case Terminal.TerminalMessage(tplayer, msg, order) =>
|
|
order match {
|
|
case Terminal.BuyExosuit(exosuit, subtype) =>
|
|
if(tplayer.ExoSuit == exosuit) { //just refresh armor points
|
|
//we should never actually reach this point through conventional in-game methods
|
|
sendResponse(PacketCoding.CreateGamePacket(0, ItemTransactionResultMessage (msg.terminal_guid, TransactionType.Buy, true)))
|
|
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))
|
|
}
|
|
else { //load a complete new exo-suit and shuffle the inventory around
|
|
//TODO if we're transitioning into a MAX suit, the subtype dictates the type of arm(s) if the holster list is empty
|
|
//save inventory before it gets cleared (empty holsters)
|
|
sendResponse(PacketCoding.CreateGamePacket(0, ItemTransactionResultMessage (msg.terminal_guid, TransactionType.Buy, true)))
|
|
val beforeHolsters = clearHolsters(tplayer.Holsters().iterator)
|
|
val beforeInventory = tplayer.Inventory.Clear()
|
|
//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
|
|
(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))
|
|
//fill holsters
|
|
val (afterHolsters, toInventory) = beforeHolsters.partition(elem => elem.obj.Size == tplayer.Slot(elem.start).Size)
|
|
afterHolsters.foreach({elem => tplayer.Slot(elem.start).Equipment = elem.obj })
|
|
val finalInventory = fillEmptyHolsters(tplayer.Holsters().iterator, toInventory ++ beforeInventory)
|
|
//draw holsters
|
|
(0 until 5).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
|
|
drop.foreach(obj => {
|
|
obj.Position = pos
|
|
obj.Orientation = orient
|
|
val definition = obj.Definition
|
|
sendResponse(
|
|
PacketCoding.CreateGamePacket(0,
|
|
ObjectCreateMessage(
|
|
definition.ObjectId,
|
|
obj.GUID,
|
|
DroppedItemData(PlacementData(pos, Vector3(0f, 0f, orient.z)), definition.Packet.ConstructorData(obj).get)
|
|
)
|
|
)
|
|
)
|
|
avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.EquipmentOnGround(tplayer.GUID, pos, orient, obj))
|
|
})
|
|
}
|
|
|
|
case Terminal.BuyEquipment(item) => ;
|
|
tplayer.Fit(item) match {
|
|
case Some(index) =>
|
|
sendResponse(PacketCoding.CreateGamePacket(0, ItemTransactionResultMessage (msg.terminal_guid, TransactionType.Buy, true)))
|
|
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)))
|
|
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 beforeHolsters = clearHolsters(tplayer.Holsters().iterator)
|
|
val beforeInventory = tplayer.Inventory.Clear()
|
|
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
|
|
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 ! UnregisterEquipment(elem.obj)
|
|
})
|
|
//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 {
|
|
//TODO was any previous free hand item deleted?
|
|
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
|
|
holsters.foreach(entry => {
|
|
PutEquipmentInSlot(tplayer, entry.obj, entry.start)
|
|
})
|
|
//put items into inventory
|
|
inventory.foreach(entry => {
|
|
PutEquipmentInSlot(tplayer, entry.obj, entry.start)
|
|
})
|
|
//TODO drop items on ground
|
|
|
|
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 ListAccountCharacters =>
|
|
val gen : AtomicInteger = new AtomicInteger(1)
|
|
|
|
//load characters
|
|
SetCharacterSelectScreenGUID(player, gen)
|
|
val health = player.Health
|
|
val stamina = player.Stamina
|
|
val armor = player.Armor
|
|
player.Spawn
|
|
sendResponse(PacketCoding.CreateGamePacket(0,
|
|
ObjectCreateMessage(ObjectClass.avatar, player.GUID, player.Definition.Packet.ConstructorData(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 PlayerLoaded(tplayer) =>
|
|
log.info(s"Player $tplayer has been loaded")
|
|
//init for whole server
|
|
//...
|
|
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
|
|
//LoadMapMessage -> BeginZoningMessage
|
|
sendResponse(PacketCoding.CreateGamePacket(0, LoadMapMessage("map13","home3",40100,25,true,3770441820L))) //VS Sanctuary
|
|
//load the now-registered player
|
|
tplayer.Spawn
|
|
sendResponse(PacketCoding.CreateGamePacket(0,
|
|
ObjectCreateDetailedMessage(ObjectClass.avatar, tplayer.GUID, tplayer.Definition.Packet.DetailedConstructorData(tplayer).get)
|
|
))
|
|
avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.LoadPlayer(tplayer.GUID, tplayer.Definition.Packet.ConstructorData(tplayer).get))
|
|
log.debug(s"ObjectCreateDetailedMessage: ${tplayer.Definition.Packet.DetailedConstructorData(tplayer).get}")
|
|
|
|
case SetCurrentAvatar(tplayer) =>
|
|
//avatar-specific
|
|
val guid = tplayer.GUID
|
|
LivePlayerList.Assign(sessionId, guid)
|
|
sendResponse(PacketCoding.CreateGamePacket(0, SetCurrentAvatarMessage(guid,0,0)))
|
|
sendResponse(PacketCoding.CreateGamePacket(0, CreateShortcutMessage(guid, 1, 0, true, Shortcut.MEDKIT)))
|
|
|
|
//temporary location
|
|
case Continent_GiveItemFromGround(tplayer, item) =>
|
|
item match {
|
|
case Some(obj) =>
|
|
val obj_guid = obj.GUID
|
|
tplayer.Fit(obj) match {
|
|
case Some(slot) =>
|
|
PickupItemFromGround(obj_guid)
|
|
tplayer.Slot(slot).Equipment = item
|
|
sendResponse(PacketCoding.CreateGamePacket(0, ObjectAttachMessage(tplayer.GUID, obj_guid, slot)))
|
|
avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.ObjectDelete(tplayer.GUID, obj_guid))
|
|
if(-1 < slot && slot < 5) {
|
|
avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.EquipmentInHand(tplayer.GUID, slot, obj))
|
|
}
|
|
case None =>
|
|
DropItemOnGround(obj, obj.Position, obj.Orientation) //restore
|
|
}
|
|
case None => ;
|
|
}
|
|
|
|
case WorldSessionActor.ResponseToSelf(pkt) =>
|
|
log.info(s"Received a direct message: $pkt")
|
|
sendResponse(pkt)
|
|
|
|
case default =>
|
|
failWithError(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 => failWithError(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 => failWithError(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 default =>
|
|
log.debug(s"Unhandled ControlPacket $default")
|
|
}
|
|
}
|
|
|
|
val terminal = Terminal(PlanetSideGUID(55000), new OrderTerminalDefinition)
|
|
|
|
import net.psforever.objects.GlobalDefinitions._
|
|
//this part is created by the player (should be in case of ConnectToWorldRequestMessage, maybe)
|
|
val energy_cell_box1 = AmmoBox(energy_cell)
|
|
val energy_cell_box2 = AmmoBox(energy_cell, 16)
|
|
val bullet_9mm_box1 = AmmoBox(bullet_9mm)
|
|
val bullet_9mm_box2 = AmmoBox(bullet_9mm)
|
|
val bullet_9mm_box3 = AmmoBox(bullet_9mm)
|
|
val bullet_9mm_box4 = AmmoBox(bullet_9mm, 25)
|
|
val bullet_9mm_AP_box = AmmoBox(bullet_9mm_AP)
|
|
val melee_ammo_box = AmmoBox(melee_ammo)
|
|
val
|
|
beamer1 = Tool(beamer)
|
|
beamer1.AmmoSlots.head.Box = energy_cell_box2
|
|
val
|
|
suppressor1 = Tool(suppressor)
|
|
suppressor1.AmmoSlots.head.Box = bullet_9mm_box4
|
|
val
|
|
forceblade1 = Tool(forceblade)
|
|
forceblade1.AmmoSlots.head.Box = melee_ammo_box
|
|
val rek = SimpleItem(remote_electronics_kit)
|
|
val lockerContainer = LockerContainer()
|
|
val
|
|
player = Player("IlllIIIlllIlIllIlllIllI", PlanetSideEmpire.VS, CharacterGender.Female, 41, 1)
|
|
player.Position = Vector3(3674.8438f, 2726.789f, 91.15625f)
|
|
player.Orientation = Vector3(0f, 0f, 90f)
|
|
player.Continent = "home3"
|
|
player.Slot(0).Equipment = beamer1
|
|
player.Slot(2).Equipment = suppressor1
|
|
player.Slot(4).Equipment = forceblade1
|
|
player.Slot(5).Equipment = lockerContainer
|
|
player.Slot(6).Equipment = bullet_9mm_box1
|
|
player.Slot(9).Equipment = bullet_9mm_box2
|
|
player.Slot(12).Equipment = bullet_9mm_box3
|
|
player.Slot(33).Equipment = bullet_9mm_AP_box
|
|
player.Slot(36).Equipment = energy_cell_box1
|
|
player.Slot(39).Equipment = rek
|
|
|
|
//for player2
|
|
val energy_cell_box3 = AmmoBox(PlanetSideGUID(187), energy_cell)
|
|
val energy_cell_box4 = AmmoBox(PlanetSideGUID(177), energy_cell, 16)
|
|
val bullet_9mm_box5 = AmmoBox(PlanetSideGUID(183), bullet_9mm)
|
|
val bullet_9mm_box6 = AmmoBox(PlanetSideGUID(184), bullet_9mm)
|
|
val bullet_9mm_box7 = AmmoBox(PlanetSideGUID(185), bullet_9mm)
|
|
val bullet_9mm_box8 = AmmoBox(PlanetSideGUID(179), bullet_9mm, 25)
|
|
val bullet_9mm_AP_box2 = AmmoBox(PlanetSideGUID(186), bullet_9mm_AP)
|
|
val melee_ammo_box2 = AmmoBox(PlanetSideGUID(181), melee_ammo)
|
|
|
|
val
|
|
beamer2 = Tool(PlanetSideGUID(176), beamer)
|
|
beamer2.AmmoSlots.head.Box = energy_cell_box4
|
|
val
|
|
suppressor2 = Tool(PlanetSideGUID(178), suppressor)
|
|
suppressor2.AmmoSlots.head.Box = bullet_9mm_box8
|
|
val
|
|
forceblade2 = Tool(PlanetSideGUID(180), forceblade)
|
|
forceblade2.AmmoSlots.head.Box = melee_ammo_box2
|
|
val
|
|
rek2 = SimpleItem(PlanetSideGUID(188), remote_electronics_kit)
|
|
val
|
|
lockerContainer2 = LockerContainer(PlanetSideGUID(182))
|
|
val
|
|
player2 = Player(PlanetSideGUID(275), "Doppelganger", PlanetSideEmpire.NC, CharacterGender.Female, 41, 1)
|
|
player2.Position = Vector3(3680f, 2726.789f, 91.15625f)
|
|
player2.Orientation = Vector3(0f, 0f, 0f)
|
|
player2.Continent = "home3"
|
|
player2.Slot(0).Equipment = beamer2
|
|
player2.Slot(2).Equipment = suppressor2
|
|
player2.Slot(4).Equipment = forceblade2
|
|
player2.Slot(5).Equipment = lockerContainer2
|
|
player2.Slot(6).Equipment = bullet_9mm_box5
|
|
player2.Slot(9).Equipment = bullet_9mm_box6
|
|
player2.Slot(12).Equipment = bullet_9mm_box7
|
|
player2.Slot(33).Equipment = bullet_9mm_AP_box2
|
|
player2.Slot(36).Equipment = energy_cell_box3
|
|
player2.Slot(39).Equipment = rek2
|
|
player2.Spawn
|
|
|
|
val hellfire_ammo_box = AmmoBox(PlanetSideGUID(432), hellfire_ammo)
|
|
|
|
val
|
|
fury1 = Vehicle(PlanetSideGUID(313), fury)
|
|
fury1.Faction = PlanetSideEmpire.VS
|
|
fury1.Position = Vector3(3674.8438f, 2732f, 91.15625f)
|
|
fury1.Orientation = Vector3(0.0f, 0.0f, 90.0f)
|
|
fury1.WeaponControlledFromSeat(0).get.GUID = PlanetSideGUID(300)
|
|
fury1.WeaponControlledFromSeat(0).get.AmmoSlots.head.Box = hellfire_ammo_box
|
|
|
|
val object2Hex = ObjectCreateMessage(ObjectClass.avatar, PlanetSideGUID(275), player2.Definition.Packet.ConstructorData(player2).get)
|
|
val furyHex = ObjectCreateMessage(ObjectClass.fury, PlanetSideGUID(313), fury1.Definition.Packet.ConstructorData(fury1).get)
|
|
|
|
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")
|
|
self ! ListAccountCharacters
|
|
|
|
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)
|
|
//check can spawn on last continent/location from player
|
|
//if yes, get continent guid accessors
|
|
//if no, get sanctuary guid accessors and reset the player's expectations
|
|
taskResolver ! RegisterAvatar(player)
|
|
|
|
import scala.concurrent.duration._
|
|
import scala.concurrent.ExecutionContext.Implicits.global
|
|
clientKeepAlive = context.system.scheduler.schedule(0 seconds, 500 milliseconds, self, PokeClient())
|
|
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 (VS sanctuary)
|
|
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, object2Hex))
|
|
//sendResponse(PacketCoding.CreateGamePacket(0, furyHex))
|
|
|
|
sendResponse(PacketCoding.CreateGamePacket(0, ZonePopulationUpdateMessage(PlanetSideGUID(13), 414, 138, 0, 138, 0, 138, 0, 138, 0)))
|
|
sendResponse(PacketCoding.CreateGamePacket(0, TimeOfDayMessage(1191182336)))
|
|
sendResponse(PacketCoding.CreateGamePacket(0, ReplicationStreamMessage(5, Some(6), Vector(SquadListing())))) //clear squad list
|
|
|
|
//all players are part of the same zone right now, so don't expect much
|
|
val continent = player.Continent
|
|
val player_guid = player.GUID
|
|
LivePlayerList.WorldPopulation({ case (_, char : Player) => char.Continent == continent && char.HasGUID && char.GUID != player_guid}).foreach(char => {
|
|
sendResponse(
|
|
PacketCoding.CreateGamePacket(0,
|
|
ObjectCreateMessage(ObjectClass.avatar, char.GUID, char.Definition.Packet.ConstructorData(char).get)
|
|
)
|
|
)
|
|
})
|
|
//all items are part of a single zone right now, so don't expect much
|
|
WorldSessionActor.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)
|
|
)
|
|
)
|
|
)
|
|
})
|
|
|
|
avatarService ! Join("home3")
|
|
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 == bolt_driver
|
|
case None => false
|
|
}
|
|
avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.PlayerState(avatar_guid, msg, player.Spectator, wepInHand))
|
|
//log.info("PlayerState: " + msg)
|
|
|
|
case msg @ ChildObjectStateMessage(object_guid, pitch, yaw) =>
|
|
//log.info("ChildObjectState: " + msg)
|
|
|
|
case msg @ VehicleStateMessage(vehicle_guid, unk1, pos, ang, vel, unk5, unk6, unk7, wheels, unk9, unkA) =>
|
|
//log.info("VehicleState: " + msg)
|
|
|
|
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 @ ChangeFireModeMessage(item_guid, fire_mode) =>
|
|
log.info("ChangeFireMode: " + msg)
|
|
|
|
case msg @ ChangeFireStateMessage_Start(item_guid) =>
|
|
log.info("ChangeFireState_Start: " + msg)
|
|
|
|
case msg @ ChangeFireStateMessage_Stop(item_guid) =>
|
|
log.info("ChangeFireState_Stop: " + msg)
|
|
|
|
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) {
|
|
player.FreeHand.Equipment = None
|
|
DropItemOnGround(item, player.Position, player.Orientation)
|
|
sendResponse(PacketCoding.CreateGamePacket(0, ObjectDetachMessage(player.GUID, item.GUID, player.Position, 0f, 0f, player.Orientation.z)))
|
|
avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.EquipmentOnGround(player.GUID, player.Position, player.Orientation, item))
|
|
}
|
|
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)
|
|
self ! Continent_GiveItemFromGround(player, PickupItemFromGround(item_guid))
|
|
|
|
case msg @ ReloadMessage(item_guid, ammo_clip, unk1) =>
|
|
log.info("Reload: " + msg)
|
|
val reloadValue = player.Slot(player.DrawnSlot).Equipment match {
|
|
case Some(item) =>
|
|
item match {
|
|
case tool : Tool =>
|
|
tool.FireMode.Magazine
|
|
case _ =>
|
|
0
|
|
}
|
|
case None =>
|
|
0
|
|
}
|
|
//TODO hunt for ammunition in inventory
|
|
if(reloadValue > 0) {
|
|
sendResponse(PacketCoding.CreateGamePacket(0, ReloadMessage(item_guid, reloadValue, unk1)))
|
|
}
|
|
|
|
case msg @ ObjectHeldMessage(avatar_guid, held_holsters, unk1) =>
|
|
val before = player.DrawnSlot
|
|
val after = player.DrawnSlot = held_holsters
|
|
if(before != after) {
|
|
val slot = if(after == Player.HandsDownSlot) { before } else { after }
|
|
avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.ObjectHeld(player.GUID, slot))
|
|
}
|
|
log.info("ObjectHeld: " + msg)
|
|
|
|
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 in all cases
|
|
player.Find(object_guid) match {
|
|
case Some(slot) =>
|
|
taskResolver ! RemoveEquipmentFromSlot(player, player.Slot(slot).Equipment.get, slot)
|
|
log.info("RequestDestroy: " + msg)
|
|
case None =>
|
|
sendResponse(PacketCoding.CreateGamePacket(0, ObjectDeleteMessage(object_guid, 0)))
|
|
log.warn(s"RequestDestroy: object $object_guid not found")
|
|
}
|
|
|
|
case msg @ ObjectDeleteMessage(object_guid, unk1) =>
|
|
sendResponse(PacketCoding.CreateGamePacket(0, ObjectDeleteMessage(object_guid, 0)))
|
|
log.info("ObjectDelete: " + msg)
|
|
|
|
case msg @ MoveItemMessage(item_guid, avatar_guid_1, avatar_guid_2, dest, unk1) =>
|
|
player.Find(item_guid) match {
|
|
case Some(index) =>
|
|
val indexSlot = player.Slot(index)
|
|
val destSlot = player.Slot(dest)
|
|
val item = indexSlot.Equipment.get
|
|
val destItem = destSlot.Equipment
|
|
indexSlot.Equipment = None
|
|
destSlot.Equipment = None
|
|
|
|
(destSlot.Equipment = item) match {
|
|
case Some(_) => //move item
|
|
log.info(s"MoveItem: $item_guid moved from $avatar_guid_1 @ $index to $avatar_guid_1 @ $dest")
|
|
//continue on to the code following the next match statement after resolving the match statement
|
|
destItem match {
|
|
case Some(item2) => //second item to swap?
|
|
(indexSlot.Equipment = destItem) match {
|
|
case Some(_) => //yes, swap
|
|
log.info(s"MoveItem: ${item2.GUID} swapped to $avatar_guid_1 @ $index")
|
|
//we must shuffle items around cleanly to avoid causing icons to "disappear"
|
|
if(index == Player.FreeHandSlot) { //temporarily put in safe location, A -> C
|
|
sendResponse(PacketCoding.CreateGamePacket(0, ObjectDetachMessage(player.GUID, item.GUID, Vector3(0f, 0f, 0f), 0f, 0f, 0f))) //ground
|
|
}
|
|
else {
|
|
sendResponse(PacketCoding.CreateGamePacket(0, ObjectAttachMessage(player.GUID, item.GUID, Player.FreeHandSlot))) //free hand
|
|
}
|
|
sendResponse(PacketCoding.CreateGamePacket(0, ObjectAttachMessage(player.GUID, item2.GUID, index))) //B -> A
|
|
if(0 <= index && index < 5) {
|
|
avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.EquipmentInHand(player.GUID, index, item2))
|
|
}
|
|
|
|
case None => //can't complete the swap; drop the other item on the ground
|
|
sendResponse(PacketCoding.CreateGamePacket(0, ObjectDetachMessage(player.GUID, item2.GUID, player.Position, 0f, 0f, player.Orientation.z))) //ground
|
|
avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.EquipmentOnGround(player.GUID, player.Position, player.Orientation, item2))
|
|
}
|
|
|
|
case None => ; //just move item over
|
|
}
|
|
sendResponse(PacketCoding.CreateGamePacket(0, ObjectAttachMessage(avatar_guid_1, item_guid, dest)))
|
|
if(0 <= dest && dest < 5) {
|
|
avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.EquipmentInHand(player.GUID, dest, item))
|
|
}
|
|
|
|
case None => //restore original contents
|
|
indexSlot.Equipment = item
|
|
destSlot.Equipment = destItem
|
|
}
|
|
case None => ;
|
|
}
|
|
|
|
case msg @ ChangeAmmoMessage(item_guid, unk1) =>
|
|
log.info("ChangeAmmo: " + 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)
|
|
if (itemType != 121) sendResponse(PacketCoding.CreateGamePacket(0, UseItemMessage(avatar_guid, unk1, object_guid, unk2, unk3, unk4, unk5, unk6, unk7, unk8, itemType)))
|
|
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)))
|
|
}
|
|
if (unk1 == 0 && !unk3 && unk7 == 25) {
|
|
// TODO: This should only actually be sent to doors upon opening; may break non-door items upon use
|
|
sendResponse(PacketCoding.CreateGamePacket(0, GenericObjectStateMsg(object_guid, 16)))
|
|
}
|
|
|
|
case msg @ UnuseItemMessage(player_guid, item) =>
|
|
log.info("UnuseItem: " + msg)
|
|
|
|
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, transaction_type, item_page, item_name, unk1, item_guid) =>
|
|
terminal.Actor ! Terminal.Request(player, msg)
|
|
log.info("ItemTransaction: " + msg)
|
|
|
|
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 @ WeaponFireMessage(seq_time, weapon_guid, projectile_guid, shot_origin, unk1, unk2, unk3, unk4, unk5, unk6, unk7) =>
|
|
log.info("WeaponFire: " + msg)
|
|
|
|
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, vehicle_guid, unk) =>
|
|
sendResponse(PacketCoding.CreateGamePacket(0, ObjectAttachMessage(vehicle_guid,player_guid,0)))
|
|
log.info("MounVehicleMsg: "+msg)
|
|
|
|
case msg @ DismountVehicleMsg(player_guid, unk1, unk2) =>
|
|
sendResponse(PacketCoding.CreateGamePacket(0, msg)) //should be safe; replace with ObjectDetachMessage later
|
|
log.info("DismountVehicleMsg: " + msg)
|
|
|
|
case msg @ DeployRequestMessage(player_guid, entity, unk1, unk2, unk3, pos) =>
|
|
//if you try to deploy, can not undeploy
|
|
log.info("DeployRequest: " + msg)
|
|
|
|
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(avatar_guid, attribute_type, attribute_value) =>
|
|
log.info("PlanetsideAttributeMessage: "+msg)
|
|
sendResponse(PacketCoding.CreateGamePacket(0,PlanetsideAttributeMessage(avatar_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 @ WeaponDryFireMessage(weapon) =>
|
|
log.info("WeaponDryFireMessage: "+msg)
|
|
|
|
case msg @ TargetingImplantRequest(list) =>
|
|
log.info("TargetingImplantRequest: "+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)
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Iterate over a group of `EquipmentSlot`s, some of which may be occupied with an item.
|
|
* Use `func` on any discovered `Equipment` to transform items into tasking, and add the tasking to a `List`.
|
|
* @param iter the `Iterator` of `EquipmentSlot`s
|
|
* @param func the function used to build tasking from any discovered `Equipment`
|
|
* @param list a persistent `List` of `Equipment` tasking
|
|
* @return a `List` of `Equipment` tasking
|
|
*/
|
|
@tailrec private def recursiveHolsterTaskBuilding(iter : Iterator[EquipmentSlot], func : ((Equipment)=>TaskResolver.GiveTask), list : List[TaskResolver.GiveTask] = Nil) : List[TaskResolver.GiveTask] = {
|
|
if(!iter.hasNext) {
|
|
list
|
|
}
|
|
else {
|
|
iter.next.Equipment match {
|
|
case Some(item) =>
|
|
recursiveHolsterTaskBuilding(iter, func, list :+ func(item))
|
|
case None =>
|
|
recursiveHolsterTaskBuilding(iter, func, 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 `RegisterEquipment`
|
|
* @see `PutInSlot`
|
|
*/
|
|
private def PutEquipmentInSlot(target : Player, obj : Equipment, index : Int) : Unit = {
|
|
val regTask = RegisterEquipment(obj)
|
|
obj match {
|
|
case tool : Tool =>
|
|
val linearToolTask = TaskResolver.GiveTask(regTask.task) +: regTask.subs
|
|
taskResolver ! TaskResolver.GiveTask(PutInSlot(target, tool, index).task, linearToolTask)
|
|
case _ =>
|
|
taskResolver ! 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 `UnregisterEquipment`
|
|
* @see `RemoveFromSlot`
|
|
*/
|
|
private def RemoveEquipmentFromSlot(target : Player, obj : Equipment, index : Int) : Unit = {
|
|
val regTask = UnregisterEquipment(obj)
|
|
//to avoid an error from a GUID-less object from being searchable, it is removed from the inventory first
|
|
obj match {
|
|
case _ : Tool =>
|
|
taskResolver ! TaskResolver.GiveTask(regTask.task, RemoveFromSlot(target, obj, index) +: regTask.subs)
|
|
case _ =>
|
|
taskResolver ! TaskResolver.GiveTask(regTask.task, RemoveFromSlot(target, obj, index) :: Nil)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Construct tasking that registers an object with the a globally unique identifier selected from a pool of numbers.
|
|
* The object in question is not considered to have any form of internal complexity.
|
|
* @param obj the object being registered
|
|
* @return a `TaskResolver.GiveTask` message
|
|
*/
|
|
private def RegisterObjectTask(obj : IdentifiableEntity) : TaskResolver.GiveTask = {
|
|
TaskResolver.GiveTask(
|
|
new Task() {
|
|
private val localObject = obj
|
|
private val localAccessor = accessor
|
|
|
|
override def isComplete : Task.Resolution.Value = {
|
|
try {
|
|
localObject.GUID
|
|
Task.Resolution.Success
|
|
}
|
|
catch {
|
|
case _ : Exception =>
|
|
Task.Resolution.Incomplete
|
|
}
|
|
}
|
|
|
|
def Execute(resolver : ActorRef) : Unit = {
|
|
localAccessor ! Register(localObject, resolver)
|
|
}
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Construct tasking that registers an object that is an object of type `Tool`.
|
|
* `Tool` objects have internal structures called "ammo slots;"
|
|
* each ammo slot contains a register-able `AmmoBox` object.
|
|
* @param obj the object being registered
|
|
* @return a `TaskResolver.GiveTask` message
|
|
*/
|
|
private def RegisterTool(obj : Tool) : TaskResolver.GiveTask = {
|
|
val ammoTasks : List[TaskResolver.GiveTask] = (0 until obj.MaxAmmoSlot).map(ammoIndex => RegisterObjectTask(obj.AmmoSlots(ammoIndex).Box)).toList
|
|
TaskResolver.GiveTask(RegisterObjectTask(obj).task, ammoTasks)
|
|
}
|
|
|
|
/**
|
|
* Construct tasking that registers an object, determining whether it is a complex object of type `Tool` or a more simple object type.
|
|
* @param obj the object being registered
|
|
* @return a `TaskResolver.GiveTask` message
|
|
*/
|
|
private def RegisterEquipment(obj : Equipment) : TaskResolver.GiveTask = {
|
|
obj match {
|
|
case tool : Tool =>
|
|
RegisterTool(tool)
|
|
case _ =>
|
|
RegisterObjectTask(obj)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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 : Player, 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
|
|
|
|
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(localObject)
|
|
}
|
|
|
|
override def onSuccess() : Unit = {
|
|
val definition = localObject.Definition
|
|
localAnnounce ! WorldSessionActor.ResponseToSelf(
|
|
PacketCoding.CreateGamePacket(0,
|
|
ObjectCreateDetailedMessage(
|
|
definition.ObjectId,
|
|
localObject.GUID,
|
|
ObjectCreateMessageParent(localTarget.GUID, localIndex),
|
|
definition.Packet.DetailedConstructorData(localObject).get
|
|
)
|
|
)
|
|
)
|
|
if(0 <= localIndex && localIndex < 5) {
|
|
avatarService ! AvatarServiceMessage(localTarget.Continent, 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 = {
|
|
val holsterTasks = recursiveHolsterTaskBuilding(tplayer.Holsters().iterator, RegisterEquipment)
|
|
val fifthHolsterTask = tplayer.Slot(5).Equipment match {
|
|
case Some(item) =>
|
|
RegisterEquipment(item) :: Nil
|
|
case None =>
|
|
List.empty[TaskResolver.GiveTask];
|
|
}
|
|
val inventoryTasks = tplayer.Inventory.Items.map({ case((_ : Int, entry : InventoryItem)) => RegisterEquipment(entry.obj)})
|
|
TaskResolver.GiveTask(
|
|
new Task() {
|
|
private val localPlayer = tplayer
|
|
private val localAnnounce = self
|
|
|
|
override def isComplete : Task.Resolution.Value = {
|
|
Task.Resolution.Incomplete
|
|
}
|
|
|
|
def Execute(resolver : ActorRef) : Unit = {
|
|
localAnnounce ! PlayerLoaded(localPlayer) //alerts WSA
|
|
resolver ! scala.util.Success(localPlayer)
|
|
}
|
|
}, RegisterObjectTask(tplayer) +: (holsterTasks ++ fifthHolsterTask ++ inventoryTasks)
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Construct tasking that un-registers an object.
|
|
* The object in question is not considered to have any form of internal complexity.
|
|
* @param obj the object being un-registered
|
|
* @return a `TaskResolver.GiveTask` message
|
|
*/
|
|
private def UnregisterObjectTask(obj : IdentifiableEntity) : TaskResolver.GiveTask = {
|
|
TaskResolver.GiveTask(
|
|
new Task() {
|
|
private val localObject = obj
|
|
private val localAccessor = accessor
|
|
|
|
override def isComplete : Task.Resolution.Value = {
|
|
try {
|
|
localObject.GUID
|
|
Task.Resolution.Incomplete
|
|
}
|
|
catch {
|
|
case _ : Exception =>
|
|
Task.Resolution.Success
|
|
}
|
|
}
|
|
|
|
def Execute(resolver : ActorRef) : Unit = {
|
|
localAccessor ! Unregister(localObject, resolver)
|
|
}
|
|
}
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Construct tasking that un-registers an object that is an object of type `Tool`.
|
|
* `Tool` objects have internal structures called "ammo slots;"
|
|
* each ammo slot contains a register-able `AmmoBox` object.
|
|
* @param obj the object being un-registered
|
|
* @return a `TaskResolver.GiveTask` message
|
|
*/
|
|
private def UnregisterTool(obj : Tool) : TaskResolver.GiveTask = {
|
|
val ammoTasks : List[TaskResolver.GiveTask] = (0 until obj.MaxAmmoSlot).map(ammoIndex => UnregisterObjectTask(obj.AmmoSlots(ammoIndex).Box)).toList
|
|
TaskResolver.GiveTask(UnregisterObjectTask(obj).task, ammoTasks)
|
|
}
|
|
|
|
/**
|
|
* Construct tasking that un-registers an object, determining whether it is a complex object of type `Tool` or a more simple object type.
|
|
* @param obj the object being registered
|
|
* @return a `TaskResolver.GiveTask` message
|
|
*/
|
|
private def UnregisterEquipment(obj : Equipment) : TaskResolver.GiveTask = {
|
|
obj match {
|
|
case tool : Tool =>
|
|
UnregisterTool(tool)
|
|
case _ =>
|
|
UnregisterObjectTask(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 : Player, 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
|
|
|
|
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(localObject)
|
|
}
|
|
|
|
override def onSuccess() : Unit = {
|
|
localAnnounce ! WorldSessionActor.ResponseToSelf(PacketCoding.CreateGamePacket(0, ObjectDeleteMessage(localObjectGUID, 0)))
|
|
if(0 <= localIndex && localIndex < 5) {
|
|
avatarService ! AvatarServiceMessage(localTarget.Continent, AvatarAction.ObjectDelete(localTarget.GUID, localObjectGUID))
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Construct tasking that un-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 UnregisterAvatar(tplayer : Player) : TaskResolver.GiveTask = {
|
|
val holsterTasks = recursiveHolsterTaskBuilding(tplayer.Holsters().iterator, UnregisterEquipment)
|
|
val inventoryTasks = tplayer.Inventory.Items.map({ case((_ : Int, entry : InventoryItem)) => UnregisterEquipment(entry.obj)})
|
|
val fifthHolsterTask = tplayer.Slot(5).Equipment match {
|
|
case Some(item) =>
|
|
UnregisterEquipment(item) :: Nil
|
|
case None =>
|
|
List.empty[TaskResolver.GiveTask];
|
|
}
|
|
TaskResolver.GiveTask(UnregisterObjectTask(tplayer).task, holsterTasks ++ fifthHolsterTask ++ inventoryTasks)
|
|
}
|
|
|
|
/**
|
|
* 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.Inventory.Items.foreach({ case((_, entry : InventoryItem)) =>
|
|
SetCharacterSelectScreenGUID_SelectEquipment(Some(entry.obj), gen)
|
|
})
|
|
tplayer.Slot(5).Equipment.get.GUID = PlanetSideGUID(gen.getAndIncrement)
|
|
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.Inventory.Items.foreach({ case((_, entry : InventoryItem)) =>
|
|
RemoveCharacterSelectScreenGUID_SelectEquipment(Some(entry.obj))
|
|
})
|
|
tplayer.Slot(5).Equipment.get.Invalidate()
|
|
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 => ;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add an object to the local `List` of objects on the ground.
|
|
* @param item the `Equipment` to be dropped
|
|
* @param pos where the `item` will be dropped
|
|
* @param orient in what direction the item will face when dropped
|
|
* @return the global unique identifier of the object
|
|
*/
|
|
private def DropItemOnGround(item : Equipment, pos : Vector3, orient : Vector3) : PlanetSideGUID = {
|
|
item.Position = pos
|
|
item.Orientation = orient
|
|
WorldSessionActor.equipmentOnGround += item
|
|
item.GUID
|
|
}
|
|
|
|
// private def FindItemOnGround(item_guid : PlanetSideGUID) : Option[Equipment] = {
|
|
// equipmentOnGround.find(item => item.GUID == item_guid)
|
|
// }
|
|
|
|
/**
|
|
* Remove an object from the local `List` of objects on the ground.
|
|
* @param item_guid the `Equipment` to be picked up
|
|
* @return the object being picked up
|
|
*/
|
|
private def PickupItemFromGround(item_guid : PlanetSideGUID) : Option[Equipment] = {
|
|
recursiveFindItemOnGround(WorldSessionActor.equipmentOnGround.iterator, item_guid) match {
|
|
case Some(index) =>
|
|
Some(WorldSessionActor.equipmentOnGround.remove(index))
|
|
case None =>
|
|
None
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Shift through objects on the ground to find the location of a specific item.
|
|
* @param iter an `Iterator` of `Equipment`
|
|
* @param item_guid the global unique identifier of the piece of `Equipment` being sought
|
|
* @param index the current position in the array-list structure used to create the `Iterator`
|
|
* @return the index of the object matching `item_guid`, if found;
|
|
* `None`, otherwise
|
|
*/
|
|
@tailrec private def recursiveFindItemOnGround(iter : Iterator[Equipment], item_guid : PlanetSideGUID, index : Int = 0) : Option[Int] = {
|
|
if(!iter.hasNext) {
|
|
None
|
|
}
|
|
else {
|
|
val item : Equipment = iter.next
|
|
if(item.GUID == item_guid) {
|
|
Some(index)
|
|
}
|
|
else {
|
|
recursiveFindItemOnGround(iter, item_guid, index + 1)
|
|
}
|
|
}
|
|
}
|
|
|
|
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)
|
|
|
|
/**
|
|
* A placeholder `Cancellable` object.
|
|
*/
|
|
private final val DefaultCancellable = new Cancellable() {
|
|
def cancel : Boolean = true
|
|
def isCancelled() : Boolean = true
|
|
}
|
|
|
|
//TODO this is a temporary local system; replace it in the future
|
|
//in the future, items dropped on the ground will be managed by a data structure on an external Actor representing the continent
|
|
//like so: WSA -> /GetItemOnGround/ -> continent -> /GiveItemFromGround/ -> WSA
|
|
import scala.collection.mutable.ListBuffer
|
|
private val equipmentOnGround : ListBuffer[Equipment] = ListBuffer[Equipment]()
|
|
|
|
def Distance(pos1 : Vector3, pos2 : Vector3) : Float = {
|
|
math.sqrt(DistanceSquared(pos1, pos2)).toFloat
|
|
}
|
|
|
|
def DistanceSquared(pos1 : Vector3, pos2 : Vector3) : Float = {
|
|
val dx : Float = pos1.x - pos2.x
|
|
val dy : Float = pos1.y - pos2.y
|
|
val dz : Float = pos1.z - pos2.z
|
|
(dx * dx) + (dy * dy) + (dz * dz)
|
|
}
|
|
}
|