(Experimental) Persistent brushes (config option)

Brushes can be bound to the item instead of being stored in the player's
LocalSession.
- Brushes can be shared by dropping the item
- Hasn't been fully tested, there may be bugs or performance issues
This commit is contained in:
Jesse Boyd 2017-08-31 16:03:43 +10:00
parent fd333ae17d
commit 4d02548b04
No known key found for this signature in database
GPG Key ID: 59F1DE6293AF6E1F
14 changed files with 308 additions and 32 deletions

View File

@ -3,6 +3,8 @@ package com.boydti.fawe.bukkit;
import com.boydti.fawe.Fawe;
import com.boydti.fawe.IFawe;
import com.boydti.fawe.bukkit.chat.BukkitChatManager;
import com.boydti.fawe.bukkit.listener.BrushListener;
import com.boydti.fawe.bukkit.listener.RenderListener;
import com.boydti.fawe.bukkit.regions.FactionsFeature;
import com.boydti.fawe.bukkit.regions.FactionsOneFeature;
import com.boydti.fawe.bukkit.regions.FactionsUUIDFeature;
@ -12,6 +14,9 @@ import com.boydti.fawe.bukkit.regions.PreciousStonesFeature;
import com.boydti.fawe.bukkit.regions.ResidenceFeature;
import com.boydti.fawe.bukkit.regions.TownyFeature;
import com.boydti.fawe.bukkit.regions.Worldguard;
import com.boydti.fawe.bukkit.util.BukkitTaskMan;
import com.boydti.fawe.bukkit.util.ItemUtil;
import com.boydti.fawe.bukkit.util.VaultUtil;
import com.boydti.fawe.bukkit.v0.BukkitQueue_0;
import com.boydti.fawe.bukkit.v0.BukkitQueue_All;
import com.boydti.fawe.bukkit.v0.ChunkListener;
@ -62,6 +67,7 @@ public class FaweBukkit implements IFawe, Listener {
private final BukkitMain plugin;
private VaultUtil vault;
private WorldEditPlugin worldedit;
private ItemUtil itemUtil;
public VaultUtil getVault() {
return this.vault;
@ -96,6 +102,16 @@ public class FaweBukkit implements IFawe, Listener {
e.printStackTrace();
debug("===================================");
}
if (Settings.IMP.EXPERIMENTAL.PERSISTENT_BRUSHES) {
try {
this.itemUtil = new ItemUtil();
} catch (Throwable e) {
Settings.IMP.EXPERIMENTAL.PERSISTENT_BRUSHES = false;
debug("===== PERSISTENT BRUSH FAILED =====");
e.printStackTrace();
debug("===================================");
}
}
if (Bukkit.getVersion().contains("git-Spigot")) {
debug("====== USE PAPER ======");
debug("DOWNLOAD: https://ci.destroystokyo.com/job/PaperSpigot/");
@ -235,6 +251,10 @@ public class FaweBukkit implements IFawe, Listener {
});
}
public ItemUtil getItemUtil() {
return itemUtil;
}
/**
* Vault isn't required, but used for setting player permissions (WorldEdit bypass)
* @return

View File

@ -0,0 +1,94 @@
package com.boydti.fawe.bukkit.block;
import com.boydti.fawe.Fawe;
import com.boydti.fawe.bukkit.FaweBukkit;
import com.boydti.fawe.bukkit.util.ItemUtil;
import com.boydti.fawe.object.collection.SoftHashMap;
import com.boydti.fawe.util.ReflectionUtils;
import com.sk89q.jnbt.CompoundTag;
import com.sk89q.jnbt.StringTag;
import com.sk89q.jnbt.Tag;
import com.sk89q.worldedit.LocalSession;
import com.sk89q.worldedit.blocks.BaseBlock;
import com.sk89q.worldedit.command.tool.BrushHolder;
import com.sk89q.worldedit.command.tool.BrushTool;
import com.sk89q.worldedit.entity.Player;
import java.util.HashMap;
import java.util.Map;
import org.bukkit.inventory.ItemStack;
public class BrushBoundBaseBlock extends BaseBlock implements BrushHolder {
public static SoftHashMap<Object, BrushTool> brushCache = new SoftHashMap<>();
private final LocalSession session;
private final Player player;
private ItemStack item;
private BrushTool tool;
private static CompoundTag getNBT(ItemStack item) {
ItemUtil util = Fawe.<FaweBukkit>imp().getItemUtil();
return util != null && item.hasItemMeta() ? util.getNBT(item) : null;
}
private static Object getKey(ItemStack item) {
ItemUtil util = Fawe.<FaweBukkit>imp().getItemUtil();
return util != null ? util.getNMSItem(item) : item;
}
public BrushBoundBaseBlock(Player player, LocalSession session, ItemStack item) {
super(item.getTypeId(), item.getType().getMaxDurability() != 0 ? 0 : Math.max(0, item.getDurability()), getNBT(item));
this.item = item;
this.tool = brushCache.get(getKey(item));
this.player = player;
this.session = session;
}
@Override
public BrushTool getTool() {
if (tool == null && hasNbtData()) {
String json = getNbtData().getString("weBrushJson");
if (json != null) {
try {
this.tool = BrushTool.fromString(player, session, json);
this.tool.setHolder(this);
} catch (Throwable ignore) {
ignore.printStackTrace();
}
}
}
return this.tool;
}
public ItemStack getItem() {
return item;
}
@Override
public BrushTool setTool(BrushTool tool) {
this.tool = tool;
CompoundTag nbt = getNbtData();
Map<String, Tag> map;
if (nbt == null) {
if (tool == null) {
return tool;
}
setNbtData(nbt = new CompoundTag(map = new HashMap<>()));
} else {
map = ReflectionUtils.getMap(nbt.getValue());
}
brushCache.remove(getKey(item));
if (tool != null) {
String json = tool.toString();
map.put("weBrushJson", new StringTag(json));
} else if (map.containsKey("weBrushJson")) {
map.remove("weBrushJson");
} else {
return tool;
}
item = Fawe.<FaweBukkit>imp().getItemUtil().setNBT(item, nbt);
if (tool != null) {
brushCache.put(getKey(item), tool);
}
return tool;
}
}

View File

@ -1,4 +1,4 @@
package com.boydti.fawe.bukkit;
package com.boydti.fawe.bukkit.listener;
import com.boydti.fawe.object.FawePlayer;
import com.boydti.fawe.object.brush.MovableTool;

View File

@ -1,4 +1,4 @@
package com.boydti.fawe.bukkit;
package com.boydti.fawe.bukkit.listener;
import com.boydti.fawe.Fawe;
import com.boydti.fawe.object.FawePlayer;

View File

@ -1,4 +1,4 @@
package com.boydti.fawe.bukkit;
package com.boydti.fawe.bukkit.util;
import com.boydti.fawe.util.TaskManager;
import org.apache.commons.lang.mutable.MutableInt;

View File

@ -0,0 +1,95 @@
package com.boydti.fawe.bukkit.util;
import com.boydti.fawe.bukkit.v0.BukkitQueue_0;
import com.boydti.fawe.util.ReflectionUtils;
import com.sk89q.jnbt.CompoundTag;
import com.sk89q.jnbt.Tag;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import org.bukkit.inventory.ItemStack;
public class ItemUtil {
private final Class<?> classCraftItemStack;
private final Method methodAsNMSCopy;
private final Class<?> classNMSItem;
private final Method methodGetTag;
private final Method methodHasTag;
private final Method methodSetTag;
private final Method methodAsBukkitCopy;
private final Field fieldHandle;
private SoftReference<Int2ObjectOpenHashMap<WeakReference<Tag>>> hashToNMSTag = new SoftReference(new Int2ObjectOpenHashMap<>());
public ItemUtil() throws Exception {
this.classCraftItemStack = ReflectionUtils.getCbClass("inventory.CraftItemStack");
this.classNMSItem = ReflectionUtils.getNmsClass("ItemStack");
this.methodAsNMSCopy = ReflectionUtils.setAccessible(classCraftItemStack.getDeclaredMethod("asNMSCopy", ItemStack.class));
this.methodHasTag = ReflectionUtils.setAccessible(classNMSItem.getDeclaredMethod("hasTag"));
this.methodGetTag = ReflectionUtils.setAccessible(classNMSItem.getDeclaredMethod("getTag"));
this.fieldHandle = ReflectionUtils.setAccessible(classCraftItemStack.getDeclaredField("handle"));
Class<?> classNBTTagCompound = ReflectionUtils.getNmsClass("NBTTagCompound");
this.methodSetTag = ReflectionUtils.setAccessible(classNMSItem.getDeclaredMethod("setTag", classNBTTagCompound));
this.methodAsBukkitCopy = ReflectionUtils.setAccessible(classCraftItemStack.getDeclaredMethod("asBukkitCopy", classNMSItem));
}
public Object getNMSItem(ItemStack item) {
try {
Object nmsItem = fieldHandle.get(item);
if (nmsItem == null) nmsItem = methodAsNMSCopy.invoke(null, item);
return item;
} catch (Throwable e) {
e.printStackTrace();
}
return null;
}
public CompoundTag getNBT(ItemStack item) {
try {
Object nmsItem = fieldHandle.get(item);
if (nmsItem == null) nmsItem = methodAsNMSCopy.invoke(null, item);
if (methodHasTag.invoke(nmsItem).equals(true)) {
Object nmsTag = methodGetTag.invoke(nmsItem);
if (nmsTag == null) return null;
Int2ObjectOpenHashMap<WeakReference<Tag>> map = hashToNMSTag.get();
if (map == null) {
map = new Int2ObjectOpenHashMap<>();
hashToNMSTag = new SoftReference(new Int2ObjectOpenHashMap<>(map));
}
WeakReference<Tag> nativeTagRef = map.get(nmsTag.hashCode());
if (nativeTagRef != null) {
Tag nativeTag = nativeTagRef.get();
if (nativeTag != null) return (CompoundTag) nativeTag;
}
Tag nativeTag = BukkitQueue_0.toNative(nmsTag);
map.put(nmsTag.hashCode(), new WeakReference<Tag>(nativeTag));
return null;
}
} catch (Throwable e) {
e.printStackTrace();
}
return null;
}
public ItemStack setNBT(ItemStack item, CompoundTag tag) {
try {
Object nmsItem = fieldHandle.get(item);
boolean copy = false;
if (nmsItem == null) {
copy = true;
nmsItem = methodAsNMSCopy.invoke(null, item);
}
Object nmsTag = BukkitQueue_0.fromNative(tag);
methodSetTag.invoke(nmsItem, nmsTag);
if (copy) return (ItemStack) methodAsBukkitCopy.invoke(null, nmsItem);
return item;
} catch (Throwable e) {
e.printStackTrace();
}
return null;
}
}

View File

@ -1,4 +1,4 @@
package com.boydti.fawe.bukkit;
package com.boydti.fawe.bukkit.util;
import net.milkbowl.vault.permission.Permission;
import org.bukkit.Bukkit;

View File

@ -19,7 +19,11 @@
package com.sk89q.worldedit.bukkit;
import com.boydti.fawe.Fawe;
import com.boydti.fawe.FaweCache;
import com.boydti.fawe.bukkit.FaweBukkit;
import com.boydti.fawe.bukkit.block.BrushBoundBaseBlock;
import com.boydti.fawe.config.Settings;
import com.boydti.fawe.wrappers.WorldWrapper;
import com.sk89q.util.StringUtil;
import com.sk89q.worldedit.EditSession;
@ -80,13 +84,14 @@ public class BukkitPlayer extends LocalPlayer {
}
final int typeId = itemStack.getTypeId();
switch (typeId) {
case 0:
return EditSession.nullBlock;
case ItemID.INK_SACK:
final Dye materialData = (Dye) itemStack.getData();
if (materialData.getColor() == DyeColor.BROWN) {
return FaweCache.getBlock(BlockID.COCOA_PLANT, 0);
}
break;
case ItemID.HEAD:
return new SkullBlock(0, (byte) itemStack.getDurability());
@ -97,7 +102,11 @@ public class BukkitPlayer extends LocalPlayer {
}
break;
}
return FaweCache.getBlock(typeId, itemStack.getType().getMaxDurability() != 0 ? 0 : Math.max(0, itemStack.getDurability()));
BaseBlock block = FaweCache.getBlock(typeId, itemStack.getType().getMaxDurability() != 0 ? 0 : Math.max(0, itemStack.getDurability()));
if (Settings.IMP.EXPERIMENTAL.PERSISTENT_BRUSHES && Fawe.<FaweBukkit>imp().getItemUtil() != null) {
return new BrushBoundBaseBlock(this, WorldEdit.getInstance().getSession(this), itemStack);
}
return block;
}
@Override

View File

@ -306,6 +306,10 @@ public class Settings extends Config {
" - Please provide feedback",
})
public boolean DYNAMIC_CHUNK_RENDERING = false;
@Comment({
"Allows brushes to be persistent",
})
public boolean PERSISTENT_BRUSHES = false;
}
public static class WEB {

View File

@ -41,6 +41,7 @@ import com.sk89q.jchronic.utils.Span;
import com.sk89q.jchronic.utils.Time;
import com.sk89q.worldedit.blocks.BaseBlock;
import com.sk89q.worldedit.command.tool.BlockTool;
import com.sk89q.worldedit.command.tool.BrushHolder;
import com.sk89q.worldedit.command.tool.BrushTool;
import com.sk89q.worldedit.command.tool.InvalidToolBindException;
import com.sk89q.worldedit.command.tool.SinglePickaxe;
@ -960,18 +961,26 @@ public class LocalSession {
@Nullable
public Tool getTool(Player player) {
if (tools.isEmpty()) {
if (!Settings.IMP.EXPERIMENTAL.PERSISTENT_BRUSHES && tools.isEmpty()) {
return null;
}
try {
BaseBlock block = player.getBlockInHand();
return getTool(block.getId(), block.getData());
return getTool(block, player);
} catch (WorldEditException e) {
e.printStackTrace();
return null;
}
}
public Tool getTool(BaseBlock block, Player player) {
if (block instanceof BrushHolder) {
BrushTool tool = ((BrushHolder) block).getTool();
if (tool != null) return tool;
}
return getTool(block.getId(), block.getData());
}
/**
* 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
@ -981,16 +990,15 @@ public class LocalSession {
* @return the tool, or {@code null}
* @throws InvalidToolBindException if the item can't be bound to that item
*/
@Deprecated
public BrushTool getBrushTool(int item) throws InvalidToolBindException {
return getBrushTool(item, 0, null, true);
return getBrushTool(FaweCache.getBlock(item, 0), null, true);
}
@Deprecated
public BrushTool getBrushTool(Player player) throws InvalidToolBindException {
return getBrushTool(player, true);
}
@Deprecated
public BrushTool getBrushTool(Player player, boolean create) throws InvalidToolBindException {
BaseBlock block;
try {
@ -999,18 +1007,16 @@ public class LocalSession {
e.printStackTrace();
block = EditSession.nullBlock;
}
return getBrushTool(block.getId(), block.getData(), player, create);
return getBrushTool(block, player, create);
}
@Deprecated
public BrushTool getBrushTool(int id, int data, Player player, boolean create) throws InvalidToolBindException {
Tool tool = getTool(id, data);
public BrushTool getBrushTool(BaseBlock item, Player player, boolean create) throws InvalidToolBindException {
Tool tool = getTool(item, player);
if ((tool == null || !(tool instanceof BrushTool))) {
if (create) {
tool = new BrushTool();
setTool(id, data, tool, player);
setTool(item, tool, player);
} else {
return null;
}
@ -1028,7 +1034,7 @@ public class LocalSession {
*/
@Deprecated
public void setTool(int item, @Nullable Tool tool) throws InvalidToolBindException {
setTool(item, 0, tool, null);
setTool(FaweCache.getBlock(item, 0), tool, null);
}
public void setTool(@Nullable Tool tool, Player player) throws InvalidToolBindException {
@ -1039,10 +1045,12 @@ public class LocalSession {
item = EditSession.nullBlock;
e.printStackTrace();
}
setTool(item.getId(), item.getData(), tool, player);
setTool(item, tool, player);
}
public void setTool(int id, int data, @Nullable Tool tool, Player player) throws InvalidToolBindException {
public void setTool(BaseBlock item, @Nullable Tool tool, Player player) throws InvalidToolBindException {
int id = item.getId();
int data = item.getData();
if (id > 0 && id < 255) {
throw new InvalidToolBindException(id, "Blocks can't be used");
} else if (id == config.wandItem) {
@ -1050,12 +1058,17 @@ public class LocalSession {
} else if (id == config.navigationWand) {
throw new InvalidToolBindException(id, "Already used for the navigation wand");
}
Tool previous = this.tools.put(FaweCache.getCombined(id, data), tool);
if (player != null) {
if (previous instanceof BrushTool) {
BrushTool brushTool = (BrushTool) previous;
brushTool.clear(player);
}
Tool previous;
if (player != null && (tool instanceof BrushTool || tool == null) && item instanceof BrushHolder) {
BrushHolder holder = (BrushHolder) item;
previous = holder.setTool((BrushTool) tool);
if (tool != null) ((BrushTool) tool).setHolder(holder);
} else {
previous = this.tools.put(FaweCache.getCombined(id, data), tool);
}
if (previous != null && player != null && previous instanceof BrushTool) {
BrushTool brushTool = (BrushTool) previous;
brushTool.clear(player);
}
}

View File

@ -118,7 +118,7 @@ public class BrushOptionsCommands extends MethodCommands {
String json = in.readUTF();
BrushTool tool = BrushTool.fromString(player, session, json);
BaseBlock item = player.getBlockInHand();
session.setTool(item.getId(), item.getData(), tool, player);
session.setTool(item, tool, player);
BBC.BRUSH_EQUIPPED.send(player, name);
} catch (Throwable e) {
e.printStackTrace();
@ -194,11 +194,11 @@ public class BrushOptionsCommands extends MethodCommands {
public void primary(Player player, LocalSession session, CommandContext args) throws WorldEditException {
BaseBlock item = player.getBlockInHand();
BrushTool tool = session.getBrushTool(player, false);
session.setTool(item.getId(), item.getData(), null, player);
session.setTool(item, null, player);
String cmd = "brush " + args.getJoinedStrings(0);
CommandEvent event = new CommandEvent(player, cmd);
CommandManager.getInstance().handleCommandOnCurrentThread(event);
BrushTool newTool = session.getBrushTool(item.getId(), item.getData(), player, false);
BrushTool newTool = session.getBrushTool(item, player, false);
if (newTool != null && tool != null) {
newTool.setSecondary(tool.getSecondary());
}
@ -214,11 +214,11 @@ public class BrushOptionsCommands extends MethodCommands {
public void secondary(Player player, LocalSession session, CommandContext args) throws WorldEditException {
BaseBlock item = player.getBlockInHand();
BrushTool tool = session.getBrushTool(player, false);
session.setTool(item.getId(), item.getData(), null, player);
session.setTool(item, null, player);
String cmd = "brush " + args.getJoinedStrings(0);
CommandEvent event = new CommandEvent(player, cmd);
CommandManager.getInstance().handleCommandOnCurrentThread(event);
BrushTool newTool = session.getBrushTool(item.getId(), item.getData(), player, false);
BrushTool newTool = session.getBrushTool(item, player, false);
if (newTool != null && tool != null) {
newTool.setPrimary(tool.getPrimary());
}
@ -329,6 +329,7 @@ public class BrushOptionsCommands extends MethodCommands {
settings.addSetting(BrushSettings.SettingType.SCROLL_ACTION, full);
BBC.BRUSH_SCROLL_ACTION_SET.send(player, full);
}
bt.update();
}
@Command(
@ -359,6 +360,7 @@ public class BrushOptionsCommands extends MethodCommands {
BrushSettings settings = offHand ? tool.getOffHand() : tool.getContext();
settings.addSetting(BrushSettings.SettingType.MASK, context.getString(context.argsLength() - 1));
settings.setMask(mask);
tool.update();
BBC.BRUSH_MASK.send(player);
}
@ -391,6 +393,7 @@ public class BrushOptionsCommands extends MethodCommands {
BrushSettings settings = offHand ? tool.getOffHand() : tool.getContext();
settings.addSetting(BrushSettings.SettingType.SOURCE_MASK, context.getString(context.argsLength() - 1));
settings.setSourceMask(mask);
tool.update();
BBC.BRUSH_SOURCE_MASK.send(player);
}
@ -422,6 +425,7 @@ public class BrushOptionsCommands extends MethodCommands {
BrushSettings settings = offHand ? tool.getOffHand() : tool.getContext();
settings.addSetting(BrushSettings.SettingType.TRANSFORM, context.getString(context.argsLength() - 1));
settings.setTransform(transform);
tool.update();
BBC.BRUSH_TRANSFORM.send(player);
}
@ -447,6 +451,7 @@ public class BrushOptionsCommands extends MethodCommands {
BrushSettings settings = offHand ? tool.getOffHand() : tool.getContext();
settings.setFill(pattern);
settings.addSetting(BrushSettings.SettingType.FILL, context.getString(context.argsLength() - 1));
tool.update();
BBC.BRUSH_MATERIAL.send(player);
}
@ -487,6 +492,7 @@ public class BrushOptionsCommands extends MethodCommands {
}
BrushSettings settings = offHand ? tool.getOffHand() : tool.getContext();
settings.setSize(radius);
tool.update();
BBC.BRUSH_SIZE.send(player);
}

View File

@ -23,10 +23,11 @@ public class BrushProcessor implements CallableProcessor<BrushSettings> {
public BrushSettings process(CommandLocals locals, BrushSettings settings) throws Command.CommandException, WorldEditException {
Actor actor = locals.get(Actor.class);
LocalSession session = worldEdit.getSessionManager().get(actor);
session.setTool(null, (Player) actor);
BrushTool tool = session.getBrushTool((Player) actor);
tool.setPrimary(settings);
tool.setSecondary(settings);
BBC.BRUSH_EQUIPPED.send(actor, ((String) locals.get("arguments")).split(" ")[1]);
return null;
}
}
}

View File

@ -0,0 +1,7 @@
package com.sk89q.worldedit.command.tool;
public interface BrushHolder {
BrushTool getTool();
BrushTool setTool(BrushTool tool);
}

View File

@ -81,6 +81,8 @@ public class BrushTool implements DoubleActionTraceTool, ScrollTool, MovableTool
private transient VisualExtent visualExtent;
private transient Lock lock = new ReentrantLock();
private transient BrushHolder holder;
public BrushTool(String permission) {
getContext().addPermission(permission);
}
@ -117,6 +119,14 @@ public class BrushTool implements DoubleActionTraceTool, ScrollTool, MovableTool
return tool;
}
public void setHolder(BrushHolder holder) {
this.holder = holder;
}
public boolean isSet() {
return primary.getBrush() != null || secondary.getBrush() != null;
}
@Override
public String toString() {
HashMap<String, Object> map = new HashMap<>();
@ -139,6 +149,12 @@ public class BrushTool implements DoubleActionTraceTool, ScrollTool, MovableTool
return new Gson().toJson(map);
}
public void update() {
if (holder != null) {
holder.setTool(this);
}
}
private void writeObject(java.io.ObjectOutputStream stream) throws IOException {
stream.defaultWriteObject();
stream.writeBoolean(primary == secondary);
@ -200,16 +216,19 @@ public class BrushTool implements DoubleActionTraceTool, ScrollTool, MovableTool
checkNotNull(primary);
this.primary = primary;
this.context = primary;
update();
}
public void setSecondary(BrushSettings secondary) {
checkNotNull(secondary);
this.secondary = secondary;
this.context = secondary;
update();
}
public void setTransform(ResettableExtent transform) {
getContext().setTransform(transform);
update();
}
/**
@ -246,6 +265,7 @@ public class BrushTool implements DoubleActionTraceTool, ScrollTool, MovableTool
*/
public void setMask(Mask filter) {
this.getContext().setMask(filter);
update();
}
/**
@ -255,6 +275,7 @@ public class BrushTool implements DoubleActionTraceTool, ScrollTool, MovableTool
*/
public void setSourceMask(Mask filter) {
this.getContext().setSourceMask(filter);
update();
}
/**
@ -266,6 +287,7 @@ public class BrushTool implements DoubleActionTraceTool, ScrollTool, MovableTool
@Deprecated
public void setBrush(Brush brush, String permission) {
setBrush(brush, permission, null);
update();
}
@Deprecated
@ -472,18 +494,22 @@ public class BrushTool implements DoubleActionTraceTool, ScrollTool, MovableTool
public void setScrollAction(ScrollAction scrollAction) {
this.getContext().setScrollAction(scrollAction);
update();
}
public void setTargetOffset(int targetOffset) {
this.targetOffset = targetOffset;
update();
}
public void setTargetMode(TargetMode targetMode) {
this.targetMode = targetMode != null ? targetMode : TargetMode.TARGET_BLOCK_RANGE;
update();
}
public void setTargetMask(Mask mask) {
this.targetMask = mask;
update();
}
public void setVisualMode(Player player, VisualMode visualMode) {
@ -501,6 +527,7 @@ public class BrushTool implements DoubleActionTraceTool, ScrollTool, MovableTool
}
}
}
update();
}
public TargetMode getTargetMode() {