diff --git a/src/main/scala/net/psforever/actors/session/SessionActor.scala b/src/main/scala/net/psforever/actors/session/SessionActor.scala index 27347c786..11b811805 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 a96b8924e..9c0fc7f10 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 27b3faad1..8b9a6e5c3 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 015abd93e..af5ebf448 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 f4f51b3b1..ed249af21 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 4bde843f7..db0b5d46d 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