PageRenderTime 74ms CodeModel.GetById 10ms RepoModel.GetById 0ms app.codeStats 1ms

/worldedit-core/src/main/java/com/sk89q/worldedit/EditSession.java

https://gitlab.com/Skull3x/WorldEdit
Java | 1327 lines | 644 code | 136 blank | 547 comment | 37 complexity | ce6a6d7c6c9dc69b404229b496fa5289 MD5 | raw file
  1. /*
  2. * WorldEdit, a Minecraft world manipulation toolkit
  3. * Copyright (C) sk89q <http://www.sk89q.com>
  4. * Copyright (C) WorldEdit team and contributors
  5. *
  6. * This program is free software: you can redistribute it and/or modify it
  7. * under the terms of the GNU Lesser General Public License as published by the
  8. * Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful, but WITHOUT
  12. * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13. * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
  14. * for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  18. */
  19. package com.sk89q.worldedit;
  20. import com.sk89q.worldedit.blocks.BaseBlock;
  21. import com.sk89q.worldedit.blocks.BlockID;
  22. import com.sk89q.worldedit.blocks.BlockType;
  23. import com.sk89q.worldedit.entity.BaseEntity;
  24. import com.sk89q.worldedit.entity.Entity;
  25. import com.sk89q.worldedit.event.extent.EditSessionEvent;
  26. import com.sk89q.worldedit.extent.ChangeSetExtent;
  27. import com.sk89q.worldedit.extent.Extent;
  28. import com.sk89q.worldedit.extent.MaskingExtent;
  29. import com.sk89q.worldedit.extent.NullExtent;
  30. import com.sk89q.worldedit.extent.buffer.ForgetfulExtentBuffer;
  31. import com.sk89q.worldedit.extent.cache.LastAccessExtentCache;
  32. import com.sk89q.worldedit.extent.inventory.BlockBag;
  33. import com.sk89q.worldedit.extent.inventory.BlockBagExtent;
  34. import com.sk89q.worldedit.extent.reorder.MultiStageReorder;
  35. import com.sk89q.worldedit.extent.validation.BlockChangeLimiter;
  36. import com.sk89q.worldedit.extent.validation.DataValidatorExtent;
  37. import com.sk89q.worldedit.extent.world.BlockQuirkExtent;
  38. import com.sk89q.worldedit.extent.world.ChunkLoadingExtent;
  39. import com.sk89q.worldedit.extent.world.FastModeExtent;
  40. import com.sk89q.worldedit.extent.world.SurvivalModeExtent;
  41. import com.sk89q.worldedit.function.GroundFunction;
  42. import com.sk89q.worldedit.function.RegionMaskingFilter;
  43. import com.sk89q.worldedit.function.block.BlockReplace;
  44. import com.sk89q.worldedit.function.block.Counter;
  45. import com.sk89q.worldedit.function.block.Naturalizer;
  46. import com.sk89q.worldedit.function.generator.GardenPatchGenerator;
  47. import com.sk89q.worldedit.function.mask.*;
  48. import com.sk89q.worldedit.function.operation.*;
  49. import com.sk89q.worldedit.function.pattern.BlockPattern;
  50. import com.sk89q.worldedit.function.pattern.Patterns;
  51. import com.sk89q.worldedit.function.util.RegionOffset;
  52. import com.sk89q.worldedit.function.visitor.*;
  53. import com.sk89q.worldedit.history.UndoContext;
  54. import com.sk89q.worldedit.history.change.BlockChange;
  55. import com.sk89q.worldedit.history.changeset.BlockOptimizedHistory;
  56. import com.sk89q.worldedit.history.changeset.ChangeSet;
  57. import com.sk89q.worldedit.internal.expression.Expression;
  58. import com.sk89q.worldedit.internal.expression.ExpressionException;
  59. import com.sk89q.worldedit.internal.expression.runtime.RValue;
  60. import com.sk89q.worldedit.math.interpolation.Interpolation;
  61. import com.sk89q.worldedit.math.interpolation.KochanekBartelsInterpolation;
  62. import com.sk89q.worldedit.math.interpolation.Node;
  63. import com.sk89q.worldedit.math.noise.RandomNoise;
  64. import com.sk89q.worldedit.math.transform.AffineTransform;
  65. import com.sk89q.worldedit.patterns.Pattern;
  66. import com.sk89q.worldedit.patterns.SingleBlockPattern;
  67. import com.sk89q.worldedit.regions.*;
  68. import com.sk89q.worldedit.regions.shape.ArbitraryBiomeShape;
  69. import com.sk89q.worldedit.regions.shape.ArbitraryShape;
  70. import com.sk89q.worldedit.regions.shape.RegionShape;
  71. import com.sk89q.worldedit.regions.shape.WorldEditExpressionEnvironment;
  72. import com.sk89q.worldedit.util.*;
  73. import com.sk89q.worldedit.util.collection.DoubleArrayList;
  74. import com.sk89q.worldedit.util.eventbus.EventBus;
  75. import com.sk89q.worldedit.world.NullWorld;
  76. import com.sk89q.worldedit.world.World;
  77. import com.sk89q.worldedit.world.biome.BaseBiome;
  78. import javax.annotation.Nullable;
  79. import java.util.*;
  80. import java.util.logging.Level;
  81. import java.util.logging.Logger;
  82. import static com.google.common.base.Preconditions.checkArgument;
  83. import static com.google.common.base.Preconditions.checkNotNull;
  84. import static com.sk89q.worldedit.regions.Regions.*;
  85. /**
  86. * An {@link Extent} that handles history, {@link BlockBag}s, change limits,
  87. * block re-ordering, and much more. Most operations in WorldEdit use this class.
  88. *
  89. * <p>Most of the actual functionality is implemented with a number of other
  90. * {@link Extent}s that are chained together. For example, history is logged
  91. * using the {@link ChangeSetExtent}.</p>
  92. */
  93. @SuppressWarnings({"FieldCanBeLocal", "deprecation"})
  94. public class EditSession implements Extent {
  95. private static final Logger log = Logger.getLogger(EditSession.class.getCanonicalName());
  96. /**
  97. * Used by {@link #setBlock(Vector, BaseBlock, Stage)} to
  98. * determine which {@link Extent}s should be bypassed.
  99. */
  100. public enum Stage {
  101. BEFORE_HISTORY,
  102. BEFORE_REORDER,
  103. BEFORE_CHANGE
  104. }
  105. @SuppressWarnings("ProtectedField")
  106. protected final World world;
  107. private final ChangeSet changeSet = new BlockOptimizedHistory();
  108. private @Nullable FastModeExtent fastModeExtent;
  109. private final SurvivalModeExtent survivalExtent;
  110. private @Nullable ChunkLoadingExtent chunkLoadingExtent;
  111. private @Nullable LastAccessExtentCache cacheExtent;
  112. private @Nullable BlockQuirkExtent quirkExtent;
  113. private @Nullable DataValidatorExtent validator;
  114. private final BlockBagExtent blockBagExtent;
  115. private final MultiStageReorder reorderExtent;
  116. private @Nullable ChangeSetExtent changeSetExtent;
  117. private final MaskingExtent maskingExtent;
  118. private final BlockChangeLimiter changeLimiter;
  119. private final Extent bypassReorderHistory;
  120. private final Extent bypassHistory;
  121. private final Extent bypassNone;
  122. @SuppressWarnings("deprecation")
  123. private Mask oldMask;
  124. /**
  125. * Create a new instance.
  126. *
  127. * @param world a world
  128. * @param maxBlocks the maximum number of blocks that can be changed, or -1 to use no limit
  129. * @deprecated use {@link WorldEdit#getEditSessionFactory()} to create {@link EditSession}s
  130. */
  131. @SuppressWarnings("deprecation")
  132. @Deprecated
  133. public EditSession(LocalWorld world, int maxBlocks) {
  134. this(world, maxBlocks, null);
  135. }
  136. /**
  137. * Create a new instance.
  138. *
  139. * @param world a world
  140. * @param maxBlocks the maximum number of blocks that can be changed, or -1 to use no limit
  141. * @param blockBag the block bag to set, or null to use none
  142. * @deprecated use {@link WorldEdit#getEditSessionFactory()} to create {@link EditSession}s
  143. */
  144. @Deprecated
  145. public EditSession(LocalWorld world, int maxBlocks, @Nullable BlockBag blockBag) {
  146. this(WorldEdit.getInstance().getEventBus(), world, maxBlocks, blockBag, new EditSessionEvent(world, null, maxBlocks, null));
  147. }
  148. /**
  149. * Construct the object with a maximum number of blocks and a block bag.
  150. *
  151. * @param eventBus the event bus
  152. * @param world the world
  153. * @param maxBlocks the maximum number of blocks that can be changed, or -1 to use no limit
  154. * @param blockBag an optional {@link BlockBag} to use, otherwise null
  155. * @param event the event to call with the extent
  156. */
  157. EditSession(EventBus eventBus, World world, int maxBlocks, @Nullable BlockBag blockBag, EditSessionEvent event) {
  158. checkNotNull(eventBus);
  159. checkArgument(maxBlocks >= -1, "maxBlocks >= -1 required");
  160. checkNotNull(event);
  161. this.world = world;
  162. if (world != null) {
  163. Extent extent;
  164. // These extents are ALWAYS used
  165. extent = fastModeExtent = new FastModeExtent(world, false);
  166. extent = survivalExtent = new SurvivalModeExtent(extent, world);
  167. extent = quirkExtent = new BlockQuirkExtent(extent, world);
  168. extent = chunkLoadingExtent = new ChunkLoadingExtent(extent, world);
  169. extent = cacheExtent = new LastAccessExtentCache(extent);
  170. extent = wrapExtent(extent, eventBus, event, Stage.BEFORE_CHANGE);
  171. extent = validator = new DataValidatorExtent(extent, world);
  172. extent = blockBagExtent = new BlockBagExtent(extent, blockBag);
  173. // This extent can be skipped by calling rawSetBlock()
  174. extent = reorderExtent = new MultiStageReorder(extent, false);
  175. extent = wrapExtent(extent, eventBus, event, Stage.BEFORE_REORDER);
  176. // These extents can be skipped by calling smartSetBlock()
  177. extent = changeSetExtent = new ChangeSetExtent(extent, changeSet);
  178. extent = maskingExtent = new MaskingExtent(extent, Masks.alwaysTrue());
  179. extent = changeLimiter = new BlockChangeLimiter(extent, maxBlocks);
  180. extent = wrapExtent(extent, eventBus, event, Stage.BEFORE_HISTORY);
  181. this.bypassReorderHistory = blockBagExtent;
  182. this.bypassHistory = reorderExtent;
  183. this.bypassNone = extent;
  184. } else {
  185. Extent extent = new NullExtent();
  186. extent = survivalExtent = new SurvivalModeExtent(extent, NullWorld.getInstance());
  187. extent = blockBagExtent = new BlockBagExtent(extent, blockBag);
  188. extent = reorderExtent = new MultiStageReorder(extent, false);
  189. extent = maskingExtent = new MaskingExtent(extent, Masks.alwaysTrue());
  190. extent = changeLimiter = new BlockChangeLimiter(extent, maxBlocks);
  191. this.bypassReorderHistory = extent;
  192. this.bypassHistory = extent;
  193. this.bypassNone = extent;
  194. }
  195. }
  196. private Extent wrapExtent(Extent extent, EventBus eventBus, EditSessionEvent event, Stage stage) {
  197. event = event.clone(stage);
  198. event.setExtent(extent);
  199. eventBus.post(event);
  200. return event.getExtent();
  201. }
  202. /**
  203. * Get the world.
  204. *
  205. * @return the world
  206. */
  207. public World getWorld() {
  208. return world;
  209. }
  210. /**
  211. * Get the underlying {@link ChangeSet}.
  212. *
  213. * @return the change set
  214. */
  215. public ChangeSet getChangeSet() {
  216. return changeSet;
  217. }
  218. /**
  219. * Get the maximum number of blocks that can be changed. -1 will be returned
  220. * if it the limit disabled.
  221. *
  222. * @return the limit (&gt;= 0) or -1 for no limit
  223. */
  224. public int getBlockChangeLimit() {
  225. return changeLimiter.getLimit();
  226. }
  227. /**
  228. * Set the maximum number of blocks that can be changed.
  229. *
  230. * @param limit the limit (&gt;= 0) or -1 for no limit
  231. */
  232. public void setBlockChangeLimit(int limit) {
  233. changeLimiter.setLimit(limit);
  234. }
  235. /**
  236. * Returns queue status.
  237. *
  238. * @return whether the queue is enabled
  239. */
  240. public boolean isQueueEnabled() {
  241. return reorderExtent.isEnabled();
  242. }
  243. /**
  244. * Queue certain types of block for better reproduction of those blocks.
  245. */
  246. public void enableQueue() {
  247. reorderExtent.setEnabled(true);
  248. }
  249. /**
  250. * Disable the queue. This will flush the queue.
  251. */
  252. public void disableQueue() {
  253. if (isQueueEnabled()) {
  254. flushQueue();
  255. }
  256. reorderExtent.setEnabled(true);
  257. }
  258. /**
  259. * Get the mask.
  260. *
  261. * @return mask, may be null
  262. */
  263. public Mask getMask() {
  264. return oldMask;
  265. }
  266. /**
  267. * Set a mask.
  268. *
  269. * @param mask mask or null
  270. */
  271. public void setMask(Mask mask) {
  272. this.oldMask = mask;
  273. if (mask == null) {
  274. maskingExtent.setMask(Masks.alwaysTrue());
  275. } else {
  276. maskingExtent.setMask(mask);
  277. }
  278. }
  279. /**
  280. * Set the mask.
  281. *
  282. * @param mask the mask
  283. * @deprecated Use {@link #setMask(Mask)}
  284. */
  285. @Deprecated
  286. public void setMask(com.sk89q.worldedit.masks.Mask mask) {
  287. if (mask == null) {
  288. setMask((Mask) null);
  289. } else {
  290. setMask(Masks.wrap(mask));
  291. }
  292. }
  293. /**
  294. * Get the {@link SurvivalModeExtent}.
  295. *
  296. * @return the survival simulation extent
  297. */
  298. public SurvivalModeExtent getSurvivalExtent() {
  299. return survivalExtent;
  300. }
  301. /**
  302. * Set whether fast mode is enabled.
  303. *
  304. * <p>Fast mode may skip lighting checks or adjacent block
  305. * notification.</p>
  306. *
  307. * @param enabled true to enable
  308. */
  309. public void setFastMode(boolean enabled) {
  310. if (fastModeExtent != null) {
  311. fastModeExtent.setEnabled(enabled);
  312. }
  313. }
  314. /**
  315. * Return fast mode status.
  316. *
  317. * <p>Fast mode may skip lighting checks or adjacent block
  318. * notification.</p>
  319. *
  320. * @return true if enabled
  321. */
  322. public boolean hasFastMode() {
  323. return fastModeExtent != null && fastModeExtent.isEnabled();
  324. }
  325. /**
  326. * Get the {@link BlockBag} is used.
  327. *
  328. * @return a block bag or null
  329. */
  330. public BlockBag getBlockBag() {
  331. return blockBagExtent.getBlockBag();
  332. }
  333. /**
  334. * Set a {@link BlockBag} to use.
  335. *
  336. * @param blockBag the block bag to set, or null to use none
  337. */
  338. public void setBlockBag(BlockBag blockBag) {
  339. blockBagExtent.setBlockBag(blockBag);
  340. }
  341. /**
  342. * Gets the list of missing blocks and clears the list for the next
  343. * operation.
  344. *
  345. * @return a map of missing blocks
  346. */
  347. public Map<Integer, Integer> popMissingBlocks() {
  348. return blockBagExtent.popMissing();
  349. }
  350. /**
  351. * Get the number of blocks changed, including repeated block changes.
  352. *
  353. * <p>This number may not be accurate.</p>
  354. *
  355. * @return the number of block changes
  356. */
  357. public int getBlockChangeCount() {
  358. return changeSet.size();
  359. }
  360. @Override
  361. public BaseBiome getBiome(Vector2D position) {
  362. return bypassNone.getBiome(position);
  363. }
  364. @Override
  365. public boolean setBiome(Vector2D position, BaseBiome biome) {
  366. return bypassNone.setBiome(position, biome);
  367. }
  368. @Override
  369. public BaseBlock getLazyBlock(Vector position) {
  370. return world.getLazyBlock(position);
  371. }
  372. @Override
  373. public BaseBlock getBlock(Vector position) {
  374. return world.getBlock(position);
  375. }
  376. /**
  377. * Get a block type at the given position.
  378. *
  379. * @param position the position
  380. * @return the block type
  381. * @deprecated Use {@link #getLazyBlock(Vector)} or {@link #getBlock(Vector)}
  382. */
  383. @Deprecated
  384. public int getBlockType(Vector position) {
  385. return world.getBlockType(position);
  386. }
  387. /**
  388. * Get a block data at the given position.
  389. *
  390. * @param position the position
  391. * @return the block data
  392. * @deprecated Use {@link #getLazyBlock(Vector)} or {@link #getBlock(Vector)}
  393. */
  394. @Deprecated
  395. public int getBlockData(Vector position) {
  396. return world.getBlockData(position);
  397. }
  398. /**
  399. * Gets the block type at a position.
  400. *
  401. * @param position the position
  402. * @return a block
  403. * @deprecated Use {@link #getBlock(Vector)}
  404. */
  405. @Deprecated
  406. public BaseBlock rawGetBlock(Vector position) {
  407. return getBlock(position);
  408. }
  409. /**
  410. * Returns the highest solid 'terrain' block which can occur naturally.
  411. *
  412. * @param x the X coordinate
  413. * @param z the Z cooridnate
  414. * @param minY minimal height
  415. * @param maxY maximal height
  416. * @return height of highest block found or 'minY'
  417. */
  418. public int getHighestTerrainBlock(int x, int z, int minY, int maxY) {
  419. return getHighestTerrainBlock(x, z, minY, maxY, false);
  420. }
  421. /**
  422. * Returns the highest solid 'terrain' block which can occur naturally.
  423. *
  424. * @param x the X coordinate
  425. * @param z the Z coordinate
  426. * @param minY minimal height
  427. * @param maxY maximal height
  428. * @param naturalOnly look at natural blocks or all blocks
  429. * @return height of highest block found or 'minY'
  430. */
  431. public int getHighestTerrainBlock(int x, int z, int minY, int maxY, boolean naturalOnly) {
  432. for (int y = maxY; y >= minY; --y) {
  433. Vector pt = new Vector(x, y, z);
  434. int id = getBlockType(pt);
  435. int data = getBlockData(pt);
  436. if (naturalOnly ? BlockType.isNaturalTerrainBlock(id, data) : !BlockType.canPassThrough(id, data)) {
  437. return y;
  438. }
  439. }
  440. return minY;
  441. }
  442. /**
  443. * Set a block, bypassing both history and block re-ordering.
  444. *
  445. * @param position the position to set the block at
  446. * @param block the block
  447. * @param stage the level
  448. * @return whether the block changed
  449. * @throws WorldEditException thrown on a set error
  450. */
  451. public boolean setBlock(Vector position, BaseBlock block, Stage stage) throws WorldEditException {
  452. switch (stage) {
  453. case BEFORE_HISTORY:
  454. return bypassNone.setBlock(position, block);
  455. case BEFORE_CHANGE:
  456. return bypassHistory.setBlock(position, block);
  457. case BEFORE_REORDER:
  458. return bypassReorderHistory.setBlock(position, block);
  459. }
  460. throw new RuntimeException("New enum entry added that is unhandled here");
  461. }
  462. /**
  463. * Set a block, bypassing both history and block re-ordering.
  464. *
  465. * @param position the position to set the block at
  466. * @param block the block
  467. * @return whether the block changed
  468. */
  469. public boolean rawSetBlock(Vector position, BaseBlock block) {
  470. try {
  471. return setBlock(position, block, Stage.BEFORE_CHANGE);
  472. } catch (WorldEditException e) {
  473. throw new RuntimeException("Unexpected exception", e);
  474. }
  475. }
  476. /**
  477. * Set a block, bypassing history but still utilizing block re-ordering.
  478. *
  479. * @param position the position to set the block at
  480. * @param block the block
  481. * @return whether the block changed
  482. */
  483. public boolean smartSetBlock(Vector position, BaseBlock block) {
  484. try {
  485. return setBlock(position, block, Stage.BEFORE_REORDER);
  486. } catch (WorldEditException e) {
  487. throw new RuntimeException("Unexpected exception", e);
  488. }
  489. }
  490. @Override
  491. public boolean setBlock(Vector position, BaseBlock block) throws MaxChangedBlocksException {
  492. try {
  493. return setBlock(position, block, Stage.BEFORE_HISTORY);
  494. } catch (MaxChangedBlocksException e) {
  495. throw e;
  496. } catch (WorldEditException e) {
  497. throw new RuntimeException("Unexpected exception", e);
  498. }
  499. }
  500. /**
  501. * Sets the block at a position, subject to both history and block re-ordering.
  502. *
  503. * @param position the position
  504. * @param pattern a pattern to use
  505. * @return Whether the block changed -- not entirely dependable
  506. * @throws MaxChangedBlocksException thrown if too many blocks are changed
  507. */
  508. @SuppressWarnings("deprecation")
  509. public boolean setBlock(Vector position, Pattern pattern) throws MaxChangedBlocksException {
  510. return setBlock(position, pattern.next(position));
  511. }
  512. /**
  513. * Set blocks that are in a set of positions and return the number of times
  514. * that the block set calls returned true.
  515. *
  516. * @param vset a set of positions
  517. * @param pattern the pattern
  518. * @return the number of changed blocks
  519. * @throws MaxChangedBlocksException thrown if too many blocks are changed
  520. */
  521. @SuppressWarnings("deprecation")
  522. private int setBlocks(Set<Vector> vset, Pattern pattern) throws MaxChangedBlocksException {
  523. int affected = 0;
  524. for (Vector v : vset) {
  525. affected += setBlock(v, pattern) ? 1 : 0;
  526. }
  527. return affected;
  528. }
  529. /**
  530. * Set a block (only if a previous block was not there) if {@link Math#random()}
  531. * returns a number less than the given probability.
  532. *
  533. * @param position the position
  534. * @param block the block
  535. * @param probability a probability between 0 and 1, inclusive
  536. * @return whether a block was changed
  537. * @throws MaxChangedBlocksException thrown if too many blocks are changed
  538. */
  539. @SuppressWarnings("deprecation")
  540. public boolean setChanceBlockIfAir(Vector position, BaseBlock block, double probability)
  541. throws MaxChangedBlocksException {
  542. return Math.random() <= probability && setBlockIfAir(position, block);
  543. }
  544. /**
  545. * Set a block only if there's no block already there.
  546. *
  547. * @param position the position
  548. * @param block the block to set
  549. * @return if block was changed
  550. * @throws MaxChangedBlocksException thrown if too many blocks are changed
  551. * @deprecated Use your own method
  552. */
  553. @Deprecated
  554. public boolean setBlockIfAir(Vector position, BaseBlock block) throws MaxChangedBlocksException {
  555. return getBlock(position).isAir() && setBlock(position, block);
  556. }
  557. @Override
  558. @Nullable
  559. public Entity createEntity(com.sk89q.worldedit.util.Location location, BaseEntity entity) {
  560. return bypassNone.createEntity(location, entity);
  561. }
  562. /**
  563. * Insert a contrived block change into the history.
  564. *
  565. * @param position the position
  566. * @param existing the previous block at that position
  567. * @param block the new block
  568. * @deprecated Get the change set with {@link #getChangeSet()} and add the change with that
  569. */
  570. @Deprecated
  571. public void rememberChange(Vector position, BaseBlock existing, BaseBlock block) {
  572. changeSet.add(new BlockChange(position.toBlockVector(), existing, block));
  573. }
  574. /**
  575. * Restores all blocks to their initial state.
  576. *
  577. * @param editSession a new {@link EditSession} to perform the undo in
  578. */
  579. public void undo(EditSession editSession) {
  580. UndoContext context = new UndoContext();
  581. context.setExtent(editSession.bypassHistory);
  582. Operations.completeBlindly(ChangeSetExecutor.createUndo(changeSet, context));
  583. editSession.flushQueue();
  584. }
  585. /**
  586. * Sets to new state.
  587. *
  588. * @param editSession a new {@link EditSession} to perform the redo in
  589. */
  590. public void redo(EditSession editSession) {
  591. UndoContext context = new UndoContext();
  592. context.setExtent(editSession.bypassHistory);
  593. Operations.completeBlindly(ChangeSetExecutor.createRedo(changeSet, context));
  594. editSession.flushQueue();
  595. }
  596. /**
  597. * Get the number of changed blocks.
  598. *
  599. * @return the number of changes
  600. */
  601. public int size() {
  602. return getBlockChangeCount();
  603. }
  604. @Override
  605. public Vector getMinimumPoint() {
  606. return getWorld().getMinimumPoint();
  607. }
  608. @Override
  609. public Vector getMaximumPoint() {
  610. return getWorld().getMaximumPoint();
  611. }
  612. @Override
  613. public List<? extends Entity> getEntities(Region region) {
  614. return bypassNone.getEntities(region);
  615. }
  616. @Override
  617. public List<? extends Entity> getEntities() {
  618. return bypassNone.getEntities();
  619. }
  620. /**
  621. * Finish off the queue.
  622. */
  623. public void flushQueue() {
  624. Operations.completeBlindly(commit());
  625. }
  626. @Override
  627. public @Nullable Operation commit() {
  628. return bypassNone.commit();
  629. }
  630. /**
  631. * Count the number of blocks of a given list of types in a region.
  632. *
  633. * @param region the region
  634. * @param searchIDs a list of IDs to search
  635. * @return the number of found blocks
  636. */
  637. public int countBlock(Region region, Set<Integer> searchIDs) {
  638. Set<BaseBlock> passOn = new HashSet<BaseBlock>();
  639. for (Integer i : searchIDs) {
  640. passOn.add(new BaseBlock(i, -1));
  641. }
  642. return countBlocks(region, passOn);
  643. }
  644. /**
  645. * Count the number of blocks of a list of types in a region.
  646. *
  647. * @param region the region
  648. * @param searchBlocks the list of blocks to search
  649. * @return the number of blocks that matched the pattern
  650. */
  651. public int countBlocks(Region region, Set<BaseBlock> searchBlocks) {
  652. FuzzyBlockMask mask = new FuzzyBlockMask(this, searchBlocks);
  653. Counter count = new Counter();
  654. RegionMaskingFilter filter = new RegionMaskingFilter(mask, count);
  655. RegionVisitor visitor = new RegionVisitor(region, filter);
  656. Operations.completeBlindly(visitor); // We can't throw exceptions, nor do we expect any
  657. return count.getCount();
  658. }
  659. /**
  660. * Fills an area recursively in the X/Z directions.
  661. *
  662. * @param origin the location to start from
  663. * @param block the block to fill with
  664. * @param radius the radius of the spherical area to fill
  665. * @param depth the maximum depth, starting from the origin
  666. * @param recursive whether a breadth-first search should be performed
  667. * @return number of blocks affected
  668. * @throws MaxChangedBlocksException thrown if too many blocks are changed
  669. */
  670. @SuppressWarnings("deprecation")
  671. public int fillXZ(Vector origin, BaseBlock block, double radius, int depth, boolean recursive)
  672. throws MaxChangedBlocksException {
  673. return fillXZ(origin, new SingleBlockPattern(block), radius, depth, recursive);
  674. }
  675. /**
  676. * Fills an area recursively in the X/Z directions.
  677. *
  678. * @param origin the origin to start the fill from
  679. * @param pattern the pattern to fill with
  680. * @param radius the radius of the spherical area to fill, with 0 as the smallest radius
  681. * @param depth the maximum depth, starting from the origin, with 1 as the smallest depth
  682. * @param recursive whether a breadth-first search should be performed
  683. * @return number of blocks affected
  684. * @throws MaxChangedBlocksException thrown if too many blocks are changed
  685. */
  686. @SuppressWarnings("deprecation")
  687. public int fillXZ(Vector origin, Pattern pattern, double radius, int depth, boolean recursive) throws MaxChangedBlocksException {
  688. checkNotNull(origin);
  689. checkNotNull(pattern);
  690. checkArgument(radius >= 0, "radius >= 0");
  691. checkArgument(depth >= 1, "depth >= 1");
  692. MaskIntersection mask = new MaskIntersection(
  693. new RegionMask(new EllipsoidRegion(null, origin, new Vector(radius, radius, radius))),
  694. new BoundedHeightMask(
  695. Math.max(origin.getBlockY() - depth + 1, 0),
  696. Math.min(getWorld().getMaxY(), origin.getBlockY())),
  697. Masks.negate(new ExistingBlockMask(this)));
  698. // Want to replace blocks
  699. BlockReplace replace = new BlockReplace(this, Patterns.wrap(pattern));
  700. // Pick how we're going to visit blocks
  701. RecursiveVisitor visitor;
  702. if (recursive) {
  703. visitor = new RecursiveVisitor(mask, replace);
  704. } else {
  705. visitor = new DownwardVisitor(mask, replace, origin.getBlockY());
  706. }
  707. // Start at the origin
  708. visitor.visit(origin);
  709. // Execute
  710. Operations.completeLegacy(visitor);
  711. return visitor.getAffected();
  712. }
  713. /**
  714. * Remove a cuboid above the given position with a given apothem and a given height.
  715. *
  716. * @param position base position
  717. * @param apothem an apothem of the cuboid (on the XZ plane), where the minimum is 1
  718. * @param height the height of the cuboid, where the minimum is 1
  719. * @return number of blocks affected
  720. * @throws MaxChangedBlocksException thrown if too many blocks are changed
  721. */
  722. @SuppressWarnings("deprecation")
  723. public int removeAbove(Vector position, int apothem, int height) throws MaxChangedBlocksException {
  724. checkNotNull(position);
  725. checkArgument(apothem >= 1, "apothem >= 1");
  726. checkArgument(height >= 1, "height >= 1");
  727. Region region = new CuboidRegion(
  728. getWorld(), // Causes clamping of Y range
  729. position.add(-apothem + 1, 0, -apothem + 1),
  730. position.add(apothem - 1, height - 1, apothem - 1));
  731. Pattern pattern = new SingleBlockPattern(new BaseBlock(BlockID.AIR));
  732. return setBlocks(region, pattern);
  733. }
  734. /**
  735. * Remove a cuboid below the given position with a given apothem and a given height.
  736. *
  737. * @param position base position
  738. * @param apothem an apothem of the cuboid (on the XZ plane), where the minimum is 1
  739. * @param height the height of the cuboid, where the minimum is 1
  740. * @return number of blocks affected
  741. * @throws MaxChangedBlocksException thrown if too many blocks are changed
  742. */
  743. @SuppressWarnings("deprecation")
  744. public int removeBelow(Vector position, int apothem, int height) throws MaxChangedBlocksException {
  745. checkNotNull(position);
  746. checkArgument(apothem >= 1, "apothem >= 1");
  747. checkArgument(height >= 1, "height >= 1");
  748. Region region = new CuboidRegion(
  749. getWorld(), // Causes clamping of Y range
  750. position.add(-apothem + 1, 0, -apothem + 1),
  751. position.add(apothem - 1, -height + 1, apothem - 1));
  752. Pattern pattern = new SingleBlockPattern(new BaseBlock(BlockID.AIR));
  753. return setBlocks(region, pattern);
  754. }
  755. /**
  756. * Remove blocks of a certain type nearby a given position.
  757. *
  758. * @param position center position of cuboid
  759. * @param blockType the block type to match
  760. * @param apothem an apothem of the cuboid, where the minimum is 1
  761. * @return number of blocks affected
  762. * @throws MaxChangedBlocksException thrown if too many blocks are changed
  763. */
  764. @SuppressWarnings("deprecation")
  765. public int removeNear(Vector position, int blockType, int apothem) throws MaxChangedBlocksException {
  766. checkNotNull(position);
  767. checkArgument(apothem >= 1, "apothem >= 1");
  768. Mask mask = new FuzzyBlockMask(this, new BaseBlock(blockType, -1));
  769. Vector adjustment = new Vector(1, 1, 1).multiply(apothem - 1);
  770. Region region = new CuboidRegion(
  771. getWorld(), // Causes clamping of Y range
  772. position.add(adjustment.multiply(-1)),
  773. position.add(adjustment));
  774. Pattern pattern = new SingleBlockPattern(new BaseBlock(BlockID.AIR));
  775. return replaceBlocks(region, mask, pattern);
  776. }
  777. /**
  778. * Sets all the blocks inside a region to a given block type.
  779. *
  780. * @param region the region
  781. * @param block the block
  782. * @return number of blocks affected
  783. * @throws MaxChangedBlocksException thrown if too many blocks are changed
  784. */
  785. @SuppressWarnings("deprecation")
  786. public int setBlocks(Region region, BaseBlock block) throws MaxChangedBlocksException {
  787. return setBlocks(region, new SingleBlockPattern(block));
  788. }
  789. /**
  790. * Sets all the blocks inside a region to a given pattern.
  791. *
  792. * @param region the region
  793. * @param pattern the pattern that provides the replacement block
  794. * @return number of blocks affected
  795. * @throws MaxChangedBlocksException thrown if too many blocks are changed
  796. */
  797. @SuppressWarnings("deprecation")
  798. public int setBlocks(Region region, Pattern pattern) throws MaxChangedBlocksException {
  799. checkNotNull(region);
  800. checkNotNull(pattern);
  801. BlockReplace replace = new BlockReplace(this, Patterns.wrap(pattern));
  802. RegionVisitor visitor = new RegionVisitor(region, replace);
  803. Operations.completeLegacy(visitor);
  804. return visitor.getAffected();
  805. }
  806. /**
  807. * Replaces all the blocks matching a given filter, within a given region, to a block
  808. * returned by a given pattern.
  809. *
  810. * @param region the region to replace the blocks within
  811. * @param filter a list of block types to match, or null to use {@link com.sk89q.worldedit.masks.ExistingBlockMask}
  812. * @param replacement the replacement block
  813. * @return number of blocks affected
  814. * @throws MaxChangedBlocksException thrown if too many blocks are changed
  815. */
  816. @SuppressWarnings("deprecation")
  817. public int replaceBlocks(Region region, Set<BaseBlock> filter, BaseBlock replacement) throws MaxChangedBlocksException {
  818. return replaceBlocks(region, filter, new SingleBlockPattern(replacement));
  819. }
  820. /**
  821. * Replaces all the blocks matching a given filter, within a given region, to a block
  822. * returned by a given pattern.
  823. *
  824. * @param region the region to replace the blocks within
  825. * @param filter a list of block types to match, or null to use {@link com.sk89q.worldedit.masks.ExistingBlockMask}
  826. * @param pattern the pattern that provides the new blocks
  827. * @return number of blocks affected
  828. * @throws MaxChangedBlocksException thrown if too many blocks are changed
  829. */
  830. @SuppressWarnings("deprecation")
  831. public int replaceBlocks(Region region, Set<BaseBlock> filter, Pattern pattern) throws MaxChangedBlocksException {
  832. Mask mask = filter == null ? new ExistingBlockMask(this) : new FuzzyBlockMask(this, filter);
  833. return replaceBlocks(region, mask, pattern);
  834. }
  835. /**
  836. * Replaces all the blocks matching a given mask, within a given region, to a block
  837. * returned by a given pattern.
  838. *
  839. * @param region the region to replace the blocks within
  840. * @param mask the mask that blocks must match
  841. * @param pattern the pattern that provides the new blocks
  842. * @return number of blocks affected
  843. * @throws MaxChangedBlocksException thrown if too many blocks are changed
  844. */
  845. @SuppressWarnings("deprecation")
  846. public int replaceBlocks(Region region, Mask mask, Pattern pattern) throws MaxChangedBlocksException {
  847. checkNotNull(region);
  848. checkNotNull(mask);
  849. checkNotNull(pattern);
  850. BlockReplace replace = new BlockReplace(this, Patterns.wrap(pattern));
  851. RegionMaskingFilter filter = new RegionMaskingFilter(mask, replace);
  852. RegionVisitor visitor = new RegionVisitor(region, filter);
  853. Operations.completeLegacy(visitor);
  854. return visitor.getAffected();
  855. }
  856. /**
  857. * Sets the blocks at the center of the given region to the given pattern.
  858. * If the center sits between two blocks on a certain axis, then two blocks
  859. * will be placed to mark the center.
  860. *
  861. * @param region the region to find the center of
  862. * @param pattern the replacement pattern
  863. * @return the number of blocks placed
  864. * @throws MaxChangedBlocksException thrown if too many blocks are changed
  865. */
  866. @SuppressWarnings("deprecation")
  867. public int center(Region region, Pattern pattern) throws MaxChangedBlocksException {
  868. checkNotNull(region);
  869. checkNotNull(pattern);
  870. Vector center = region.getCenter();
  871. Region centerRegion = new CuboidRegion(
  872. getWorld(), // Causes clamping of Y range
  873. new Vector((int) center.getX(), (int) center.getY(), (int) center.getZ()),
  874. center.toBlockVector());
  875. return setBlocks(centerRegion, pattern);
  876. }
  877. /**
  878. * Make the faces of the given region as if it was a {@link CuboidRegion}.
  879. *
  880. * @param region the region
  881. * @param block the block to place
  882. * @return number of blocks affected
  883. * @throws MaxChangedBlocksException thrown if too many blocks are changed
  884. */
  885. @SuppressWarnings("deprecation")
  886. public int makeCuboidFaces(Region region, BaseBlock block) throws MaxChangedBlocksException {
  887. return makeCuboidFaces(region, new SingleBlockPattern(block));
  888. }
  889. /**
  890. * Make the faces of the given region as if it was a {@link CuboidRegion}.
  891. *
  892. * @param region the region
  893. * @param pattern the pattern to place
  894. * @return number of blocks affected
  895. * @throws MaxChangedBlocksException thrown if too many blocks are changed
  896. */
  897. @SuppressWarnings("deprecation")
  898. public int makeCuboidFaces(Region region, Pattern pattern) throws MaxChangedBlocksException {
  899. checkNotNull(region);
  900. checkNotNull(pattern);
  901. CuboidRegion cuboid = CuboidRegion.makeCuboid(region);
  902. Region faces = cuboid.getFaces();
  903. return setBlocks(faces, pattern);
  904. }
  905. /**
  906. * Make the faces of the given region. The method by which the faces are found
  907. * may be inefficient, because there may not be an efficient implementation supported
  908. * for that specific shape.
  909. *
  910. * @param region the region
  911. * @param pattern the pattern to place
  912. * @return number of blocks affected
  913. * @throws MaxChangedBlocksException thrown if too many blocks are changed
  914. */
  915. @SuppressWarnings("deprecation")
  916. public int makeFaces(final Region region, Pattern pattern) throws MaxChangedBlocksException {
  917. checkNotNull(region);
  918. checkNotNull(pattern);
  919. if (region instanceof CuboidRegion) {
  920. return makeCuboidFaces(region, pattern);
  921. } else {
  922. return new RegionShape(region).generate(this, pattern, true);
  923. }
  924. }
  925. /**
  926. * Make the walls (all faces but those parallel to the X-Z plane) of the given region
  927. * as if it was a {@link CuboidRegion}.
  928. *
  929. * @param region the region
  930. * @param block the block to place
  931. * @return number of blocks affected
  932. * @throws MaxChangedBlocksException thrown if too many blocks are changed
  933. */
  934. @SuppressWarnings("deprecation")
  935. public int makeCuboidWalls(Region region, BaseBlock block) throws MaxChangedBlocksException {
  936. return makeCuboidWalls(region, new SingleBlockPattern(block));
  937. }
  938. /**
  939. * Make the walls (all faces but those parallel to the X-Z plane) of the given region
  940. * as if it was a {@link CuboidRegion}.
  941. *
  942. * @param region the region
  943. * @param pattern the pattern to place
  944. * @return number of blocks affected
  945. * @throws MaxChangedBlocksException thrown if too many blocks are changed
  946. */
  947. @SuppressWarnings("deprecation")
  948. public int makeCuboidWalls(Region region, Pattern pattern) throws MaxChangedBlocksException {
  949. checkNotNull(region);
  950. checkNotNull(pattern);
  951. CuboidRegion cuboid = CuboidRegion.makeCuboid(region);
  952. Region faces = cuboid.getWalls();
  953. return setBlocks(faces, pattern);
  954. }
  955. /**
  956. * Make the walls of the given region. The method by which the walls are found
  957. * may be inefficient, because there may not be an efficient implementation supported
  958. * for that specific shape.
  959. *
  960. * @param region the region
  961. * @param pattern the pattern to place
  962. * @return number of blocks affected
  963. * @throws MaxChangedBlocksException thrown if too many blocks are changed
  964. */
  965. @SuppressWarnings("deprecation")
  966. public int makeWalls(final Region region, Pattern pattern) throws MaxChangedBlocksException {
  967. checkNotNull(region);
  968. checkNotNull(pattern);
  969. if (region instanceof CuboidRegion) {
  970. return makeCuboidWalls(region, pattern);
  971. } else {
  972. final int minY = region.getMinimumPoint().getBlockY();
  973. final int maxY = region.getMaximumPoint().getBlockY();
  974. final ArbitraryShape shape = new RegionShape(region) {
  975. @Override
  976. protected BaseBlock getMaterial(int x, int y, int z, BaseBlock defaultMaterial) {
  977. if (y > maxY || y < minY) {
  978. // Put holes into the floor and ceiling by telling ArbitraryShape that the shape goes on outside the region
  979. return defaultMaterial;
  980. }
  981. return super.getMaterial(x, y, z, defaultMaterial);
  982. }
  983. };
  984. return shape.generate(this, pattern, true);
  985. }
  986. }
  987. /**
  988. * Places a layer of blocks on top of ground blocks in the given region
  989. * (as if it were a cuboid).
  990. *
  991. * @param region the region
  992. * @param block the placed block
  993. * @return number of blocks affected
  994. * @throws MaxChangedBlocksException thrown if too many blocks are changed
  995. */
  996. @SuppressWarnings("deprecation")
  997. public int overlayCuboidBlocks(Region region, BaseBlock block) throws MaxChangedBlocksException {
  998. checkNotNull(block);
  999. return overlayCuboidBlocks(region, new SingleBlockPattern(block));
  1000. }
  1001. /**
  1002. * Places a layer of blocks on top of ground blocks in the given region
  1003. * (as if it were a cuboid).
  1004. *
  1005. * @param region the region
  1006. * @param pattern the placed block pattern
  1007. * @return number of blocks affected
  1008. * @throws MaxChangedBlocksException thrown if too many blocks are changed
  1009. */
  1010. @SuppressWarnings("deprecation")
  1011. public int overlayCuboidBlocks(Region region, Pattern pattern) throws MaxChangedBlocksException {
  1012. checkNotNull(region);
  1013. checkNotNull(pattern);
  1014. BlockReplace replace = new BlockReplace(this, Patterns.wrap(pattern));
  1015. RegionOffset offset = new RegionOffset(new Vector(0, 1, 0), replace);
  1016. GroundFunction ground = new GroundFunction(new ExistingBlockMask(this), offset);
  1017. LayerVisitor visitor = new LayerVisitor(asFlatRegion(region), minimumBlockY(region), maximumBlockY(region), ground);
  1018. Operations.completeLegacy(visitor);
  1019. return ground.getAffected();
  1020. }
  1021. /**
  1022. * Turns the first 3 layers into dirt/grass and the bottom layers
  1023. * into rock, like a natural Minecraft mountain.
  1024. *
  1025. * @param region the region to affect
  1026. * @return number of blocks affected
  1027. * @throws MaxChangedBlocksException thrown if too many blocks are changed
  1028. */
  1029. public int naturalizeCuboidBlocks(Region region) throws MaxChangedBlocksException {
  1030. checkNotNull(region);
  1031. Naturalizer naturalizer = new Naturalizer(this);
  1032. FlatRegion flatRegion = Regions.asFlatRegion(region);
  1033. LayerVisitor visitor = new LayerVisitor(flatRegion, minimumBlockY(region), maximumBlockY(region), naturalizer);
  1034. Operations.completeLegacy(visitor);
  1035. return naturalizer.getAffected();
  1036. }
  1037. /**
  1038. * Stack a cuboid region.
  1039. *
  1040. * @param region the region to stack
  1041. * @param dir the direction to stack
  1042. * @param count the number of times to stack
  1043. * @param copyAir true to also copy air blocks
  1044. * @return number of blocks affected
  1045. * @throws MaxChangedBlocksException thrown if too many blocks are changed
  1046. */
  1047. public int stackCuboidRegion(Region region, Vector dir, int count, boolean copyAir) throws MaxChangedBlocksException {
  1048. checkNotNull(region);
  1049. checkNotNull(dir);
  1050. checkArgument(count >= 1, "count >= 1 required");
  1051. Vector size = region.getMaximumPoint().subtract(region.getMinimumPoint()).add(1, 1, 1);
  1052. Vector to = region.getMinimumPoint();
  1053. ForwardExtentCopy copy = new ForwardExtentCopy(this, region, this, to);
  1054. copy.setRepetitions(count);
  1055. copy.setTransform(new AffineTransform().translate(dir.multiply(size)));
  1056. if (!copyAir) {
  1057. copy.setSourceMask(new ExistingBlockMask(this));
  1058. }
  1059. Operations.completeLegacy(copy);
  1060. return copy.getAffected();
  1061. }
  1062. /**
  1063. * Move the blocks in a region a certain direction.
  1064. *
  1065. * @param region the region to move
  1066. * @param dir the direction
  1067. * @param distance the distance to move
  1068. * @param copyAir true to copy air blocks
  1069. * @param replacement the replacement block to fill in after moving, or null to use air
  1070. * @return number of blocks moved
  1071. * @throws MaxChangedBlocksException thrown if too many blocks are changed
  1072. */
  1073. public int moveRegion(Region region, Vector dir, int distance, boolean copyAir, BaseBlock replacement) throws MaxChangedBlocksException {
  1074. checkNotNull(region);
  1075. checkNotNull(dir);
  1076. checkArgument(distance >= 1, "distance >= 1 required");
  1077. Vector to = region.getMinimumPoint();
  1078. // Remove the original blocks
  1079. com.sk89q.worldedit.function.pattern.Pattern pattern = replacement != null ?
  1080. new BlockPattern(replacement) :
  1081. new BlockPattern(new BaseBlock(BlockID.AIR));
  1082. BlockReplace remove = new BlockReplace(this, pattern);
  1083. // Copy to a buffer so we don't destroy our original before we can copy all the blocks from it
  1084. ForgetfulExtentBuffer buffer = new ForgetfulExtentBuffer(this, new RegionMask(region));
  1085. ForwardExtentCopy copy = new ForwardExtentCopy(this, region, buffer, to);
  1086. copy.setTransform(new AffineTransform().translate(dir.multiply(distance)));
  1087. copy.setSourceFunction(remove); // Remove
  1088. copy.setRemovingEntities(true);
  1089. if (!copyAir) {
  1090. copy.setSourceMask(new ExistingBlockMask(this));
  1091. }
  1092. // Then we need to copy the buffer to the world
  1093. BlockReplace replace = new BlockReplace(this, buffer);
  1094. RegionVisitor visitor = new RegionVisitor(buffer.asRegion(), replace);
  1095. OperationQueue operation = new OperationQueue(copy, visitor);
  1096. Operations.completeLegacy(operation);
  1097. return copy.getAffected();
  1098. }
  1099. /**
  1100. * Move the blocks in a region a certain direction.
  1101. *
  1102. * @param region the region to move
  1103. * @param dir the direction
  1104. * @param distance the distance to move
  1105. * @param copyAir true to copy air blocks
  1106. * @param replacement the replacement block to fill in after moving, or null to use air
  1107. * @return number of blocks moved
  1108. * @throws MaxChangedBlocksException thrown if too many blocks are changed
  1109. */
  1110. public int moveCuboidRegion(Region region, Vector dir, int distance, boolean copyAir, BaseBlock replacement) throws MaxChangedBlocksException {
  1111. return moveRegion(region, dir, distance, copyAir, replacement);
  1112. }
  1113. /**
  1114. * Drain nearby pools of water or lava.
  1115. *
  1116. * @param origin the origin to drain from, which will search a 3x3 area
  1117. * @param radius the radius of the removal, where a value should be 0 or greater
  1118. * @return number of blocks affected
  1119. * @throws MaxChangedBlocksException thrown if too many blocks are changed
  1120. */
  1121. public int drainArea(Vector origin, double radius) throws MaxChangedBlocksException {
  1122. checkNotNull(origin);
  1123. checkArgument(radius >= 0, "radius >= 0 required");
  1124. MaskIntersection mask = new MaskIntersection(
  1125. new BoundedHeightMask(0, getWorld().getMaxY()),
  1126. new RegionMask(new EllipsoidRegion(null, origin, new Vector(radius, radius, radius))),
  1127. getWorld().createLiquidMask());
  1128. BlockReplace replace = new BlockReplace(this, new BlockPattern(new BaseBlock(BlockID.AIR)));
  1129. RecursiveVisitor visitor = new RecursiveVisitor(mask, replace);
  1130. // Around the origin in a 3x3 block
  1131. for (BlockVector position : CuboidRegion.fromCenter(origin, 1)) {
  1132. if (mask.test(position)) {
  1133. visitor.visit(position);
  1134. }
  1135. }
  1136. Operations.completeLegacy(visitor);
  1137. return visitor.getAffected();
  1138. }
  1139. /**
  1140. * Fix liquids so that they turn into stationary blocks and extend outward.
  1141. *
  1142. * @param origin the original position
  1143. * @param radius the radius to fix
  1144. * @param moving the block ID of the moving liquid
  1145. * @param stationary the block ID of the stationary liquid
  1146. * @return number of blocks affected
  1147. * @throws MaxChangedBlocksException thrown if too many blocks are changed
  1148. */
  1149. public int fixLiquid(Vector origin, double radius, int moving, int stationary) throws MaxChangedBlocksException {
  1150. checkNotNull(origin);
  1151. checkArgument(radius >= 0, "radius >= 0 required");
  1152. // Our origins can only be liquids
  1153. BlockMask liquidMask = new BlockMask(
  1154. this,
  1155. new BaseBlock(moving, -1),
  1156. new BaseBlock(stationary, -1));
  1157. // But we will also visit air blocks
  1158. MaskIntersection blockMask =
  1159. new MaskUnion(liquidMask,
  1160. new BlockMask(
  1161. this,
  1162. new BaseBlock(BlockID.AIR)));
  1163. // There are boundaries that the routine needs to stay in
  1164. MaskIntersection mask = new MaskIntersection(
  1165. new BoundedHeightMask(0, Math.min(origin.getBlockY(), getWorld().getMaxY())),
  1166. new RegionMask(new EllipsoidRegion(null, origin, new Vector(radius, radius, radius))),
  1167. blockMask);
  1168. BlockReplace replace = new BlockReplace(this, new BlockPattern(new BaseBlock(stationary)));
  1169. NonRisingVisitor visitor = new NonRisingVisitor(mask, replace);
  1170. // Around the origin in a 3x3 block
  1171. for (BlockVector position : CuboidRegion.fromCenter(origin, 1)) {
  1172. if (liquidMask.test(position)) {
  1173. visitor.visit(position);
  1174. }
  1175. }
  1176. Operations.completeLegacy(visitor);
  1177. return visitor.getAffected();
  1178. }
  1179. /**
  1180. * Makes a cylinder.
  1181. *
  1182. * @param pos Center of the cylinder
  1183. * @param block The block pattern to use
  1184. * @param radius The cylinder's radius
  1185. * @param height The cylinder's up/down extent. If negative, extend downward.
  1186. * @param filled If false, only a shell will be generated.
  1187. * @return number of blocks changed
  1188. * @throws MaxChangedBlocksException thrown if too many blocks are changed
  1189. */
  1190. public int makeCylinder(Vector pos, Pattern block, double radius, int height, boolean filled) throws MaxChangedBlocksException {
  1191. return makeCylinder(pos, block, radi