mirror of
https://github.com/BentoBoxWorld/Boxed.git
synced 2024-11-30 12:54:33 +01:00
Merge pull request #88 from BentoBoxWorld/on_demand_structures
On demand structures
This commit is contained in:
commit
b72bb5b45e
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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");
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user