Now with a block populator.

This helps copy over the blocks from the seed world that need extra
settings and also populates entities.
This commit is contained in:
tastybento 2022-12-11 10:31:43 -08:00
parent 739b1d5eab
commit d730ba725a
3 changed files with 347 additions and 24 deletions

View File

@ -1,8 +1,10 @@
package world.bentobox.boxed;
import java.util.Arrays;
import java.util.Collections;
import org.bukkit.Bukkit;
import org.bukkit.Chunk;
import org.bukkit.ChunkSnapshot;
import org.bukkit.Difficulty;
import org.bukkit.Material;
@ -11,6 +13,7 @@ import org.bukkit.World.Environment;
import org.bukkit.WorldCreator;
import org.bukkit.entity.SpawnCategory;
import org.bukkit.generator.BiomeProvider;
import org.bukkit.generator.BlockPopulator;
import org.bukkit.generator.ChunkGenerator;
import org.eclipse.jdt.annotation.Nullable;
@ -22,8 +25,10 @@ import world.bentobox.bentobox.api.configuration.WorldSettings;
import world.bentobox.bentobox.api.flags.Flag;
import world.bentobox.bentobox.api.flags.Flag.Mode;
import world.bentobox.bentobox.api.flags.Flag.Type;
import world.bentobox.bentobox.blueprints.BlueprintClipboard;
import world.bentobox.bentobox.managers.RanksManager;
import world.bentobox.boxed.generators.BoxedBiomeGenerator;
import world.bentobox.boxed.generators.BoxedBlockPopulator;
import world.bentobox.boxed.generators.BoxedChunkGenerator;
import world.bentobox.boxed.generators.BoxedSeedChunkGenerator;
import world.bentobox.boxed.generators.SeedBiomeGenerator;
@ -61,6 +66,7 @@ public class Boxed extends GameModeAddon {
private World seedWorldNether;
private World seedWorldEnd;
private BiomeProvider boxedBiomeProvider;
private BlockPopulator boxedBlockPopulator;
@Override
public void onLoad() {
@ -116,7 +122,7 @@ public class Boxed extends GameModeAddon {
// Register listeners
this.registerListener(new AdvancementListener(this));
this.registerListener(new EnderPearlListener(this));
this.registerListener(new NewAreaListener(this));
//this.registerListener(new NewAreaListener(this));
// Register placeholders
PlaceholdersManager phManager = new PlaceholdersManager(this);
@ -155,9 +161,9 @@ public class Boxed extends GameModeAddon {
.environment(Environment.NORMAL)
.seed(getSettings().getSeed())
.createWorld();
seedWorld.setDifficulty(Difficulty.PEACEFUL); // No damage wanted in this world.
saveChunks(seedWorld);
seedWorld.setDifficulty(Difficulty.EASY);
copyChunks(seedWorld);
seedWorld.setSpawnLocation(settings.getSeedX(), 64, settings.getSeedZ());
// Unload seed world
@ -180,8 +186,8 @@ public class Boxed extends GameModeAddon {
.environment(Environment.NETHER)
.seed(getSettings().getSeed())
.createWorld();
seedWorldNether.setDifficulty(Difficulty.PEACEFUL); // No damage wanted in this world.
saveChunks(seedWorldNether);
seedWorldNether.setDifficulty(Difficulty.EASY); // No damage wanted in this world.
copyChunks(seedWorldNether);
if (getServer().getWorld(worldName + NETHER) == null) {
log("Creating Boxed's Nether...");
@ -199,7 +205,11 @@ public class Boxed extends GameModeAddon {
*/
}
private void saveChunks(World seedWorld) {
/**
* Copies chunks from the seed world so they can be pasted in the game world
* @param seedWorld - souce world
*/
private void copyChunks(World seedWorld) {
BoxedChunkGenerator gen;
int startX = 0;
int startZ = 0;
@ -220,8 +230,7 @@ public class Boxed extends GameModeAddon {
int last = 0;
for (int x = -size; x <= size; x ++) {
for (int z = -size; z <= size; z++) {
ChunkSnapshot chunk = seedWorld.getChunkAt(startX + x, startZ + z).getChunkSnapshot(true, true, false);
gen.setChunk(x, z, chunk);
gen.setChunk(x, z, seedWorld.getChunkAt(startX + x, startZ + z));
count++;
int p = (int) (count / percent * 100);
if (p % 10 == 0 && p != last) {
@ -256,6 +265,7 @@ public class Boxed extends GameModeAddon {
worldName2 = env.equals(World.Environment.NETHER) ? worldName2 + NETHER : worldName2;
worldName2 = env.equals(World.Environment.THE_END) ? worldName2 + THE_END : worldName2;
boxedBiomeProvider = new BoxedBiomeGenerator(this);
boxedBlockPopulator = new BoxedBlockPopulator(this);
World w = WorldCreator
.name(worldName2)
.generator(env.equals(World.Environment.NETHER) ? netherChunkGenerator : chunkGenerator)
@ -331,4 +341,11 @@ public class Boxed extends GameModeAddon {
return advManager;
}
/**
* @return the boxedBlockPopulator
*/
public BlockPopulator getBoxedBlockPopulator() {
return boxedBlockPopulator;
}
}

View File

@ -0,0 +1,149 @@
package world.bentobox.boxed.generators;
import java.util.Map;
import java.util.Objects;
import java.util.Random;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.Banner;
import org.bukkit.block.Block;
import org.bukkit.block.BlockState;
import org.bukkit.block.CreatureSpawner;
import org.bukkit.entity.Entity;
import org.bukkit.generator.BlockPopulator;
import org.bukkit.generator.LimitedRegion;
import org.bukkit.generator.WorldInfo;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryHolder;
import org.bukkit.util.Vector;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.blueprints.dataobjects.BlueprintBlock;
import world.bentobox.bentobox.blueprints.dataobjects.BlueprintCreatureSpawner;
import world.bentobox.bentobox.util.Pair;
import world.bentobox.boxed.Boxed;
import world.bentobox.boxed.generators.BoxedChunkGenerator.ChestData;
import world.bentobox.boxed.generators.BoxedChunkGenerator.ChunkStore;
/**
* @author tastybento
*
*/
public class BoxedBlockPopulator extends BlockPopulator {
private Boxed addon;
private int size;
/**
* @param addon
*/
public BoxedBlockPopulator(Boxed addon) {
this.addon = addon;
this.size = (int)(addon.getSettings().getIslandDistance() / 16D); // Size is chunks
}
@Override
public void populate(WorldInfo worldInfo, Random random, int chunkX, int chunkZ, LimitedRegion limitedRegion) {
Map<Pair<Integer, Integer>, ChunkStore> chunks = addon.getChunkGenerator(worldInfo.getEnvironment()).getChunks();
// TODO: Make this work for the Nether!
//// BentoBox.getInstance().logDebug("Populate " + chunkX + " " + chunkZ);
World world = Bukkit.getWorld(worldInfo.getUID());
int height = worldInfo.getMaxHeight();
int minY = worldInfo.getMinHeight();
int xx = BoxedChunkGenerator.repeatCalc(chunkX, size);
int zz = BoxedChunkGenerator.repeatCalc(chunkZ, size);
Pair<Integer, Integer> coords = new Pair<>(xx, zz);
if (chunks.containsKey(coords)) {
//// BentoBox.getInstance().logDebug("Populating ");
ChunkStore data = chunks.get(coords);
// Paste entities
data.bpEnts().forEach(e -> {
Location l = getLoc(world, e.relativeLoc().clone(), chunkX, chunkZ);
if (limitedRegion.isInRegion(l)) {
Entity ent = limitedRegion.spawnEntity(l, e.entity().getType());
e.entity().configureEntity(ent);
}
});
//// BentoBox.getInstance().logDebug("Tile Entities ");
// Fill chests
limitedRegion.getTileEntities().forEach(te -> {
// BentoBox.getInstance().logDebug("Tile entity = " + te.getType() + " at " + te.getLocation());
for (ChestData cd : data.chests()) {
// TODO: HANG HERE
Location chestLoc = getLoc(world, cd.relativeLoc().clone(), chunkX, chunkZ);
if (limitedRegion.isInRegion(chestLoc) && te.getLocation().equals(chestLoc)) {
// BentoBox.getInstance().logDebug("Expected location " + chestLoc);
this.setBlockState(te, cd.chest());
}
}
});
//// BentoBox.getInstance().logDebug("Done");
}
}
private Location getLoc(World w, Vector v, int chunkX, int chunkZ) {
v.add(new Vector(chunkX << 4, 0, chunkZ << 4));
return v.toLocation(w);
}
/**
* Handles signs, chests and mob spawner blocks
*
* @param block - block
* @param bpBlock - config
*/
public void setBlockState(BlockState bs, BlueprintBlock bpBlock) {
// BentoBox.getInstance().logDebug(bpBlock.getBlockData());
// Chests, in general
if (bs instanceof InventoryHolder holder) {
// BentoBox.getInstance().logDebug("Type: " + bs.getType());
Inventory ih = holder.getInventory();
// BentoBox.getInstance().logDebug("holder size = " + ih.getSize());
// BentoBox.getInstance().logDebug("stored inventory size = " + bpBlock.getInventory().size());
// This approach is required to avoid an array out of bounds error that shouldn't occur IMO
for (int i = 0; i < ih.getSize(); i++) {
ih.setItem(i, bpBlock.getInventory().get(i));
}
//bpBlock.getInventory().forEach(ih::setItem);
}
// Mob spawners
else if (bs instanceof CreatureSpawner spawner) {
// BentoBox.getInstance().logDebug("Spawner");
setSpawner(spawner, bpBlock.getCreatureSpawner());
}
// Banners
else if (bs instanceof Banner banner && bpBlock.getBannerPatterns() != null) {
// BentoBox.getInstance().logDebug("Banner");
bpBlock.getBannerPatterns().removeIf(Objects::isNull);
banner.setPatterns(bpBlock.getBannerPatterns());
banner.update(true, false);
}
// BentoBox.getInstance().logDebug("Block state complete");
}
/**
* Set the spawner setting from the blueprint
*
* @param spawner - spawner
* @param s - blueprint spawner
*/
public void setSpawner(CreatureSpawner spawner, BlueprintCreatureSpawner s) {
// BentoBox.getInstance().logDebug("Setting spawner");
spawner.setSpawnedType(s.getSpawnedType());
spawner.setMaxNearbyEntities(s.getMaxNearbyEntities());
spawner.setMaxSpawnDelay(s.getMaxSpawnDelay());
spawner.setMinSpawnDelay(s.getMinSpawnDelay());
spawner.setDelay(s.getDelay());
spawner.setRequiredPlayerRange(s.getRequiredPlayerRange());
spawner.setSpawnRange(s.getSpawnRange());
// BentoBox.getInstance().logDebug("Now updating...");
spawner.update(true, false);
// BentoBox.getInstance().logDebug("Spawner updated");
}
}

View File

@ -1,20 +1,51 @@
package world.bentobox.boxed.generators;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Random;
import org.bukkit.Chunk;
import org.bukkit.ChunkSnapshot;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.Banner;
import org.bukkit.block.Block;
import org.bukkit.block.BlockState;
import org.bukkit.block.CreatureSpawner;
import org.bukkit.block.Sign;
import org.bukkit.entity.AbstractHorse;
import org.bukkit.entity.Ageable;
import org.bukkit.entity.ChestedHorse;
import org.bukkit.entity.Horse;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.entity.Tameable;
import org.bukkit.entity.Villager;
import org.bukkit.generator.BiomeProvider;
import org.bukkit.generator.BlockPopulator;
import org.bukkit.generator.ChunkGenerator;
import org.bukkit.generator.WorldInfo;
import org.bukkit.inventory.InventoryHolder;
import org.bukkit.inventory.ItemStack;
import org.bukkit.material.Attachable;
import org.bukkit.material.Colorable;
import org.bukkit.util.Vector;
import world.bentobox.bentobox.blueprints.dataobjects.BlueprintBlock;
import world.bentobox.bentobox.blueprints.dataobjects.BlueprintCreatureSpawner;
import world.bentobox.bentobox.blueprints.dataobjects.BlueprintEntity;
import world.bentobox.bentobox.util.Pair;
import world.bentobox.boxed.Boxed;
/**
* Chunk generator for all environments
* @author tastybento
*
*/
@ -22,7 +53,10 @@ public class BoxedChunkGenerator extends ChunkGenerator {
private final Boxed addon;
private final int size;
private Map<Pair<Integer, Integer>, ChunkSnapshot> chunks = new HashMap<>();
private Map<Pair<Integer, Integer>, ChunkStore> chunks = new HashMap<>();
public record ChunkStore(ChunkSnapshot snapshot, List<EntityData> bpEnts, List<ChestData> chests) {};
public record EntityData(Vector relativeLoc, BlueprintEntity entity) {};
public record ChestData(Vector relativeLoc, BlueprintBlock chest) {};
//private final WorldRef wordRefNether;
@ -36,13 +70,30 @@ public class BoxedChunkGenerator extends ChunkGenerator {
return addon.getBoxedBiomeProvider();
}
@Override
public List<BlockPopulator> getDefaultPopulators(World world) {
world.getPopulators().add(addon.getBoxedBlockPopulator());
return world.getPopulators();
}
/**
* Save a chunk
* @param z - chunk z coord
* @param x - chunk x coord
* @param chunk the chunk to set
*/
public void setChunk(int x, int z, ChunkSnapshot chunk) {
chunks.put(new Pair<>(x, z), chunk);
public void setChunk(int x, int z, Chunk chunk) {
List<LivingEntity> ents = Arrays.stream(chunk.getEntities())
.filter(Objects::nonNull)
.filter(e -> !(e instanceof Player))
.filter(e -> e instanceof LivingEntity)
.map(LivingEntity.class::cast)
.toList();
// Grab entities
List<EntityData> bpEnts = this.setEntities(ents);
// Grab tile entities
List<ChestData> chests = Arrays.stream(chunk.getTileEntities()).map(t -> new ChestData(getLocInChunk(t.getLocation()), this.getBluePrintBlock(t.getBlock()))).toList();
chunks.put(new Pair<>(x, z), new ChunkStore(chunk.getChunkSnapshot(false, true, false), bpEnts, chests));
}
/**
@ -51,14 +102,7 @@ public class BoxedChunkGenerator extends ChunkGenerator {
* @return chunk snapshot or null if there is none
*/
public ChunkSnapshot getChunk(int x, int z) {
return chunks.get(new Pair<>(x, z));
}
/**
* @param chunks the chunks to set
*/
public void setChunks(Map<Pair<Integer, Integer>, ChunkSnapshot> chunks) {
this.chunks = chunks;
return chunks.get(new Pair<>(x, z)).snapshot;
}
@Override
@ -81,7 +125,7 @@ public class BoxedChunkGenerator extends ChunkGenerator {
return;
}
// Copy the chunk
ChunkSnapshot chunk = chunks.get(coords);
ChunkSnapshot chunk = chunks.get(coords).snapshot;
copyChunkVerbatim(cd, chunk, minY, height);
}
@ -96,6 +140,7 @@ public class BoxedChunkGenerator extends ChunkGenerator {
}
}
/*
private void copyChunk(ChunkData cd, ChunkSnapshot chunk, int minY, int height) {
for (int x = 0; x < 16; x ++) {
for (int z = 0; z < 16; z++) {
@ -117,7 +162,7 @@ public class BoxedChunkGenerator extends ChunkGenerator {
}
}
}
*/
/**
* Calculates the repeating value for a given size
* @param chunkCoord chunk coord
@ -137,10 +182,10 @@ public class BoxedChunkGenerator extends ChunkGenerator {
/**
* @return the chunks
*/
public Map<Pair<Integer, Integer>, ChunkSnapshot> getChunks() {
public Map<Pair<Integer, Integer>, ChunkStore> getChunks() {
return chunks;
}
/*
private static boolean isInWater(Material m) {
return switch (m) {
// Underwater plants
@ -193,6 +238,118 @@ public class BoxedChunkGenerator extends ChunkGenerator {
default -> false;
};
}
*/
private List<EntityData> setEntities(Collection<LivingEntity> entities) {
List<EntityData> bpEnts = new ArrayList<>();
for (LivingEntity entity: entities) {
BlueprintEntity bpe = new BlueprintEntity();
bpe.setType(entity.getType());
bpe.setCustomName(entity.getCustomName());
if (entity instanceof Villager villager) {
setVillager(villager, bpe);
}
if (entity instanceof Colorable c) {
if (c.getColor() != null) {
bpe.setColor(c.getColor());
}
}
if (entity instanceof Tameable) {
bpe.setTamed(((Tameable)entity).isTamed());
}
if (entity instanceof ChestedHorse) {
bpe.setChest(((ChestedHorse)entity).isCarryingChest());
}
// Only set if child. Most animals are adults
if (entity instanceof Ageable && !((Ageable)entity).isAdult()) {
bpe.setAdult(false);
}
if (entity instanceof AbstractHorse horse) {
bpe.setDomestication(horse.getDomestication());
bpe.setInventory(new HashMap<>());
for (int i = 0; i < horse.getInventory().getSize(); i++) {
ItemStack item = horse.getInventory().getItem(i);
if (item != null) {
bpe.getInventory().put(i, item);
}
}
}
if (entity instanceof Horse horse) {
bpe.setStyle(horse.getStyle());
}
bpEnts.add(new EntityData(getLocInChunk(entity.getLocation()), bpe));
}
return bpEnts;
}
private Vector getLocInChunk(Location l) {
return new Vector(l.getBlockX() % 16, l.getBlockY(), l.getBlockZ() % 16);
}
/**
* Set the villager stats
* @param v - villager
* @param bpe - Blueprint Entity
*/
private void setVillager(Villager v, BlueprintEntity bpe) {
bpe.setExperience(v.getVillagerExperience());
bpe.setLevel(v.getVillagerLevel());
bpe.setProfession(v.getProfession());
bpe.setVillagerType(v.getVillagerType());
}
/**
* Converts the block into a BluePrintBlock that can be pasted later
* @param block - block to convert
* @return Blueprint block
*/
private BlueprintBlock getBluePrintBlock(Block block) {
// Block state
BlockState blockState = block.getState();
BlueprintBlock b = new BlueprintBlock(block.getBlockData().getAsString());
// Signs
if (blockState instanceof Sign sign) {
b.setSignLines(Arrays.asList(sign.getLines()));
b.setGlowingText(sign.isGlowingText());
}
// Chests
if (blockState instanceof InventoryHolder ih) {
b.setInventory(new HashMap<>());
for (int i = 0; i < ih.getInventory().getSize(); i++) {
ItemStack item = ih.getInventory().getItem(i);
if (item != null) {
b.getInventory().put(i, item);
}
}
}
// Spawner type
if (blockState instanceof CreatureSpawner spawner) {
b.setCreatureSpawner(getSpawner(spawner));
}
// Banners
if (blockState instanceof Banner banner) {
b.setBannerPatterns(banner.getPatterns());
}
return b;
}
private BlueprintCreatureSpawner getSpawner(CreatureSpawner spawner) {
BlueprintCreatureSpawner cs = new BlueprintCreatureSpawner();
cs.setSpawnedType(spawner.getSpawnedType());
cs.setDelay(spawner.getDelay());
cs.setMaxNearbyEntities(spawner.getMaxNearbyEntities());
cs.setMaxSpawnDelay(spawner.getMaxSpawnDelay());
cs.setMinSpawnDelay(spawner.getMinSpawnDelay());
cs.setRequiredPlayerRange(spawner.getRequiredPlayerRange());
cs.setSpawnRange(spawner.getSpawnRange());
return cs;
}
@Override
public boolean shouldGenerateNoise() {