5858import world .bentobox .boxed .objects .BoxedJigsawBlock ;
5959import world .bentobox .boxed .objects .BoxedStructureBlock ;
6060import world .bentobox .boxed .objects .IslandStructures ;
61+ import world .bentobox .boxed .objects .ToBePlacedStructures ;
62+ import world .bentobox .boxed .objects .ToBePlacedStructures .StructureRecord ;
6163
6264/**
6365 * @author tastybento Place structures in areas after they are created
6466 */
6567public class NewAreaListener implements Listener {
6668
67- /**
68- * Structure record contains the name of the structure, the structure itself,
69- * where it was placed and enums for rotation, mirror, and a flag to paste mobs
70- * or not.
71- *
72- * @param name - name of structure
73- * @param structure - Structure object
74- * @param location - location where it has been placed
75- * @param rot - rotation
76- * @param mirror - mirror setting
77- * @param noMobs - if false, mobs not pasted
78- */
79- public record StructureRecord (String name , Structure structure , Location location , StructureRotation rot ,
80- Mirror mirror , Boolean noMobs ) {
81- }
82-
8369 private static final Map <Integer , EntityType > BUTCHER_ANIMALS = Map .of (0 , EntityType .COW , 1 , EntityType .SHEEP , 2 ,
8470 EntityType .PIG );
8571 private static final List <BlockFace > CARDINALS = List .of (BlockFace .NORTH , BlockFace .SOUTH , BlockFace .EAST ,
@@ -95,16 +81,30 @@ public record StructureRecord(String name, Structure structure, Location locatio
9581 "village_snowy" , "village_taiga" );
9682 private final Boxed addon ;
9783 private final File structureFile ;
84+ /**
85+ * Queue for structures that have been determined to be built now
86+ */
9887 private final Queue <StructureRecord > itemsToBuild = new LinkedList <>();
88+
89+ /**
90+ * Store for structures that are pending being built, e.g., waiting until the chunk they are is in loaded
91+ */
92+ private final Map <Pair <Integer , Integer >, List <StructureRecord >> pending ;
93+
94+ /**
95+ * A cache of all structures that have been placed. Used to determine if players have entered them
96+ */
97+ private final Map <String , IslandStructures > islandStructureCache = new HashMap <>();
98+
9999 private static final Random rand = new Random ();
100100 private boolean pasting = true ;
101101 private static final Gson gson = new Gson ();
102- Pair < Integer , Integer > min = new Pair <>( 0 , 0 ) ;
103- Pair < Integer , Integer > max = new Pair <>( 0 , 0 ) ;
102+ private static final String TODO = "ToDo" ;
103+ private static final String COULD_NOT_LOAD = "Could not load " ;
104104 // Database handler for structure data
105105 private final Database <IslandStructures > handler ;
106- private final Map < String , IslandStructures > islandStructureCache = new HashMap <>() ;
107- private Map < Pair < Integer , Integer >, List < StructureRecord >> readyToBuild = new HashMap <>();
106+ private final Database < ToBePlacedStructures > toPlace ;
107+
108108 private static String bukkitVersion = "v" + Bukkit .getBukkitVersion ().replace ('.' , '_' ).replace ('-' , '_' );
109109 private static String pluginPackageName ;
110110
@@ -120,12 +120,20 @@ public NewAreaListener(Boxed addon) {
120120 structureFile = new File (addon .getDataFolder (), "structures.yml" );
121121 // Get database ready
122122 handler = new Database <>(addon , IslandStructures .class );
123- // Try to build something every second
123+ // Load the pending structures
124+ toPlace = new Database <>(addon , ToBePlacedStructures .class );
125+ pending = this .loadToDos ().getReadyToBuild ();
126+ // Try to build something
124127 runStructurePrinter ();
125128 }
126129
130+ /**
131+ * Runs a recurring task to build structures in the queue and register Jar structures.
132+ */
127133 private void runStructurePrinter () {
134+ // Set up recurring task
128135 Bukkit .getScheduler ().runTaskTimer (addon .getPlugin (), this ::buildStructure , 100 , 60 );
136+ // Run through all the structures in the Jar and register them with the server
129137 for (String js : JAR_STRUCTURES ) {
130138 addon .saveResource ("structures/" + js + ".nbt" , false );
131139 File structureFile = new File (addon .getDataFolder (), "structures/" + js + ".nbt" );
@@ -142,7 +150,7 @@ private void runStructurePrinter() {
142150 }
143151
144152 /**
145- * Build something in the queue
153+ * Build something in the queue. Structures are built one by one
146154 */
147155 private void buildStructure () {
148156 // Only kick off a build if there is something to build and something isn't
@@ -158,7 +166,12 @@ private void placeStructure(StructureRecord item) {
158166 // Set the semaphore - only paste one at a time
159167 pasting = true ;
160168 // Place the structure - this cannot be done async
161- item .structure ().place (item .location (), true , item .rot (), item .mirror (), -1 , 1 , rand );
169+ Structure structure = Bukkit .getStructureManager ().loadStructure (NamespacedKey .fromString (item .structure ()));
170+ if (structure == null ) {
171+ BentoBox .getInstance ().logError (COULD_NOT_LOAD + item .structure ());
172+ return ;
173+ }
174+ structure .place (item .location (), true , item .rot (), item .mirror (), -1 , 1 , rand );
162175 addon .log (item .name () + " placed at " + item .location ().getWorld ().getName () + " "
163176 + Util .xyz (item .location ().toVector ()));
164177 // Remove any jigsaw artifacts
@@ -214,15 +227,19 @@ public void onChunkLoad(ChunkLoadEvent e) {
214227 return ;
215228 }
216229 Pair <Integer , Integer > chunkCoords = new Pair <Integer , Integer >(chunk .getX (), chunk .getZ ());
217- if (this . readyToBuild .containsKey (chunkCoords )) {
218- Iterator <StructureRecord > it = this . readyToBuild .get (chunkCoords ).iterator ();
230+ if (pending .containsKey (chunkCoords )) {
231+ Iterator <StructureRecord > it = pending .get (chunkCoords ).iterator ();
219232 while (it .hasNext ()) {
220233 StructureRecord item = it .next ();
221234 if (item .location ().getWorld ().equals (e .getWorld ())) {
222235 this .itemsToBuild .add (item );
223236 it .remove ();
224237 }
225238 }
239+ // Save to latest to the database
240+ ToBePlacedStructures tbd = new ToBePlacedStructures ();
241+ tbd .setReadyToBuild (pending );
242+ toPlace .saveObjectAsync (tbd );
226243 }
227244 }
228245
@@ -336,46 +353,53 @@ private void place(ConfigurationSection section, Location center, Environment en
336353 if (world == null ) {
337354 return ;
338355 }
339- // Loop through the structures in the file - there could be more than one
356+
357+ Map <Pair <Integer , Integer >, List <StructureRecord >> readyToBuild = new HashMap <>();
358+
340359 for (String vector : section .getKeys (false )) {
341- StructureRotation rot = StructureRotation .NONE ;
342- Mirror mirror = Mirror .NONE ;
343- boolean noMobs = false ;
344- String name = section .getString (vector );
345- // Check for rotation
346- String [] split = name .split ("," );
347- if (split .length > 1 ) {
348- // Rotation
349- rot = Enums .getIfPresent (StructureRotation .class , split [1 ].strip ().toUpperCase (Locale .ENGLISH ))
350- .or (StructureRotation .NONE );
351- name = split [0 ];
352- }
353- if (split .length == 3 ) {
354- // Mirror
355- mirror = Enums .getIfPresent (Mirror .class , split [2 ].strip ().toUpperCase (Locale .ENGLISH )).or (Mirror .NONE );
356- }
357- if (split .length == 4 ) {
358- noMobs = split [3 ].strip ().toUpperCase (Locale .ENGLISH ).equals ("NO_MOBS" );
359- }
360- // Load Structure
361- Structure s = Bukkit .getStructureManager ().loadStructure (NamespacedKey .fromString ("minecraft:" + name ));
362- if (s == null ) {
363- BentoBox .getInstance ().logError ("Could not load " + name );
360+ String [] nameParts = section .getString (vector ).split ("," );
361+ String name = nameParts [0 ].strip ();
362+ StructureRotation rotation = nameParts .length > 1
363+ ? Enums .getIfPresent (StructureRotation .class , nameParts [1 ].strip ().toUpperCase (Locale .ENGLISH )).or (
364+ StructureRotation .NONE )
365+ : StructureRotation .NONE ;
366+ Mirror mirror = nameParts .length > 2
367+ ? Enums .getIfPresent (Mirror .class , nameParts [2 ].strip ().toUpperCase (Locale .ENGLISH )).or (Mirror .NONE )
368+ : Mirror .NONE ;
369+ boolean noMobs = nameParts .length > 3 && "NO_MOBS" .equalsIgnoreCase (nameParts [3 ].strip ());
370+
371+ // Check the structure exists
372+ Structure structure = Bukkit .getStructureManager ()
373+ .loadStructure (NamespacedKey .fromString ("minecraft:" + name ));
374+ if (structure == null ) {
375+ BentoBox .getInstance ().logError (COULD_NOT_LOAD + name );
364376 return ;
365377 }
366- // Extract coords
367- String [] value = vector .split ("," );
368- if (value .length > 2 ) {
369- int x = Integer .parseInt (value [0 ].strip ()) + center .getBlockX ();
370- int y = Integer .parseInt (value [1 ].strip ());
371- int z = Integer .parseInt (value [2 ].strip ()) + center .getBlockZ ();
372- Location l = new Location (world , x , y , z );
373- readyToBuild .computeIfAbsent (new Pair <Integer , Integer >(x >> 4 , z >> 4 ), k -> new ArrayList <>())
374- .add (new StructureRecord (name , s , l , rot , mirror , noMobs ));
378+
379+ String [] coords = vector .split ("," );
380+ if (coords .length > 2 ) {
381+ int x = Integer .parseInt (coords [0 ].strip ()) + center .getBlockX ();
382+ int y = Integer .parseInt (coords [1 ].strip ());
383+ int z = Integer .parseInt (coords [2 ].strip ()) + center .getBlockZ ();
384+ Location location = new Location (world , x , y , z );
385+
386+ readyToBuild .computeIfAbsent (new Pair <>(x >> 4 , z >> 4 ), k -> new ArrayList <>())
387+ .add (new StructureRecord (name , "minecraft:" + name , location ,
388+ rotation , mirror , noMobs ));
375389 } else {
376- addon .logError ("Structure file syntax error: " + vector + ": " + Arrays .toString (value ));
390+ addon .logError ("Structure file syntax error: " + vector + ": " + Arrays .toString (coords ));
377391 }
378392 }
393+
394+ ToBePlacedStructures tbd = this .loadToDos ();
395+ Map <Pair <Integer , Integer >, List <StructureRecord >> mergedMap = tbd .getReadyToBuild ();
396+ readyToBuild .forEach ((key , value ) -> mergedMap .merge (key , value , (list1 , list2 ) -> {
397+ list1 .addAll (list2 );
398+ return list1 ;
399+ }));
400+
401+ tbd .setReadyToBuild (readyToBuild );
402+ toPlace .saveObjectAsync (tbd );
379403 }
380404
381405 /**
@@ -387,7 +411,11 @@ private void place(ConfigurationSection section, Location center, Environment en
387411 */
388412 public static BoundingBox removeJigsaw (StructureRecord item ) {
389413 Location loc = item .location ();
390- Structure structure = item .structure ();
414+ Structure structure = Bukkit .getStructureManager ().loadStructure (NamespacedKey .fromString (item .structure ()));
415+ if (structure == null ) {
416+ BentoBox .getInstance ().logError (COULD_NOT_LOAD + item .structure ());
417+ return new BoundingBox ();
418+ }
391419 StructureRotation structureRotation = item .rot ();
392420 String key = item .name ();
393421
@@ -600,4 +628,18 @@ private static String nmsData(Block block) {
600628 return handler .nmsData (block );
601629 }
602630
631+ private ToBePlacedStructures loadToDos () {
632+ if (!toPlace .objectExists (TODO )) {
633+ return new ToBePlacedStructures ();
634+ }
635+ ToBePlacedStructures list = toPlace .loadObject (TODO );
636+ if (list == null ) {
637+ return new ToBePlacedStructures ();
638+ }
639+ if (!list .getReadyToBuild ().isEmpty ()) {
640+ addon .log ("Loaded " + list .getReadyToBuild ().size () + " structure todos." );
641+ }
642+ return list ;
643+ }
644+
603645}
0 commit comments