|
37 | 37 | import org.bukkit.event.entity.CreatureSpawnEvent; |
38 | 38 | import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason; |
39 | 39 | import org.bukkit.event.entity.EntityBreedEvent; |
| 40 | +import org.bukkit.event.entity.EntityPortalEvent; |
40 | 41 | import org.bukkit.event.entity.EntityRemoveEvent; |
41 | 42 | import org.bukkit.event.Event; |
42 | 43 | import org.bukkit.event.block.Action; |
@@ -803,6 +804,74 @@ void testReloadedEntityDecrementsWhenItDiesOffIsland() throws Exception { |
803 | 804 | assertFalse(entityIslandMap().containsKey(enderman.getUniqueId())); |
804 | 805 | } |
805 | 806 |
|
| 807 | + // --- EntityPortalEvent / phantom-count tests --- |
| 808 | + |
| 809 | + @Test |
| 810 | + void testEntityPortalTransfersCountBetweenEnvironments() throws Exception { |
| 811 | + Location netherLoc = mockNetherLocation(); |
| 812 | + LivingEntity chicken = mockEntity(EntityType.CHICKEN, location); |
| 813 | + ibc.incrementEntity(Environment.NORMAL, EntityType.CHICKEN); |
| 814 | + |
| 815 | + ell.onEntityPortal(new EntityPortalEvent(chicken, location, netherLoc)); |
| 816 | + |
| 817 | + assertEquals(0, ibc.getEntityCount(Environment.NORMAL, EntityType.CHICKEN)); |
| 818 | + assertEquals(1, ibc.getEntityCount(Environment.NETHER, EntityType.CHICKEN)); |
| 819 | + assertEquals("test-island-id", entityIslandMap().get(chicken.getUniqueId())); |
| 820 | + } |
| 821 | + |
| 822 | + @Test |
| 823 | + void testPortaledEntityDeathStillDecrements() throws Exception { |
| 824 | + // Regression: Paper never fires an EntityRemoveEvent for the dimension change itself |
| 825 | + // (RemovalReason.CHANGED_DIMENSION has a null Bukkit cause), so the old justPortaled |
| 826 | + // guard was never consumed and instead swallowed the entity's real death decrement, |
| 827 | + // leaving a phantom count in the destination environment. |
| 828 | + Location netherLoc = mockNetherLocation(); |
| 829 | + World nether = netherLoc.getWorld(); |
| 830 | + LivingEntity chicken = mockEntity(EntityType.CHICKEN, location); |
| 831 | + ibc.incrementEntity(Environment.NORMAL, EntityType.CHICKEN); |
| 832 | + ell.onEntityPortal(new EntityPortalEvent(chicken, location, netherLoc)); |
| 833 | + assertEquals(1, ibc.getEntityCount(Environment.NETHER, EntityType.CHICKEN)); |
| 834 | + |
| 835 | + // The entity now lives in the nether and dies there. |
| 836 | + when(chicken.getWorld()).thenReturn(nether); |
| 837 | + when(chicken.getLocation()).thenReturn(netherLoc); |
| 838 | + ell.onEntityRemove(new EntityRemoveEvent(chicken, EntityRemoveEvent.Cause.DEATH)); |
| 839 | + |
| 840 | + assertEquals(0, ibc.getEntityCount(Environment.NETHER, EntityType.CHICKEN)); |
| 841 | + assertFalse(entityIslandMap().containsKey(chicken.getUniqueId())); |
| 842 | + } |
| 843 | + |
| 844 | + @Test |
| 845 | + void testRoundTripPortalThenDeathLeavesNoPhantomCount() throws Exception { |
| 846 | + // The reported symptom: a mob portals to the nether, comes back, and dies in the |
| 847 | + // overworld — the overworld count must return to zero, not stick at a phantom 1. |
| 848 | + Location netherLoc = mockNetherLocation(); |
| 849 | + World nether = netherLoc.getWorld(); |
| 850 | + LivingEntity chicken = mockEntity(EntityType.CHICKEN, location); |
| 851 | + ibc.incrementEntity(Environment.NORMAL, EntityType.CHICKEN); |
| 852 | + |
| 853 | + ell.onEntityPortal(new EntityPortalEvent(chicken, location, netherLoc)); |
| 854 | + when(chicken.getWorld()).thenReturn(nether); |
| 855 | + when(chicken.getLocation()).thenReturn(netherLoc); |
| 856 | + ell.onEntityPortal(new EntityPortalEvent(chicken, netherLoc, location)); |
| 857 | + when(chicken.getWorld()).thenReturn(world); |
| 858 | + when(chicken.getLocation()).thenReturn(location); |
| 859 | + |
| 860 | + ell.onEntityRemove(new EntityRemoveEvent(chicken, EntityRemoveEvent.Cause.DEATH)); |
| 861 | + |
| 862 | + assertEquals(0, ibc.getEntityCount(Environment.NORMAL, EntityType.CHICKEN)); |
| 863 | + assertEquals(0, ibc.getEntityCount(Environment.NETHER, EntityType.CHICKEN)); |
| 864 | + } |
| 865 | + |
| 866 | + private Location mockNetherLocation() { |
| 867 | + World nether = mock(World.class); |
| 868 | + when(nether.getEnvironment()).thenReturn(Environment.NETHER); |
| 869 | + when(addon.inGameModeWorld(nether)).thenReturn(true); |
| 870 | + Location netherLoc = mock(Location.class); |
| 871 | + when(netherLoc.getWorld()).thenReturn(nether); |
| 872 | + return netherLoc; |
| 873 | + } |
| 874 | + |
806 | 875 | @SuppressWarnings("unchecked") |
807 | 876 | private Map<UUID, String> entityIslandMap() throws Exception { |
808 | 877 | Field f = EntityLimitListener.class.getDeclaredField("entityIslandMap"); |
|
0 commit comments