44import java .io .IOException ;
55import java .util .Arrays ;
66import java .util .Collections ;
7+ import java .util .HashMap ;
78import java .util .List ;
89import java .util .Locale ;
10+ import java .util .Map ;
911import java .util .Optional ;
1012import java .util .Random ;
13+ import java .util .Stack ;
1114
1215import org .bukkit .Bukkit ;
1316import org .bukkit .Location ;
17+ import org .bukkit .Material ;
1418import org .bukkit .NamespacedKey ;
19+ import org .bukkit .block .BlockState ;
20+ import org .bukkit .block .data .BlockData ;
1521import org .bukkit .block .structure .Mirror ;
1622import org .bukkit .block .structure .StructureRotation ;
1723import org .bukkit .configuration .InvalidConfigurationException ;
1824import org .bukkit .configuration .file .YamlConfiguration ;
1925import org .bukkit .structure .Structure ;
26+ import org .bukkit .util .BlockTransformer ;
27+ import org .bukkit .util .Vector ;
2028
2129import com .google .common .base .Enums ;
2230
@@ -52,6 +60,8 @@ public class AdminPlaceStructureCommand extends CompositeCommand {
5260 private Mirror mirror = Mirror .NONE ;
5361 private boolean noMobs ;
5462
63+ private final Stack <StructureRecord > placedStructures = new Stack <>();
64+
5565 public AdminPlaceStructureCommand (CompositeCommand parent ) {
5666 super (parent , "place" );
5767 }
@@ -68,15 +78,20 @@ public void setup() {
6878
6979 @ Override
7080 public boolean canExecute (User user , String label , List <String > args ) {
81+ if (args .size () == 1 && args .get (0 ).equalsIgnoreCase ("undo" )) {
82+ return true ; // Allow "undo" command without additional checks
83+ }
84+
7185 // Initialize
7286 sr = StructureRotation .NONE ;
7387 mirror = Mirror .NONE ;
7488
7589 // Check world
76- if (!((Boxed )getAddon ()).inWorld (getWorld ())) {
90+ if (!((Boxed ) getAddon ()).inWorld (getWorld ())) {
7791 user .sendMessage ("boxed.commands.boxadmin.place.wrong-world" );
7892 return false ;
7993 }
94+
8095 /*
8196 * Acceptable syntax with number of args:
8297 * 1. place <structure>
@@ -85,49 +100,56 @@ public boolean canExecute(User user, String label, List<String> args) {
85100 * 6. place <structure> ~ ~ ~ ROTATION MIRROR
86101 * 7. place <structure> ~ ~ ~ ROTATION MIRROR NO_MOBS
87102 */
88- // Format is place <structure> ~ ~ ~ or coords
89103 if (args .isEmpty () || args .size () == 2 || args .size () == 3 || args .size () > 6 ) {
90104 this .showHelp (this , user );
91105 return false ;
92106 }
107+
93108 // First arg must always be the structure name
94109 List <String > options = Bukkit .getStructureManager ().getStructures ().keySet ().stream ().map (NamespacedKey ::getKey ).toList ();
95110 if (!options .contains (args .get (0 ).toLowerCase (Locale .ENGLISH ))) {
96111 user .sendMessage ("boxed.commands.boxadmin.place.unknown-structure" );
97112 return false ;
98113 }
114+
99115 // If that is all we have, we're done
100116 if (args .size () == 1 ) {
101117 return true ;
102118 }
119+
103120 // Next come the coordinates - there must be at least 3 of them
104121 if ((!args .get (1 ).equals ("~" ) && !Util .isInteger (args .get (1 ), true ))
105122 || (!args .get (2 ).equals ("~" ) && !Util .isInteger (args .get (2 ), true ))
106123 || (!args .get (3 ).equals ("~" ) && !Util .isInteger (args .get (3 ), true ))) {
107124 user .sendMessage ("boxed.commands.boxadmin.place.use-integers" );
108125 return false ;
109126 }
127+
110128 // If that is all we have, we're done
111129 if (args .size () == 4 ) {
112130 return true ;
113131 }
114- // But there is more!
132+
133+ // Handle rotation
115134 sr = Enums .getIfPresent (StructureRotation .class , args .get (4 ).toUpperCase (Locale .ENGLISH )).orNull ();
116135 if (sr == null ) {
117136 user .sendMessage ("boxed.commands.boxadmin.place.unknown-rotation" );
118137 Arrays .stream (StructureRotation .values ()).map (StructureRotation ::name ).forEach (user ::sendRawMessage );
119138 return false ;
120139 }
140+
121141 if (args .size () == 5 ) {
122142 return true ;
123143 }
124- // But there is more!
144+
145+ // Handle mirror
125146 mirror = Enums .getIfPresent (Mirror .class , args .get (5 ).toUpperCase (Locale .ENGLISH )).orNull ();
126147 if (mirror == null ) {
127148 user .sendMessage ("boxed.commands.boxadmin.place.unknown-mirror" );
128149 Arrays .stream (Mirror .values ()).map (Mirror ::name ).forEach (user ::sendRawMessage );
129150 return false ;
130151 }
152+
131153 if (args .size () == 7 ) {
132154 if (args .get (6 ).toUpperCase (Locale .ENGLISH ).equals ("NO_MOBS" )) {
133155 noMobs = true ;
@@ -136,20 +158,40 @@ public boolean canExecute(User user, String label, List<String> args) {
136158 return false ;
137159 }
138160 }
161+
139162 // Syntax is okay
140163 return true ;
141164 }
142165
143166 @ Override
144167 public boolean execute (User user , String label , List <String > args ) {
168+ if (args .size () == 1 && args .get (0 ).equalsIgnoreCase ("undo" )) {
169+ return undoLastPlacement (user );
170+ }
171+
145172 NamespacedKey tag = NamespacedKey .fromString (args .get (0 ).toLowerCase (Locale .ENGLISH ));
146173 Structure s = Bukkit .getStructureManager ().loadStructure (tag );
147- int x = args .size () == 1 || args .get (1 ).equals ("~" ) ? user .getLocation ().getBlockX () : Integer .parseInt (args .get (1 ).trim ());
148- int y = args .size () == 1 || args .get (2 ).equals ("~" ) ? user .getLocation ().getBlockY () : Integer .parseInt (args .get (2 ).trim ());
149- int z = args .size () == 1 || args .get (3 ).equals ("~" ) ? user .getLocation ().getBlockZ () : Integer .parseInt (args .get (3 ).trim ());
174+ int x = args .size () == 1 || args .get (1 ).equals ("~" ) ? user .getLocation ().getBlockX ()
175+ : Integer .parseInt (args .get (1 ).trim ());
176+ int y = args .size () == 1 || args .get (2 ).equals ("~" ) ? user .getLocation ().getBlockY ()
177+ : Integer .parseInt (args .get (2 ).trim ());
178+ int z = args .size () == 1 || args .get (3 ).equals ("~" ) ? user .getLocation ().getBlockZ ()
179+ : Integer .parseInt (args .get (3 ).trim ());
150180 Location spot = new Location (user .getWorld (), x , y , z );
151- s .place (spot , true , sr , mirror , PALETTE , INTEGRITY , new Random ());
152- NewAreaListener .removeJigsaw (new StructureRecord (tag .getKey (), tag .getKey (), spot , sr , mirror , noMobs ));
181+ Map <Vector , BlockData > removedBlocks = new HashMap <>();
182+ BlockTransformer store = (region , xx , yy , zz , current , state ) -> {
183+ // Store the state
184+ removedBlocks .put (new Vector (xx , yy , zz ), region .getBlockData (xx , yy , zz ));
185+ return state .getOriginal ();
186+ };
187+
188+ s .place (spot , true , sr , mirror , PALETTE , INTEGRITY , new Random (), Collections .singleton (store ), // Transformer to store blocks
189+ Collections .emptyList () // No entity transformers
190+ );
191+ NewAreaListener
192+ .removeJigsaw (new StructureRecord (tag .getKey (), tag .getKey (), spot , sr , mirror , noMobs , removedBlocks ));
193+ placedStructures .push (new StructureRecord (tag .getKey (), tag .getKey (), spot , sr , mirror , noMobs , removedBlocks )); // Track the placement
194+
153195 boolean result = saveStructure (spot , tag , user , sr , mirror );
154196 if (result ) {
155197 user .sendMessage ("boxed.commands.boxadmin.place.saved" );
@@ -184,9 +226,74 @@ private boolean saveStructure(Location spot, NamespacedKey tag, User user, Struc
184226
185227 }
186228
229+ private boolean undoLastPlacement (User user ) {
230+ if (placedStructures .isEmpty ()) {
231+ user .sendMessage ("boxed.commands.boxadmin.place.no-undo" );
232+ return false ;
233+ }
234+
235+ StructureRecord lastRecord = placedStructures .pop ();
236+ NamespacedKey tag = NamespacedKey .fromString (lastRecord .name ());
237+ Structure s = Bukkit .getStructureManager ().loadStructure (tag );
238+
239+ if (s == null ) {
240+ user .sendMessage ("boxed.commands.boxadmin.place.undo-failed" );
241+ return false ;
242+ }
243+
244+ BlockTransformer erase = (region , x , y , z , current , state ) -> {
245+ Vector v = new Vector (x , y , z );
246+ if (lastRecord .removedBlocks ().containsKey (v )) {
247+ return lastRecord .removedBlocks ().get (v ).createBlockState ();
248+ }
249+ BlockState airState = Material .AIR .createBlockData ().createBlockState ();
250+ return airState ;
251+ };
252+
253+ s .place (
254+ lastRecord .location (),
255+ false , // Don't respawn entities
256+ lastRecord .rot (), lastRecord .mirror (),
257+ PALETTE ,
258+ 1.0f , // Integrity = 1 means "place everything"
259+ new Random (),
260+ Collections .singleton (erase ), // Transformer to erase blocks
261+ Collections .emptyList () // No entity transformers
262+ );
263+ lastRecord .removedBlocks ().clear ();
264+ removeStructure (lastRecord .location (), tag , user ); // Remove from config
265+
266+ user .sendMessage ("boxed.commands.boxadmin.place.undo-success" );
267+ return true ;
268+ }
269+
270+ private boolean removeStructure (Location spot , NamespacedKey tag , User user ) {
271+ return getAddon ().getIslands ().getIslandAt (spot ).map (i -> {
272+ int xx = spot .getBlockX () - i .getCenter ().getBlockX ();
273+ int zz = spot .getBlockZ () - i .getCenter ().getBlockZ ();
274+ File structures = new File (getAddon ().getDataFolder (), STRUCTURE_FILE );
275+ YamlConfiguration config = new YamlConfiguration ();
276+ try {
277+ config .load (structures );
278+ String key = spot .getWorld ().getEnvironment ().name ().toLowerCase (Locale .ENGLISH ) + "." + xx + "," + spot .getBlockY () + "," + zz ;
279+ if (config .contains (key )) {
280+ config .set (key , null ); // Remove the entry
281+ config .save (structures );
282+ return true ;
283+ }
284+ } catch (IOException | InvalidConfigurationException e ) {
285+ e .printStackTrace ();
286+ }
287+ return false ;
288+ }).orElse (false );
289+ }
290+
187291 @ Override
188292 public Optional <List <String >> tabComplete (User user , String alias , List <String > args )
189293 {
294+ if (args .size () == 1 ) {
295+ return Optional .of (Util .tabLimit (Arrays .asList ("undo" ), args .get (0 )));
296+ }
190297 String lastArg = !args .isEmpty () ? args .get (args .size () - 1 ) : "" ;
191298 if (args .size () == 2 ) {
192299 return Optional .of (Util .tabLimit (Bukkit .getStructureManager ().getStructures ().keySet ().stream ().map (NamespacedKey ::getKey ).toList (), lastArg ));
@@ -205,4 +312,7 @@ public Optional<List<String>> tabComplete(User user, String alias, List<String>
205312 }
206313 return Optional .of (Collections .emptyList ());
207314 }
315+
316+
317+
208318}
0 commit comments