mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-22 08:07:42 +00:00
Restore natural mob spawning
- Restore cave spawning and correct floor placement across platforms. - Enforce category population limits and clamp ambient spawn batches. - Honor mob-spawning rules and entity-ticking chunk admission. - Separate chunk saving from configurable distance-despawn retention. - Add spawning regressions and Folia gameplay acceptance coverage.
This commit is contained in:
1 parent
4ef6fc7cc6
commit
a765e76aa3
31 files changed
+1712
-87
No files matched your search
+74
-5
@@ -2,6 +2,13 @@ package art.arcane.iris.modded;
|
||||
|
||||
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeEntityBehavior;
|
||||
|
||||
import art.arcane.iris.world.entity.IrisEntity;
|
||||
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeSpawnedEntity;
|
||||
import net.minecraft.SharedConstants;
|
||||
import net.minecraft.server.Bootstrap;
|
||||
import net.minecraft.world.entity.EntityType;
|
||||
import org.junit.BeforeClass;
|
||||
import net.minecraft.world.entity.Mob;
|
||||
import org.junit.Test;
|
||||
import org.junit.Before;
|
||||
import art.arcane.volmlib.nativelib.modded.EntityBehaviorTags;
|
||||
@@ -11,18 +18,31 @@ import java.util.Set;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.atLeastOnce;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class ModdedEntityPersistenceTest {
|
||||
@BeforeClass
|
||||
public static void bootstrapMinecraftRegistries() {
|
||||
SharedConstants.tryDetectVersion();
|
||||
Bootstrap.bootStrap();
|
||||
}
|
||||
|
||||
@Before
|
||||
public void configureEntityTags() {
|
||||
NativeEntityBehavior.bind(new EntityBehaviorTags("iris_non_persistent", "iris_unaware"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void generatedNonPersistentEntityIsExcludedFromVanillaSaves() {
|
||||
public void explicitlyExcludedEntityIsExcludedFromVanillaSaves() {
|
||||
Set<String> tags = new HashSet<>();
|
||||
|
||||
NativeEntityBehavior.configurePersistenceTags(tags, false);
|
||||
NativeEntityBehavior.configureSavingTags(tags, false);
|
||||
|
||||
assertFalse(NativeEntityBehavior.shouldSave(tags, true));
|
||||
}
|
||||
@@ -30,9 +50,9 @@ public class ModdedEntityPersistenceTest {
|
||||
@Test
|
||||
public void positivePersistenceRemovesTheSaveExclusion() {
|
||||
Set<String> tags = new HashSet<>();
|
||||
NativeEntityBehavior.configurePersistenceTags(tags, false);
|
||||
NativeEntityBehavior.configureSavingTags(tags, false);
|
||||
|
||||
NativeEntityBehavior.configurePersistenceTags(tags, true);
|
||||
NativeEntityBehavior.configureSavingTags(tags, true);
|
||||
|
||||
assertTrue(NativeEntityBehavior.shouldSave(tags, true));
|
||||
}
|
||||
@@ -41,8 +61,57 @@ public class ModdedEntityPersistenceTest {
|
||||
public void interceptionNeverOverridesVanillaSaveRejection() {
|
||||
Set<String> tags = new HashSet<>();
|
||||
|
||||
NativeEntityBehavior.configurePersistenceTags(tags, true);
|
||||
NativeEntityBehavior.configureSavingTags(tags, true);
|
||||
|
||||
assertFalse(NativeEntityBehavior.shouldSave(tags, false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void defaultAmbientMobSavesWithoutBecomingPermanent() {
|
||||
Mob mob = mob();
|
||||
NativeSpawnedEntity entity = new NativeSpawnedEntity(mob);
|
||||
IrisEntity options = new IrisEntity();
|
||||
|
||||
ModdedEntitySpawner.applyPersistence(options, entity, false);
|
||||
entity.configureMob(options);
|
||||
|
||||
assertTrue(NativeEntityBehavior.shouldSave(mob, true));
|
||||
verify(mob, never()).setPersistenceRequired();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void explicitKeepForceAndRemovalFlagsControlNativeDespawn() {
|
||||
for (boolean keep : new boolean[]{false, true}) {
|
||||
for (boolean force : new boolean[]{false, true}) {
|
||||
for (boolean removable : new boolean[]{false, true}) {
|
||||
Mob mob = mob();
|
||||
NativeSpawnedEntity entity = new NativeSpawnedEntity(mob);
|
||||
IrisEntity options = new IrisEntity().setKeepEntity(keep).setRemovable(removable);
|
||||
|
||||
ModdedEntitySpawner.applyPersistence(options, entity, force);
|
||||
entity.configureMob(options);
|
||||
|
||||
assertTrue(NativeEntityBehavior.shouldSave(mob, true));
|
||||
if (keep || force || !removable) {
|
||||
verify(mob, atLeastOnce()).setPersistenceRequired();
|
||||
} else {
|
||||
verify(mob, never()).setPersistenceRequired();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Mob mob() {
|
||||
Mob mob = mock(Mob.class);
|
||||
EntityType<?> type = mock(EntityType.class);
|
||||
Set<String> tags = new HashSet<>();
|
||||
when(type.canSerialize()).thenReturn(true);
|
||||
doReturn(type).when(mob).getType();
|
||||
when(mob.entityTags()).thenReturn(tags);
|
||||
when(mob.shouldBeSaved()).thenReturn(true);
|
||||
when(mob.removeTag(anyString())).thenAnswer(invocation -> tags.remove(invocation.getArgument(0)));
|
||||
when(mob.addTag(anyString())).thenAnswer(invocation -> tags.add(invocation.getArgument(0)));
|
||||
return mob;
|
||||
}
|
||||
}
|
||||
+31
-1
@@ -21,7 +21,7 @@ import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class ModdedEntitySpawnerParityTest {
|
||||
@Test
|
||||
public void collisionCheckUsesPaperInclusiveIntegerDimensions() {
|
||||
public void landBodyFitsTwoBlockCaveAndRejectsSecondBlockCeiling() {
|
||||
Set<BlockPos> checked = new HashSet<>();
|
||||
|
||||
boolean clear = NativeEntityRuntime.isAreaClearForSpawn(
|
||||
@@ -32,6 +32,36 @@ public class ModdedEntitySpawnerParityTest {
|
||||
|
||||
assertTrue(clear);
|
||||
assertEquals(Set.of(new BlockPos(10, 64, 20), new BlockPos(10, 65, 20)), checked);
|
||||
assertTrue(NativeEntityRuntime.isAreaClearForSpawn(10, 64, 20, 0.6F, 1.95F,
|
||||
(x, y, z) -> y >= 64 && y < 66));
|
||||
assertFalse(NativeEntityRuntime.isAreaClearForSpawn(10, 64, 20, 0.6F, 1.95F,
|
||||
(x, y, z) -> y == 64));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fractionalWideLandBodiesIncludeAdjacentObstructions() {
|
||||
Set<BlockPos> checked = new HashSet<>();
|
||||
assertTrue(NativeEntityRuntime.isAreaClearForSpawn(-1, -49, -1, 1.3F, 1.95F,
|
||||
(x, y, z) -> {
|
||||
checked.add(new BlockPos(x, y, z));
|
||||
return true;
|
||||
}));
|
||||
assertEquals(18, checked.size());
|
||||
assertTrue(checked.contains(new BlockPos(-2, -48, -2)));
|
||||
assertTrue(checked.contains(new BlockPos(0, -49, 0)));
|
||||
assertFalse(NativeEntityRuntime.isAreaClearForSpawn(-1, -49, -1, 1.3F, 1.95F,
|
||||
(x, y, z) -> !new BlockPos(x, y, z).equals(new BlockPos(0, -48, -1))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void exactLandBodyBoundariesExcludeUntouchedBlocks() {
|
||||
Set<BlockPos> checked = new HashSet<>();
|
||||
assertTrue(NativeEntityRuntime.isAreaClearForSpawn(0, -49, 0, 1F, 2F,
|
||||
(x, y, z) -> {
|
||||
checked.add(new BlockPos(x, y, z));
|
||||
return true;
|
||||
}));
|
||||
assertEquals(Set.of(new BlockPos(0, -49, 0), new BlockPos(0, -48, 0)), checked);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+97
-1
@@ -3,12 +3,21 @@ package art.arcane.iris.modded;
|
||||
import art.arcane.volmlib.nativelib.minecraft26_2.modded.ModdedBlockState;
|
||||
|
||||
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeEntityRuntime;
|
||||
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeSpawnedEntity;
|
||||
import art.arcane.volmlib.nativelib.terrain.NativeWorld;
|
||||
import art.arcane.iris.generation.mantle.EngineMantle;
|
||||
import art.arcane.iris.generation.runtime.Engine;
|
||||
import art.arcane.iris.generation.runtime.SeedManager;
|
||||
import art.arcane.iris.pack.value.IrisPosition;
|
||||
|
||||
import art.arcane.iris.world.entity.IrisEntity;
|
||||
import art.arcane.iris.world.entity.IrisEntitySpawn;
|
||||
import art.arcane.iris.world.entity.IrisSpawnGroup;
|
||||
import art.arcane.iris.world.entity.IrisSpawner;
|
||||
import art.arcane.iris.generation.decoration.IrisSurface;
|
||||
import art.arcane.volmlib.util.math.RNG;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import art.arcane.volmlib.util.matter.slices.MarkerMatter;
|
||||
import net.minecraft.SharedConstants;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
@@ -23,7 +32,9 @@ import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.mockito.MockedStatic;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -33,8 +44,15 @@ import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.mockStatic;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class ModdedWaterEntitySpawnTest {
|
||||
@@ -48,7 +66,8 @@ public class ModdedWaterEntitySpawnTest {
|
||||
}
|
||||
}
|
||||
BuiltInRegistries.BLOCK.prepareTagReload(new TagLoader.LoadResult<>(Registries.BLOCK, Map.of(
|
||||
BlockTags.BLOCKS_MOTION, List.of(Blocks.OAK_FENCE.builtInRegistryHolder())))).apply();
|
||||
BlockTags.BLOCKS_MOTION, List.of(Blocks.OAK_FENCE.builtInRegistryHolder(),
|
||||
Blocks.STONE.builtInRegistryHolder())))).apply();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -75,6 +94,22 @@ public class ModdedWaterEntitySpawnTest {
|
||||
assertFalse(ModdedEntitySpawner.isAreaClearForSpawn(new NativeEntityRuntime(level), entity, 0, 78, 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nativeLandClearanceAcceptsEveryAirBlockAndRejectsStone() {
|
||||
ServerLevel level = mock(ServerLevel.class);
|
||||
NativeEntityRuntime runtime = new NativeEntityRuntime(level);
|
||||
IrisEntity entity = new IrisEntity().setType("zombie").setSurface(IrisSurface.LAND);
|
||||
for (Block air : List.of(Blocks.AIR, Blocks.CAVE_AIR, Blocks.VOID_AIR)) {
|
||||
when(level.getBlockState(any(BlockPos.class))).thenReturn(air.defaultBlockState());
|
||||
assertTrue(ModdedEntitySpawner.isAreaClearForSpawn(runtime, entity, -1, -49, -1));
|
||||
}
|
||||
when(level.getBlockState(any(BlockPos.class))).thenAnswer(invocation -> {
|
||||
BlockPos position = invocation.getArgument(0);
|
||||
return position.getY() == -48 ? Blocks.STONE.defaultBlockState() : Blocks.CAVE_AIR.defaultBlockState();
|
||||
});
|
||||
assertFalse(ModdedEntitySpawner.isAreaClearForSpawn(runtime, entity, -1, -49, -1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void waterloggedSolidBlocksStillObstructWaterSpawns() {
|
||||
assertTrue(ModdedWorldManager.matchesSurface(IrisSurface.WATER,
|
||||
@@ -107,6 +142,29 @@ public class ModdedWaterEntitySpawnTest {
|
||||
assertEquals(Set.of(new BlockPos(0, 78, 0)), checked);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void spawnAndRiseStartUseFloorHeightOnLandAndCenterHeightInFluids() {
|
||||
for (IrisSurface surface : List.of(IrisSurface.LAND, IrisSurface.WATER, IrisSurface.LAVA)) {
|
||||
for (boolean rise : new boolean[]{false, true}) {
|
||||
Engine engine = mock(Engine.class);
|
||||
NativeEntityRuntime runtime = mock(NativeEntityRuntime.class);
|
||||
NativeSpawnedEntity created = mock(NativeSpawnedEntity.class);
|
||||
IrisEntity entity = new IrisEntity().setSpecialType("test:mob")
|
||||
.setSurface(surface).setSpawnEffectRiseOutOfGround(rise);
|
||||
when(runtime.chunksSafe(-1, 0)).thenReturn(true);
|
||||
when(runtime.hasPlayersNearby(any(NativeEntityRuntime.Position.class), eq(32D))).thenReturn(true);
|
||||
when(runtime.spawnCustom(eq("test:mob"), any(NativeEntityRuntime.Position.class), any()))
|
||||
.thenReturn(created);
|
||||
|
||||
assertSame(created, ModdedEntitySpawner.spawn(engine, entity, runtime, -1, -49, 0, new RNG(1L)));
|
||||
|
||||
double targetY = -49 + (surface.isFluid() ? 0.5 : 0) - (rise ? 5 : 0);
|
||||
verify(runtime).spawnCustom(eq("test:mob"),
|
||||
eq(new NativeEntityRuntime.Position(-0.5, targetY, 0.5)), any());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mixedSpawnerUsesFluidDepthWithoutChangingLandMembers() {
|
||||
RNG rng = new RNG(1L);
|
||||
@@ -117,4 +175,42 @@ public class ModdedWaterEntitySpawnTest {
|
||||
assertNull(IrisEntitySpawn.selectSurfaceSpawnY(IrisSpawnGroup.NORMAL, IrisSurface.WATER, 78, 78, rng));
|
||||
assertNull(IrisEntitySpawn.selectSurfaceSpawnY(IrisSpawnGroup.UNDERWATER, IrisSurface.WATER, 78, 78, rng));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void caveFloorMarkerSpawnsAboveSupportAtNegativeWorldHeight() throws ReflectiveOperationException {
|
||||
Engine engine = mock(Engine.class);
|
||||
EngineMantle mantle = mock(EngineMantle.class);
|
||||
when(engine.getMantle()).thenReturn(mantle);
|
||||
when(engine.getSeedManager()).thenReturn(mock(SeedManager.class));
|
||||
when(mantle.findMarkers(-2, 3, MarkerMatter.CAVE_FLOOR))
|
||||
.thenReturn(new KList<>(new IrisPosition(-25, -49, 57)));
|
||||
NativeWorld world = mock(NativeWorld.class);
|
||||
when(world.minHeight()).thenReturn(-64);
|
||||
when(world.maxHeight()).thenReturn(320);
|
||||
when(world.nativeHandle()).thenReturn(mock(ServerLevel.class));
|
||||
when(world.getBlock(anyInt(), anyInt(), anyInt())).thenAnswer(invocation ->
|
||||
ModdedBlockState.of((int) invocation.getArgument(1) <= -50
|
||||
? Blocks.STONE.defaultBlockState() : Blocks.CAVE_AIR.defaultBlockState(), null));
|
||||
IrisEntity entity = new IrisEntity().setSurface(IrisSurface.LAND);
|
||||
IrisEntitySpawn entry = spy(new IrisEntitySpawn().setMinSpawns(1).setMaxSpawns(1));
|
||||
doReturn(entity).when(entry).getRealEntity(engine);
|
||||
IrisSpawner spawner = new IrisSpawner().setGroup(IrisSpawnGroup.CAVE);
|
||||
Method spawn = ModdedWorldManager.class.getDeclaredMethod("spawnEntry", NativeWorld.class,
|
||||
IrisEntitySpawn.class, IrisSpawner.class, int.class, int.class, int.class);
|
||||
spawn.setAccessible(true);
|
||||
ModdedWorldManager manager = new ModdedWorldManager(engine);
|
||||
try (MockedStatic<ModdedEntitySpawner> entities = mockStatic(ModdedEntitySpawner.class)) {
|
||||
entities.when(() -> ModdedEntitySpawner.isAreaClearForSpawn(any(NativeEntityRuntime.class),
|
||||
eq(entity), eq(-25), eq(-49), eq(57))).thenReturn(true);
|
||||
entities.when(() -> ModdedEntitySpawner.spawn(eq(engine), eq(entity), any(NativeEntityRuntime.class),
|
||||
eq(-25), eq(-49), eq(57), any(RNG.class))).thenReturn(mock(NativeSpawnedEntity.class));
|
||||
|
||||
assertEquals(1, spawn.invoke(manager, world, entry, spawner, -2, 3, 1));
|
||||
|
||||
entities.verify(() -> ModdedEntitySpawner.spawn(eq(engine), eq(entity), any(NativeEntityRuntime.class),
|
||||
eq(-25), eq(-49), eq(57), any(RNG.class)));
|
||||
} finally {
|
||||
manager.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
package art.arcane.volmlib.nativelib.minecraft26_2.modded;
|
||||
|
||||
import art.arcane.volmlib.nativelib.terrain.NativeWorld;
|
||||
import net.minecraft.SharedConstants;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.server.Bootstrap;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.EntityType;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.entity.MobCategory;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
import net.minecraft.world.level.gamerules.GameRules;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.isNull;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class NativeSpawnPopulationTest {
|
||||
@BeforeClass
|
||||
public static void bootstrap() {
|
||||
SharedConstants.tryDetectVersion();
|
||||
Bootstrap.bootStrap();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void categoryCountsExcludePlayersAndNeighboringChunks() {
|
||||
ServerLevel level = mock(ServerLevel.class);
|
||||
NativeWorld world = mock(NativeWorld.class);
|
||||
when(world.nativeHandle()).thenReturn(level);
|
||||
when(level.getMinY()).thenReturn(-64);
|
||||
when(level.getMaxY()).thenReturn(320);
|
||||
LivingEntity frog = mob(MobCategory.CREATURE, -4, 7);
|
||||
LivingEntity zombie = mob(MobCategory.MONSTER, -4, 7);
|
||||
LivingEntity neighbor = mob(MobCategory.MONSTER, -3, 7);
|
||||
Player player = mock(Player.class);
|
||||
when(player.blockPosition()).thenReturn(new BlockPos(-60, 50, 120));
|
||||
List<Entity> entities = List.of(frog, frog, frog, zombie, neighbor, player);
|
||||
when(level.getEntities(isNull(Entity.class), any(AABB.class), any()))
|
||||
.thenAnswer(invocation -> {
|
||||
Predicate<? super Entity> filter = invocation.getArgument(2);
|
||||
return entities.stream().filter(filter).toList();
|
||||
});
|
||||
assertEquals(Map.of("creature", 3, "monster", 1),
|
||||
NativeSpawnQueries.livingEntityCategories(world, -4, 7));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ambientAdmissionRequiresMobSpawningAndTickingForRecurringAttempts() {
|
||||
ServerLevel level = mock(ServerLevel.class);
|
||||
NativeWorld world = mock(NativeWorld.class);
|
||||
GameRules rules = mock(GameRules.class);
|
||||
when(world.nativeHandle()).thenReturn(level);
|
||||
when(level.getMinY()).thenReturn(-64);
|
||||
when(level.getGameRules()).thenReturn(rules);
|
||||
when(rules.get(GameRules.SPAWN_MOBS)).thenReturn(false);
|
||||
assertFalse(NativeSpawnQueries.ambientAllowed(world, -4, 7, true));
|
||||
assertFalse(NativeSpawnQueries.ambientAllowed(world, -4, 7, false));
|
||||
when(rules.get(GameRules.SPAWN_MOBS)).thenReturn(true);
|
||||
assertTrue(NativeSpawnQueries.ambientAllowed(world, -4, 7, true));
|
||||
assertFalse(NativeSpawnQueries.ambientAllowed(world, -4, 7, false));
|
||||
when(level.isPositionEntityTicking(new BlockPos(-56, -64, 120))).thenReturn(true);
|
||||
assertTrue(NativeSpawnQueries.ambientAllowed(world, -4, 7, false));
|
||||
}
|
||||
|
||||
private static LivingEntity mob(MobCategory category, int chunkX, int chunkZ) {
|
||||
EntityType<?> type = mock(EntityType.class);
|
||||
when(type.getCategory()).thenReturn(category);
|
||||
LivingEntity entity = mock(LivingEntity.class);
|
||||
doReturn(type).when(entity).getType();
|
||||
when(entity.blockPosition()).thenReturn(new BlockPos((chunkX << 4) + 8, 32, (chunkZ << 4) + 8));
|
||||
return entity;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user