Merge pull request #88 from BentoBoxWorld/on_demand_structures

On demand structures
This commit is contained in:
tastybento 2024-07-27 17:05:11 -07:00 committed by GitHub
commit b72bb5b45e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 188 additions and 63 deletions

View File

@ -7,9 +7,14 @@ import world.bentobox.bentobox.api.addons.Pladdon;
public class BoxedPladdon extends Pladdon {
private Boxed addon;
@Override
public Addon getAddon() {
return new Boxed();
if (addon == null) {
addon = new Boxed();
}
return addon;
}
}

View File

@ -26,7 +26,7 @@ import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.util.Util;
import world.bentobox.boxed.Boxed;
import world.bentobox.boxed.listeners.NewAreaListener;
import world.bentobox.boxed.listeners.NewAreaListener.StructureRecord;
import world.bentobox.boxed.objects.ToBePlacedStructures.StructureRecord;
/**
* Enables admins to place templates in a Box and have them recorded for future boxes.
@ -149,7 +149,7 @@ public class AdminPlaceStructureCommand extends CompositeCommand {
int z = args.size() == 1 || args.get(3).equals("~") ? user.getLocation().getBlockZ() : Integer.parseInt(args.get(3).trim());
Location spot = new Location(user.getWorld(), x, y, z);
s.place(spot, true, sr, mirror, PALETTE, INTEGRITY, new Random());
NewAreaListener.removeJigsaw(new StructureRecord(tag.getKey(), s, spot, sr, mirror, noMobs));
NewAreaListener.removeJigsaw(new StructureRecord(tag.getKey(), tag.getKey(), spot, sr, mirror, noMobs));
boolean result = saveStructure(spot, tag, user, sr, mirror);
if (result) {
user.sendMessage("boxed.commands.boxadmin.place.saved");

View File

@ -58,28 +58,14 @@ import world.bentobox.boxed.nms.AbstractMetaData;
import world.bentobox.boxed.objects.BoxedJigsawBlock;
import world.bentobox.boxed.objects.BoxedStructureBlock;
import world.bentobox.boxed.objects.IslandStructures;
import world.bentobox.boxed.objects.ToBePlacedStructures;
import world.bentobox.boxed.objects.ToBePlacedStructures.StructureRecord;
/**
* @author tastybento Place structures in areas after they are created
*/
public class NewAreaListener implements Listener {
/**
* Structure record contains the name of the structure, the structure itself,
* where it was placed and enums for rotation, mirror, and a flag to paste mobs
* or not.
*
* @param name - name of structure
* @param structure - Structure object
* @param location - location where it has been placed
* @param rot - rotation
* @param mirror - mirror setting
* @param noMobs - if false, mobs not pasted
*/
public record StructureRecord(String name, Structure structure, Location location, StructureRotation rot,
Mirror mirror, Boolean noMobs) {
}
private static final Map<Integer, EntityType> BUTCHER_ANIMALS = Map.of(0, EntityType.COW, 1, EntityType.SHEEP, 2,
EntityType.PIG);
private static final List<BlockFace> CARDINALS = List.of(BlockFace.NORTH, BlockFace.SOUTH, BlockFace.EAST,
@ -95,16 +81,30 @@ public class NewAreaListener implements Listener {
"village_snowy", "village_taiga");
private final Boxed addon;
private final File structureFile;
/**
* Queue for structures that have been determined to be built now
*/
private final Queue<StructureRecord> itemsToBuild = new LinkedList<>();
/**
* Store for structures that are pending being built, e.g., waiting until the chunk they are is in loaded
*/
private final Map<Pair<Integer, Integer>, List<StructureRecord>> pending;
/**
* A cache of all structures that have been placed. Used to determine if players have entered them
*/
private final Map<String, IslandStructures> islandStructureCache = new HashMap<>();
private static final Random rand = new Random();
private boolean pasting = true;
private static final Gson gson = new Gson();
Pair<Integer, Integer> min = new Pair<>(0, 0);
Pair<Integer, Integer> max = new Pair<>(0, 0);
private static final String TODO = "ToDo";
private static final String COULD_NOT_LOAD = "Could not load ";
// Database handler for structure data
private final Database<IslandStructures> handler;
private final Map<String, IslandStructures> islandStructureCache = new HashMap<>();
private Map<Pair<Integer, Integer>, List<StructureRecord>> readyToBuild = new HashMap<>();
private final Database<ToBePlacedStructures> toPlace;
private static String bukkitVersion = "v" + Bukkit.getBukkitVersion().replace('.', '_').replace('-', '_');
private static String pluginPackageName;
@ -120,12 +120,20 @@ public class NewAreaListener implements Listener {
structureFile = new File(addon.getDataFolder(), "structures.yml");
// Get database ready
handler = new Database<>(addon, IslandStructures.class);
// Try to build something every second
// Load the pending structures
toPlace = new Database<>(addon, ToBePlacedStructures.class);
pending = this.loadToDos().getReadyToBuild();
// Try to build something
runStructurePrinter();
}
/**
* Runs a recurring task to build structures in the queue and register Jar structures.
*/
private void runStructurePrinter() {
// Set up recurring task
Bukkit.getScheduler().runTaskTimer(addon.getPlugin(), this::buildStructure, 100, 60);
// Run through all the structures in the Jar and register them with the server
for (String js : JAR_STRUCTURES) {
addon.saveResource("structures/" + js + ".nbt", false);
File structureFile = new File(addon.getDataFolder(), "structures/" + js + ".nbt");
@ -142,7 +150,7 @@ public class NewAreaListener implements Listener {
}
/**
* Build something in the queue
* Build something in the queue. Structures are built one by one
*/
private void buildStructure() {
// Only kick off a build if there is something to build and something isn't
@ -158,7 +166,12 @@ public class NewAreaListener implements Listener {
// Set the semaphore - only paste one at a time
pasting = true;
// Place the structure - this cannot be done async
item.structure().place(item.location(), true, item.rot(), item.mirror(), -1, 1, rand);
Structure structure = Bukkit.getStructureManager().loadStructure(NamespacedKey.fromString(item.structure()));
if (structure == null) {
BentoBox.getInstance().logError(COULD_NOT_LOAD + item.structure());
return;
}
structure.place(item.location(), true, item.rot(), item.mirror(), -1, 1, rand);
addon.log(item.name() + " placed at " + item.location().getWorld().getName() + " "
+ Util.xyz(item.location().toVector()));
// Remove any jigsaw artifacts
@ -214,8 +227,8 @@ public class NewAreaListener implements Listener {
return;
}
Pair<Integer, Integer> chunkCoords = new Pair<Integer, Integer>(chunk.getX(), chunk.getZ());
if (this.readyToBuild.containsKey(chunkCoords)) {
Iterator<StructureRecord> it = this.readyToBuild.get(chunkCoords).iterator();
if (pending.containsKey(chunkCoords)) {
Iterator<StructureRecord> it = pending.get(chunkCoords).iterator();
while (it.hasNext()) {
StructureRecord item = it.next();
if (item.location().getWorld().equals(e.getWorld())) {
@ -223,6 +236,10 @@ public class NewAreaListener implements Listener {
it.remove();
}
}
// Save to latest to the database
ToBePlacedStructures tbd = new ToBePlacedStructures();
tbd.setReadyToBuild(pending);
toPlace.saveObjectAsync(tbd);
}
}
@ -336,46 +353,53 @@ public class NewAreaListener implements Listener {
if (world == null) {
return;
}
// Loop through the structures in the file - there could be more than one
Map<Pair<Integer, Integer>, List<StructureRecord>> readyToBuild = new HashMap<>();
for (String vector : section.getKeys(false)) {
StructureRotation rot = StructureRotation.NONE;
Mirror mirror = Mirror.NONE;
boolean noMobs = false;
String name = section.getString(vector);
// Check for rotation
String[] split = name.split(",");
if (split.length > 1) {
// Rotation
rot = Enums.getIfPresent(StructureRotation.class, split[1].strip().toUpperCase(Locale.ENGLISH))
.or(StructureRotation.NONE);
name = split[0];
}
if (split.length == 3) {
// Mirror
mirror = Enums.getIfPresent(Mirror.class, split[2].strip().toUpperCase(Locale.ENGLISH)).or(Mirror.NONE);
}
if (split.length == 4) {
noMobs = split[3].strip().toUpperCase(Locale.ENGLISH).equals("NO_MOBS");
}
// Load Structure
Structure s = Bukkit.getStructureManager().loadStructure(NamespacedKey.fromString("minecraft:" + name));
if (s == null) {
BentoBox.getInstance().logError("Could not load " + name);
String[] nameParts = section.getString(vector).split(",");
String name = nameParts[0].strip();
StructureRotation rotation = nameParts.length > 1
? Enums.getIfPresent(StructureRotation.class, nameParts[1].strip().toUpperCase(Locale.ENGLISH)).or(
StructureRotation.NONE)
: StructureRotation.NONE;
Mirror mirror = nameParts.length > 2
? Enums.getIfPresent(Mirror.class, nameParts[2].strip().toUpperCase(Locale.ENGLISH)).or(Mirror.NONE)
: Mirror.NONE;
boolean noMobs = nameParts.length > 3 && "NO_MOBS".equalsIgnoreCase(nameParts[3].strip());
// Check the structure exists
Structure structure = Bukkit.getStructureManager()
.loadStructure(NamespacedKey.fromString("minecraft:" + name));
if (structure == null) {
BentoBox.getInstance().logError(COULD_NOT_LOAD + name);
return;
}
// Extract coords
String[] value = vector.split(",");
if (value.length > 2) {
int x = Integer.parseInt(value[0].strip()) + center.getBlockX();
int y = Integer.parseInt(value[1].strip());
int z = Integer.parseInt(value[2].strip()) + center.getBlockZ();
Location l = new Location(world, x, y, z);
readyToBuild.computeIfAbsent(new Pair<Integer, Integer>(x >> 4, z >> 4), k -> new ArrayList<>())
.add(new StructureRecord(name, s, l, rot, mirror, noMobs));
String[] coords = vector.split(",");
if (coords.length > 2) {
int x = Integer.parseInt(coords[0].strip()) + center.getBlockX();
int y = Integer.parseInt(coords[1].strip());
int z = Integer.parseInt(coords[2].strip()) + center.getBlockZ();
Location location = new Location(world, x, y, z);
readyToBuild.computeIfAbsent(new Pair<>(x >> 4, z >> 4), k -> new ArrayList<>())
.add(new StructureRecord(name, "minecraft:" + name, location,
rotation, mirror, noMobs));
} else {
addon.logError("Structure file syntax error: " + vector + ": " + Arrays.toString(value));
addon.logError("Structure file syntax error: " + vector + ": " + Arrays.toString(coords));
}
}
ToBePlacedStructures tbd = this.loadToDos();
Map<Pair<Integer, Integer>, List<StructureRecord>> mergedMap = tbd.getReadyToBuild();
readyToBuild.forEach((key, value) -> mergedMap.merge(key, value, (list1, list2) -> {
list1.addAll(list2);
return list1;
}));
tbd.setReadyToBuild(readyToBuild);
toPlace.saveObjectAsync(tbd);
}
/**
@ -387,7 +411,11 @@ public class NewAreaListener implements Listener {
*/
public static BoundingBox removeJigsaw(StructureRecord item) {
Location loc = item.location();
Structure structure = item.structure();
Structure structure = Bukkit.getStructureManager().loadStructure(NamespacedKey.fromString(item.structure()));
if (structure == null) {
BentoBox.getInstance().logError(COULD_NOT_LOAD + item.structure());
return new BoundingBox();
}
StructureRotation structureRotation = item.rot();
String key = item.name();
@ -600,4 +628,18 @@ public class NewAreaListener implements Listener {
return handler.nmsData(block);
}
private ToBePlacedStructures loadToDos() {
if (!toPlace.objectExists(TODO)) {
return new ToBePlacedStructures();
}
ToBePlacedStructures list = toPlace.loadObject(TODO);
if (list == null) {
return new ToBePlacedStructures();
}
if (!list.getReadyToBuild().isEmpty()) {
addon.log("Loaded " + list.getReadyToBuild().size() + " structure todos.");
}
return list;
}
}

View File

@ -0,0 +1,78 @@
package world.bentobox.boxed.objects;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.bukkit.Location;
import org.bukkit.block.structure.Mirror;
import org.bukkit.block.structure.StructureRotation;
import com.google.gson.annotations.Expose;
import world.bentobox.bentobox.database.objects.DataObject;
import world.bentobox.bentobox.database.objects.Table;
import world.bentobox.bentobox.util.Pair;
/**
* Stores all the structures to be placed in the world. This is a queue that is done over
* time to avoid lag and if the server is stopped then the pending list is saved here
* @author tastybento
*
*/
@Table(name = "ToBePlacedStructures")
public class ToBePlacedStructures implements DataObject {
/**
* Structure record contains the name of the structure, the structure itself,
* where it was placed and enums for rotation, mirror, and a flag to paste mobs
* or not.
*
* @param name - name of structure
* @param structure - Structure namespaced key
* @param location - location where it has been placed
* @param rot - rotation
* @param mirror - mirror setting
* @param noMobs - if false, mobs not pasted
*/
public record StructureRecord(@Expose String name, @Expose String structure, @Expose Location location,
@Expose StructureRotation rot, @Expose Mirror mirror, @Expose Boolean noMobs) {
}
@Expose
String uniqueId = "ToDo";
@Expose
private Map<Pair<Integer, Integer>, List<StructureRecord>> readyToBuild = new HashMap<>();
/**
* @return the uniqueId
*/
public String getUniqueId() {
return uniqueId;
}
/**
* @param uniqueId the uniqueId to set
*/
public void setUniqueId(String uniqueId) {
this.uniqueId = uniqueId;
}
/**
* @return the readyToBuild
*/
public Map<Pair<Integer, Integer>, List<StructureRecord>> getReadyToBuild() {
if (readyToBuild == null) {
readyToBuild = new HashMap<>();
}
return readyToBuild;
}
/**
* @param readyToBuild the readyToBuild to set
*/
public void setReadyToBuild(Map<Pair<Integer, Integer>, List<StructureRecord>> readyToBuild) {
this.readyToBuild = readyToBuild;
}
}