Level Utilities
Amber provides utilities for working with game levels/worlds, including scheduled tasks, item drops, and world-level operations. These utilities help simplify common world tasks and provide consistent behavior across platforms.
WARNING
Note: The utility classes described here are currently marked as deprecated and will be replaced with versioned alternatives in a future release. They are still functional but consider them as legacy APIs.
LevelHelper
The LevelHelper class provides methods for scheduled tasks and item drops in the world.
Scheduled Tasks
Run tasks at regular intervals in the world:
import com.iamkaf.amber.api.level.LevelHelper;
public class WorldScheduler {
// Run every 100 ticks (5 seconds)
public static void tickEvent(Level level) {
LevelHelper.runEveryXTicks(level, 100, gameTime -> {
// Check all players in the level
level.players().forEach(player -> {
// Apply periodic effect
if (player.hasEffect(MobEffects.REGENERATION)) {
player.addEffect(new MobEffectInstance(
MobEffects.REGENERATION,
200, // 10 seconds
0, // Amplifier
true, // Ambient particles
true // Show icon
));
}
});
});
// Run every 20 ticks (1 second)
LevelHelper.runEveryXTicks(level, 20, gameTime -> {
// Update world time display
if (level instanceof ServerLevel serverLevel) {
updateWorldClock(serverLevel);
}
});
// Run every 1200 ticks (1 minute)
LevelHelper.runEveryXTicks(level, 1200, gameTime -> {
// Save world data periodically
if (level instanceof ServerLevel serverLevel) {
saveCustomData(serverLevel, gameTime);
}
});
// Run every 6000 ticks (5 minutes)
LevelHelper.runEveryXTicks(level, 6000, gameTime -> {
// Cleanup old entities
if (level instanceof ServerLevel serverLevel) {
cleanupOldEntities(serverLevel);
}
});
}
private static void updateWorldClock(ServerLevel level) {
// Custom clock update logic
long dayTime = level.getDayTime() % 24000;
int hours = (int) ((dayTime + 6000) / 1000) % 24;
int minutes = (int) ((dayTime % 1000) * 60 / 1000);
String timeString = String.format("%02d:%02d", hours, minutes);
// Update all players with time
level.players().forEach(player -> {
ActionBarMessages.showStatus(player, "Time: " + timeString);
});
}
private static void saveCustomData(ServerLevel level, long gameTime) {
// Custom data saving logic
Constants.LOG.info("Saving world data at time: " + gameTime);
}
private static void cleanupOldEntities(ServerLevel level) {
// Remove old item entities
level.getEntitiesOfClass(ItemEntity.class,
entity -> entity.age > 6000 // 5 minutes
).forEach(Entity::discard);
Constants.LOG.info("Cleaned up old entities");
}
}Item Drops
Drop items in the world:
public class WorldDrops {
// Drop single item
public static void dropReward(Level level, Vec3 pos, Item item, int count) {
ItemStack stack = new ItemStack(item, count);
LevelHelper.dropItem(level, stack, pos);
}
// Drop with custom velocity
public static void dropWithVelocity(Level level, Vec3 pos, ItemStack stack, Vec3 velocity) {
LevelHelper.dropItem(level, stack, pos, velocity);
}
// Create explosion drops
public static void createExplosionDrops(Level level, Vec3 center, List<ItemStack> drops) {
Random random = new Random();
for (ItemStack drop : drops) {
// Random position within explosion radius
double angle = random.nextDouble() * 2 * Math.PI;
double distance = random.nextDouble() * 3.0; // 3 block radius
Vec3 dropPos = center.add(
Math.cos(angle) * distance,
0.5, // Slight upward offset
Math.sin(angle) * distance
);
// Random velocity away from center
Vec3 velocity = new Vec3(
Math.cos(angle) * 0.3,
random.nextDouble() * 0.2 + 0.1,
Math.sin(angle) * 0.3
);
LevelHelper.dropItem(level, drop, dropPos, velocity);
}
}
// Drop from loot table
public static void dropLootTable(Level level, Vec3 pos, ResourceLocation lootTableId, LootContextParamSet parameters) {
LootParams.Builder builder = new LootParams.Builder((ServerLevel) level);
// Build context parameters based on what you need
if (level instanceof ServerLevel serverLevel) {
builder.withParameter(LootContextParams.ORIGIN, Vec3.atCenterOf(BlockPos.containing(pos)));
}
LootParams lootParams = builder.build(parameters);
LootTable lootTable = level.getServer().getLootData().getLootTable(lootTableId);
// Generate and drop items
lootTable.getRandomItems(lootParams).forEach(stack -> {
LevelHelper.dropItem(level, stack, pos);
});
}
// Drop with chance
public static void dropWithChance(Level level, Vec3 pos, Item item, float chance) {
if (level.random.nextFloat() < chance) {
LevelHelper.dropItem(level, item, pos);
}
}
// Drop multiple items with different chances
public static void dropMultipleWithChance(Level level, Vec3 pos, Map<Item, Float> drops) {
for (Map.Entry<Item, Float> entry : drops.entrySet()) {
if (level.random.nextFloat() < entry.getValue()) {
LevelHelper.dropItem(level, entry.getKey(), pos);
}
}
}
// Drop in circle pattern
public static void dropInCircle(Level level, Vec3 center, Item item, int count, double radius) {
for (int i = 0; i < count; i++) {
double angle = (double) i / count * 2 * Math.PI;
Vec3 dropPos = center.add(
Math.cos(angle) * radius,
0.5,
Math.sin(angle) * radius
);
LevelHelper.dropItem(level, item, dropPos);
}
}
}Advanced World Operations
World Generation
Modify world generation:
public class WorldGeneration {
// Generate custom structure
public static void generateCustomStructure(Level level, BlockPos origin) {
// Generate a simple tower
for (int y = 0; y < 5; y++) {
for (int x = -1; x <= 1; x++) {
for (int z = -1; z <= 1; z++) {
BlockPos pos = origin.offset(x, y, z);
if (x == 0 && z == 0) {
// Center column
level.setBlock(pos, Blocks.STONE_BRICKS.defaultBlockState(), 3);
} else if (y == 0) {
// Foundation
level.setBlock(pos, Blocks.STONE_BRICKS.defaultBlockState(), 3);
} else {
// Walls
level.setBlock(pos, Blocks.AIR.defaultBlockState(), 3);
}
}
}
}
// Add roof
for (int x = -2; x <= 2; x++) {
for (int z = -2; z <= 2; z++) {
BlockPos pos = origin.offset(x, 5, z);
level.setBlock(pos, Blocks.STONE_SLAB.defaultBlockState(), 3);
}
}
}
// Generate ore vein
public static void generateOreVein(Level level, BlockPos center, Block ore, int size) {
Random random = new Random(center.asLong());
for (int i = 0; i < size; i++) {
// Random position within vein
int x = center.getX() + random.nextInt(7) - 3;
int y = center.getY() + random.nextInt(5) - 2;
int z = center.getZ() + random.nextInt(7) - 3;
BlockPos pos = new BlockPos(x, y, z);
// Only replace stone
if (level.getBlockState(pos).is(Blocks.STONE)) {
level.setBlock(pos, ore.defaultBlockState(), 3);
}
}
}
// Generate tree
public static void generateCustomTree(Level level, BlockPos pos) {
// Generate trunk
for (int y = 0; y < 6; y++) {
level.setBlock(pos.above(y), Blocks.OAK_LOG.defaultBlockState(), 3);
}
// Generate leaves
for (int x = -2; x <= 2; x++) {
for (int y = 3; y <= 5; y++) {
for (int z = -2; z <= 2; z++) {
BlockPos leafPos = pos.above(y).offset(x, 0, z);
// Skip trunk area
if (Math.abs(x) <= 1 && Math.abs(z) <= 1 && y < 5) {
continue;
}
// Random leaf placement
if (level.random.nextFloat() < 0.8f) {
level.setBlock(leafPos, Blocks.OAK_LEAVES.defaultBlockState(), 3);
}
}
}
}
}
}World Effects
Apply effects to the world:
public class WorldEffects {
// Create weather effect
public static void createWeatherEffect(Level level, BlockPos center, int radius, WeatherEffect effect) {
for (int x = -radius; x <= radius; x++) {
for (int z = -radius; z <= radius; z++) {
BlockPos pos = center.offset(x, 0, z);
if (pos.distSqr(center) <= radius * radius) {
effect.apply(level, pos);
}
}
}
}
// Weather effect interface
public interface WeatherEffect {
void apply(Level level, BlockPos pos);
}
// Rain effect
public static final WeatherEffect RAIN_EFFECT = (level, pos) -> {
if (level.isRaining() && level.canSeeSky(pos)) {
// Place water at position if it's air
if (level.getBlockState(pos).isAir()) {
level.setBlock(pos, Blocks.WATER.defaultBlockState(), 3);
}
}
};
// Lightning effect
public static final WeatherEffect LIGHTNING_EFFECT = (level, pos) -> {
if (level instanceof ServerLevel serverLevel && level.random.nextFloat() < 0.1f) {
LightningBolt lightning = EntityType.LIGHTNING_BOLT.create(serverLevel);
if (lightning != null) {
lightning.setPos(Vec3.atBottomCenterOf(pos));
serverLevel.addFreshEntity(lightning);
// Create fire
if (level.getBlockState(pos).isAir()) {
level.setBlock(pos, Blocks.FIRE.defaultBlockState(), 3);
}
}
}
};
// Create particle effect
public static void createParticleEffect(Level level, Vec3 center, ParticleOptions particle, int count, double radius) {
for (int i = 0; i < count; i++) {
// Random position within radius
double angle = level.random.nextDouble() * 2 * Math.PI;
double distance = level.random.nextDouble() * radius;
Vec3 particlePos = center.add(
Math.cos(angle) * distance,
level.random.nextDouble() * 2 - 1, // Random height
Math.sin(angle) * distance
);
level.addParticle(particle, particlePos.x, particlePos.y, particlePos.z,
0, 0, 0); // No velocity
}
}
// Create explosion effect
public static void createExplosionEffect(Level level, Vec3 center, float power, boolean causeFire) {
level.explode(null, center.x, center.y, center.z, power,
causeFire ? Level.ExplosionInteraction.MOB : Level.ExplosionInteraction.BLOCK);
// Add particles
createParticleEffect(level, center, ParticleTypes.EXPLOSION, 20, power);
}
}World Scanning
Scan and analyze the world:
public class WorldScanner {
// Find blocks in area
public static List<BlockPos> findBlocks(Level level, BlockPos center, int radius, Block targetBlock) {
List<BlockPos> found = new ArrayList<>();
for (int x = -radius; x <= radius; x++) {
for (int y = -radius; y <= radius; y++) {
for (int z = -radius; z <= radius; z++) {
BlockPos pos = center.offset(x, y, z);
if (level.getBlockState(pos).is(targetBlock)) {
found.add(pos);
}
}
}
}
return found;
}
// Count blocks in area
public static Map<Block, Integer> countBlocks(Level level, BlockPos center, int radius) {
Map<Block, Integer> counts = new HashMap<>();
for (int x = -radius; x <= radius; x++) {
for (int y = -radius; y <= radius; y++) {
for (int z = -radius; z <= radius; z++) {
BlockPos pos = center.offset(x, y, z);
Block block = level.getBlockState(pos).getBlock();
counts.merge(block, 1, Integer::sum);
}
}
}
return counts;
}
// Find nearest block
public static BlockPos findNearestBlock(Level level, BlockPos center, Block targetBlock, int maxRadius) {
BlockPos nearest = null;
double nearestDistance = maxRadius * maxRadius;
for (int x = -maxRadius; x <= maxRadius; x++) {
for (int y = -maxRadius; y <= maxRadius; y++) {
for (int z = -maxRadius; z <= maxRadius; z++) {
BlockPos pos = center.offset(x, y, z);
if (level.getBlockState(pos).is(targetBlock)) {
double distance = pos.distSqr(center);
if (distance < nearestDistance) {
nearest = pos;
nearestDistance = distance;
}
}
}
}
}
return nearest;
}
// Check if area is safe for player
public static boolean isSafeArea(Level level, BlockPos center, int radius) {
for (int x = -radius; x <= radius; x++) {
for (int y = -radius; y <= radius; y++) {
for (int z = -radius; z <= radius; z++) {
BlockPos pos = center.offset(x, y, z);
BlockState state = level.getBlockState(pos);
// Check for dangerous blocks
if (state.is(Blocks.LAVA) || state.is(Blocks.FIRE) ||
state.getBlock() instanceof AbstractFireBlock) {
return false;
}
}
}
}
return true;
}
// Generate world statistics
public static Component generateWorldStats(Level level, BlockPos center, int radius) {
Map<Block, Integer> counts = countBlocks(level, center, radius);
// Sort by count
List<Map.Entry<Block, Integer>> sorted = counts.entrySet().stream()
.sorted(Map.Entry.<Block, Integer>comparingByValue().reversed())
.limit(10)
.toList();
MutableComponent stats = Component.literal("World Statistics (" + radius + " block radius):\n");
for (Map.Entry<Block, Integer> entry : sorted) {
Component blockName = Component.translatable(entry.getKey().getDescriptionId());
stats.append(Component.literal("- " + blockName.getString() + ": " + entry.getValue() + "\n"));
}
return stats;
}
}Best Practices
1. Performance
Consider performance when affecting large areas:
public static void efficientWorldOperation(Level level, BlockPos center, int radius, WorldOperation operation) {
// Use chunk loading to avoid loading too many chunks
ChunkPos centerChunk = new ChunkPos(center);
int chunkRadius = (radius + 15) / 16; // Convert to chunk radius
for (int chunkX = -chunkRadius; chunkX <= chunkRadius; chunkX++) {
for (int chunkZ = -chunkRadius; chunkZ <= chunkRadius; chunkZ++) {
ChunkPos chunkPos = new ChunkPos(centerChunk.x + chunkX, centerChunk.z + chunkZ);
// Only operate if chunk is loaded
if (level.hasChunk(chunkPos.x, chunkPos.z)) {
operation.operateOnChunk(level, chunkPos);
}
}
}
}
public interface WorldOperation {
void operateOnChunk(Level level, ChunkPos chunkPos);
}2. Side Safety
Always check which side you're on:
public static void safeWorldOperation(Level level, Runnable operation) {
if (level.isClientSide()) {
// Client-side only
operation.run();
} else {
// Server-side
if (level instanceof ServerLevel) {
operation.run();
}
}
}3. Validation
Validate world operations:
public static boolean safeBlockPlacement(Level level, BlockPos pos, BlockState state) {
// Check if position is loaded
if (!level.hasChunkAt(pos)) {
return false;
}
// Check if position is valid
if (pos.getY() < level.getMinBuildHeight() || pos.getY() > level.getMaxBuildHeight()) {
return false;
}
// Place block
level.setBlock(pos, state, 3);
return true;
}4. Randomness
Use consistent random seeds for predictable results:
public static void predictableWorldGeneration(Level level, BlockPos pos, long seed) {
Random random = new Random(seed);
// Use random for consistent generation
for (int i = 0; i < 10; i++) {
BlockPos genPos = pos.offset(
random.nextInt(11) - 5,
random.nextInt(11) - 5,
random.nextInt(11) - 5
);
// Generate something at genPos
}
}These level utilities provide helpful shortcuts for common world operations while maintaining cross-platform compatibility.