Fixed mining and timeout settings

- Pending biome reads no longer cancel block breaking
- Custom loot resolves saved biome history after accepted breaks
- Native drops survive failed reads and runtime teardown without duplication
- Tree felling captures the original tool for fallback drops
- Modded block loot resolves one historical biome context
- Timeout settings persist atomically through the actual server configuration paths
- Tests cover deferred loot, teardown and timeout persistence
This commit is contained in:
Brian Neumann-Fopiano committed 2026-10-12 09:27:26 -04:00
1 parent 2145e90b1c
commit e3673afb1e
17 files changed
+1220 -329

No files matched your search

@@ -31,6 +31,7 @@ import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeBlockDropHooks;
import art.arcane.iris.pack.loading.IrisData;
import art.arcane.iris.generation.runtime.Engine;
import art.arcane.iris.generation.runtime.BiomeEnvironment;
import art.arcane.iris.structure.placement.LootResolver;
import art.arcane.iris.generation.decoration.tree.TreeBlockMaterial;
import art.arcane.iris.generation.biome.IrisBiome;
@@ -181,26 +182,30 @@ public final class ModdedBlockBreakHandler {
NativeBlockState brokenState,
Engine engine
) {
KList<IrisBlockDrops> providers = providers(engine, position, brokenState);
if (providers.isEmpty()) {
return Result.empty();
}
BiomeEnvironment environment = engine.getBiomeEnvironment(position.x(),
position.y() - engine.getMinHeight(), position.z());
try (BiomeEnvironment.Scope ignored = engine.openBiomeEnvironmentScope(environment)) {
KList<IrisBlockDrops> providers = providers(environment, brokenState);
if (providers.isEmpty()) {
return Result.empty();
}
KList<NativeItemStack> drops = new KList<>();
boolean replaceVanillaDrops = false;
for (IrisBlockDrops provider : providers) {
replaceVanillaDrops |= provider.isReplaceVanillaDrops();
for (IrisLoot loot : provider.getDrops()) {
if (!LootResolver.oneIn(RNG.r, loot.getRarity())) {
continue;
}
NativeItemStack stack = ModdedItemTranslator.stack(loot, RNG.r, ModdedItemTranslator.context(level));
if (stack != null && !stack.isEmpty()) {
drops.add(stack);
KList<NativeItemStack> drops = new KList<>();
boolean replaceVanillaDrops = false;
for (IrisBlockDrops provider : providers) {
replaceVanillaDrops |= provider.isReplaceVanillaDrops();
for (IrisLoot loot : provider.getDrops()) {
if (!LootResolver.oneIn(RNG.r, loot.getRarity())) {
continue;
}
NativeItemStack stack = ModdedItemTranslator.stack(loot, RNG.r, ModdedItemTranslator.context(level));
if (stack != null && !stack.isEmpty()) {
drops.add(stack);
}
}
}
return new Result(drops, replaceVanillaDrops, null);
}
return new Result(drops, replaceVanillaDrops, null);
}
private static void removeMarker(Engine engine, NativeBlockPoint position) {
@@ -225,22 +230,17 @@ public final class ModdedBlockBreakHandler {
engine.getMantle().getMantle().remove(position.x(), mantleY, position.z(), TreeBlockMaterial.class);
}
private static KList<IrisBlockDrops> providers(Engine engine, NativeBlockPoint position, NativeBlockState brokenState) {
private static KList<IrisBlockDrops> providers(BiomeEnvironment environment, NativeBlockState brokenState) {
KList<IrisBlockDrops> providers = new KList<>();
IrisData data = engine.getData();
int relativeY = position.y() - engine.getMinHeight();
IrisBiome biome = engine.getBiome(position.x(), relativeY, position.z());
if (biome != null) {
addMatching(providers, biome.getBlockDrops(), brokenState, data);
}
IrisData data = environment.data();
IrisBiome biome = environment.biome();
addMatching(providers, biome.getBlockDrops(), brokenState, data);
if (skipsParents(providers)) {
return providers;
}
IrisRegion region = engine.getRegion(position.x(), relativeY, position.z());
if (region != null) {
addMatching(providers, region.getBlockDrops(), brokenState, data);
}
addMatching(providers, engine.getDimension().getBlockDrops(), brokenState, data);
IrisRegion region = environment.region();
addMatching(providers, region.getBlockDrops(), brokenState, data);
addMatching(providers, environment.dimension().getBlockDrops(), brokenState, data);
return providers;
}
@@ -18,6 +18,21 @@
package art.arcane.iris.modded;
import art.arcane.iris.generation.biome.IrisBiome;
import art.arcane.iris.generation.block.IrisBlockData;
import art.arcane.iris.generation.runtime.BiomeEnvironment;
import art.arcane.iris.generation.runtime.Engine;
import art.arcane.iris.generation.terrain.IrisDimension;
import art.arcane.iris.generation.terrain.IrisRegion;
import art.arcane.iris.pack.loading.IrisData;
import art.arcane.iris.world.loot.IrisBlockDrops;
import art.arcane.iris.world.loot.IrisLoot;
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeItemStack;
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeWorldGenerators;
import art.arcane.volmlib.nativelib.terrain.NativeBlockPoint;
import art.arcane.volmlib.nativelib.terrain.NativeWorld;
import art.arcane.volmlib.util.collection.KList;
import art.arcane.volmlib.util.math.RNG;
import net.minecraft.core.Direction;
import art.arcane.volmlib.nativelib.minecraft26_2.modded.ModdedBlockState;
import net.minecraft.world.level.block.Blocks;
@@ -27,9 +42,22 @@ import org.junit.Test;
import org.junit.BeforeClass;
import net.minecraft.SharedConstants;
import net.minecraft.server.Bootstrap;
import org.mockito.MockedStatic;
import java.util.concurrent.atomic.AtomicBoolean;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
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.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public class ModdedBlockBreakHandlerTest {
@BeforeClass
@@ -48,4 +76,97 @@ public class ModdedBlockBreakHandlerTest {
assertTrue(ModdedBlockBreakHandler.matchesState(north, east, false));
assertFalse(ModdedBlockBreakHandler.matchesState(north, ModdedBlockState.of(Blocks.COBBLESTONE.defaultBlockState(), null), false));
}
@Test
public void historicalEnvironmentSuppliesAllRulesAndScopesMatchingAndLoot() {
try (DropFixture fixture = new DropFixture()) {
fixture.dimensionRule.setReplaceVanillaDrops(true);
ModdedBlockBreakHandler.Result result = fixture.evaluate();
assertTrue(result.replaceVanillaDrops());
assertEquals(3, result.drops().size());
assertSame(fixture.item, result.drops().getFirst());
assertFalse(fixture.scoped.get());
verify(fixture.engine).getBiomeEnvironment(16, 136, 32);
verify(fixture.engine).openBiomeEnvironmentScope(fixture.environment);
verify(fixture.engine, never()).getData();
verify(fixture.engine, never()).getDimension();
verify(fixture.engine, never()).getBiome(anyInt(), anyInt(), anyInt());
verify(fixture.engine, never()).getRegion(anyInt(), anyInt(), anyInt());
}
}
@Test
public void historicalBiomeCanSkipRegionAndDimensionDrops() {
try (DropFixture fixture = new DropFixture()) {
fixture.biomeRule.setSkipParents(true);
fixture.dimensionRule.setReplaceVanillaDrops(true);
ModdedBlockBreakHandler.Result result = fixture.evaluate();
assertFalse(result.replaceVanillaDrops());
assertEquals(1, result.drops().size());
assertFalse(fixture.scoped.get());
verify(fixture.region, never()).getBlockDrops();
verify(fixture.dimension, never()).getBlockDrops();
}
}
private static final class DropFixture implements AutoCloseable {
private final NativeWorld world = mock(NativeWorld.class);
private final Engine engine = mock(Engine.class);
private final IrisBiome biome = mock(IrisBiome.class);
private final IrisRegion region = mock(IrisRegion.class);
private final IrisDimension dimension = mock(IrisDimension.class);
private final IrisData data = mock(IrisData.class);
private final BiomeEnvironment environment = new BiomeEnvironment(7L, biome, region, dimension, data);
private final ModdedBlockState state = ModdedBlockState.of(Blocks.COBBLESTONE.defaultBlockState(), null);
private final NativeItemStack item = mock(NativeItemStack.class);
private final IrisLoot loot = new IrisLoot().setRarity(1);
private final IrisBlockData configuredBlock = mock(IrisBlockData.class);
private final IrisBlockDrops biomeRule = rule();
private final IrisBlockDrops regionRule = rule();
private final IrisBlockDrops dimensionRule = rule();
private final AtomicBoolean scoped = new AtomicBoolean();
private final MockedStatic<NativeWorldGenerators> generators = mockStatic(NativeWorldGenerators.class);
private final MockedStatic<ModdedItemTranslator> translator = mockStatic(ModdedItemTranslator.class);
private DropFixture() {
IrisModdedChunkGenerator generator = mock(IrisModdedChunkGenerator.class);
generators.when(() -> NativeWorldGenerators.find(world, IrisModdedChunkGenerator.class)).thenReturn(generator);
when(generator.engineIfBound()).thenReturn(engine);
when(engine.getMinHeight()).thenReturn(-64);
when(engine.getBiomeEnvironment(16, 136, 32)).thenReturn(environment);
when(engine.openBiomeEnvironmentScope(environment)).thenAnswer(invocation -> {
assertTrue(scoped.compareAndSet(false, true));
return (BiomeEnvironment.Scope) () -> scoped.set(false);
});
when(biome.getBlockDrops()).thenReturn(new KList<>(biomeRule));
when(region.getBlockDrops()).thenReturn(new KList<>(regionRule));
when(dimension.getBlockDrops()).thenReturn(new KList<>(dimensionRule));
when(configuredBlock.getBlockData(data)).thenAnswer(invocation -> {
assertTrue(scoped.get());
return state;
});
translator.when(() -> ModdedItemTranslator.stack(eq(loot), eq(RNG.r), any())).thenAnswer(invocation -> {
assertTrue(scoped.get());
return item;
});
}
private IrisBlockDrops rule() {
return new IrisBlockDrops().setBlocks(new KList<>(configuredBlock)).setDrops(new KList<>(loot));
}
private ModdedBlockBreakHandler.Result evaluate() {
return ModdedBlockBreakHandler.evaluateManagedDrops(world, new NativeBlockPoint(16, 72, 32), state);
}
@Override
public void close() {
translator.close();
generators.close();
}
}
}