mirror of
https://github.com/plexusorg/Plex-FAWE.git
synced 2024-08-28 11:24:34 +00:00
50ab8ad5c7
* Feature/main/propagate diff annotations (#1187) * 25% done * More work * More work * 50% * More work * 75% * 100% & cleanup * Update adapters * Squish squash, applesauce commit275ba9bd84
Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Sat Jul 17 01:10:20 2021 +0200 Update dependency com.comphenix.protocol:ProtocolLib to v4.7.0 (#1173) Co-authored-by: Renovate Bot <bot@renovateapp.com> commit9fd8984804
Author: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Date: Sat Jul 17 01:09:29 2021 +0200 Update dependency org.checkerframework:checker-qual to v3.16.0 (#1184) Co-authored-by: Renovate Bot <bot@renovateapp.com> commit861fb45e5c
Author: dordsor21 <dordsor21@gmail.com> Date: Fri Jul 16 19:07:02 2021 +0100 Fix #1075 commit420c45a29a
Author: dordsor21 <dordsor21@gmail.com> Date: Fri Jul 16 18:48:21 2021 +0100 Entity removal should be on the main thread as we're just passing through rather than doing chunk operations - Fixes #1164 - Not working: butcher/remove history commit4d4db7dcd0
Author: SirYwell <hannesgreule@outlook.de> Date: Fri Jul 16 17:52:44 2021 +0200 Make sure leaves category is loaded for heightmaps (fixes #1176) commitc98f6e4f37
Author: dordsor21 <dordsor21@gmail.com> Date: Fri Jul 16 10:44:52 2021 +0100 Do not allow generation commands to generate outside selection commit2485f5eccc
Author: dordsor21 <dordsor21@gmail.com> Date: Fri Jul 16 10:43:15 2021 +0100 EditSession needs to override some Extent methods to ensure block changes are correctly set through the various extents Fixes #1152 commitd9418ec8ae
Author: dordsor21 <dordsor21@gmail.com> Date: Fri Jul 16 09:52:44 2021 +0100 Undo part of41073bb1a0
Fixes #1178 * Update Upstream fb1fb84 Fixed typo and grammar * We don't support custom heights yet * Casing inconsistency * Address a few comments * Address comments * Don't refactor to AP classpath * Document annotation style * Refactoring & shade cleanup * Address a few comments * More work * Resolve comments not being resolved yet * Feature/main/propagate diff annotations (#1187) (#1194) * Remove beta package, fix history packages, move classes out of object package * Resolve comments not being resolved yet * Remove beta package, fix history packages, move classes out of object package Co-authored-by: NotMyFault <mc.cache@web.de> * brushes should be under brush * More refactoring - Filters/processors should be in the same place and are related to extents - Transforms are in `extent.transform` in upstream * Move history classes under history * Update adapters Co-authored-by: dordsor21 <dordsor21@gmail.com>
1739 lines
54 KiB
Java
1739 lines
54 KiB
Java
/*
|
|
* WorldEdit, a Minecraft world manipulation toolkit
|
|
* Copyright (C) sk89q <http://www.sk89q.com>
|
|
* Copyright (C) WorldEdit team and contributors
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
package com.sk89q.worldedit;
|
|
|
|
import com.fastasyncworldedit.core.Fawe;
|
|
import com.fastasyncworldedit.core.configuration.Caption;
|
|
import com.fastasyncworldedit.core.configuration.Settings;
|
|
import com.fastasyncworldedit.core.internal.io.FaweInputStream;
|
|
import com.fastasyncworldedit.core.object.FaweLimit;
|
|
import com.fastasyncworldedit.core.internal.io.FaweOutputStream;
|
|
import com.fastasyncworldedit.core.history.DiskStorageHistory;
|
|
import com.fastasyncworldedit.core.extent.clipboard.MultiClipboardHolder;
|
|
import com.fastasyncworldedit.core.extent.ResettableExtent;
|
|
import com.fastasyncworldedit.core.util.BrushCache;
|
|
import com.fastasyncworldedit.core.util.EditSessionBuilder;
|
|
import com.fastasyncworldedit.core.util.MainUtil;
|
|
import com.fastasyncworldedit.core.util.StringMan;
|
|
import com.fastasyncworldedit.core.util.TextureHolder;
|
|
import com.fastasyncworldedit.core.util.TextureUtil;
|
|
import com.fastasyncworldedit.core.wrappers.WorldWrapper;
|
|
import com.sk89q.jchronic.Chronic;
|
|
import com.sk89q.jchronic.Options;
|
|
import com.sk89q.jchronic.utils.Span;
|
|
import com.sk89q.jchronic.utils.Time;
|
|
import com.sk89q.worldedit.blocks.BaseItem;
|
|
import com.sk89q.worldedit.blocks.BaseItemStack;
|
|
import com.sk89q.worldedit.command.tool.BlockTool;
|
|
import com.sk89q.worldedit.command.tool.BrushTool;
|
|
import com.sk89q.worldedit.command.tool.InvalidToolBindException;
|
|
import com.sk89q.worldedit.command.tool.NavigationWand;
|
|
import com.sk89q.worldedit.command.tool.SelectionWand;
|
|
import com.sk89q.worldedit.command.tool.SinglePickaxe;
|
|
import com.sk89q.worldedit.command.tool.Tool;
|
|
import com.sk89q.worldedit.entity.Player;
|
|
import com.sk89q.worldedit.extension.platform.Actor;
|
|
import com.sk89q.worldedit.extension.platform.Locatable;
|
|
import com.sk89q.worldedit.extent.inventory.BlockBag;
|
|
import com.sk89q.worldedit.function.mask.Mask;
|
|
import com.sk89q.worldedit.function.operation.ChangeSetExecutor;
|
|
import com.sk89q.worldedit.history.changeset.ChangeSet;
|
|
import com.sk89q.worldedit.internal.cui.CUIEvent;
|
|
import com.sk89q.worldedit.internal.cui.CUIRegion;
|
|
import com.sk89q.worldedit.internal.cui.SelectionShapeEvent;
|
|
import com.sk89q.worldedit.internal.cui.ServerCUIHandler;
|
|
import com.sk89q.worldedit.math.BlockVector3;
|
|
import com.sk89q.worldedit.regions.Region;
|
|
import com.sk89q.worldedit.regions.RegionSelector;
|
|
import com.sk89q.worldedit.regions.selector.CuboidRegionSelector;
|
|
import com.sk89q.worldedit.regions.selector.RegionSelectorType;
|
|
import com.sk89q.worldedit.session.ClipboardHolder;
|
|
import com.sk89q.worldedit.util.Countable;
|
|
import com.sk89q.worldedit.util.HandSide;
|
|
import com.sk89q.worldedit.util.Identifiable;
|
|
import com.sk89q.worldedit.util.SideEffectSet;
|
|
import com.sk89q.worldedit.util.nbt.CompoundBinaryTag;
|
|
import com.sk89q.worldedit.world.World;
|
|
import com.sk89q.worldedit.world.block.BaseBlock;
|
|
import com.sk89q.worldedit.world.block.BlockState;
|
|
import com.sk89q.worldedit.world.item.ItemType;
|
|
import com.sk89q.worldedit.world.item.ItemTypes;
|
|
import com.sk89q.worldedit.world.snapshot.experimental.Snapshot;
|
|
import com.zaxxer.sparsebits.SparseBitSet;
|
|
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
|
|
|
import java.io.File;
|
|
import java.io.FileInputStream;
|
|
import java.io.FileOutputStream;
|
|
import java.io.IOException;
|
|
import java.time.ZoneId;
|
|
import java.util.Calendar;
|
|
import java.util.Collections;
|
|
import java.util.LinkedList;
|
|
import java.util.List;
|
|
import java.util.ListIterator;
|
|
import java.util.Objects;
|
|
import java.util.TimeZone;
|
|
import java.util.UUID;
|
|
import java.util.concurrent.atomic.AtomicBoolean;
|
|
import java.util.concurrent.locks.Lock;
|
|
import java.util.concurrent.locks.ReentrantLock;
|
|
import java.util.stream.Collectors;
|
|
import javax.annotation.Nonnull;
|
|
import javax.annotation.Nullable;
|
|
|
|
import static com.google.common.base.Preconditions.checkNotNull;
|
|
|
|
/**
|
|
* Stores session information.
|
|
*/
|
|
public class LocalSession implements TextureHolder {
|
|
|
|
private static final transient int CUI_VERSION_UNINITIALIZED = -1;
|
|
public static transient int MAX_HISTORY_SIZE = 15;
|
|
|
|
// Non-session related fields
|
|
private transient LocalConfiguration config;
|
|
private final transient AtomicBoolean dirty = new AtomicBoolean();
|
|
|
|
// Single-connection lifetime fields
|
|
private transient int failedCuiAttempts = 0;
|
|
private transient boolean hasCUISupport = false;
|
|
private transient int cuiVersion = CUI_VERSION_UNINITIALIZED;
|
|
|
|
// Session related
|
|
private transient RegionSelector selector = new CuboidRegionSelector();
|
|
private transient boolean placeAtPos1 = false;
|
|
//FAWE start
|
|
private final transient List<Object> history = Collections.synchronizedList(new LinkedList<>() {
|
|
@Override
|
|
public Object get(int index) {
|
|
Object value = super.get(index);
|
|
if (value instanceof Integer) {
|
|
value = getChangeSet(value);
|
|
set(index, value);
|
|
}
|
|
return value;
|
|
}
|
|
|
|
@Override
|
|
public Object remove(int index) {
|
|
return getChangeSet(super.remove(index));
|
|
}
|
|
});
|
|
private transient volatile Integer historyNegativeIndex;
|
|
private transient final Lock historyWriteLock = new ReentrantLock(true);
|
|
private final transient Int2ObjectOpenHashMap<Tool> tools = new Int2ObjectOpenHashMap<>(0);
|
|
private transient Mask sourceMask;
|
|
private transient TextureUtil texture;
|
|
private transient ResettableExtent transform = null;
|
|
private transient World currentWorld;
|
|
//FAWE end
|
|
private transient ClipboardHolder clipboard;
|
|
private transient final Object clipboardLock = new Object();
|
|
private transient boolean superPickaxe = false;
|
|
private transient BlockTool pickaxeMode = new SinglePickaxe();
|
|
private transient int maxBlocksChanged = -1;
|
|
private transient int maxTimeoutTime;
|
|
private transient boolean useInventory;
|
|
private transient com.sk89q.worldedit.world.snapshot.Snapshot snapshot;
|
|
private transient Snapshot snapshotExperimental;
|
|
private transient SideEffectSet sideEffectSet = SideEffectSet.defaults();
|
|
private transient Mask mask;
|
|
private transient ZoneId timezone = ZoneId.systemDefault();
|
|
private transient UUID uuid;
|
|
private transient volatile long historySize = 0;
|
|
|
|
private transient BlockVector3 cuiTemporaryBlock;
|
|
@SuppressWarnings("unused")
|
|
private transient EditSession.ReorderMode reorderMode = EditSession.ReorderMode.MULTI_STAGE;
|
|
private transient List<Countable<BlockState>> lastDistribution;
|
|
private transient World worldOverride;
|
|
private transient boolean tickingWatchdog = false;
|
|
private transient boolean hasBeenToldVersion;
|
|
|
|
// Saved properties
|
|
private String lastScript;
|
|
private RegionSelectorType defaultSelector;
|
|
private boolean useServerCUI = false; // Save this to not annoy players.
|
|
private ItemType wandItem;
|
|
private ItemType navWandItem;
|
|
|
|
/**
|
|
* Construct the object.
|
|
*
|
|
* <p>{@link #setConfiguration(LocalConfiguration)} should be called
|
|
* later with configuration.</p>
|
|
*/
|
|
public LocalSession() {
|
|
}
|
|
|
|
/**
|
|
* Construct the object.
|
|
*
|
|
* @param config the configuration
|
|
*/
|
|
public LocalSession(@Nullable LocalConfiguration config) {
|
|
this.config = config;
|
|
}
|
|
|
|
/**
|
|
* Set the configuration.
|
|
*
|
|
* @param config the configuration
|
|
*/
|
|
public void setConfiguration(LocalConfiguration config) {
|
|
checkNotNull(config);
|
|
this.config = config;
|
|
}
|
|
|
|
/**
|
|
* Called on post load of the session from persistent storage.
|
|
*/
|
|
public void postLoad() {
|
|
if (defaultSelector != null) {
|
|
this.selector = defaultSelector.createSelector();
|
|
}
|
|
}
|
|
|
|
//FAWE start
|
|
public boolean loadSessionHistoryFromDisk(UUID uuid, World world) {
|
|
if (world == null || uuid == null) {
|
|
return false;
|
|
}
|
|
if (Settings.IMP.HISTORY.USE_DISK) {
|
|
MAX_HISTORY_SIZE = Integer.MAX_VALUE;
|
|
}
|
|
world = WorldWrapper.unwrap(world);
|
|
if (!world.equals(currentWorld)) {
|
|
this.uuid = uuid;
|
|
// Save history
|
|
saveHistoryNegativeIndex(uuid, currentWorld);
|
|
history.clear();
|
|
currentWorld = world;
|
|
// Load history
|
|
if (loadHistoryChangeSets(uuid, currentWorld)) {
|
|
loadHistoryNegativeIndex(uuid, currentWorld);
|
|
return true;
|
|
}
|
|
historyNegativeIndex = 0;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private boolean loadHistoryChangeSets(UUID uuid, World world) {
|
|
SparseBitSet set = new SparseBitSet();
|
|
final File folder = MainUtil.getFile(Fawe.imp().getDirectory(), Settings.IMP.PATHS.HISTORY + File.separator + world.getName() + File.separator + uuid);
|
|
if (folder.isDirectory()) {
|
|
folder.listFiles(pathname -> {
|
|
String name = pathname.getName();
|
|
Integer val = null;
|
|
if (pathname.isDirectory()) {
|
|
val = StringMan.toInteger(name, 0, name.length());
|
|
|
|
} else {
|
|
int i = name.lastIndexOf('.');
|
|
if (i != -1) {
|
|
val = StringMan.toInteger(name, 0, i);
|
|
}
|
|
}
|
|
if (val != null) {
|
|
set.set(val);
|
|
}
|
|
return false;
|
|
});
|
|
}
|
|
if (!set.isEmpty()) {
|
|
historySize = MainUtil.getTotalSize(folder.toPath());
|
|
for (int index = set.nextSetBit(0); index != -1; index = set.nextSetBit(index + 1)) {
|
|
history.add(index);
|
|
}
|
|
} else {
|
|
historySize = 0;
|
|
}
|
|
return !set.isEmpty();
|
|
}
|
|
|
|
private void loadHistoryNegativeIndex(UUID uuid, World world) {
|
|
if (!Settings.IMP.HISTORY.USE_DISK) {
|
|
return;
|
|
}
|
|
File file = MainUtil.getFile(Fawe.imp().getDirectory(), Settings.IMP.PATHS.HISTORY + File.separator + world.getName() + File.separator + uuid + File.separator + "index");
|
|
if (file.exists()) {
|
|
try (FaweInputStream is = new FaweInputStream(new FileInputStream(file))) {
|
|
historyNegativeIndex = Math.min(Math.max(0, is.readInt()), history.size());
|
|
} catch (IOException e) {
|
|
e.printStackTrace();
|
|
}
|
|
} else {
|
|
historyNegativeIndex = 0;
|
|
}
|
|
}
|
|
|
|
private void saveHistoryNegativeIndex(UUID uuid, World world) {
|
|
if (world == null || !Settings.IMP.HISTORY.USE_DISK) {
|
|
return;
|
|
}
|
|
File file = MainUtil.getFile(Fawe.imp().getDirectory(), Settings.IMP.PATHS.HISTORY + File.separator + world.getName() + File.separator + uuid + File.separator + "index");
|
|
if (getHistoryNegativeIndex() != 0) {
|
|
try {
|
|
if (!file.exists()) {
|
|
file.getParentFile().mkdirs();
|
|
file.createNewFile();
|
|
}
|
|
try (FaweOutputStream os = new FaweOutputStream(new FileOutputStream(file))) {
|
|
os.writeInt(getHistoryNegativeIndex());
|
|
}
|
|
} catch (IOException e) {
|
|
e.printStackTrace();
|
|
}
|
|
} else if (file.exists()) {
|
|
file.delete();
|
|
}
|
|
}
|
|
//FAWE end
|
|
|
|
/**
|
|
* Get whether this session is "dirty" and has changes that needs to
|
|
* be committed.
|
|
*
|
|
* @return true if dirty
|
|
*/
|
|
public boolean isDirty() {
|
|
return dirty.get();
|
|
}
|
|
|
|
/**
|
|
* Set this session as dirty.
|
|
*/
|
|
private void setDirty() {
|
|
dirty.set(true);
|
|
}
|
|
|
|
//FAWE start
|
|
public int getHistoryIndex() {
|
|
return history.size() - 1 - (historyNegativeIndex == null ? 0 : historyNegativeIndex);
|
|
}
|
|
|
|
public int getHistoryNegativeIndex() {
|
|
return (historyNegativeIndex == null ? historyNegativeIndex = 0 : historyNegativeIndex);
|
|
}
|
|
|
|
public List<ChangeSet> getHistory() {
|
|
return history.stream().map(this::getChangeSet).collect(Collectors.toList());
|
|
}
|
|
|
|
public boolean save() {
|
|
saveHistoryNegativeIndex(uuid, currentWorld);
|
|
if (defaultSelector == RegionSelectorType.CUBOID) {
|
|
defaultSelector = null;
|
|
}
|
|
if (lastScript != null || defaultSelector != null) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
//FAWE end
|
|
|
|
/**
|
|
* Get whether this session is "dirty" and has changes that needs to
|
|
* be committed, and reset it to {@code false}.
|
|
*
|
|
* @return true if the dirty value was {@code true}
|
|
*/
|
|
public boolean compareAndResetDirty() {
|
|
return dirty.compareAndSet(true, false);
|
|
}
|
|
|
|
/**
|
|
* Get the session's timezone.
|
|
*
|
|
* @return the timezone
|
|
*/
|
|
public ZoneId getTimeZone() {
|
|
return timezone;
|
|
}
|
|
|
|
/**
|
|
* Set the session's timezone.
|
|
*
|
|
* @param timezone the user's timezone
|
|
*/
|
|
public void setTimezone(ZoneId timezone) {
|
|
checkNotNull(timezone);
|
|
this.timezone = timezone;
|
|
}
|
|
|
|
/**
|
|
* Clear history.
|
|
*/
|
|
public void clearHistory() {
|
|
history.clear();
|
|
//FAWE start
|
|
historyNegativeIndex = 0;
|
|
historySize = 0;
|
|
currentWorld = null;
|
|
//FAWE end
|
|
}
|
|
|
|
/**
|
|
* Remember an edit session for the undo history. If the history maximum
|
|
* size is reached, old edit sessions will be discarded.
|
|
*
|
|
* @param editSession the edit session
|
|
*/
|
|
public void remember(EditSession editSession) {
|
|
checkNotNull(editSession);
|
|
|
|
// Don't store anything if no changes were made
|
|
if (editSession.size() == 0) {
|
|
return;
|
|
}
|
|
|
|
//FAWE start
|
|
Player player = editSession.getPlayer();
|
|
int limit = player == null ? Integer.MAX_VALUE : player.getLimit().MAX_HISTORY;
|
|
remember(editSession, true, limit);
|
|
}
|
|
|
|
private ChangeSet getChangeSet(Object o) {
|
|
if (o instanceof ChangeSet) {
|
|
ChangeSet cs = (ChangeSet) o;
|
|
try {
|
|
cs.close();
|
|
} catch (IOException e) {
|
|
e.printStackTrace();
|
|
return null;
|
|
}
|
|
return cs;
|
|
}
|
|
if (o instanceof Integer) {
|
|
File folder = MainUtil.getFile(Fawe.imp().getDirectory(), Settings.IMP.PATHS.HISTORY + File.separator + currentWorld.getName() + File.separator + uuid);
|
|
File specific = new File(folder, o.toString());
|
|
if (specific.isDirectory()) {
|
|
// TODO NOT IMPLEMENTED
|
|
// return new AnvilHistory(currentWorld.getName(), specific);
|
|
} else {
|
|
return new DiskStorageHistory(currentWorld, this.uuid, (Integer) o);
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public void remember(Identifiable player, World world, ChangeSet changeSet, FaweLimit limit) {
|
|
historyWriteLock.lock();
|
|
try {
|
|
if (Settings.IMP.HISTORY.USE_DISK) {
|
|
LocalSession.MAX_HISTORY_SIZE = Integer.MAX_VALUE;
|
|
}
|
|
if (changeSet.size() == 0) {
|
|
return;
|
|
}
|
|
loadSessionHistoryFromDisk(player.getUniqueId(), world);
|
|
if (changeSet instanceof ChangeSet) {
|
|
ListIterator<Object> iter = history.listIterator();
|
|
int i = 0;
|
|
int cutoffIndex = history.size() - getHistoryNegativeIndex();
|
|
while (iter.hasNext()) {
|
|
Object item = iter.next();
|
|
if (++i > cutoffIndex) {
|
|
ChangeSet oldChangeSet;
|
|
if (item instanceof ChangeSet) {
|
|
oldChangeSet = (ChangeSet) item;
|
|
} else {
|
|
oldChangeSet = getChangeSet(item);
|
|
}
|
|
historySize -= MainUtil.getSize(oldChangeSet);
|
|
iter.remove();
|
|
}
|
|
}
|
|
}
|
|
historySize += MainUtil.getSize(changeSet);
|
|
history.add(changeSet);
|
|
if (getHistoryNegativeIndex() != 0) {
|
|
setDirty();
|
|
historyNegativeIndex = 0;
|
|
}
|
|
if (limit != null) {
|
|
int limitMb = limit.MAX_HISTORY;
|
|
while (((!Settings.IMP.HISTORY.USE_DISK && history.size() > MAX_HISTORY_SIZE) || (historySize >> 20) > limitMb) && history.size() > 1) {
|
|
ChangeSet item = (ChangeSet) history.remove(0);
|
|
item.delete();
|
|
long size = MainUtil.getSize(item);
|
|
historySize -= size;
|
|
}
|
|
}
|
|
} finally {
|
|
historyWriteLock.unlock();
|
|
}
|
|
}
|
|
|
|
public void remember(EditSession editSession, boolean append, int limitMb) {
|
|
historyWriteLock.lock();
|
|
try {
|
|
if (Settings.IMP.HISTORY.USE_DISK) {
|
|
LocalSession.MAX_HISTORY_SIZE = Integer.MAX_VALUE;
|
|
}
|
|
// It should have already been flushed, but just in case!
|
|
editSession.flushQueue();
|
|
if (editSession.getChangeSet() == null || limitMb == 0 || historySize >> 20 > limitMb && !append) {
|
|
return;
|
|
}
|
|
|
|
ChangeSet changeSet = editSession.getChangeSet();
|
|
if (changeSet.isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
Player player = editSession.getPlayer();
|
|
if (player != null) {
|
|
loadSessionHistoryFromDisk(player.getUniqueId(), editSession.getWorld());
|
|
}
|
|
// Destroy any sessions after this undo point
|
|
if (append) {
|
|
ListIterator<Object> iter = history.listIterator();
|
|
int i = 0;
|
|
int cutoffIndex = history.size() - getHistoryNegativeIndex();
|
|
while (iter.hasNext()) {
|
|
Object item = iter.next();
|
|
if (++i > cutoffIndex) {
|
|
ChangeSet oldChangeSet;
|
|
if (item instanceof ChangeSet) {
|
|
oldChangeSet = (ChangeSet) item;
|
|
} else {
|
|
oldChangeSet = getChangeSet(item);
|
|
}
|
|
historySize -= MainUtil.getSize(oldChangeSet);
|
|
iter.remove();
|
|
}
|
|
}
|
|
}
|
|
|
|
historySize += MainUtil.getSize(changeSet);
|
|
if (append) {
|
|
history.add(changeSet);
|
|
if (getHistoryNegativeIndex() != 0) {
|
|
setDirty();
|
|
historyNegativeIndex = 0;
|
|
}
|
|
} else {
|
|
history.add(0, changeSet);
|
|
}
|
|
while (((!Settings.IMP.HISTORY.USE_DISK && history.size() > MAX_HISTORY_SIZE) || (historySize >> 20) > limitMb) && history.size() > 1) {
|
|
ChangeSet item = (ChangeSet) history.remove(0);
|
|
item.delete();
|
|
long size = MainUtil.getSize(item);
|
|
historySize -= size;
|
|
}
|
|
} finally {
|
|
historyWriteLock.unlock();
|
|
}
|
|
}
|
|
//FAWE end
|
|
|
|
/**
|
|
* Performs an undo.
|
|
*
|
|
* @param newBlockBag a new block bag
|
|
* @param actor the actor
|
|
* @return whether anything was undone
|
|
*/
|
|
public EditSession undo(@Nullable BlockBag newBlockBag, Actor actor) {
|
|
checkNotNull(actor);
|
|
//FAWE start - use our logic
|
|
World world = ((Player) actor).getWorldForEditing();
|
|
loadSessionHistoryFromDisk(actor.getUniqueId(), world);
|
|
if (getHistoryNegativeIndex() < history.size()) {
|
|
ChangeSet changeSet = getChangeSet(history.get(getHistoryIndex()));
|
|
EditSessionBuilder builder = new EditSessionBuilder(world)
|
|
.checkMemory(false)
|
|
.changeSetNull()
|
|
.fastmode(false)
|
|
.limitUnprocessed((Player)actor)
|
|
.player((Player)actor)
|
|
.blockBag(getBlockBag((Player)actor));
|
|
if (!actor.getLimit().RESTRICT_HISTORY_TO_REGIONS) {
|
|
builder.allowedRegionsEverywhere();
|
|
}
|
|
try (EditSession newEditSession = builder.build()) {
|
|
newEditSession.setBlocks(changeSet, ChangeSetExecutor.Type.UNDO);
|
|
setDirty();
|
|
historyNegativeIndex++;
|
|
return newEditSession;
|
|
}
|
|
} else {
|
|
int size = history.size();
|
|
if (getHistoryNegativeIndex() != size) {
|
|
historyNegativeIndex = history.size();
|
|
setDirty();
|
|
}
|
|
return null;
|
|
}
|
|
//FAWE end
|
|
}
|
|
|
|
/**
|
|
* Performs a redo
|
|
*
|
|
* @param newBlockBag a new block bag
|
|
* @param actor the actor
|
|
* @return whether anything was redone
|
|
*/
|
|
//FAWE start - use our logic
|
|
public EditSession redo(@Nullable BlockBag newBlockBag, Actor actor) {
|
|
checkNotNull(actor);
|
|
World world = ((Player) actor).getWorldForEditing();
|
|
loadSessionHistoryFromDisk(actor.getUniqueId(), world);
|
|
if (getHistoryNegativeIndex() > 0) {
|
|
setDirty();
|
|
historyNegativeIndex--;
|
|
ChangeSet changeSet = getChangeSet(history.get(getHistoryIndex()));
|
|
try (EditSession newEditSession = new EditSessionBuilder(world)
|
|
.allowedRegionsEverywhere()
|
|
.checkMemory(false)
|
|
.changeSetNull()
|
|
.fastmode(false)
|
|
.limitUnprocessed((Player)actor)
|
|
.player((Player)actor)
|
|
.blockBag(getBlockBag((Player)actor))
|
|
.build()) {
|
|
newEditSession.setBlocks(changeSet, ChangeSetExecutor.Type.REDO);
|
|
return newEditSession;
|
|
}
|
|
}
|
|
//FAWE end
|
|
|
|
return null;
|
|
}
|
|
|
|
public boolean hasWorldOverride() {
|
|
return this.worldOverride != null;
|
|
}
|
|
|
|
@Nullable
|
|
public World getWorldOverride() {
|
|
return this.worldOverride;
|
|
}
|
|
|
|
public void setWorldOverride(@Nullable World worldOverride) {
|
|
this.worldOverride = worldOverride;
|
|
}
|
|
|
|
public boolean isTickingWatchdog() {
|
|
return tickingWatchdog;
|
|
}
|
|
|
|
public void setTickingWatchdog(boolean tickingWatchdog) {
|
|
this.tickingWatchdog = tickingWatchdog;
|
|
}
|
|
|
|
/**
|
|
* Get the default region selector.
|
|
*
|
|
* @return the default region selector
|
|
*/
|
|
public RegionSelectorType getDefaultRegionSelector() {
|
|
return defaultSelector;
|
|
}
|
|
|
|
/**
|
|
* Set the default region selector.
|
|
*
|
|
* @param defaultSelector the default region selector
|
|
*/
|
|
public void setDefaultRegionSelector(RegionSelectorType defaultSelector) {
|
|
checkNotNull(defaultSelector);
|
|
this.defaultSelector = defaultSelector;
|
|
setDirty();
|
|
}
|
|
|
|
/**
|
|
* Get the region selector for defining the selection. If the selection
|
|
* was defined for a different world, the old selection will be discarded.
|
|
*
|
|
* @param world the world
|
|
* @return position the position
|
|
*/
|
|
public RegionSelector getRegionSelector(World world) {
|
|
checkNotNull(world);
|
|
if (selector.getWorld() == null || !selector.getWorld().equals(world)) {
|
|
selector.setWorld(world);
|
|
selector.clear();
|
|
if (hasWorldOverride() && !world.equals(getWorldOverride())) {
|
|
setWorldOverride(null);
|
|
}
|
|
}
|
|
return selector;
|
|
}
|
|
|
|
/**
|
|
* Set the region selector.
|
|
*
|
|
* @param world the world
|
|
* @param selector the selector
|
|
*/
|
|
public void setRegionSelector(World world, RegionSelector selector) {
|
|
checkNotNull(world);
|
|
checkNotNull(selector);
|
|
selector.setWorld(world);
|
|
this.selector = selector;
|
|
if (hasWorldOverride() && !world.equals(getWorldOverride())) {
|
|
setWorldOverride(null);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns true if the region is fully defined for the specified world.
|
|
*
|
|
* @param world the world
|
|
* @return true if a region selection is defined
|
|
*/
|
|
public boolean isSelectionDefined(World world) {
|
|
checkNotNull(world);
|
|
if (selector.getIncompleteRegion().getWorld() == null || !selector.getIncompleteRegion().getWorld().equals(world)) {
|
|
return false;
|
|
}
|
|
return selector.isDefined();
|
|
}
|
|
|
|
/**
|
|
* Get the selection region. If you change the region, you should
|
|
* call learnRegionChanges(). If the selection is not fully defined,
|
|
* the {@code IncompleteRegionException} exception will be thrown.
|
|
*
|
|
* <p>Note that this method will return a region in the current selection world,
|
|
* which is not guaranteed to be the player's world or even the current world
|
|
* override. If you require a specific world, use the
|
|
* {@link LocalSession#getSelection(World)} overload instead.
|
|
*
|
|
* @return the selected region
|
|
* @throws IncompleteRegionException if the region is not fully defined
|
|
*/
|
|
public Region getSelection() throws IncompleteRegionException {
|
|
return getSelection(getSelectionWorld());
|
|
}
|
|
|
|
/**
|
|
* Get the selection region. If you change the region, you should
|
|
* call learnRegionChanges(). If the selection is defined in
|
|
* a different world, or the selection isn't fully defined,
|
|
* the {@code IncompleteRegionException} exception will be thrown.
|
|
*
|
|
* @param world the world
|
|
* @return a region
|
|
* @throws IncompleteRegionException if no region is selected, or the provided world is null
|
|
*/
|
|
public Region getSelection(@Nullable World world) throws IncompleteRegionException {
|
|
if (world == null || selector.getIncompleteRegion().getWorld() == null
|
|
|| !selector.getIncompleteRegion().getWorld().equals(world)) {
|
|
throw new IncompleteRegionException() {
|
|
@Override
|
|
public synchronized Throwable fillInStackTrace() {
|
|
return this;
|
|
}
|
|
};
|
|
}
|
|
return selector.getRegion();
|
|
}
|
|
|
|
/**
|
|
* Get the selection world.
|
|
*
|
|
* @return the the world of the selection
|
|
*/
|
|
@Nullable
|
|
public World getSelectionWorld() {
|
|
World world = selector.getIncompleteRegion().getWorld();
|
|
if (world instanceof WorldWrapper) {
|
|
return ((WorldWrapper) world).getParent();
|
|
}
|
|
return world;
|
|
}
|
|
|
|
/**
|
|
* Gets the clipboard.
|
|
*
|
|
* @return clipboard
|
|
* @throws EmptyClipboardException thrown if no clipboard is set
|
|
*/
|
|
public ClipboardHolder getClipboard() throws EmptyClipboardException {
|
|
//FAWE start
|
|
synchronized (clipboardLock) {
|
|
if (clipboard == null) {
|
|
throw new EmptyClipboardException();
|
|
}
|
|
//FAWE end
|
|
return clipboard;
|
|
}
|
|
}
|
|
|
|
//FAWE start
|
|
@Nullable
|
|
public ClipboardHolder getExistingClipboard() {
|
|
synchronized (clipboardLock) {
|
|
if (clipboard == null) {
|
|
return null;
|
|
}
|
|
return clipboard;
|
|
}
|
|
}
|
|
|
|
public void addClipboard(@Nonnull MultiClipboardHolder toAppend) {
|
|
checkNotNull(toAppend);
|
|
ClipboardHolder existing = getExistingClipboard();
|
|
MultiClipboardHolder multi;
|
|
if (existing instanceof MultiClipboardHolder) {
|
|
multi = (MultiClipboardHolder) existing;
|
|
for (ClipboardHolder holder : toAppend.getHolders()) {
|
|
multi.add(holder);
|
|
}
|
|
} else {
|
|
multi = toAppend;
|
|
if (existing != null) {
|
|
multi.add(existing);
|
|
}
|
|
}
|
|
setClipboard(multi);
|
|
}
|
|
//FAWE end
|
|
|
|
/**
|
|
* Sets the clipboard.
|
|
*
|
|
* <p>Pass {@code null} to clear the clipboard.</p>
|
|
*
|
|
* @param clipboard the clipboard, or null if the clipboard is to be cleared
|
|
*/
|
|
public void setClipboard(@Nullable ClipboardHolder clipboard) {
|
|
//FAWE start
|
|
synchronized (clipboardLock) {
|
|
if (this.clipboard == clipboard) {
|
|
return;
|
|
}
|
|
|
|
if (this.clipboard != null) {
|
|
if (clipboard == null || !clipboard.contains(this.clipboard.getClipboard())) {
|
|
this.clipboard.close();
|
|
}
|
|
}
|
|
//FAWE end
|
|
this.clipboard = clipboard;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return true always - see deprecation notice
|
|
* @deprecated The wand is now a tool that can be bound/unbound.
|
|
*/
|
|
@Deprecated
|
|
public boolean isToolControlEnabled() {
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @param toolControl unused - see deprecation notice
|
|
* @deprecated The wand is now a tool that can be bound/unbound.
|
|
*/
|
|
@Deprecated
|
|
public void setToolControl(boolean toolControl) {
|
|
}
|
|
|
|
/**
|
|
* Get the maximum number of blocks that can be changed in an edit session.
|
|
*
|
|
* @return block change limit
|
|
*/
|
|
public int getBlockChangeLimit() {
|
|
return maxBlocksChanged;
|
|
}
|
|
|
|
/**
|
|
* Set the maximum number of blocks that can be changed.
|
|
*
|
|
* @param maxBlocksChanged the maximum number of blocks changed
|
|
*/
|
|
public void setBlockChangeLimit(int maxBlocksChanged) {
|
|
this.maxBlocksChanged = maxBlocksChanged;
|
|
}
|
|
|
|
/**
|
|
* Get the maximum time allowed for certain executions to run before cancelling them, such as expressions.
|
|
*
|
|
* @return timeout time, in milliseconds
|
|
*/
|
|
public int getTimeout() {
|
|
return maxTimeoutTime;
|
|
}
|
|
|
|
/**
|
|
* Set the maximum number of blocks that can be changed.
|
|
*
|
|
* @param timeout the time, in milliseconds, to limit certain executions to, or -1 to disable
|
|
*/
|
|
public void setTimeout(int timeout) {
|
|
this.maxTimeoutTime = timeout;
|
|
}
|
|
|
|
/**
|
|
* Checks whether the super pick axe is enabled.
|
|
*
|
|
* @return status
|
|
*/
|
|
public boolean hasSuperPickAxe() {
|
|
return superPickaxe;
|
|
}
|
|
|
|
/**
|
|
* Enable super pick axe.
|
|
*/
|
|
public void enableSuperPickAxe() {
|
|
superPickaxe = true;
|
|
}
|
|
|
|
/**
|
|
* Disable super pick axe.
|
|
*/
|
|
public void disableSuperPickAxe() {
|
|
superPickaxe = false;
|
|
}
|
|
|
|
/**
|
|
* Toggle the super pick axe.
|
|
*
|
|
* @return whether the super pick axe is now enabled
|
|
*/
|
|
public boolean toggleSuperPickAxe() {
|
|
superPickaxe = !superPickaxe;
|
|
return superPickaxe;
|
|
}
|
|
|
|
/**
|
|
* Get the position use for commands that take a center point
|
|
* (i.e. //forestgen, etc.).
|
|
*
|
|
* @param actor the actor
|
|
* @return the position to use
|
|
* @throws IncompleteRegionException thrown if a region is not fully selected
|
|
*/
|
|
public BlockVector3 getPlacementPosition(Actor actor) throws IncompleteRegionException {
|
|
checkNotNull(actor);
|
|
if (!placeAtPos1) {
|
|
if (actor instanceof Locatable) {
|
|
return ((Locatable) actor).getBlockLocation().toVector().toBlockPoint();
|
|
} else {
|
|
throw new IncompleteRegionException();
|
|
}
|
|
}
|
|
|
|
return selector.getPrimaryPosition();
|
|
}
|
|
|
|
public void setPlaceAtPos1(boolean placeAtPos1) {
|
|
this.placeAtPos1 = placeAtPos1;
|
|
}
|
|
|
|
public boolean isPlaceAtPos1() {
|
|
return placeAtPos1;
|
|
}
|
|
|
|
/**
|
|
* Toggle placement position.
|
|
*
|
|
* @return whether "place at position 1" is now enabled
|
|
*/
|
|
public boolean togglePlacementPosition() {
|
|
placeAtPos1 = !placeAtPos1;
|
|
return placeAtPos1;
|
|
}
|
|
|
|
/**
|
|
* Get a block bag for a player.
|
|
*
|
|
* @param player the player to get the block bag for
|
|
* @return a block bag
|
|
*/
|
|
@Nullable
|
|
public BlockBag getBlockBag(Player player) {
|
|
checkNotNull(player);
|
|
//FAWE start - inventory mode
|
|
if (!useInventory && player.getLimit().INVENTORY_MODE == 0) {
|
|
//FAWE end
|
|
return null;
|
|
}
|
|
return player.getInventoryBlockBag();
|
|
}
|
|
|
|
/**
|
|
* Get the legacy snapshot that has been selected.
|
|
*
|
|
* @return the legacy snapshot
|
|
*/
|
|
@Nullable
|
|
public com.sk89q.worldedit.world.snapshot.Snapshot getSnapshot() {
|
|
return snapshot;
|
|
}
|
|
|
|
/**
|
|
* Select a legacy snapshot.
|
|
*
|
|
* @param snapshot a legacy snapshot
|
|
*/
|
|
public void setSnapshot(@Nullable com.sk89q.worldedit.world.snapshot.Snapshot snapshot) {
|
|
this.snapshot = snapshot;
|
|
}
|
|
|
|
/**
|
|
* Get the snapshot that has been selected.
|
|
*
|
|
* @return the snapshot
|
|
*/
|
|
@Nullable
|
|
public Snapshot getSnapshotExperimental() {
|
|
return snapshotExperimental;
|
|
}
|
|
|
|
/**
|
|
* Select a snapshot.
|
|
*
|
|
* @param snapshotExperimental a snapshot
|
|
*/
|
|
public void setSnapshotExperimental(@Nullable Snapshot snapshotExperimental) {
|
|
this.snapshotExperimental = snapshotExperimental;
|
|
}
|
|
|
|
/**
|
|
* Get the assigned block tool.
|
|
*
|
|
* @return the super pickaxe tool mode
|
|
*/
|
|
public BlockTool getSuperPickaxe() {
|
|
return pickaxeMode;
|
|
}
|
|
|
|
/**
|
|
* Set the super pick axe tool.
|
|
*
|
|
* @param tool the tool to set
|
|
*/
|
|
public void setSuperPickaxe(BlockTool tool) {
|
|
checkNotNull(tool);
|
|
this.pickaxeMode = tool;
|
|
}
|
|
|
|
/**
|
|
* Get the tool assigned to the item.
|
|
*
|
|
* @param item the item type
|
|
* @return the tool, which may be {@code null}
|
|
*/
|
|
@Nullable
|
|
@Deprecated
|
|
public Tool getTool(ItemType item) {
|
|
synchronized (this.tools) {
|
|
return tools.get(item.getInternalId());
|
|
}
|
|
}
|
|
|
|
//FAWE start
|
|
@Nullable
|
|
public Tool getTool(Player player) {
|
|
loadDefaults(player, false);
|
|
if (!Settings.IMP.EXPERIMENTAL.PERSISTENT_BRUSHES && tools.isEmpty()) {
|
|
return null;
|
|
}
|
|
BaseItem item = player.getItemInHand(HandSide.MAIN_HAND);
|
|
return getTool(item, player);
|
|
}
|
|
|
|
private transient boolean loadDefaults = true;
|
|
|
|
public Tool getTool(BaseItem item, Player player) {
|
|
loadDefaults(player, false);
|
|
if (Settings.IMP.EXPERIMENTAL.PERSISTENT_BRUSHES && item.getNativeItem() != null) {
|
|
BrushTool tool = BrushCache.getTool(player, this, item);
|
|
if (tool != null) {
|
|
return tool;
|
|
}
|
|
}
|
|
return getTool(item.getType());
|
|
}
|
|
|
|
public void loadDefaults(Actor actor, boolean force) {
|
|
if (loadDefaults || force) {
|
|
loadDefaults = false;
|
|
LocalConfiguration config = WorldEdit.getInstance().getConfiguration();
|
|
if (wandItem == null) {
|
|
wandItem = ItemTypes.parse(config.wandItem);
|
|
}
|
|
if (navWandItem == null) {
|
|
navWandItem = ItemTypes.parse(config.navigationWand);
|
|
}
|
|
synchronized (this.tools) {
|
|
if (tools.get(navWandItem.getInternalId()) == null && NavigationWand.INSTANCE.canUse(actor)) {
|
|
tools.put(navWandItem.getInternalId(), NavigationWand.INSTANCE);
|
|
}
|
|
if (tools.get(wandItem.getInternalId()) == null && SelectionWand.INSTANCE.canUse(actor)) {
|
|
tools.put(wandItem.getInternalId(), SelectionWand.INSTANCE);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//FAWE end
|
|
|
|
//FAWE start - see deprecation note
|
|
/**
|
|
* Get the brush tool assigned to the item. If there is no tool assigned
|
|
* or the tool is not assigned, the slot will be replaced with the
|
|
* brush tool.
|
|
*
|
|
* @deprecated FAWE binds to the item, not the type - this allows brushes to persist
|
|
* @param item the item type
|
|
* @return the tool, or {@code null}
|
|
* @throws InvalidToolBindException if the item can't be bound to that item
|
|
*/
|
|
@Deprecated
|
|
public BrushTool getBrushTool(ItemType item) throws InvalidToolBindException {
|
|
return getBrushTool(item.getDefaultState(), null, true);
|
|
}
|
|
|
|
public BrushTool getBrushTool(Player player) throws InvalidToolBindException {
|
|
return getBrushTool(player, true);
|
|
}
|
|
|
|
public BrushTool getBrushTool(Player player, boolean create) throws InvalidToolBindException {
|
|
BaseItem item = player.getItemInHand(HandSide.MAIN_HAND);
|
|
return getBrushTool(item, player, create);
|
|
}
|
|
|
|
public BrushTool getBrushTool(BaseItem item, Player player, boolean create) throws InvalidToolBindException {
|
|
if (item.getType().hasBlockType()) {
|
|
throw new InvalidToolBindException(item.getType(), Caption.of("worldedit.error.blocks-cant-be-used"));
|
|
}
|
|
Tool tool = getTool(item, player);
|
|
if (!(tool instanceof BrushTool)) {
|
|
if (create) {
|
|
tool = new BrushTool();
|
|
setTool(item, tool, player);
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
return (BrushTool) tool;
|
|
}
|
|
//FAWE end
|
|
|
|
//FAWE start - see note of getBrushTool
|
|
/**
|
|
* Set the tool.
|
|
*
|
|
* @param item the item type
|
|
* @param tool the tool to set, which can be {@code null}
|
|
* @throws InvalidToolBindException if the item can't be bound to that item
|
|
*/
|
|
public void setTool(ItemType item, @Nullable Tool tool) throws InvalidToolBindException {
|
|
if (item.hasBlockType()) {
|
|
throw new InvalidToolBindException(item, Caption.of("worldedit.error.blocks-cant-be-used"));
|
|
}
|
|
if (tool instanceof SelectionWand) {
|
|
changeTool(this.wandItem, this.wandItem = item, tool);
|
|
setDirty();
|
|
return;
|
|
} else if (tool instanceof NavigationWand) {
|
|
changeTool(this.navWandItem, this.navWandItem = item, tool);
|
|
setDirty();
|
|
return;
|
|
}
|
|
setTool(item.getDefaultState(), tool, null);
|
|
}
|
|
|
|
public void setTool(Player player, @Nullable Tool tool) throws InvalidToolBindException {
|
|
BaseItemStack item = player.getItemInHand(HandSide.MAIN_HAND);
|
|
setTool(item, tool, player);
|
|
}
|
|
|
|
private void changeTool(ItemType oldType, ItemType newType, Tool newTool) {
|
|
if (oldType != null) {
|
|
synchronized (this.tools) {
|
|
this.tools.remove(oldType.getInternalId());
|
|
}
|
|
}
|
|
synchronized (this.tools) {
|
|
if (newTool == null) {
|
|
this.tools.remove(newType.getInternalId());
|
|
} else {
|
|
this.tools.put(newType.getInternalId(), newTool);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void setTool(BaseItem item, @Nullable Tool tool, Player player) throws InvalidToolBindException {
|
|
ItemType type = item.getType();
|
|
if (type.hasBlockType() && type.getBlockType().getMaterial().isAir()) {
|
|
throw new InvalidToolBindException(type, Caption.of("worldedit.error.blocks-cant-be-used"));
|
|
} else if (tool instanceof SelectionWand) {
|
|
changeTool(this.wandItem, this.wandItem = item.getType(), tool);
|
|
setDirty();
|
|
return;
|
|
} else if (tool instanceof NavigationWand) {
|
|
changeTool(this.navWandItem, this.navWandItem = item.getType(), tool);
|
|
setDirty();
|
|
return;
|
|
}
|
|
|
|
Tool previous;
|
|
if (player != null && (tool instanceof BrushTool || tool == null) && Settings.IMP.EXPERIMENTAL.PERSISTENT_BRUSHES && item.getNativeItem() != null) {
|
|
previous = BrushCache.getCachedTool(item);
|
|
BrushCache.setTool(item, (BrushTool) tool);
|
|
if (tool != null) {
|
|
((BrushTool) tool).setHolder(item);
|
|
} else {
|
|
synchronized (this.tools) {
|
|
this.tools.remove(type.getInternalId());
|
|
}
|
|
}
|
|
} else {
|
|
synchronized (this.tools) {
|
|
previous = this.tools.get(type.getInternalId());
|
|
if (tool != null) {
|
|
this.tools.put(type.getInternalId(), tool);
|
|
} else {
|
|
this.tools.remove(type.getInternalId());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//FAWE end
|
|
|
|
/**
|
|
* Returns whether inventory usage is enabled for this session.
|
|
*
|
|
* @return if inventory is being used
|
|
*/
|
|
public boolean isUsingInventory() {
|
|
return useInventory;
|
|
}
|
|
|
|
/**
|
|
* Set the state of inventory usage.
|
|
*
|
|
* @param useInventory if inventory is to be used
|
|
*/
|
|
public void setUseInventory(boolean useInventory) {
|
|
this.useInventory = useInventory;
|
|
}
|
|
|
|
/**
|
|
* Get the last script used.
|
|
*
|
|
* @return the last script's name
|
|
*/
|
|
@Nullable
|
|
public String getLastScript() {
|
|
return lastScript;
|
|
}
|
|
|
|
/**
|
|
* Set the last script used.
|
|
*
|
|
* @param lastScript the last script's name
|
|
*/
|
|
public void setLastScript(@Nullable String lastScript) {
|
|
this.lastScript = lastScript;
|
|
setDirty();
|
|
}
|
|
|
|
/**
|
|
* Tell the player the WorldEdit version.
|
|
*
|
|
* @param actor the actor
|
|
*/
|
|
public void tellVersion(Actor actor) {
|
|
if (hasBeenToldVersion) {
|
|
return;
|
|
}
|
|
hasBeenToldVersion = true;
|
|
actor.sendAnnouncements();
|
|
}
|
|
|
|
public boolean shouldUseServerCUI() {
|
|
return this.useServerCUI;
|
|
}
|
|
|
|
public void setUseServerCUI(boolean useServerCUI) {
|
|
this.useServerCUI = useServerCUI;
|
|
setDirty();
|
|
}
|
|
|
|
/**
|
|
* Update server-side WorldEdit CUI.
|
|
*
|
|
* @param actor The player
|
|
*/
|
|
public void updateServerCUI(Actor actor) {
|
|
if (!actor.isPlayer()) {
|
|
return; // This is for players only.
|
|
}
|
|
|
|
if (!config.serverSideCUI) {
|
|
return; // Disabled in config.
|
|
}
|
|
|
|
Player player = (Player) actor;
|
|
|
|
if (!useServerCUI || hasCUISupport) {
|
|
if (cuiTemporaryBlock != null) {
|
|
player.sendFakeBlock(cuiTemporaryBlock, null);
|
|
cuiTemporaryBlock = null;
|
|
}
|
|
return; // If it's not enabled, ignore this.
|
|
}
|
|
|
|
BaseBlock block = ServerCUIHandler.createStructureBlock(player);
|
|
if (block != null) {
|
|
CompoundBinaryTag tags = Objects.requireNonNull(
|
|
block.getNbt(), "createStructureBlock should return nbt"
|
|
);
|
|
BlockVector3 tempCuiTemporaryBlock = BlockVector3.at(
|
|
tags.getInt("x"),
|
|
tags.getInt("y"),
|
|
tags.getInt("z")
|
|
);
|
|
// If it's null, we don't need to do anything. The old was already removed.
|
|
if (cuiTemporaryBlock != null && !tempCuiTemporaryBlock.equals(cuiTemporaryBlock)) {
|
|
// Update the existing block if it's the same location
|
|
player.sendFakeBlock(cuiTemporaryBlock, null);
|
|
}
|
|
cuiTemporaryBlock = tempCuiTemporaryBlock;
|
|
player.sendFakeBlock(cuiTemporaryBlock, block);
|
|
} else if (cuiTemporaryBlock != null) {
|
|
// Remove the old block
|
|
player.sendFakeBlock(cuiTemporaryBlock, null);
|
|
cuiTemporaryBlock = null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Dispatch a CUI event but only if the actor has CUI support.
|
|
*
|
|
* @param actor the actor
|
|
* @param event the event
|
|
*/
|
|
public void dispatchCUIEvent(Actor actor, CUIEvent event) {
|
|
checkNotNull(actor);
|
|
checkNotNull(event);
|
|
|
|
if (hasCUISupport) {
|
|
actor.dispatchCUIEvent(event);
|
|
} else if (useServerCUI) {
|
|
updateServerCUI(actor);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Dispatch the initial setup CUI messages.
|
|
*
|
|
* @param actor the actor
|
|
*/
|
|
public void dispatchCUISetup(Actor actor) {
|
|
if (selector != null) {
|
|
dispatchCUISelection(actor);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Send the selection information.
|
|
*
|
|
* @param actor the actor
|
|
*/
|
|
public void dispatchCUISelection(Actor actor) {
|
|
checkNotNull(actor);
|
|
|
|
if (!hasCUISupport && useServerCUI) {
|
|
updateServerCUI(actor);
|
|
return;
|
|
}
|
|
|
|
if (selector instanceof CUIRegion) {
|
|
CUIRegion tempSel = (CUIRegion) selector;
|
|
|
|
if (tempSel.getProtocolVersion() > cuiVersion) {
|
|
actor.dispatchCUIEvent(new SelectionShapeEvent(tempSel.getLegacyTypeID()));
|
|
tempSel.describeLegacyCUI(this, actor);
|
|
} else {
|
|
actor.dispatchCUIEvent(new SelectionShapeEvent(tempSel.getTypeID()));
|
|
tempSel.describeCUI(this, actor);
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Describe the selection to the CUI actor.
|
|
*
|
|
* @param actor the actor
|
|
*/
|
|
public void describeCUI(Actor actor) {
|
|
checkNotNull(actor);
|
|
|
|
//FAWE start
|
|
// TODO preload
|
|
//FAWE end
|
|
|
|
if (!hasCUISupport) {
|
|
return;
|
|
}
|
|
|
|
if (selector instanceof CUIRegion) {
|
|
CUIRegion tempSel = (CUIRegion) selector;
|
|
|
|
if (tempSel.getProtocolVersion() > cuiVersion) {
|
|
tempSel.describeLegacyCUI(this, actor);
|
|
} else {
|
|
tempSel.describeCUI(this, actor);
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle a CUI initialization message.
|
|
*
|
|
* @param text the message
|
|
*/
|
|
public void handleCUIInitializationMessage(String text, Actor actor) {
|
|
checkNotNull(text);
|
|
if (this.hasCUISupport) {
|
|
// WECUI is a bit aggressive about re-initializing itself
|
|
// the last attempt to touch handshakes didn't go well, so this will do... for now
|
|
dispatchCUISelection(actor);
|
|
return;
|
|
} else if (this.failedCuiAttempts > 3) {
|
|
return;
|
|
}
|
|
|
|
String[] split = text.split("\\|", 2);
|
|
if (split.length > 1 && split[0].equalsIgnoreCase("v")) { // enough fields and right message
|
|
if (split[1].length() > 4) {
|
|
this.failedCuiAttempts ++;
|
|
return;
|
|
}
|
|
|
|
int version;
|
|
try {
|
|
version = Integer.parseInt(split[1]);
|
|
} catch (NumberFormatException e) {
|
|
WorldEdit.logger.warn("Error while reading CUI init message: " + e.getMessage());
|
|
this.failedCuiAttempts ++;
|
|
return;
|
|
}
|
|
setCUISupport(true);
|
|
setCUIVersion(version);
|
|
dispatchCUISelection(actor);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets the status of CUI support.
|
|
*
|
|
* @return true if CUI is enabled
|
|
*/
|
|
public boolean hasCUISupport() {
|
|
return hasCUISupport;
|
|
}
|
|
|
|
/**
|
|
* Sets the status of CUI support.
|
|
*
|
|
* @param support true if CUI is enabled
|
|
*/
|
|
public void setCUISupport(boolean support) {
|
|
hasCUISupport = support;
|
|
}
|
|
|
|
/**
|
|
* Gets the client's CUI protocol version
|
|
*
|
|
* @return the CUI version
|
|
*/
|
|
public int getCUIVersion() {
|
|
return cuiVersion;
|
|
}
|
|
|
|
/**
|
|
* Sets the client's CUI protocol version
|
|
*
|
|
* @param cuiVersion the CUI version
|
|
*/
|
|
public void setCUIVersion(int cuiVersion) {
|
|
if (cuiVersion < 0) {
|
|
throw new IllegalArgumentException("CUI protocol version must be non-negative, but '" + cuiVersion + "' was received.");
|
|
}
|
|
|
|
this.cuiVersion = cuiVersion;
|
|
}
|
|
|
|
/**
|
|
* Detect date from a user's input.
|
|
*
|
|
* @param input the input to parse
|
|
* @return a date
|
|
*/
|
|
@Nullable
|
|
public Calendar detectDate(String input) {
|
|
checkNotNull(input);
|
|
|
|
TimeZone tz = TimeZone.getTimeZone(getTimeZone());
|
|
Time.setTimeZone(tz);
|
|
Options opt = new com.sk89q.jchronic.Options();
|
|
opt.setNow(Calendar.getInstance(tz));
|
|
Span date = Chronic.parse(input, opt);
|
|
if (date == null) {
|
|
return null;
|
|
} else {
|
|
return date.getBeginCalendar();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Construct a new edit session.
|
|
*
|
|
* @param actor the actor
|
|
* @return an edit session
|
|
*/
|
|
public EditSession createEditSession(Actor actor) {
|
|
return createEditSession(actor, null);
|
|
}
|
|
|
|
public EditSession createEditSession(Actor actor, String command) {
|
|
checkNotNull(actor);
|
|
|
|
World world = null;
|
|
if (hasWorldOverride()) {
|
|
world = getWorldOverride();
|
|
} else if (actor instanceof Locatable && ((Locatable) actor).getExtent() instanceof World) {
|
|
world = (World) ((Locatable) actor).getExtent();
|
|
}
|
|
|
|
// Create an edit session
|
|
//FAWE start - we don't use the edit session builder yet
|
|
EditSession editSession;
|
|
EditSessionBuilder builder = new EditSessionBuilder(world);
|
|
if (actor.isPlayer() && actor instanceof Player) {
|
|
BlockBag blockBag = getBlockBag((Player) actor);
|
|
builder.player((Player) actor);
|
|
builder.blockBag(blockBag);
|
|
}
|
|
builder.command(command);
|
|
builder.fastmode(!this.sideEffectSet.doesApplyAny());
|
|
|
|
editSession = builder.build();
|
|
|
|
if (mask != null) {
|
|
editSession.setMask(mask);
|
|
}
|
|
if (sourceMask != null) {
|
|
editSession.setSourceMask(sourceMask);
|
|
}
|
|
if (transform != null) {
|
|
editSession.addTransform(transform);
|
|
}
|
|
editSession.setTickingWatchdog(tickingWatchdog);
|
|
|
|
return editSession;
|
|
}
|
|
//FAWE end
|
|
|
|
private void prepareEditingExtents(EditSession editSession, Actor actor) {
|
|
editSession.setSideEffectApplier(sideEffectSet);
|
|
editSession.setReorderMode(reorderMode);
|
|
if (editSession.getSurvivalExtent() != null) {
|
|
editSession.getSurvivalExtent().setStripNbt(!actor.hasPermission("worldedit.setnbt"));
|
|
}
|
|
editSession.setTickingWatchdog(tickingWatchdog);
|
|
}
|
|
|
|
/**
|
|
* Gets the side effect applier of this session.
|
|
*
|
|
* @return the side effect applier
|
|
*/
|
|
public SideEffectSet getSideEffectSet() {
|
|
return this.sideEffectSet;
|
|
}
|
|
|
|
/**
|
|
* Sets the side effect applier for this session
|
|
*
|
|
* @param sideEffectSet the side effect applier
|
|
*/
|
|
public void setSideEffectSet(SideEffectSet sideEffectSet) {
|
|
this.sideEffectSet = sideEffectSet;
|
|
}
|
|
|
|
/**
|
|
* Checks if the session has fast mode enabled.
|
|
*
|
|
* @return true if fast mode is enabled
|
|
*/
|
|
@Deprecated
|
|
public boolean hasFastMode() {
|
|
return !this.sideEffectSet.doesApplyAny();
|
|
}
|
|
|
|
/**
|
|
* Set fast mode.
|
|
*
|
|
* @param fastMode true if fast mode is enabled
|
|
*/
|
|
@Deprecated
|
|
public void setFastMode(boolean fastMode) {
|
|
this.sideEffectSet = fastMode ? SideEffectSet.none() : SideEffectSet.defaults();
|
|
}
|
|
|
|
/**
|
|
* Gets the reorder mode of the session.
|
|
*
|
|
* @return The reorder mode
|
|
*/
|
|
public EditSession.ReorderMode getReorderMode() {
|
|
return EditSession.ReorderMode.FAST;
|
|
}
|
|
|
|
/**
|
|
* Sets the reorder mode of the session.
|
|
*
|
|
* @param reorderMode The reorder mode
|
|
*/
|
|
public void setReorderMode(EditSession.ReorderMode reorderMode) {
|
|
}
|
|
|
|
/**
|
|
* Get the mask.
|
|
*
|
|
* @return mask, may be null
|
|
*/
|
|
public Mask getMask() {
|
|
return mask;
|
|
}
|
|
|
|
//FAWE start
|
|
/**
|
|
* Get the mask.
|
|
*
|
|
* @return mask, may be null
|
|
*/
|
|
public Mask getSourceMask() {
|
|
return sourceMask;
|
|
}
|
|
//FAWE end
|
|
|
|
/**
|
|
* Set a mask.
|
|
*
|
|
* @param mask mask or null
|
|
*/
|
|
public void setMask(Mask mask) {
|
|
this.mask = mask;
|
|
}
|
|
|
|
//FAWE start
|
|
/**
|
|
* Set a mask.
|
|
*
|
|
* @param mask mask or null
|
|
*/
|
|
public void setSourceMask(Mask mask) {
|
|
this.sourceMask = mask;
|
|
}
|
|
|
|
public synchronized void setTextureUtil(TextureUtil texture) {
|
|
this.texture = texture;
|
|
}
|
|
|
|
/**
|
|
* Get the TextureUtil currently being used
|
|
*/
|
|
@Override
|
|
public TextureUtil getTextureUtil() {
|
|
TextureUtil tmp = texture;
|
|
if (tmp == null) {
|
|
synchronized (this) {
|
|
tmp = Fawe.get().getCachedTextureUtil(true, 0, 100);
|
|
this.texture = tmp;
|
|
}
|
|
}
|
|
return tmp;
|
|
}
|
|
//FAWE end
|
|
|
|
/**
|
|
* Get the preferred wand item for this user, or {@code null} to use the default
|
|
* @return item id of wand item, or {@code null}
|
|
*/
|
|
public String getWandItem() {
|
|
return wandItem.getId();
|
|
}
|
|
|
|
/**
|
|
* Get the preferred navigation wand item for this user, or {@code null} to use the default
|
|
* @return item id of nav wand item, or {@code null}
|
|
*/
|
|
public String getNavWandItem() {
|
|
return navWandItem.getId();
|
|
}
|
|
|
|
/**
|
|
* Get the last block distribution stored in this session.
|
|
*
|
|
* @return block distribution or {@code null}
|
|
*/
|
|
public List<Countable<BlockState>> getLastDistribution() {
|
|
return lastDistribution == null ? null : Collections.unmodifiableList(lastDistribution);
|
|
}
|
|
|
|
/**
|
|
* Store a block distribution in this session.
|
|
*/
|
|
public void setLastDistribution(List<Countable<BlockState>> dist) {
|
|
lastDistribution = dist;
|
|
}
|
|
|
|
public ResettableExtent getTransform() {
|
|
return transform;
|
|
}
|
|
|
|
public void setTransform(ResettableExtent transform) {
|
|
this.transform = transform;
|
|
}
|
|
|
|
|
|
/**
|
|
* Call when this session has become inactive.
|
|
*
|
|
* <p>This is for internal use only.</p>
|
|
*/
|
|
public void onIdle() {
|
|
this.cuiVersion = CUI_VERSION_UNINITIALIZED;
|
|
this.hasCUISupport = false;
|
|
this.failedCuiAttempts = 0;
|
|
}
|
|
}
|