mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-22 16:17:44 +00:00
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:
1 parent
2145e90b1c
commit
e3673afb1e
17 files changed
+1220
-329
No files matched your search
@@ -990,6 +990,7 @@ public class Iris extends VolmitPlugin implements Listener, ReloadAware {
|
||||
}
|
||||
|
||||
public void onDisable() {
|
||||
preservePendingBlockDrops();
|
||||
papiLifecycleActive = false;
|
||||
if (updateNotifier != null) {
|
||||
updateNotifier.close();
|
||||
@@ -1034,6 +1035,7 @@ public class Iris extends VolmitPlugin implements Listener, ReloadAware {
|
||||
|
||||
@Override
|
||||
public CompletionStage<Void> commitReload(ReloadAware.PreUnloadReason reason) {
|
||||
preservePendingBlockDrops();
|
||||
papiLifecycleActive = false;
|
||||
teardownPapi();
|
||||
if (IrisToolbelt.isServerStopping()) {
|
||||
@@ -1283,6 +1285,20 @@ public class Iris extends VolmitPlugin implements Listener, ReloadAware {
|
||||
&& preservation.hasActiveResources();
|
||||
}
|
||||
|
||||
private void preservePendingBlockDrops() {
|
||||
for (World world : Bukkit.getWorlds()) {
|
||||
PlatformChunkGenerator generator = IrisToolbelt.access(world);
|
||||
if (generator != null && generator.getEngine() != null
|
||||
&& generator.getEngine().getWorldManager() instanceof IrisWorldManager manager) {
|
||||
manager.flushPendingBlockDropsAsync().whenComplete((ignored, failure) -> {
|
||||
if (failure != null) {
|
||||
Iris.reportError("Failed to preserve pending block loot before Iris teardown.", failure);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void drainWorldGenerators(String reason, long timeoutSeconds) {
|
||||
List<World> irisWorlds = new ArrayList<>();
|
||||
List<PlatformChunkGenerator> generators = new ArrayList<>();
|
||||
|
||||
+18
-4
@@ -3,19 +3,33 @@ package art.arcane.iris.world.tree;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.block.BlockBreakEvent;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Objects;
|
||||
|
||||
final class RoutedBlockBreakEvent extends BlockBreakEvent implements BlockDropRouter {
|
||||
private final TreeFellerSVC service;
|
||||
private final FellingRun run;
|
||||
private final ItemStack tool;
|
||||
|
||||
RoutedBlockBreakEvent(Block block, Player player, FellingRun run, TreeFellerSVC service) {
|
||||
super(block, player);
|
||||
this.run = run;
|
||||
this.service = service;
|
||||
RoutedBlockBreakEvent(Options options) {
|
||||
super(options.block(), options.player());
|
||||
run = options.run();
|
||||
service = options.service();
|
||||
tool = Objects.requireNonNull(options.tool(), "tool").clone();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ItemStack> captureNativeDrops() {
|
||||
return getBlock().getDrops(tool);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean routeDrop(Object drop) {
|
||||
return service.isServiceEnabled() && run.presentation.routeDrop(drop);
|
||||
}
|
||||
|
||||
record Options(Block block, Player player, FellingRun run, TreeFellerSVC service, ItemStack tool) {
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -387,7 +387,8 @@ final class TreeFellingRunner {
|
||||
return;
|
||||
}
|
||||
|
||||
BlockBreakEvent probe = new RoutedBlockBreakEvent(block, run.candidate.player(), run, service);
|
||||
BlockBreakEvent probe = new RoutedBlockBreakEvent(new RoutedBlockBreakEvent.Options(
|
||||
block, run.candidate.player(), run, service, reservation.toolForDrops()));
|
||||
service.managedEvents.add(probe);
|
||||
try {
|
||||
Bukkit.getPluginManager().callEvent(probe);
|
||||
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
package art.arcane.iris.world.tree;
|
||||
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class RoutedBlockBreakEventTest {
|
||||
@Test
|
||||
public void nativeLootUsesTheReservedToolCloneWithoutReadingPlayerInventory() {
|
||||
Block block = mock(Block.class);
|
||||
Player player = mock(Player.class);
|
||||
ItemStack reservedTool = mock(ItemStack.class);
|
||||
ItemStack capturedTool = mock(ItemStack.class);
|
||||
ItemStack drop = mock(ItemStack.class);
|
||||
Collection<ItemStack> expected = List.of(drop);
|
||||
when(reservedTool.clone()).thenReturn(capturedTool);
|
||||
when(block.getDrops(capturedTool)).thenReturn(expected);
|
||||
RoutedBlockBreakEvent event = new RoutedBlockBreakEvent(new RoutedBlockBreakEvent.Options(
|
||||
block, player, mock(FellingRun.class), mock(TreeFellerSVC.class), reservedTool));
|
||||
|
||||
reservedTool.setType(Material.AIR);
|
||||
|
||||
assertSame(expected, event.captureNativeDrops());
|
||||
verify(reservedTool).clone();
|
||||
verify(block).getDrops(capturedTool);
|
||||
verify(block, never()).getDrops(reservedTool);
|
||||
verifyNoInteractions(player);
|
||||
}
|
||||
}
|
||||
+28
-28
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+121
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user