diff --git a/src/main/scala/net/psforever/actors/session/SessionActor.scala b/src/main/scala/net/psforever/actors/session/SessionActor.scala index 27347c78..11b81180 100644 --- a/src/main/scala/net/psforever/actors/session/SessionActor.scala +++ b/src/main/scala/net/psforever/actors/session/SessionActor.scala @@ -1822,6 +1822,9 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con CancelZoningProcessWithDescriptiveReason("cancel_dmg") } + case AvatarResponse.DropSpecialItem() => + DropSpecialSlotItem() + case AvatarResponse.Killed(mount) => val respawnTimer = 300.seconds //drop free hand item diff --git a/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala b/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala index a96b8924..9c0fc7f1 100644 --- a/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala +++ b/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala @@ -707,6 +707,11 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm super.CancelJammeredStatus(target) //uninitialize implants avatarActor ! AvatarActor.DeinitializeImplants() + + // This would normally happen async as part of AvatarAction.Killed, but if it doesn't happen before deleting calling AvatarAction.ObjectDelete on the player the LLU will end up invisible to others if carried + // Therefore, queue it up to happen first. + events ! AvatarServiceMessage(nameChannel, AvatarAction.DropSpecialItem()) + events ! AvatarServiceMessage( nameChannel, AvatarAction.Killed(player_guid, target.VehicleSeated) diff --git a/src/main/scala/net/psforever/services/avatar/AvatarService.scala b/src/main/scala/net/psforever/services/avatar/AvatarService.scala index 27b3faad..8b9a6e5c 100644 --- a/src/main/scala/net/psforever/services/avatar/AvatarService.scala +++ b/src/main/scala/net/psforever/services/avatar/AvatarService.scala @@ -415,6 +415,9 @@ class AvatarService(zone: Zone) extends Actor { ) ) + case AvatarAction.DropSpecialItem() => + AvatarEvents.publish(AvatarServiceResponse(s"/$forChannel/Avatar", Service.defaultPlayerGUID, AvatarResponse.DropSpecialItem())) + case _ => ; } diff --git a/src/main/scala/net/psforever/services/avatar/AvatarServiceMessage.scala b/src/main/scala/net/psforever/services/avatar/AvatarServiceMessage.scala index 015abd93..af5ebf44 100644 --- a/src/main/scala/net/psforever/services/avatar/AvatarServiceMessage.scala +++ b/src/main/scala/net/psforever/services/avatar/AvatarServiceMessage.scala @@ -149,6 +149,7 @@ object AvatarAction { inventory: List[InventoryItem], drop: List[InventoryItem] ) extends Action + final case class DropSpecialItem() extends Action final case class TeardownConnection() extends Action // final case class PlayerStateShift(killer : PlanetSideGUID, victim : PlanetSideGUID) extends Action diff --git a/src/main/scala/net/psforever/services/avatar/AvatarServiceResponse.scala b/src/main/scala/net/psforever/services/avatar/AvatarServiceResponse.scala index f4f51b3b..ed249af2 100644 --- a/src/main/scala/net/psforever/services/avatar/AvatarServiceResponse.scala +++ b/src/main/scala/net/psforever/services/avatar/AvatarServiceResponse.scala @@ -117,6 +117,7 @@ object AvatarResponse { inventory: List[InventoryItem], drop: List[InventoryItem] ) extends Response + final case class DropSpecialItem() extends Response final case class TeardownConnection() extends Response // final case class PlayerStateShift(itemID : PlanetSideGUID) extends Response diff --git a/src/test/scala/objects/PlayerControlTest.scala b/src/test/scala/objects/PlayerControlTest.scala index 4bde843f..db0b5d46 100644 --- a/src/test/scala/objects/PlayerControlTest.scala +++ b/src/test/scala/objects/PlayerControlTest.scala @@ -526,7 +526,7 @@ class PlayerControlDeathStandingTest extends ActorTest { assert(player2.isAlive) player2.Actor ! Vitality.Damage(applyDamageTo) - val msg_avatar = avatarProbe.receiveN(7, 500 milliseconds) + val msg_avatar = avatarProbe.receiveN(8, 500 milliseconds) val msg_stamina = probe.receiveOne(500 milliseconds) activityProbe.expectNoMessage(200 milliseconds) assert( @@ -543,25 +543,31 @@ class PlayerControlDeathStandingTest extends ActorTest { ) assert( msg_avatar(1) match { + case AvatarServiceMessage("TestCharacter2", AvatarAction.DropSpecialItem()) => true + case _ => false + } + ) + assert( + msg_avatar(2) match { case AvatarServiceMessage("TestCharacter2", AvatarAction.Killed(PlanetSideGUID(2), None)) => true case _ => false } ) assert( - msg_avatar(2) match { + msg_avatar(3) match { case AvatarServiceMessage("test", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 0, _)) => true case _ => false } ) assert( - msg_avatar(3) match { + msg_avatar(4) match { case AvatarServiceMessage("TestCharacter2", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 7, _)) => true case _ => false } ) assert( - msg_avatar(4) match { + msg_avatar(5) match { case AvatarServiceMessage( "TestCharacter2", AvatarAction.SendResponse(_, DestroyMessage(PlanetSideGUID(2), PlanetSideGUID(1), _, _)) @@ -571,7 +577,7 @@ class PlayerControlDeathStandingTest extends ActorTest { } ) assert( - msg_avatar(5) match { + msg_avatar(6) match { case AvatarServiceMessage( "TestCharacter2", AvatarAction.SendResponse( @@ -584,7 +590,7 @@ class PlayerControlDeathStandingTest extends ActorTest { } ) assert( - msg_avatar(6) match { + msg_avatar(7) match { case AvatarServiceMessage("test", AvatarAction.DestroyDisplay(killer, victim, _, _)) if killer.Name.equals(player1.Name) && victim.Name.equals(player2.Name) => true @@ -666,7 +672,7 @@ class PlayerControlDeathSeatedTest extends ActorTest { assert(player2.isAlive) player2.Actor ! Vitality.Damage(applyDamageTo) - val msg_avatar = avatarProbe.receiveN(8, 500 milliseconds) + val msg_avatar = avatarProbe.receiveN(9, 500 milliseconds) val msg_stamina = probe.receiveOne(500 milliseconds) activityProbe.expectNoMessage(200 milliseconds) assert( @@ -677,6 +683,12 @@ class PlayerControlDeathSeatedTest extends ActorTest { ) assert( msg_avatar.head match { + case AvatarServiceMessage("TestCharacter2", AvatarAction.DropSpecialItem()) => true + case _ => false + } + ) + assert( + msg_avatar(1) match { case AvatarServiceMessage( "TestCharacter2", AvatarAction.Killed(PlanetSideGUID(2), Some(PlanetSideGUID(7))) @@ -686,7 +698,7 @@ class PlayerControlDeathSeatedTest extends ActorTest { } ) assert( - msg_avatar(1) match { + msg_avatar(2) match { case AvatarServiceMessage( "TestCharacter2", AvatarAction.SendResponse(_, ObjectDetachMessage(PlanetSideGUID(7), PlanetSideGUID(2), _, _, _, _)) @@ -696,7 +708,7 @@ class PlayerControlDeathSeatedTest extends ActorTest { } ) assert( - msg_avatar(2) match { + msg_avatar(3) match { case AvatarServiceMessage( "TestCharacter2", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 29, 1) @@ -706,19 +718,19 @@ class PlayerControlDeathSeatedTest extends ActorTest { } ) assert( - msg_avatar(3) match { + msg_avatar(4) match { case AvatarServiceMessage("test", AvatarAction.ObjectDelete(PlanetSideGUID(2), PlanetSideGUID(2), _)) => true case _ => false } ) assert( - msg_avatar(4) match { + msg_avatar(5) match { case AvatarServiceMessage("test", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 0, _)) => true case _ => false } ) assert( - msg_avatar(5) match { + msg_avatar(6) match { case AvatarServiceMessage( "TestCharacter2", AvatarAction.SendResponse(_, DestroyMessage(PlanetSideGUID(2), PlanetSideGUID(1), _, _)) @@ -728,7 +740,7 @@ class PlayerControlDeathSeatedTest extends ActorTest { } ) assert( - msg_avatar(6) match { + msg_avatar(7) match { case AvatarServiceMessage( "TestCharacter2", AvatarAction.SendResponse( @@ -741,7 +753,7 @@ class PlayerControlDeathSeatedTest extends ActorTest { } ) assert( - msg_avatar(7) match { + msg_avatar(8) match { case AvatarServiceMessage("test", AvatarAction.DestroyDisplay(killer, victim, _, _)) if killer.Name.equals(player1.Name) && victim.Name.equals(player2.Name) => true