From f6f7ad561715cac2790bddadaacf2b802ce5a3f8 Mon Sep 17 00:00:00 2001 From: FateJH Date: Tue, 22 May 2018 19:13:59 -0400 Subject: [PATCH] unified code paths for dropping an item, and reinforced code path for picking an item back up; LocalService will handle some of the work now; ActionResultMessage embraces its simplistic nature (we don't have enough error messages) --- .../net/psforever/objects/zones/Zone.scala | 28 +-- .../psforever/objects/zones/ZoneActor.scala | 4 +- .../objects/zones/ZoneGroundActor.scala | 26 +-- .../packet/game/ActionResultMessage.scala | 17 +- .../packet/game/ObjectDetachMessage.scala | 8 + .../scala/game/ActionResultMessageTest.scala | 4 +- .../scala/game/ObjectDetachMessageTest.scala | 16 +- common/src/test/scala/objects/ZoneTest.scala | 15 +- .../src/main/scala/WorldSessionActor.scala | 184 ++++++++++-------- .../scala/services/local/LocalAction.scala | 2 + .../scala/services/local/LocalResponse.scala | 2 + .../scala/services/local/LocalService.scala | 12 ++ 12 files changed, 192 insertions(+), 126 deletions(-) diff --git a/common/src/main/scala/net/psforever/objects/zones/Zone.scala b/common/src/main/scala/net/psforever/objects/zones/Zone.scala index 1c25abaa..4ac4d109 100644 --- a/common/src/main/scala/net/psforever/objects/zones/Zone.scala +++ b/common/src/main/scala/net/psforever/objects/zones/Zone.scala @@ -386,27 +386,15 @@ object Zone { final case class NoValidSpawnPoint(zone_number : Int, spawn_group : Option[Int]) } - /** - * Message to relinguish an item and place in on the ground. - * @param item the piece of `Equipment` - * @param pos where it is dropped - * @param orient in which direction it is facing when dropped - */ - final case class DropItemOnGround(item : Equipment, pos : Vector3, orient : Vector3) + object Ground { + final case class DropItem(item : Equipment, pos : Vector3, orient : Vector3) + final case class ItemOnGround(item : Equipment, pos : Vector3, orient : Vector3) + final case class CanNotDropItem(item : Equipment) - /** - * Message to attempt to acquire an item from the ground (before somoene else?). - * @param player who wants the piece of `Equipment` - * @param item_guid the unique identifier of the piece of `Equipment` - */ - final case class GetItemOnGround(player : Player, item_guid : PlanetSideGUID) - - /** - * Message to give an item from the ground to a specific user. - * @param player who wants the piece of `Equipment` - * @param item the piece of `Equipment` - */ - final case class ItemFromGround(player : Player, item : Equipment) + final case class PickupItem(item_guid : PlanetSideGUID) + final case class ItemInHand(item : Equipment) + final case class CanNotPickupItem(item_guid : PlanetSideGUID) + } object Vehicle { final case class Spawn(vehicle : Vehicle) diff --git a/common/src/main/scala/net/psforever/objects/zones/ZoneActor.scala b/common/src/main/scala/net/psforever/objects/zones/ZoneActor.scala index 4d6f0c4f..80f6a547 100644 --- a/common/src/main/scala/net/psforever/objects/zones/ZoneActor.scala +++ b/common/src/main/scala/net/psforever/objects/zones/ZoneActor.scala @@ -50,10 +50,10 @@ class ZoneActor(zone : Zone) extends Actor { zone.Population forward msg //frwd to Ground Actor - case msg @ Zone.DropItemOnGround => + case msg @ Zone.Ground.DropItem => zone.Ground forward msg - case msg @ Zone.GetItemOnGround => + case msg @ Zone.Ground.PickupItem => zone.Ground forward msg //frwd to Vehicle Actor diff --git a/common/src/main/scala/net/psforever/objects/zones/ZoneGroundActor.scala b/common/src/main/scala/net/psforever/objects/zones/ZoneGroundActor.scala index 5a001e47..b44be2c3 100644 --- a/common/src/main/scala/net/psforever/objects/zones/ZoneGroundActor.scala +++ b/common/src/main/scala/net/psforever/objects/zones/ZoneGroundActor.scala @@ -16,18 +16,22 @@ class ZoneGroundActor(equipmentOnGround : ListBuffer[Equipment]) extends Actor { //private[this] val log = org.log4s.getLogger def receive : Receive = { - case Zone.DropItemOnGround(item, pos, orient) => - item.Position = pos - item.Orientation = orient - equipmentOnGround += item - - case Zone.GetItemOnGround(player, item_guid) => - FindItemOnGround(item_guid) match { - case Some(item) => - sender ! Zone.ItemFromGround(player, item) + case Zone.Ground.DropItem(item, pos, orient) => + sender ! (FindItemOnGround(item.GUID) match { case None => - org.log4s.getLogger.warn(s"item on ground $item_guid was requested by $player for pickup but was not found") - } + equipmentOnGround += item + Zone.Ground.ItemOnGround(item, pos, orient) + case Some(_) => + Zone.Ground.CanNotDropItem(item) + }) + + case Zone.Ground.PickupItem(item_guid) => + sender ! (FindItemOnGround(item_guid) match { + case Some(item) => + Zone.Ground.ItemInHand(item) + case None => + Zone.Ground.CanNotPickupItem(item_guid) + }) case _ => ; } diff --git a/common/src/main/scala/net/psforever/packet/game/ActionResultMessage.scala b/common/src/main/scala/net/psforever/packet/game/ActionResultMessage.scala index fe7f0b44..b8b7d9c5 100644 --- a/common/src/main/scala/net/psforever/packet/game/ActionResultMessage.scala +++ b/common/src/main/scala/net/psforever/packet/game/ActionResultMessage.scala @@ -19,13 +19,18 @@ final case class ActionResultMessage(successful : Boolean, } object ActionResultMessage extends Marshallable[ActionResultMessage] { - def apply() : ActionResultMessage = { - ActionResultMessage(true, None) - } + /** + * A message where the result is always a pass. + * @return an `ActionResultMessage` object + */ + def Pass : ActionResultMessage = ActionResultMessage(true, None) - def apply(error : Long) : ActionResultMessage = { - ActionResultMessage(false, Some(error)) - } + /** + * A message where the result is always a failure. + * @param error the error code + * @return an `ActionResultMessage` object + */ + def Fail(error : Long) : ActionResultMessage = ActionResultMessage(false, Some(error)) implicit val codec : Codec[ActionResultMessage] = ( ("successful" | bool) >>:~ { res => diff --git a/common/src/main/scala/net/psforever/packet/game/ObjectDetachMessage.scala b/common/src/main/scala/net/psforever/packet/game/ObjectDetachMessage.scala index a68fa1fb..5ab2d7b2 100644 --- a/common/src/main/scala/net/psforever/packet/game/ObjectDetachMessage.scala +++ b/common/src/main/scala/net/psforever/packet/game/ObjectDetachMessage.scala @@ -40,6 +40,14 @@ final case class ObjectDetachMessage(parent_guid : PlanetSideGUID, } object ObjectDetachMessage extends Marshallable[ObjectDetachMessage] { + def apply(parent_guid : PlanetSideGUID, child_guid : PlanetSideGUID, pos : Vector3, orient : Vector3) : ObjectDetachMessage = { + ObjectDetachMessage(parent_guid, child_guid, pos, orient.x, orient.y, orient.z) + } + + def apply(parent_guid : PlanetSideGUID, child_guid : PlanetSideGUID, pos : Vector3, orient_z : Float) : ObjectDetachMessage = { + ObjectDetachMessage(parent_guid, child_guid, pos, 0, 0, orient_z) + } + implicit val codec : Codec[ObjectDetachMessage] = ( ("parent_guid" | PlanetSideGUID.codec) :: ("child_guid" | PlanetSideGUID.codec) :: diff --git a/common/src/test/scala/game/ActionResultMessageTest.scala b/common/src/test/scala/game/ActionResultMessageTest.scala index a6bce43c..29c9d950 100644 --- a/common/src/test/scala/game/ActionResultMessageTest.scala +++ b/common/src/test/scala/game/ActionResultMessageTest.scala @@ -38,7 +38,7 @@ class ActionResultMessageTest extends Specification { } "encode (pass, minimal)" in { - val msg = ActionResultMessage() + val msg = ActionResultMessage.Pass val pkt = PacketCoding.EncodePacket(msg).require.toByteVector pkt mustEqual string_pass @@ -52,7 +52,7 @@ class ActionResultMessageTest extends Specification { } "encode (fail, minimal)" in { - val msg = ActionResultMessage(1) + val msg = ActionResultMessage.Fail(1) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector pkt mustEqual string_fail diff --git a/common/src/test/scala/game/ObjectDetachMessageTest.scala b/common/src/test/scala/game/ObjectDetachMessageTest.scala index f7ee8b52..a5266513 100644 --- a/common/src/test/scala/game/ObjectDetachMessageTest.scala +++ b/common/src/test/scala/game/ObjectDetachMessageTest.scala @@ -26,10 +26,24 @@ class ObjectDetachMessageTest extends Specification { } } - "encode" in { + "encode (1)" in { val msg = ObjectDetachMessage(PlanetSideGUID(2916), PlanetSideGUID(2502), Vector3(3567.1406f, 2988.0078f, 71.84375f), 0f, 0f, 270f) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector pkt mustEqual string } + + "encode (2)" in { + val msg = ObjectDetachMessage(PlanetSideGUID(2916), PlanetSideGUID(2502), Vector3(3567.1406f, 2988.0078f, 71.84375f), Vector3(0f, 0f, 270f)) + val pkt = PacketCoding.EncodePacket(msg).require.toByteVector + + pkt mustEqual string + } + + "encode (3)" in { + val msg = ObjectDetachMessage(PlanetSideGUID(2916), PlanetSideGUID(2502), Vector3(3567.1406f, 2988.0078f, 71.84375f), 270f) + val pkt = PacketCoding.EncodePacket(msg).require.toByteVector + + pkt mustEqual string + } } diff --git a/common/src/test/scala/objects/ZoneTest.scala b/common/src/test/scala/objects/ZoneTest.scala index 6137b993..84868f23 100644 --- a/common/src/test/scala/objects/ZoneTest.scala +++ b/common/src/test/scala/objects/ZoneTest.scala @@ -477,7 +477,7 @@ class ZoneGroundTest extends ActorTest { assert(zone.EquipmentOnGround.isEmpty) assert(item.Position == Vector3.Zero) assert(item.Orientation == Vector3.Zero) - zone.Ground ! Zone.DropItemOnGround(item, Vector3(1.1f, 2.2f, 3.3f), Vector3(4.4f, 5.5f, 6.6f)) + zone.Ground ! Zone.Ground.DropItem(item, Vector3(1.1f, 2.2f, 3.3f), Vector3(4.4f, 5.5f, 6.6f)) expectNoMsg(Duration.create(100, "ms")) assert(zone.EquipmentOnGround == List(item)) @@ -490,17 +490,16 @@ class ZoneGroundTest extends ActorTest { val player = Player(Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)) system.actorOf(Props(classOf[ZoneTest.ZoneInitActor], zone), "get-item-test-good") ! "!" receiveOne(Duration.create(200, "ms")) //consume - zone.Ground ! Zone.DropItemOnGround(item, Vector3.Zero, Vector3.Zero) + zone.Ground ! Zone.Ground.DropItem(item, Vector3.Zero, Vector3.Zero) expectNoMsg(Duration.create(100, "ms")) assert(zone.EquipmentOnGround == List(item)) - zone.Ground ! Zone.GetItemOnGround(player, PlanetSideGUID(10)) + zone.Ground ! Zone.Ground.PickupItem(PlanetSideGUID(10)) val reply = receiveOne(Duration.create(100, "ms")) assert(zone.EquipmentOnGround.isEmpty) - assert(reply.isInstanceOf[Zone.ItemFromGround]) - assert(reply.asInstanceOf[Zone.ItemFromGround].player == player) - assert(reply.asInstanceOf[Zone.ItemFromGround].item == item) + assert(reply.isInstanceOf[Zone.Ground.ItemInHand]) + assert(reply.asInstanceOf[Zone.Ground.ItemInHand].item == item) } "get item from ground (failure)" in { @@ -508,11 +507,11 @@ class ZoneGroundTest extends ActorTest { val player = Player(Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)) system.actorOf(Props(classOf[ZoneTest.ZoneInitActor], zone), "get-item-test-fail") ! "!" receiveOne(Duration.create(200, "ms")) //consume - zone.Ground ! Zone.DropItemOnGround(item, Vector3.Zero, Vector3.Zero) + zone.Ground ! Zone.Ground.DropItem(item, Vector3.Zero, Vector3.Zero) expectNoMsg(Duration.create(100, "ms")) assert(zone.EquipmentOnGround == List(item)) - zone.Ground ! Zone.GetItemOnGround(player, PlanetSideGUID(11)) //wrong guid + zone.Ground ! Zone.Ground.PickupItem(PlanetSideGUID(11)) //wrong guid expectNoMsg(Duration.create(500, "ms")) } } diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index 8ec79f16..4ad2820d 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -255,7 +255,7 @@ class WorldSessionActor extends Actor with MDCContextAware { case AvatarResponse.ChangeAmmo(weapon_guid, weapon_slot, previous_guid, ammo_id, ammo_guid, ammo_data) => if(tplayer_guid != guid) { - sendResponse(ObjectDetachMessage(weapon_guid, previous_guid, Vector3(0,0,0), 0f, 0f, 0f)) + sendResponse(ObjectDetachMessage(weapon_guid, previous_guid, Vector3.Zero, 0)) sendResponse( ObjectCreateMessage( ammo_id, @@ -405,7 +405,7 @@ class WorldSessionActor extends Actor with MDCContextAware { } case LocalServiceResponse(_, guid, reply) => - val tplayer_guid = if(player.HasGUID) { player.GUID} else { PlanetSideGUID(0) } + val tplayer_guid = if(player.HasGUID) { player.GUID} else { PlanetSideGUID(-1) } reply match { case LocalResponse.DoorOpens(door_guid) => if(tplayer_guid != guid) { @@ -415,16 +415,21 @@ class WorldSessionActor extends Actor with MDCContextAware { case LocalResponse.DoorCloses(door_guid) => //door closes for everyone sendResponse(GenericObjectStateMsg(door_guid, 17)) + case LocalResponse.DropItem(id, item_guid, data) => + if(tplayer_guid != guid) { + sendResponse(ObjectCreateMessage(id, item_guid, data)) + } + case LocalResponse.HackClear(target_guid, unk1, unk2) => sendResponse(HackMessage(0, target_guid, guid, 0, unk1, HackState.HackCleared, unk2)) case LocalResponse.HackObject(target_guid, unk1, unk2) => - if(player.GUID != guid) { + if(tplayer_guid != guid) { sendResponse(HackMessage(0, target_guid, guid, 100, unk1, HackState.Hacked, unk2)) } case LocalResponse.ProximityTerminalEffect(object_guid, effectState) => - if(player.GUID != guid) { + if(tplayer_guid != guid) { sendResponse(ProximityTerminalUseMessage(PlanetSideGUID(0), object_guid, effectState)) } @@ -465,7 +470,7 @@ class WorldSessionActor extends Actor with MDCContextAware { } case VehicleResponse.DetachFromRails(vehicle_guid, pad_guid, pad_position, pad_orientation_z) => - sendResponse(ObjectDetachMessage(pad_guid, vehicle_guid, pad_position + Vector3(0,0,0.5f), 0, 0, pad_orientation_z)) + sendResponse(ObjectDetachMessage(pad_guid, vehicle_guid, pad_position + Vector3(0,0,0.5f), pad_orientation_z)) case VehicleResponse.InventoryState(obj, parent_guid, start, con_data) => if(tplayer_guid != guid) { @@ -844,12 +849,10 @@ class WorldSessionActor extends Actor with MDCContextAware { }) //drop items on ground val pos = tplayer.Position - val orient = tplayer.Orientation + val orient = Vector3(0,0, tplayer.Orientation.z) ((dropHolsters ++ dropInventory).map(_.obj) ++ drop).foreach(obj => { - continent.Ground ! Zone.DropItemOnGround(obj, pos, Vector3(0f, 0f, orient.z)) - sendResponse(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)) + //TODO make a sound when dropping stuff + continent.Ground ! Zone.Ground.DropItem(obj, pos, orient) }) sendResponse(ItemTransactionResultMessage (msg.terminal_guid, TransactionType.Buy, true)) } @@ -938,12 +941,9 @@ class WorldSessionActor extends Actor with MDCContextAware { }) //drop stuff on ground val pos = tplayer.Position - val orient = tplayer.Orientation + val orient = Vector3(0,0, tplayer.Orientation.z) ((dropHolsters ++ dropInventory).map(_.obj)).foreach(obj => { - continent.Ground ! Zone.DropItemOnGround(obj, pos, Vector3(0f, 0f, orient.z)) - sendResponse(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)) + continent.Ground ! Zone.Ground.DropItem(obj, pos, orient) }) case Terminal.VehicleLoadout(definition, weapons, inventory) => @@ -1177,7 +1177,7 @@ class WorldSessionActor extends Actor with MDCContextAware { case VehicleSpawnPad.ServerVehicleOverrideStart(vehicle, pad) => val vdef = vehicle.Definition if(vehicle.Seats(0).isOccupied) { - sendResponse(ObjectDetachMessage(pad.GUID, vehicle.GUID, pad.Position + Vector3(0, 0, 0.5f), 0, 0, pad.Orientation.z)) + sendResponse(ObjectDetachMessage(pad.GUID, vehicle.GUID, pad.Position + Vector3(0, 0, 0.5f), pad.Orientation.z)) } ServerVehicleOverride(vehicle, vdef.AutoPilotSpeed1, GlobalDefinitions.isFlightVehicle(vdef):Int) @@ -1375,6 +1375,62 @@ class WorldSessionActor extends Actor with MDCContextAware { RequestSanctuaryZoneSpawn(player, zone_number) } + case Zone.Ground.ItemOnGround(item, pos, orient) => + item.Position = pos + item.Orientation = Vector3(0,0, orient.z) //only one kind of rotation is important + val exclusionId = player.Find(item) match { + case Some(slotNum) => + player.Slot(slotNum).Equipment = None + sendResponse(ObjectDetachMessage(player.GUID, item.GUID, pos, orient.z)) + sendResponse(ActionResultMessage.Pass) + player.GUID //we're dropping it; don't need to see it dropped again + case None => + PlanetSideGUID(0) //object is being introduced into the world upon drop + } + localService ! LocalServiceMessage(continent.Id, LocalAction.DropItem(exclusionId, item)) + + case Zone.Ground.CanNotDropItem(item) => + log.warn(s"DropItem: $player tried to drop a $item on the ground, but he missed") + player.Find(item) match { + case None => //item in limbo + taskResolver ! GUIDTask.UnregisterEquipment(item)(continent.GUID) + case Some(_) => ; + } + + case Zone.Ground.ItemInHand(item) => + player.Fit(item) match { + case Some(slotNum) => + val item_guid = item.GUID + val player_guid = player.GUID + player.Slot(slotNum).Equipment = item + val definition = item.Definition + sendResponse( + ObjectCreateDetailedMessage( + definition.ObjectId, + item_guid, + ObjectCreateMessageParent(player_guid, slotNum), + definition.Packet.DetailedConstructorData(item).get + ) + ) + avatarService ! AvatarServiceMessage(continent.Id, if(player.VisibleSlots.contains(slotNum)) { + AvatarAction.EquipmentInHand(player_guid, player_guid, slotNum, item) + } + else { + AvatarAction.ObjectDelete(player_guid, item_guid) + }) + sendResponse(ActionResultMessage.Pass) + case None => + continent.Ground ! Zone.Ground.DropItem(item, item.Position, item.Orientation) //restore previous state + } + + case Zone.Ground.CanNotPickupItem(item_guid) => + continent.GUID(item_guid) match { + case Some(item) => + log.warn(s"DropItem: finding a $item on the ground was suggested, but $player can not reach it") + case None => + log.warn(s"DropItem: finding an item ($item_guid) on the ground was suggested, but $player can not see it") + } + case InterstellarCluster.ClientInitializationComplete() => StopBundlingPackets() LivePlayerList.Add(sessionId, avatar) @@ -1467,31 +1523,6 @@ class WorldSessionActor extends Actor with MDCContextAware { //TacticsMessage StopBundlingPackets() - - 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( - ObjectCreateDetailedMessage( - definition.ObjectId, - obj_guid, - ObjectCreateMessageParent(player_guid, slot), - definition.Packet.DetailedConstructorData(item).get - ) - ) - sendResponse(ActionResultMessage()) - if(tplayer.VisibleSlots.contains(slot)) { - avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.EquipmentInHand(player_guid, 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) { @@ -1607,18 +1638,17 @@ class WorldSessionActor extends Actor with MDCContextAware { import scala.concurrent.ExecutionContext.Implicits.global clientKeepAlive.cancel clientKeepAlive = context.system.scheduler.schedule(0 seconds, 500 milliseconds, self, PokeClient()) - log.warn(PacketCoding.DecodePacket(hex"d2327e7b8a972b95113881003710").toString) case msg @ CharacterCreateRequestMessage(name, head, voice, gender, empire) => log.info("Handling " + msg) - sendResponse(ActionResultMessage(true, None)) + sendResponse(ActionResultMessage.Pass) self ! ListAccountCharacters case msg @ CharacterRequestMessage(charId, action) => log.info("Handling " + msg) action match { case CharacterRequestAction.Delete => - sendResponse(ActionResultMessage(false, Some(1))) + sendResponse(ActionResultMessage.Fail(1)) case CharacterRequestAction.Select => //TODO check if can spawn on last continent/location from player? //TODO if yes, get continent guid accessors @@ -1828,7 +1858,7 @@ class WorldSessionActor extends Actor with MDCContextAware { import scala.concurrent.duration._ import scala.concurrent.ExecutionContext.Implicits.global context.system.scheduler.scheduleOnce(50 milliseconds, self, UnregisterCorpseOnVehicleDisembark(player)) - //sendResponse(ObjectDetachMessage(vehicle_guid, player.GUID, Vector3.Zero, 0, 0, 0)) + //sendResponse(ObjectDetachMessage(vehicle_guid, player.GUID, Vector3.Zero, 0)) //sendResponse(PlayerStateShiftMessage(ShiftState(1, Vector3.Zero, 0))) } @@ -1989,8 +2019,8 @@ class WorldSessionActor extends Actor with MDCContextAware { val tailReloadValue : Int = if(xs.isEmpty) { 0 } else { xs.map(_.obj.asInstanceOf[AmmoBox].Capacity).reduceLeft(_ + _) } val sumReloadValue : Int = originalBoxCapacity + tailReloadValue val previousBox = tool.AmmoSlot.Box //current magazine in tool - sendResponse(ObjectDetachMessage(tool.GUID, previousBox.GUID, Vector3(0f, 0f, 0f), 0f, 0f, 0f)) - sendResponse(ObjectDetachMessage(player.GUID, box.GUID, Vector3(0f, 0f, 0f), 0f, 0f, 0f)) + sendResponse(ObjectDetachMessage(tool.GUID, previousBox.GUID, Vector3.Zero, 0f)) + sendResponse(ObjectDetachMessage(player.GUID, box.GUID, Vector3.Zero, 0f)) obj.Inventory -= x.start //remove replacement ammo from inventory val ammoSlotIndex = tool.FireMode.AmmoSlotIndex tool.AmmoSlots(ammoSlotIndex).Box = box //put replacement ammo in tool @@ -2158,27 +2188,37 @@ class WorldSessionActor extends Actor with MDCContextAware { sendResponse(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(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") + log.info(s"DropItem: $msg") + continent.GUID(item_guid) match { + case Some(item : Equipment) => + player.FreeHand.Equipment match { + case Some(_) => + if(item.GUID == item_guid) { + continent.Ground ! Zone.Ground.DropItem(item, player.Position, player.Orientation) + } + case None => + log.warn(s"DropItem: $player wanted to drop a $item, but it wasn't at hand") } + case Some(obj) => //TODO LLU + log.warn(s"DropItem: $player wanted to drop a $obj, but that isn't possible") case None => - log.error(s"$player wanted to drop an item, but it was not in hand") + log.warn(s"DropItem: $player wanted to drop an item ($item_guid), but it was nowhere to be found") } case msg @ PickupItemMessage(item_guid, player_guid, unk1, unk2) => - log.info("PickupItem: " + msg) - continent.Ground ! Zone.GetItemOnGround(player, item_guid) + log.info(s"PickupItem: $msg") + continent.GUID(item_guid) match { + case Some(item : Equipment) => + player.Fit(item) match { + case Some(_) => + continent.Ground ! Zone.Ground.PickupItem(item_guid) + case None => //skip + sendResponse(ActionResultMessage.Fail(16)) //error code? + } + case _ => + log.warn(s"PickupItem: $player requested an item that doesn't exist in this zone; assume client-side garbage data") + sendResponse(ObjectDeleteMessage(item_guid, 0)) + } case msg @ ReloadMessage(item_guid, ammo_clip, unk1) => log.info("Reload: " + msg) @@ -3847,14 +3887,14 @@ class WorldSessionActor extends Actor with MDCContextAware { case Some(InventoryItem(item2, destIndex)) => //yes, swap //cleanly shuffle items around to avoid losing icons //the next ObjectDetachMessage is necessary to avoid icons being lost, but only as part of this swap - sendResponse(ObjectDetachMessage(source_guid, item_guid, Vector3.Zero, 0f, 0f, 0f)) + sendResponse(ObjectDetachMessage(source_guid, item_guid, Vector3.Zero, 0f)) val item2_guid = item2.GUID destination.Slot(destIndex).Equipment = None //remove the swap item from destination (indexSlot.Equipment = item2) match { case Some(_) => //item and item2 swapped places successfully log.info(s"MoveItem: $item2 swapped to $source @ $index") //remove item2 from destination - sendResponse(ObjectDetachMessage(destination_guid, item2_guid, Vector3.Zero, 0f, 0f, 0f)) + sendResponse(ObjectDetachMessage(destination_guid, item2_guid, Vector3.Zero, 0f)) destination match { case obj : Vehicle => vehicleService ! VehicleServiceMessage(s"${obj.Actor}", VehicleAction.UnstowEquipment(player_guid, item2_guid)) @@ -3897,8 +3937,8 @@ class WorldSessionActor extends Actor with MDCContextAware { val pos = source.Position val sourceOrientZ = source.Orientation.z val orient : Vector3 = Vector3(0f, 0f, sourceOrientZ) - continent.Actor ! Zone.DropItemOnGround(item2, pos, orient) - sendResponse(ObjectDetachMessage(destination_guid, item2_guid, pos, 0f, 0f, sourceOrientZ)) //ground + continent.Ground ! Zone.Ground.DropItem(item2, pos, orient) + sendResponse(ObjectDetachMessage(destination_guid, item2_guid, pos, sourceOrientZ)) //ground val objDef = item2.Definition destination match { case obj : Vehicle => @@ -3956,15 +3996,7 @@ class WorldSessionActor extends Actor with MDCContextAware { * @param item the item */ def NormalItemDrop(obj : PlanetSideGameObject with Container, zone : Zone, service : ActorRef)(item : Equipment) : Unit = { - val itemGUID = item.GUID - val ang = obj.Orientation.z - val pos = obj.Position - val orient = Vector3(0f, 0f, ang) - item.Position = pos - item.Orientation = orient - zone.Ground ! Zone.DropItemOnGround(item, pos, orient) - val itemDef = item.Definition - service ! AvatarServiceMessage(zone.Id, AvatarAction.EquipmentOnGround(Service.defaultPlayerGUID, pos, orient, itemDef.ObjectId, itemGUID, itemDef.Packet.ConstructorData(item).get)) + continent.Ground ! Zone.Ground.DropItem(item, obj.Position, Vector3(0f, 0f, obj.Orientation.z)) } /** diff --git a/pslogin/src/main/scala/services/local/LocalAction.scala b/pslogin/src/main/scala/services/local/LocalAction.scala index 1c04f2e7..2674e517 100644 --- a/pslogin/src/main/scala/services/local/LocalAction.scala +++ b/pslogin/src/main/scala/services/local/LocalAction.scala @@ -1,6 +1,7 @@ // Copyright (c) 2017 PSForever package services.local +import net.psforever.objects.equipment.Equipment import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.serverobject.doors.Door import net.psforever.objects.zones.Zone @@ -12,6 +13,7 @@ object LocalAction { final case class DoorOpens(player_guid : PlanetSideGUID, continent : Zone, door : Door) extends Action final case class DoorCloses(player_guid : PlanetSideGUID, door_guid : PlanetSideGUID) extends Action + final case class DropItem(player_guid : PlanetSideGUID, item : Equipment) extends Action final case class HackClear(player_guid : PlanetSideGUID, target : PlanetSideServerObject, unk1 : Long, unk2 : Long = 8L) extends Action final case class HackTemporarily(player_guid : PlanetSideGUID, continent : Zone, target : PlanetSideServerObject, unk1 : Long, unk2 : Long = 8L) extends Action final case class ProximityTerminalEffect(player_guid : PlanetSideGUID, object_guid : PlanetSideGUID, effectState : Boolean) extends Action diff --git a/pslogin/src/main/scala/services/local/LocalResponse.scala b/pslogin/src/main/scala/services/local/LocalResponse.scala index fdc2aa37..4313db72 100644 --- a/pslogin/src/main/scala/services/local/LocalResponse.scala +++ b/pslogin/src/main/scala/services/local/LocalResponse.scala @@ -1,6 +1,7 @@ // Copyright (c) 2017 PSForever package services.local +import net.psforever.packet.game.objectcreate.ConstructorData import net.psforever.packet.game.{PlanetSideGUID, TriggeredSound} import net.psforever.types.Vector3 @@ -9,6 +10,7 @@ object LocalResponse { final case class DoorOpens(door_guid : PlanetSideGUID) extends Response final case class DoorCloses(door_guid : PlanetSideGUID) extends Response + final case class DropItem(item_id : Int, item_guid : PlanetSideGUID, item_data : ConstructorData) extends Response final case class HackClear(target_guid : PlanetSideGUID, unk1 : Long, unk2 : Long) extends Response final case class HackObject(target_guid : PlanetSideGUID, unk1 : Long, unk2 : Long) extends Response final case class ProximityTerminalEffect(object_guid : PlanetSideGUID, effectState : Boolean) extends Response diff --git a/pslogin/src/main/scala/services/local/LocalService.scala b/pslogin/src/main/scala/services/local/LocalService.scala index 5c01ee1a..01df3c74 100644 --- a/pslogin/src/main/scala/services/local/LocalService.scala +++ b/pslogin/src/main/scala/services/local/LocalService.scala @@ -2,6 +2,7 @@ package services.local import akka.actor.{Actor, Props} +import net.psforever.packet.game.objectcreate.{DroppedItemData, PlacementData} import services.local.support.{DoorCloseActor, HackClearActor} import services.{GenericEventBus, Service} @@ -46,6 +47,17 @@ class LocalService extends Actor { LocalEvents.publish( LocalServiceResponse(s"/$forChannel/Local", player_guid, LocalResponse.DoorCloses(door_guid)) ) + case LocalAction.DropItem(player_guid, item) => + val definition = item.Definition + val objectData = DroppedItemData( + PlacementData(item.Position, item.Orientation), + definition.Packet.ConstructorData(item).get + ) + LocalEvents.publish( + LocalServiceResponse(s"/$forChannel/Local", player_guid, + LocalResponse.DropItem(definition.ObjectId, item.GUID, objectData) + ) + ) case LocalAction.HackClear(player_guid, target, unk1, unk2) => LocalEvents.publish( LocalServiceResponse(s"/$forChannel/Local", player_guid, LocalResponse.HackClear(target.GUID, unk1, unk2))