Make progress
This commit is contained in:
@@ -24,11 +24,20 @@ public class ChildCommandAddress extends ModifiableCommandAddress {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static ChildCommandAddress newPlaceHolderCommand(String name, String... aliases) {
|
public static ChildCommandAddress newPlaceHolderCommand(String name, String... aliases) {
|
||||||
ChildCommandAddress rv = new ChildCommandAddress(DefaultGroupCommand.getInstance(), name, aliases);
|
ChildCommandAddress rv = new ChildCommandAddress();
|
||||||
HelpCommand.registerAsChild(rv);
|
rv.setupAsPlaceholder(name, aliases);
|
||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setupAsPlaceholder(String name, String... aliases) {
|
||||||
|
if (!hasCommand()) {
|
||||||
|
setCommand(DefaultGroupCommand.getInstance());
|
||||||
|
}
|
||||||
|
|
||||||
|
addNameAndAliases(name, aliases);
|
||||||
|
HelpCommand.registerAsChild(this);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isRoot() {
|
public boolean isRoot() {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -145,43 +145,50 @@ public abstract class Command {
|
|||||||
ExecutionContext executionContext = new ExecutionContext(sender, caller, this, buffer, false);
|
ExecutionContext executionContext = new ExecutionContext(sender, caller, this, buffer, false);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
//System.out.println("In Command.execute(sender, caller, buffer)#try{");
|
executeWithContext(executionContext);
|
||||||
int i, n;
|
|
||||||
for (i = 0, n = contextFilterPostParameterIndex; i < n; i++) {
|
|
||||||
contextFilters.get(i).filterContext(executionContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
executionContext.parseParameters();
|
|
||||||
|
|
||||||
for (n = contextFilters.size(); i < n; i++) {
|
|
||||||
contextFilters.get(i).filterContext(executionContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
//System.out.println("Post-contextfilters");
|
|
||||||
|
|
||||||
String message = execute(sender, executionContext);
|
|
||||||
caller.getChatController().sendMessage(sender, EMessageType.RESULT, message);
|
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
caller.getChatController().handleException(sender, executionContext, t);
|
caller.getChatController().handleException(sender, executionContext, t);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void executeWithContext(ExecutionContext context) throws CommandException {
|
||||||
|
//System.out.println("In Command.execute(sender, caller, buffer)#try{");
|
||||||
|
int i, n;
|
||||||
|
for (i = 0, n = contextFilterPostParameterIndex; i < n; i++) {
|
||||||
|
contextFilters.get(i).filterContext(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
context.parseParameters();
|
||||||
|
|
||||||
|
for (n = contextFilters.size(); i < n; i++) {
|
||||||
|
contextFilters.get(i).filterContext(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
//System.out.println("Post-contextfilters");
|
||||||
|
|
||||||
|
String message = execute(context.getSender(), context);
|
||||||
|
context.getAddress().getChatController().sendMessage(context.getSender(), EMessageType.RESULT, message);
|
||||||
|
}
|
||||||
|
|
||||||
public abstract String execute(CommandSender sender, ExecutionContext context) throws CommandException;
|
public abstract String execute(CommandSender sender, ExecutionContext context) throws CommandException;
|
||||||
|
|
||||||
public List<String> tabComplete(CommandSender sender, ICommandAddress caller, Location location, ArgumentBuffer buffer) {
|
public List<String> tabComplete(CommandSender sender, ICommandAddress caller, Location location, ArgumentBuffer buffer) {
|
||||||
ExecutionContext executionContext = new ExecutionContext(sender, caller, this, buffer, true);
|
ExecutionContext executionContext = new ExecutionContext(sender, caller, this, buffer, true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
int i, n;
|
return tabCompleteWithContext(executionContext, location);
|
||||||
for (i = 0, n = contextFilterPostParameterIndex; i < n; i++) {
|
|
||||||
contextFilters.get(i).filterContext(executionContext);
|
|
||||||
}
|
|
||||||
} catch (CommandException ex) {
|
} catch (CommandException ex) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
executionContext.parseParametersQuietly();
|
public List<String> tabCompleteWithContext(ExecutionContext context, Location location) throws CommandException {
|
||||||
return tabComplete(sender, executionContext, location);
|
int i, n;
|
||||||
|
for (i = 0, n = contextFilterPostParameterIndex; i < n; i++) {
|
||||||
|
contextFilters.get(i).filterContext(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
context.parseParametersQuietly();
|
||||||
|
return tabComplete(context.getSender(), context, location);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<String> tabComplete(CommandSender sender, ExecutionContext context, Location location) {
|
public List<String> tabComplete(CommandSender sender, ExecutionContext context, Location location) {
|
||||||
|
|||||||
@@ -197,12 +197,38 @@ public final class CommandBuilder {
|
|||||||
public CommandBuilder group(String name, String... aliases) {
|
public CommandBuilder group(String name, String... aliases) {
|
||||||
ChildCommandAddress address = cur.getChild(name);
|
ChildCommandAddress address = cur.getChild(name);
|
||||||
if (address == null || !name.equals(address.getMainKey())) {
|
if (address == null || !name.equals(address.getMainKey())) {
|
||||||
cur.addChild(address = ChildCommandAddress.newPlaceHolderCommand(name, aliases));
|
address = new ChildCommandAddress();
|
||||||
|
address.setupAsPlaceholder(name, aliases);
|
||||||
|
cur.addChild(address);
|
||||||
}
|
}
|
||||||
cur = address;
|
cur = address;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Similar to {@link #group(String, String[])} but this will force overwrite any present group,
|
||||||
|
* using the address passed. The address MUST be an instance of {@link ChildCommandAddress}.
|
||||||
|
*
|
||||||
|
* <p>The address must not have a parent or any keys</p>
|
||||||
|
*
|
||||||
|
* @param address the address object to use
|
||||||
|
* @param name the main key
|
||||||
|
* @param aliases any aliases
|
||||||
|
* @return this
|
||||||
|
* @throws IllegalArgumentException if any of the requirements set out above aren't met
|
||||||
|
*/
|
||||||
|
public CommandBuilder group(ICommandAddress address, String name, String... aliases) {
|
||||||
|
if (address.hasParent() || address.getMainKey() != null || !(address instanceof ChildCommandAddress)) {
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
|
|
||||||
|
ChildCommandAddress asChild = (ChildCommandAddress) address;
|
||||||
|
asChild.setupAsPlaceholder(name, aliases);
|
||||||
|
cur.addChild(address);
|
||||||
|
cur = asChild;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the description of a group created by {@link #group(String, String...)}
|
* Sets the description of a group created by {@link #group(String, String...)}
|
||||||
* Can be called subsequently to making a call to {@link #group(String, String...)}
|
* Can be called subsequently to making a call to {@link #group(String, String...)}
|
||||||
|
|||||||
@@ -17,14 +17,14 @@ import java.util.*;
|
|||||||
* It is also responsible for keeping track of the parameter to complete in the case of a tab completion.
|
* It is also responsible for keeping track of the parameter to complete in the case of a tab completion.
|
||||||
*/
|
*/
|
||||||
public class ExecutionContext {
|
public class ExecutionContext {
|
||||||
private final CommandSender sender;
|
private CommandSender sender;
|
||||||
private final ICommandAddress address;
|
private ICommandAddress address;
|
||||||
private final Command command;
|
private Command command;
|
||||||
private final ArgumentBuffer originalBuffer;
|
private ArgumentBuffer originalBuffer;
|
||||||
private final ArgumentBuffer processedBuffer;
|
private ArgumentBuffer processedBuffer;
|
||||||
|
|
||||||
// caches the buffer's cursor before parsing. This is needed to provide the original input of the player.
|
// caches the buffer's cursor before parsing. This is needed to provide the original input of the player.
|
||||||
private final int cursorStart;
|
private int cursorStart;
|
||||||
|
|
||||||
// when the context starts parsing parameters, this flag is set, and any subsequent calls to #parseParameters() throw an IllegalStateException.
|
// when the context starts parsing parameters, this flag is set, and any subsequent calls to #parseParameters() throw an IllegalStateException.
|
||||||
private boolean attemptedToParse;
|
private boolean attemptedToParse;
|
||||||
@@ -48,8 +48,14 @@ public class ExecutionContext {
|
|||||||
// if this flag is set, any messages sent through the sendMessage methods are discarded.
|
// if this flag is set, any messages sent through the sendMessage methods are discarded.
|
||||||
private boolean muted;
|
private boolean muted;
|
||||||
|
|
||||||
|
public ExecutionContext(CommandSender sender, boolean tabComplete) {
|
||||||
|
this.sender = Objects.requireNonNull(sender);
|
||||||
|
this.muted = tabComplete;
|
||||||
|
this.tabComplete = tabComplete;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct an execution context, making it ready to parse the parameter values.
|
* Construct an execution context that is ready to parse the parameter values.
|
||||||
*
|
*
|
||||||
* @param sender the sender
|
* @param sender the sender
|
||||||
* @param address the address
|
* @param address the address
|
||||||
@@ -57,11 +63,22 @@ public class ExecutionContext {
|
|||||||
* @param tabComplete true if this execution is a tab-completion
|
* @param tabComplete true if this execution is a tab-completion
|
||||||
*/
|
*/
|
||||||
public ExecutionContext(CommandSender sender, ICommandAddress address, Command command, ArgumentBuffer buffer, boolean tabComplete) {
|
public ExecutionContext(CommandSender sender, ICommandAddress address, Command command, ArgumentBuffer buffer, boolean tabComplete) {
|
||||||
this.sender = Objects.requireNonNull(sender);
|
this(sender, tabComplete);
|
||||||
|
targetAcquired(address, command, buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void requireAddressPresent(boolean present) {
|
||||||
|
//noinspection DoubleNegation
|
||||||
|
if ((address != null) != present) {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void targetAcquired(ICommandAddress address, Command command, ArgumentBuffer buffer) {
|
||||||
|
requireAddressPresent(false);
|
||||||
|
|
||||||
this.address = Objects.requireNonNull(address);
|
this.address = Objects.requireNonNull(address);
|
||||||
this.command = Objects.requireNonNull(command);
|
this.command = Objects.requireNonNull(command);
|
||||||
this.muted = tabComplete;
|
|
||||||
this.tabComplete = tabComplete;
|
|
||||||
|
|
||||||
// If its tab completing, keep the empty element that might be at the end of the buffer
|
// If its tab completing, keep the empty element that might be at the end of the buffer
|
||||||
// due to a space at the end of the command.
|
// due to a space at the end of the command.
|
||||||
@@ -80,7 +97,8 @@ public class ExecutionContext {
|
|||||||
*
|
*
|
||||||
* @throws CommandException if an error occurs while parsing the parameters.
|
* @throws CommandException if an error occurs while parsing the parameters.
|
||||||
*/
|
*/
|
||||||
public synchronized void parseParameters() throws CommandException {
|
synchronized void parseParameters() throws CommandException {
|
||||||
|
requireAddressPresent(true);
|
||||||
if (attemptedToParse) {
|
if (attemptedToParse) {
|
||||||
throw new IllegalStateException();
|
throw new IllegalStateException();
|
||||||
}
|
}
|
||||||
@@ -101,7 +119,8 @@ public class ExecutionContext {
|
|||||||
* This method is typically used by tab completions.
|
* This method is typically used by tab completions.
|
||||||
* After calling this method, the context is ready to provide completions.
|
* After calling this method, the context is ready to provide completions.
|
||||||
*/
|
*/
|
||||||
public synchronized void parseParametersQuietly() {
|
synchronized void parseParametersQuietly() {
|
||||||
|
requireAddressPresent(true);
|
||||||
if (attemptedToParse) {
|
if (attemptedToParse) {
|
||||||
throw new IllegalStateException();
|
throw new IllegalStateException();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -125,6 +125,16 @@ public interface ICommandAddress {
|
|||||||
*/
|
*/
|
||||||
ICommandAddress getChild(String key);
|
ICommandAddress getChild(String key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Query for a child at the given key, with the given context for reference.
|
||||||
|
* Can be used to override behaviour of the tree.
|
||||||
|
*
|
||||||
|
* @param key the key. The name or alias of a command.
|
||||||
|
* @param context context of a command being executed
|
||||||
|
* @return the child, or null if it's not found, altered freely by the implementation
|
||||||
|
*/
|
||||||
|
ICommandAddress getChild(String key, ExecutionContext context) throws CommandException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the command dispatcher for this tree
|
* Get the command dispatcher for this tree
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -30,8 +30,23 @@ public interface ICommandDispatcher {
|
|||||||
* @param buffer the command itself as a buffer.
|
* @param buffer the command itself as a buffer.
|
||||||
* @return the address that is the target of the command.
|
* @return the address that is the target of the command.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
ICommandAddress getCommandTarget(CommandSender sender, ArgumentBuffer buffer);
|
ICommandAddress getCommandTarget(CommandSender sender, ArgumentBuffer buffer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Similar to {@link #getDeepChild(ArgumentBuffer)},
|
||||||
|
* but this method incorporates checks on the command of traversed children:
|
||||||
|
* {@link Command#isVisibleTo(CommandSender)}
|
||||||
|
* and {@link Command#takePrecedenceOverSubcommand(String, ArgumentBuffer)}
|
||||||
|
* <p>
|
||||||
|
* The target of a command is never null, however, the same instance might be returned, and the returned address might not hold a command.
|
||||||
|
*
|
||||||
|
* @param context the context of the command. The context must not have its address set.
|
||||||
|
* @param buffer the command itself as a buffer.
|
||||||
|
* @return the address that is the target of the command.
|
||||||
|
*/
|
||||||
|
ICommandAddress getCommandTarget(ExecutionContext context, ArgumentBuffer buffer) throws CommandException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* dispatch the command
|
* dispatch the command
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -120,6 +120,11 @@ public abstract class ModifiableCommandAddress implements ICommandAddress {
|
|||||||
return children.get(key);
|
return children.get(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChildCommandAddress getChild(String key, ExecutionContext context) throws CommandException {
|
||||||
|
return getChild(key);
|
||||||
|
}
|
||||||
|
|
||||||
public void addChild(ICommandAddress child) {
|
public void addChild(ICommandAddress child) {
|
||||||
if (!(child instanceof ChildCommandAddress)) {
|
if (!(child instanceof ChildCommandAddress)) {
|
||||||
throw new IllegalArgumentException("Argument must be a ChildCommandAddress");
|
throw new IllegalArgumentException("Argument must be a ChildCommandAddress");
|
||||||
|
|||||||
@@ -123,8 +123,6 @@ public class RootCommandAddress extends ModifiableCommandAddress implements ICom
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ModifiableCommandAddress getCommandTarget(CommandSender sender, ArgumentBuffer buffer) {
|
public ModifiableCommandAddress getCommandTarget(CommandSender sender, ArgumentBuffer buffer) {
|
||||||
//System.out.println("Buffer cursor upon getCommandTarget: " + buffer.getCursor());
|
|
||||||
|
|
||||||
ModifiableCommandAddress cur = this;
|
ModifiableCommandAddress cur = this;
|
||||||
ChildCommandAddress child;
|
ChildCommandAddress child;
|
||||||
while (buffer.hasNext()) {
|
while (buffer.hasNext()) {
|
||||||
@@ -139,16 +137,25 @@ public class RootCommandAddress extends ModifiableCommandAddress implements ICom
|
|||||||
cur = child;
|
cur = child;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
return cur;
|
||||||
if (!cur.hasCommand() && cur.hasHelpCommand()) {
|
}
|
||||||
cur = cur.getHelpCommand();
|
|
||||||
} else {
|
@Override
|
||||||
while (!cur.hasCommand() && cur.hasParent()) {
|
public ModifiableCommandAddress getCommandTarget(ExecutionContext context, ArgumentBuffer buffer) throws CommandException {
|
||||||
cur = cur.getParent();
|
CommandSender sender = context.getSender();
|
||||||
|
ModifiableCommandAddress cur = this;
|
||||||
|
ChildCommandAddress child;
|
||||||
|
while (buffer.hasNext()) {
|
||||||
|
child = cur.getChild(buffer.next(), context);
|
||||||
|
if (child == null
|
||||||
|
|| (child.hasCommand() && !child.getCommand().isVisibleTo(sender))
|
||||||
|
|| (cur.hasCommand() && cur.getCommand().takePrecedenceOverSubcommand(buffer.peekPrevious(), buffer.getUnaffectingCopy()))) {
|
||||||
buffer.rewind();
|
buffer.rewind();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cur = child;
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
return cur;
|
return cur;
|
||||||
}
|
}
|
||||||
@@ -165,18 +172,32 @@ public class RootCommandAddress extends ModifiableCommandAddress implements ICom
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean dispatchCommand(CommandSender sender, ArgumentBuffer buffer) {
|
public boolean dispatchCommand(CommandSender sender, ArgumentBuffer buffer) {
|
||||||
ModifiableCommandAddress targetAddress = getCommandTarget(sender, buffer);
|
ExecutionContext context = new ExecutionContext(sender, false);
|
||||||
Command target = targetAddress.getCommand();
|
|
||||||
|
|
||||||
if (target == null || target instanceof DefaultGroupCommand) {
|
ModifiableCommandAddress targetAddress = null;
|
||||||
if (targetAddress.hasHelpCommand()) {
|
|
||||||
target = targetAddress.getHelpCommand().getCommand();
|
try {
|
||||||
} else if (target == null){
|
targetAddress = getCommandTarget(context, buffer);
|
||||||
return false;
|
Command target = targetAddress.getCommand();
|
||||||
|
|
||||||
|
if (target == null || target instanceof DefaultGroupCommand) {
|
||||||
|
if (targetAddress.hasHelpCommand()) {
|
||||||
|
target = targetAddress.getHelpCommand().getCommand();
|
||||||
|
} else if (target == null){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
context.targetAcquired(targetAddress, target, buffer);
|
||||||
|
target.executeWithContext(context);
|
||||||
|
|
||||||
|
} catch (Throwable t) {
|
||||||
|
if (targetAddress == null) {
|
||||||
|
targetAddress = this;
|
||||||
|
}
|
||||||
|
targetAddress.getChatController().handleException(sender, context, t);
|
||||||
}
|
}
|
||||||
|
|
||||||
target.execute(sender, targetAddress, buffer);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,28 +213,38 @@ public class RootCommandAddress extends ModifiableCommandAddress implements ICom
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<String> getTabCompletions(CommandSender sender, Location location, ArgumentBuffer buffer) {
|
public List<String> getTabCompletions(CommandSender sender, Location location, ArgumentBuffer buffer) {
|
||||||
ICommandAddress target = getCommandTarget(sender, buffer);
|
ExecutionContext context = new ExecutionContext(sender, true);
|
||||||
List<String> out = target.hasCommand() ? target.getCommand().tabComplete(sender, target, location, buffer.getUnaffectingCopy()) : Collections.emptyList();
|
|
||||||
|
|
||||||
int cursor = buffer.getCursor();
|
try {
|
||||||
String input;
|
ICommandAddress target = getCommandTarget(context, buffer);
|
||||||
if (cursor >= buffer.size()) {
|
List<String> out = target.hasCommand()
|
||||||
input = "";
|
? target.getCommand().tabComplete(sender, target, location, buffer.getUnaffectingCopy())
|
||||||
} else {
|
: Collections.emptyList();
|
||||||
input = buffer.get(cursor).toLowerCase();
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean wrapped = false;
|
int cursor = buffer.getCursor();
|
||||||
for (String child : target.getChildren().keySet()) {
|
String input;
|
||||||
if (child.toLowerCase().startsWith(input)) {
|
if (cursor >= buffer.size()) {
|
||||||
if (!wrapped) {
|
input = "";
|
||||||
out = new ArrayList<>(out);
|
} else {
|
||||||
wrapped = true;
|
input = buffer.get(cursor).toLowerCase();
|
||||||
}
|
|
||||||
out.add(child);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean wrapped = false;
|
||||||
|
for (String child : target.getChildren().keySet()) {
|
||||||
|
if (child.toLowerCase().startsWith(input)) {
|
||||||
|
if (!wrapped) {
|
||||||
|
out = new ArrayList<>(out);
|
||||||
|
wrapped = true;
|
||||||
|
}
|
||||||
|
out.add(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
|
||||||
|
} catch (CommandException ex) {
|
||||||
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
return out;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -177,10 +177,12 @@ public class ReflectiveRegistration {
|
|||||||
GroupEntry matchEntry = matchEntries[i];
|
GroupEntry matchEntry = matchEntries[i];
|
||||||
if (patterns[i].matcher(name).matches()) {
|
if (patterns[i].matcher(name).matches()) {
|
||||||
if (addresses[i] == null) {
|
if (addresses[i] == null) {
|
||||||
addresses[i] = ChildCommandAddress.newPlaceHolderCommand(matchEntry.group(), matchEntry.groupAliases());
|
ChildCommandAddress placeholder = new ChildCommandAddress();
|
||||||
groupRootAddress.addChild(addresses[i]);
|
placeholder.setupAsPlaceholder(matchEntry.group(), matchEntry.groupAliases());
|
||||||
generateCommands(addresses[i], matchEntry.generatedCommands());
|
addresses[i] = placeholder;
|
||||||
setDescription(addresses[i], matchEntry.description(), matchEntry.shortDescription());
|
groupRootAddress.addChild(placeholder);
|
||||||
|
generateCommands(placeholder, matchEntry.generatedCommands());
|
||||||
|
setDescription(placeholder, matchEntry.description(), matchEntry.shortDescription());
|
||||||
}
|
}
|
||||||
return addresses[i];
|
return addresses[i];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,12 +37,12 @@ abstract class ParcelGenerator : ChunkGenerator() {
|
|||||||
abstract fun makeParcelLocatorAndBlockManager(worldId: ParcelWorldId,
|
abstract fun makeParcelLocatorAndBlockManager(worldId: ParcelWorldId,
|
||||||
container: ParcelContainer,
|
container: ParcelContainer,
|
||||||
coroutineScope: CoroutineScope,
|
coroutineScope: CoroutineScope,
|
||||||
worktimeLimiter: WorktimeLimiter): Pair<ParcelLocator, ParcelBlockManager>
|
workDispatcher: WorkDispatcher): Pair<ParcelLocator, ParcelBlockManager>
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ParcelBlockManager {
|
interface ParcelBlockManager {
|
||||||
val world: World
|
val world: World
|
||||||
val worktimeLimiter: WorktimeLimiter
|
val workDispatcher: WorkDispatcher
|
||||||
val parcelTraverser: RegionTraverser
|
val parcelTraverser: RegionTraverser
|
||||||
|
|
||||||
// fun getBottomBlock(parcel: ParcelId): Vec2i
|
// fun getBottomBlock(parcel: ParcelId): Vec2i
|
||||||
@@ -61,7 +61,7 @@ interface ParcelBlockManager {
|
|||||||
|
|
||||||
fun swapParcels(parcel1: ParcelId, parcel2: ParcelId): Worker
|
fun swapParcels(parcel1: ParcelId, parcel2: ParcelId): Worker
|
||||||
|
|
||||||
fun submitBlockVisitor(vararg parcelIds: ParcelId, task: TimeLimitedTask): Worker
|
fun submitBlockVisitor(vararg parcelIds: ParcelId, task: WorkerTask): Worker
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to update owner blocks in the corner of the parcel
|
* Used to update owner blocks in the corner of the parcel
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ package io.dico.parcels2
|
|||||||
import io.dico.dicore.Registrator
|
import io.dico.dicore.Registrator
|
||||||
import io.dico.dicore.command.EOverridePolicy
|
import io.dico.dicore.command.EOverridePolicy
|
||||||
import io.dico.dicore.command.ICommandDispatcher
|
import io.dico.dicore.command.ICommandDispatcher
|
||||||
import io.dico.parcels2.blockvisitor.TickWorktimeLimiter
|
import io.dico.parcels2.blockvisitor.BukkitWorkDispatcher
|
||||||
import io.dico.parcels2.blockvisitor.WorktimeLimiter
|
import io.dico.parcels2.blockvisitor.WorkDispatcher
|
||||||
import io.dico.parcels2.command.getParcelCommands
|
import io.dico.parcels2.command.getParcelCommands
|
||||||
import io.dico.parcels2.defaultimpl.GlobalPrivilegesManagerImpl
|
import io.dico.parcels2.defaultimpl.GlobalPrivilegesManagerImpl
|
||||||
import io.dico.parcels2.defaultimpl.ParcelProviderImpl
|
import io.dico.parcels2.defaultimpl.ParcelProviderImpl
|
||||||
@@ -44,7 +44,7 @@ class ParcelsPlugin : JavaPlugin(), CoroutineScope, PluginScheduler {
|
|||||||
|
|
||||||
override val coroutineContext: CoroutineContext = MainThreadDispatcher(this)
|
override val coroutineContext: CoroutineContext = MainThreadDispatcher(this)
|
||||||
override val plugin: Plugin get() = this
|
override val plugin: Plugin get() = this
|
||||||
val worktimeLimiter: WorktimeLimiter by lazy { TickWorktimeLimiter(this, options.tickWorktime) }
|
val workDispatcher: WorkDispatcher by lazy { BukkitWorkDispatcher(this, options.tickWorktime) }
|
||||||
|
|
||||||
override fun onEnable() {
|
override fun onEnable() {
|
||||||
plogger.info("Debug enabled: ${plogger.isDebugEnabled}")
|
plogger.info("Debug enabled: ${plogger.isDebugEnabled}")
|
||||||
@@ -55,11 +55,11 @@ class ParcelsPlugin : JavaPlugin(), CoroutineScope, PluginScheduler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onDisable() {
|
override fun onDisable() {
|
||||||
val hasWorkers = worktimeLimiter.workers.isNotEmpty()
|
val hasWorkers = workDispatcher.workers.isNotEmpty()
|
||||||
if (hasWorkers) {
|
if (hasWorkers) {
|
||||||
plogger.warn("Parcels is attempting to complete all ${worktimeLimiter.workers.size} remaining jobs before shutdown...")
|
plogger.warn("Parcels is attempting to complete all ${workDispatcher.workers.size} remaining jobs before shutdown...")
|
||||||
}
|
}
|
||||||
worktimeLimiter.completeAllTasks()
|
workDispatcher.completeAllTasks()
|
||||||
if (hasWorkers) {
|
if (hasWorkers) {
|
||||||
plogger.info("Parcels has completed the remaining jobs.")
|
plogger.info("Parcels has completed the remaining jobs.")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package io.dico.parcels2.blockvisitor
|
||||||
|
|
||||||
|
import org.bukkit.block.Block
|
||||||
|
import org.bukkit.block.BlockState
|
||||||
|
import org.bukkit.block.Sign
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
|
interface ExtraBlockChange {
|
||||||
|
fun update(block: Block)
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class BlockStateChange<T : BlockState> : ExtraBlockChange {
|
||||||
|
abstract val stateClass: KClass<T>
|
||||||
|
|
||||||
|
abstract fun update(state: T)
|
||||||
|
|
||||||
|
override fun update(block: Block) {
|
||||||
|
val state = block.state
|
||||||
|
if (stateClass.isInstance(state)) {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
update(state as T)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SignStateChange(state: Sign) : BlockStateChange<Sign>() {
|
||||||
|
val lines = state.lines
|
||||||
|
|
||||||
|
override val stateClass: KClass<Sign>
|
||||||
|
get() = Sign::class
|
||||||
|
|
||||||
|
override fun update(state: Sign) {
|
||||||
|
for (i in lines.indices) {
|
||||||
|
val line = lines[i]
|
||||||
|
state.setLine(i, line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -235,87 +235,3 @@ inline class TraverseDirection(val bits: Int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
private typealias Scope = SequenceScope<Vec3i>
|
|
||||||
private typealias ScopeAction = suspend Scope.(Int, Int, Int) -> Unit
|
|
||||||
|
|
||||||
@Suppress("NON_EXHAUSTIVE_WHEN")
|
|
||||||
suspend fun Scope.traverserLogic(
|
|
||||||
region: Region,
|
|
||||||
order: TraverseOrder,
|
|
||||||
direction: TraverseDirection
|
|
||||||
) = with(direction) {
|
|
||||||
val (primary, secondary, tertiary) = order.toArray()
|
|
||||||
val (origin, size) = region
|
|
||||||
|
|
||||||
when (order.primary) {
|
|
||||||
Dimension.X ->
|
|
||||||
when (order.secondary) {
|
|
||||||
Dimension.Y -> {
|
|
||||||
directionOf(primary).traverse(primary.extract(size)) { p ->
|
|
||||||
directionOf(secondary).traverse(secondary.extract(size)) { s ->
|
|
||||||
directionOf(tertiary).traverse(tertiary.extract(size)) { t ->
|
|
||||||
yield(origin.add(p, s, t))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Dimension.Z -> {
|
|
||||||
directionOf(primary).traverse(primary.extract(size)) { p ->
|
|
||||||
directionOf(secondary).traverse(secondary.extract(size)) { s ->
|
|
||||||
directionOf(tertiary).traverse(tertiary.extract(size)) { t ->
|
|
||||||
yield(origin.add(p, t, s))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Dimension.Y ->
|
|
||||||
when (order.secondary) {
|
|
||||||
Dimension.X -> {
|
|
||||||
directionOf(primary).traverse(primary.extract(size)) { p ->
|
|
||||||
directionOf(secondary).traverse(secondary.extract(size)) { s ->
|
|
||||||
directionOf(tertiary).traverse(tertiary.extract(size)) { t ->
|
|
||||||
yield(origin.add(s, p, t))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Dimension.Z -> {
|
|
||||||
directionOf(primary).traverse(primary.extract(size)) { p ->
|
|
||||||
directionOf(secondary).traverse(secondary.extract(size)) { s ->
|
|
||||||
directionOf(tertiary).traverse(tertiary.extract(size)) { t ->
|
|
||||||
yield(origin.add(t, p, s))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Dimension.Z ->
|
|
||||||
when (order.secondary) {
|
|
||||||
Dimension.X -> {
|
|
||||||
directionOf(primary).traverse(primary.extract(size)) { p ->
|
|
||||||
directionOf(secondary).traverse(secondary.extract(size)) { s ->
|
|
||||||
directionOf(tertiary).traverse(tertiary.extract(size)) { t ->
|
|
||||||
yield(origin.add(s, t, p))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Dimension.Y -> {
|
|
||||||
directionOf(primary).traverse(primary.extract(size)) { p ->
|
|
||||||
directionOf(secondary).traverse(secondary.extract(size)) { s ->
|
|
||||||
directionOf(tertiary).traverse(tertiary.extract(size)) { t ->
|
|
||||||
yield(origin.add(t, s, p))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
*/
|
|
||||||
@@ -6,12 +6,11 @@ import io.dico.parcels2.util.get
|
|||||||
import org.bukkit.Bukkit
|
import org.bukkit.Bukkit
|
||||||
import org.bukkit.Material
|
import org.bukkit.Material
|
||||||
import org.bukkit.World
|
import org.bukkit.World
|
||||||
import org.bukkit.block.Block
|
import org.bukkit.block.Sign
|
||||||
import org.bukkit.block.data.BlockData
|
import org.bukkit.block.data.BlockData
|
||||||
|
|
||||||
private val air = Bukkit.createBlockData(Material.AIR)
|
private val air = Bukkit.createBlockData(Material.AIR)
|
||||||
|
|
||||||
// TODO order paste such that attachables are placed after the block they depend on
|
|
||||||
class Schematic {
|
class Schematic {
|
||||||
val size: Vec3i get() = _size!!
|
val size: Vec3i get() = _size!!
|
||||||
private var _size: Vec3i? = null
|
private var _size: Vec3i? = null
|
||||||
@@ -21,7 +20,7 @@ class Schematic {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private var blockDatas: Array<BlockData?>? = null
|
private var blockDatas: Array<BlockData?>? = null
|
||||||
private val extra = mutableMapOf<Vec3i, (Block) -> Unit>()
|
private val extra = mutableListOf<Pair<Vec3i, ExtraBlockChange>>()
|
||||||
private var isLoaded = false; private set
|
private var isLoaded = false; private set
|
||||||
private val traverser: RegionTraverser = RegionTraverser.upward
|
private val traverser: RegionTraverser = RegionTraverser.upward
|
||||||
|
|
||||||
@@ -32,7 +31,7 @@ class Schematic {
|
|||||||
val blocks = traverser.traverseRegion(region)
|
val blocks = traverser.traverseRegion(region)
|
||||||
val total = region.blockCount.toDouble()
|
val total = region.blockCount.toDouble()
|
||||||
|
|
||||||
for ((index, vec) in blocks.withIndex()) {
|
loop@ for ((index, vec) in blocks.withIndex()) {
|
||||||
markSuspensionPoint()
|
markSuspensionPoint()
|
||||||
setProgress(index / total)
|
setProgress(index / total)
|
||||||
|
|
||||||
@@ -40,6 +39,14 @@ class Schematic {
|
|||||||
if (block.y > 255) continue
|
if (block.y > 255) continue
|
||||||
val blockData = block.blockData
|
val blockData = block.blockData
|
||||||
data[index] = blockData
|
data[index] = blockData
|
||||||
|
|
||||||
|
val extraChange = when (blockData.material) {
|
||||||
|
Material.SIGN,
|
||||||
|
Material.WALL_SIGN -> SignStateChange(block.state as Sign)
|
||||||
|
else -> continue@loop
|
||||||
|
}
|
||||||
|
|
||||||
|
extra += (vec - region.origin) to extraChange
|
||||||
}
|
}
|
||||||
|
|
||||||
isLoaded = true
|
isLoaded = true
|
||||||
@@ -47,53 +54,65 @@ class Schematic {
|
|||||||
|
|
||||||
suspend fun WorkerScope.paste(world: World, position: Vec3i) {
|
suspend fun WorkerScope.paste(world: World, position: Vec3i) {
|
||||||
if (!isLoaded) throw IllegalStateException()
|
if (!isLoaded) throw IllegalStateException()
|
||||||
|
|
||||||
val region = Region(position, _size!!)
|
val region = Region(position, _size!!)
|
||||||
val blocks = traverser.traverseRegion(region, worldHeight = world.maxHeight)
|
val blocks = traverser.traverseRegion(region, worldHeight = world.maxHeight)
|
||||||
val blockDatas = blockDatas!!
|
val blockDatas = blockDatas!!
|
||||||
var postponed = hashMapOf<Vec3i, BlockData>()
|
var postponed = hashMapOf<Vec3i, BlockData>()
|
||||||
|
|
||||||
// 90% of the progress of this job is allocated to this code block
|
val total = region.blockCount.toDouble()
|
||||||
delegateWork(0.9) {
|
var processed = 0
|
||||||
for ((index, vec) in blocks.withIndex()) {
|
|
||||||
markSuspensionPoint()
|
|
||||||
val block = world[vec]
|
|
||||||
val type = blockDatas[index] ?: air
|
|
||||||
if (type !== air && isAttachable(type.material)) {
|
|
||||||
val supportingBlock = vec + getSupportingBlock(type)
|
|
||||||
|
|
||||||
if (!postponed.containsKey(supportingBlock) && traverser.comesFirst(vec, supportingBlock)) {
|
for ((index, vec) in blocks.withIndex()) {
|
||||||
block.blockData = type
|
markSuspensionPoint()
|
||||||
} else {
|
setProgress(index / total)
|
||||||
postponed[vec] = type
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
val block = world[vec]
|
||||||
|
val type = blockDatas[index] ?: air
|
||||||
|
if (type !== air && isAttachable(type.material)) {
|
||||||
|
val supportingBlock = vec + getSupportingBlock(type)
|
||||||
|
|
||||||
|
if (!postponed.containsKey(supportingBlock) && traverser.comesFirst(vec, supportingBlock)) {
|
||||||
block.blockData = type
|
block.blockData = type
|
||||||
|
setProgress(++processed / total)
|
||||||
|
} else {
|
||||||
|
postponed[vec] = type
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
block.blockData = type
|
||||||
|
setProgress(++processed / total)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
delegateWork {
|
while (!postponed.isEmpty()) {
|
||||||
while (!postponed.isEmpty()) {
|
markSuspensionPoint()
|
||||||
val newMap = hashMapOf<Vec3i, BlockData>()
|
val newMap = hashMapOf<Vec3i, BlockData>()
|
||||||
for ((vec, type) in postponed) {
|
for ((vec, type) in postponed) {
|
||||||
val supportingBlock = vec + getSupportingBlock(type)
|
val supportingBlock = vec + getSupportingBlock(type)
|
||||||
if (supportingBlock in postponed && supportingBlock != vec) {
|
if (supportingBlock in postponed && supportingBlock != vec) {
|
||||||
newMap[vec] = type
|
newMap[vec] = type
|
||||||
} else {
|
} else {
|
||||||
world[vec].blockData = type
|
world[vec].blockData = type
|
||||||
}
|
setProgress(++processed / total)
|
||||||
}
|
}
|
||||||
postponed = newMap
|
|
||||||
}
|
}
|
||||||
|
postponed = newMap
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should be negligible so we don't track progress
|
||||||
|
for ((vec, extraChange) in extra) {
|
||||||
|
markSuspensionPoint()
|
||||||
|
val block = world[position + vec]
|
||||||
|
extraChange.update(block)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getLoadTask(world: World, region: Region): TimeLimitedTask = {
|
fun getLoadTask(world: World, region: Region): WorkerTask = {
|
||||||
load(world, region)
|
load(world, region)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getPasteTask(world: World, position: Vec3i): TimeLimitedTask = {
|
fun getPasteTask(world: World, position: Vec3i): WorkerTask = {
|
||||||
paste(world, position)
|
paste(world, position)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,17 +16,17 @@ import kotlin.coroutines.intrinsics.COROUTINE_SUSPENDED
|
|||||||
import kotlin.coroutines.intrinsics.suspendCoroutineUninterceptedOrReturn
|
import kotlin.coroutines.intrinsics.suspendCoroutineUninterceptedOrReturn
|
||||||
import kotlin.coroutines.resume
|
import kotlin.coroutines.resume
|
||||||
|
|
||||||
typealias TimeLimitedTask = suspend WorkerScope.() -> Unit
|
typealias WorkerTask = suspend WorkerScope.() -> Unit
|
||||||
typealias WorkerUpdateLister = Worker.(Double, Long) -> Unit
|
typealias WorkerUpdateLister = Worker.(Double, Long) -> Unit
|
||||||
|
|
||||||
data class TickWorktimeOptions(var workTime: Int, var tickInterval: Int)
|
data class TickWorktimeOptions(var workTime: Int, var tickInterval: Int)
|
||||||
|
|
||||||
interface WorktimeLimiter {
|
interface WorkDispatcher {
|
||||||
/**
|
/**
|
||||||
* Submit a [task] that should be run synchronously, but limited such that it does not stall the server
|
* Submit a [task] that should be run synchronously, but limited such that it does not stall the server
|
||||||
* a bunch
|
* a bunch
|
||||||
*/
|
*/
|
||||||
fun submit(task: TimeLimitedTask): Worker
|
fun dispatch(task: WorkerTask): Worker
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a list of all workers
|
* Get a list of all workers
|
||||||
@@ -39,14 +39,20 @@ interface WorktimeLimiter {
|
|||||||
fun completeAllTasks()
|
fun completeAllTasks()
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Timed {
|
interface WorkerAndScopeMembersUnion {
|
||||||
/**
|
/**
|
||||||
* The time that elapsed since this worker was dispatched, in milliseconds
|
* The time that elapsed since this worker was dispatched, in milliseconds
|
||||||
*/
|
*/
|
||||||
val elapsedTime: Long
|
val elapsedTime: Long
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A value indicating the progress of this worker, in the range 0.0 <= progress <= 1.0
|
||||||
|
* with no guarantees to its accuracy.
|
||||||
|
*/
|
||||||
|
val progress: Double
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Worker : Timed {
|
interface Worker : WorkerAndScopeMembersUnion {
|
||||||
/**
|
/**
|
||||||
* The coroutine associated with this worker
|
* The coroutine associated with this worker
|
||||||
*/
|
*/
|
||||||
@@ -63,12 +69,6 @@ interface Worker : Timed {
|
|||||||
*/
|
*/
|
||||||
val completionException: Throwable?
|
val completionException: Throwable?
|
||||||
|
|
||||||
/**
|
|
||||||
* A value indicating the progress of this worker, in the range 0.0 <= progress <= 1.0
|
|
||||||
* with no guarantees to its accuracy.
|
|
||||||
*/
|
|
||||||
val progress: Double
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calls the given [block] whenever the progress of this worker is updated,
|
* Calls the given [block] whenever the progress of this worker is updated,
|
||||||
* if [minInterval] milliseconds expired since the last call.
|
* if [minInterval] milliseconds expired since the last call.
|
||||||
@@ -96,18 +96,12 @@ interface Worker : Timed {
|
|||||||
//val attachment: Any?
|
//val attachment: Any?
|
||||||
}
|
}
|
||||||
|
|
||||||
interface WorkerScope : Timed {
|
interface WorkerScope : WorkerAndScopeMembersUnion {
|
||||||
/**
|
/**
|
||||||
* A task should call this frequently during its execution, such that the timer can suspend it when necessary.
|
* A task should call this frequently during its execution, such that the timer can suspend it when necessary.
|
||||||
*/
|
*/
|
||||||
suspend fun markSuspensionPoint()
|
suspend fun markSuspensionPoint()
|
||||||
|
|
||||||
/**
|
|
||||||
* A value indicating the progress of this worker, in the range 0.0 <= progress <= 1.0
|
|
||||||
* with no guarantees to its accuracy.
|
|
||||||
*/
|
|
||||||
val progress: Double
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A task should call this method to indicate its progress
|
* A task should call this method to indicate its progress
|
||||||
*/
|
*/
|
||||||
@@ -152,14 +146,14 @@ interface WorkerInternal : Worker, WorkerScope {
|
|||||||
* There is a configurable maxiumum amount of milliseconds that can be allocated to all workers together in each server tick
|
* There is a configurable maxiumum amount of milliseconds that can be allocated to all workers together in each server tick
|
||||||
* This object attempts to split that maximum amount of milliseconds equally between all jobs
|
* This object attempts to split that maximum amount of milliseconds equally between all jobs
|
||||||
*/
|
*/
|
||||||
class TickWorktimeLimiter(private val plugin: ParcelsPlugin, var options: TickWorktimeOptions) : WorktimeLimiter {
|
class BukkitWorkDispatcher(private val plugin: ParcelsPlugin, var options: TickWorktimeOptions) : WorkDispatcher {
|
||||||
// The currently registered bukkit scheduler task
|
// The currently registered bukkit scheduler task
|
||||||
private var bukkitTask: BukkitTask? = null
|
private var bukkitTask: BukkitTask? = null
|
||||||
// The workers.
|
// The workers.
|
||||||
private val _workers = LinkedList<WorkerInternal>()
|
private val _workers = LinkedList<WorkerInternal>()
|
||||||
override val workers: List<Worker> = _workers
|
override val workers: List<Worker> = _workers
|
||||||
|
|
||||||
override fun submit(task: TimeLimitedTask): Worker {
|
override fun dispatch(task: WorkerTask): Worker {
|
||||||
val worker: WorkerInternal = WorkerImpl(plugin, task)
|
val worker: WorkerInternal = WorkerImpl(plugin, task)
|
||||||
|
|
||||||
if (bukkitTask == null) {
|
if (bukkitTask == null) {
|
||||||
@@ -209,7 +203,7 @@ class TickWorktimeLimiter(private val plugin: ParcelsPlugin, var options: TickWo
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class WorkerImpl(scope: CoroutineScope, task: TimeLimitedTask) : WorkerInternal {
|
private class WorkerImpl(scope: CoroutineScope, task: WorkerTask) : WorkerInternal {
|
||||||
override val job: Job = scope.launch(start = LAZY) { task() }
|
override val job: Job = scope.launch(start = LAZY) { task() }
|
||||||
|
|
||||||
private var continuation: Continuation<Unit>? = null
|
private var continuation: Continuation<Unit>? = null
|
||||||
@@ -239,7 +233,7 @@ private class WorkerImpl(scope: CoroutineScope, task: TimeLimitedTask) : WorkerI
|
|||||||
// report any error that occurred
|
// report any error that occurred
|
||||||
completionException = exception?.also {
|
completionException = exception?.also {
|
||||||
if (it !is CancellationException)
|
if (it !is CancellationException)
|
||||||
logger.error("TimeLimitedTask generated an exception", it)
|
logger.error("WorkerTask generated an exception", it)
|
||||||
}
|
}
|
||||||
|
|
||||||
// convert to elapsed time here
|
// convert to elapsed time here
|
||||||
@@ -316,6 +310,7 @@ private class WorkerImpl(scope: CoroutineScope, task: TimeLimitedTask) : WorkerI
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isStarted = true
|
||||||
startTimeOrElapsedTime = System.currentTimeMillis()
|
startTimeOrElapsedTime = System.currentTimeMillis()
|
||||||
job.start()
|
job.start()
|
||||||
|
|
||||||
@@ -348,14 +343,3 @@ private class WorkerImpl(scope: CoroutineScope, task: TimeLimitedTask) : WorkerI
|
|||||||
this@WorkerImpl.delegateWork(this.portion, portion)
|
this@WorkerImpl.delegateWork(this.portion, portion)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
/**
|
|
||||||
* While the implementation of [kotlin.coroutines.experimental.intrinsics.intercepted] is intrinsic, it should look something like this
|
|
||||||
* We don't care for intercepting the coroutine as we want it to resume immediately when we call resume().
|
|
||||||
* Thus, above, we use an unintercepted suspension. It's not necessary as the dispatcher (or interceptor) also calls it synchronously, but whatever.
|
|
||||||
*/
|
|
||||||
private fun <T> Continuation<T>.interceptedImpl(): Continuation<T> {
|
|
||||||
return context[ContinuationInterceptor]?.interceptContinuation(this) ?: this
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
@@ -8,7 +8,6 @@ import io.dico.dicore.command.annotation.Cmd
|
|||||||
import io.dico.parcels2.ParcelsPlugin
|
import io.dico.parcels2.ParcelsPlugin
|
||||||
import io.dico.parcels2.Privilege
|
import io.dico.parcels2.Privilege
|
||||||
import io.dico.parcels2.blockvisitor.RegionTraverser
|
import io.dico.parcels2.blockvisitor.RegionTraverser
|
||||||
import io.dico.parcels2.blockvisitor.TickWorktimeLimiter
|
|
||||||
import io.dico.parcels2.doBlockOperation
|
import io.dico.parcels2.doBlockOperation
|
||||||
import org.bukkit.Bukkit
|
import org.bukkit.Bukkit
|
||||||
import org.bukkit.Material
|
import org.bukkit.Material
|
||||||
@@ -78,15 +77,15 @@ class CommandsDebug(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
|
|||||||
|
|
||||||
@Cmd("visitors")
|
@Cmd("visitors")
|
||||||
fun cmdVisitors(): Any? {
|
fun cmdVisitors(): Any? {
|
||||||
val workers = plugin.worktimeLimiter.workers
|
val workers = plugin.workDispatcher.workers
|
||||||
println(workers.map { it.job }.joinToString(separator = "\n"))
|
println(workers.map { it.job }.joinToString(separator = "\n"))
|
||||||
return "Task count: ${workers.size}"
|
return "Task count: ${workers.size}"
|
||||||
}
|
}
|
||||||
|
|
||||||
@Cmd("force_visitors")
|
@Cmd("force_visitors")
|
||||||
fun cmdForceVisitors(): Any? {
|
fun cmdForceVisitors(): Any? {
|
||||||
val workers = plugin.worktimeLimiter.workers
|
val workers = plugin.workDispatcher.workers
|
||||||
plugin.worktimeLimiter.completeAllTasks()
|
plugin.workDispatcher.completeAllTasks()
|
||||||
return "Task count: ${workers.size}"
|
return "Task count: ${workers.size}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import io.dico.parcels2.util.ext.uuid
|
|||||||
import org.bukkit.block.Biome
|
import org.bukkit.block.Biome
|
||||||
import org.bukkit.entity.Player
|
import org.bukkit.entity.Player
|
||||||
|
|
||||||
class CommandsGeneral(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
|
class CommandsGeneral(plugin: ParcelsPlugin, parent: SpecialCommandAddress) : AbstractParcelCommands(plugin) {
|
||||||
|
|
||||||
@Cmd("auto")
|
@Cmd("auto")
|
||||||
@Desc(
|
@Desc(
|
||||||
@@ -43,6 +43,10 @@ class CommandsGeneral(plugin: ParcelsPlugin) : AbstractParcelCommands(plugin) {
|
|||||||
)
|
)
|
||||||
fun ParcelScope.cmdInfo(player: Player) = parcel.infoString
|
fun ParcelScope.cmdInfo(player: Player) = parcel.infoString
|
||||||
|
|
||||||
|
init {
|
||||||
|
parent.addSpeciallyTreatedKeys("home", "h")
|
||||||
|
}
|
||||||
|
|
||||||
@Cmd("home", aliases = ["h"])
|
@Cmd("home", aliases = ["h"])
|
||||||
@Desc(
|
@Desc(
|
||||||
"Teleports you to your parcels,",
|
"Teleports you to your parcels,",
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
package io.dico.parcels2.command
|
package io.dico.parcels2.command
|
||||||
|
|
||||||
import io.dico.dicore.command.CommandBuilder
|
import io.dico.dicore.command.*
|
||||||
import io.dico.dicore.command.ICommandAddress
|
|
||||||
import io.dico.dicore.command.ICommandDispatcher
|
|
||||||
import io.dico.dicore.command.registration.reflect.ReflectiveRegistration
|
import io.dico.dicore.command.registration.reflect.ReflectiveRegistration
|
||||||
import io.dico.parcels2.Interactables
|
import io.dico.parcels2.Interactables
|
||||||
import io.dico.parcels2.ParcelsPlugin
|
import io.dico.parcels2.ParcelsPlugin
|
||||||
@@ -13,14 +11,16 @@ import java.util.Queue
|
|||||||
@Suppress("UsePropertyAccessSyntax")
|
@Suppress("UsePropertyAccessSyntax")
|
||||||
fun getParcelCommands(plugin: ParcelsPlugin): ICommandDispatcher =
|
fun getParcelCommands(plugin: ParcelsPlugin): ICommandDispatcher =
|
||||||
with(CommandBuilder()) {
|
with(CommandBuilder()) {
|
||||||
|
val parcelsAddress = SpecialCommandAddress()
|
||||||
|
|
||||||
setChatController(ParcelsChatController())
|
setChatController(ParcelsChatController())
|
||||||
addParameterType(false, ParcelParameterType(plugin.parcelProvider))
|
addParameterType(false, ParcelParameterType(plugin.parcelProvider))
|
||||||
addParameterType(false, ProfileParameterType())
|
addParameterType(false, ProfileParameterType())
|
||||||
addParameterType(true, ParcelTarget.PType(plugin.parcelProvider))
|
addParameterType(true, ParcelTarget.PType(plugin.parcelProvider, parcelsAddress))
|
||||||
|
|
||||||
group("parcel", "plot", "plots", "p") {
|
group(parcelsAddress, "parcel", "plot", "plots", "p") {
|
||||||
addRequiredPermission("parcels.command")
|
addRequiredPermission("parcels.command")
|
||||||
registerCommands(CommandsGeneral(plugin))
|
registerCommands(CommandsGeneral(plugin, parcelsAddress))
|
||||||
registerCommands(CommandsPrivilegesLocal(plugin))
|
registerCommands(CommandsPrivilegesLocal(plugin))
|
||||||
|
|
||||||
group("option", "opt", "o") {
|
group("option", "opt", "o") {
|
||||||
@@ -63,6 +63,12 @@ inline fun CommandBuilder.group(name: String, vararg aliases: String, config: Co
|
|||||||
parent()
|
parent()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline fun CommandBuilder.group(address: ICommandAddress, name: String, vararg aliases: String, config: CommandBuilder.() -> Unit) {
|
||||||
|
group(address, name, *aliases)
|
||||||
|
config()
|
||||||
|
parent()
|
||||||
|
}
|
||||||
|
|
||||||
private fun CommandBuilder.generateHelpAndSyntaxCommands(): CommandBuilder {
|
private fun CommandBuilder.generateHelpAndSyntaxCommands(): CommandBuilder {
|
||||||
generateCommands(dispatcher as ICommandAddress, "help", "syntax")
|
generateCommands(dispatcher as ICommandAddress, "help", "syntax")
|
||||||
return this
|
return this
|
||||||
@@ -80,3 +86,37 @@ private fun generateCommands(address: ICommandAddress, vararg names: String) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class SpecialCommandAddress : ChildCommandAddress() {
|
||||||
|
private val speciallyTreatedKeys = mutableListOf<String>()
|
||||||
|
|
||||||
|
// Used to allow /p h:1 syntax, which is the same as what PlotMe uses.
|
||||||
|
var speciallyParsedIndex: Int? = null; private set
|
||||||
|
|
||||||
|
fun addSpeciallyTreatedKeys(vararg keys: String) {
|
||||||
|
for (key in keys) {
|
||||||
|
speciallyTreatedKeys.add(key + ":")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(CommandException::class)
|
||||||
|
override fun getChild(key: String, context: ExecutionContext): ChildCommandAddress? {
|
||||||
|
speciallyParsedIndex = null
|
||||||
|
|
||||||
|
for (specialKey in speciallyTreatedKeys) {
|
||||||
|
if (key.startsWith(specialKey)) {
|
||||||
|
val result = getChild(specialKey.substring(0, specialKey.length - 1))
|
||||||
|
?: return null
|
||||||
|
|
||||||
|
val text = key.substring(specialKey.length)
|
||||||
|
val num = text.toIntOrNull() ?: throw CommandException("$text is not a number")
|
||||||
|
speciallyParsedIndex = num
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.getChild(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ sealed class ParcelTarget(val world: ParcelWorld, val parsedKind: Int, val isDef
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class PType(val parcelProvider: ParcelProvider) : ParameterType<ParcelTarget, Int>(ParcelTarget::class.java, Config) {
|
class PType(val parcelProvider: ParcelProvider, val parcelAddress: SpecialCommandAddress? = null) : ParameterType<ParcelTarget, Int>(ParcelTarget::class.java, Config) {
|
||||||
|
|
||||||
override fun parse(parameter: Parameter<ParcelTarget, Int>, sender: CommandSender, buffer: ArgumentBuffer): ParcelTarget {
|
override fun parse(parameter: Parameter<ParcelTarget, Int>, sender: CommandSender, buffer: ArgumentBuffer): ParcelTarget {
|
||||||
var input = buffer.next()
|
var input = buffer.next()
|
||||||
@@ -124,11 +124,25 @@ sealed class ParcelTarget(val world: ParcelWorld, val parsedKind: Int, val isDef
|
|||||||
val ownerString: String
|
val ownerString: String
|
||||||
val index: Int?
|
val index: Int?
|
||||||
|
|
||||||
|
val speciallyParsedIndex = parcelAddress?.speciallyParsedIndex
|
||||||
|
|
||||||
if (splitIdx == -1) {
|
if (splitIdx == -1) {
|
||||||
// just the index.
|
|
||||||
index = input.toIntOrNull()
|
if (speciallyParsedIndex == null) {
|
||||||
ownerString = if (index == null) input else ""
|
// just the index.
|
||||||
|
index = input.toIntOrNull()
|
||||||
|
ownerString = if (index == null) input else ""
|
||||||
|
} else {
|
||||||
|
// just the owner.
|
||||||
|
index = speciallyParsedIndex
|
||||||
|
ownerString = input
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
if (speciallyParsedIndex != null) {
|
||||||
|
invalidInput(parameter, "Duplicate home index")
|
||||||
|
}
|
||||||
|
|
||||||
ownerString = input.substring(0, splitIdx)
|
ownerString = input.substring(0, splitIdx)
|
||||||
|
|
||||||
val indexString = input.substring(splitIdx + 1)
|
val indexString = input.substring(splitIdx + 1)
|
||||||
@@ -165,7 +179,7 @@ sealed class ParcelTarget(val world: ParcelWorld, val parsedKind: Int, val isDef
|
|||||||
return ByID(world, id, kind, true)
|
return ByID(world, id, kind, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ByOwner(world, PlayerProfile(player), 0, kind, true)
|
return ByOwner(world, PlayerProfile(player), parcelAddress?.speciallyParsedIndex ?: 0, kind, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -119,9 +119,9 @@ class DefaultParcelGenerator(
|
|||||||
worldId: ParcelWorldId,
|
worldId: ParcelWorldId,
|
||||||
container: ParcelContainer,
|
container: ParcelContainer,
|
||||||
coroutineScope: CoroutineScope,
|
coroutineScope: CoroutineScope,
|
||||||
worktimeLimiter: WorktimeLimiter
|
workDispatcher: WorkDispatcher
|
||||||
): Pair<ParcelLocator, ParcelBlockManager> {
|
): Pair<ParcelLocator, ParcelBlockManager> {
|
||||||
return ParcelLocatorImpl(worldId, container) to ParcelBlockManagerImpl(worldId, coroutineScope, worktimeLimiter)
|
return ParcelLocatorImpl(worldId, container) to ParcelBlockManagerImpl(worldId, coroutineScope, workDispatcher)
|
||||||
}
|
}
|
||||||
|
|
||||||
private inline fun <T> convertBlockLocationToId(x: Int, z: Int, mapper: (Int, Int) -> T): T? {
|
private inline fun <T> convertBlockLocationToId(x: Int, z: Int, mapper: (Int, Int) -> T): T? {
|
||||||
@@ -156,7 +156,7 @@ class DefaultParcelGenerator(
|
|||||||
private inner class ParcelBlockManagerImpl(
|
private inner class ParcelBlockManagerImpl(
|
||||||
val worldId: ParcelWorldId,
|
val worldId: ParcelWorldId,
|
||||||
coroutineScope: CoroutineScope,
|
coroutineScope: CoroutineScope,
|
||||||
override val worktimeLimiter: WorktimeLimiter
|
override val workDispatcher: WorkDispatcher
|
||||||
) : ParcelBlockManagerBase(), CoroutineScope by coroutineScope {
|
) : ParcelBlockManagerBase(), CoroutineScope by coroutineScope {
|
||||||
override val world: World = this@DefaultParcelGenerator.world
|
override val world: World = this@DefaultParcelGenerator.world
|
||||||
override val parcelTraverser: RegionTraverser = RegionTraverser.convergingTo(o.floorHeight)
|
override val parcelTraverser: RegionTraverser = RegionTraverser.convergingTo(o.floorHeight)
|
||||||
@@ -222,12 +222,12 @@ class DefaultParcelGenerator(
|
|||||||
return world.getParcelById(parcelId)
|
return world.getParcelById(parcelId)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun submitBlockVisitor(vararg parcelIds: ParcelId, task: TimeLimitedTask): Worker {
|
override fun submitBlockVisitor(vararg parcelIds: ParcelId, task: WorkerTask): Worker {
|
||||||
val parcels = parcelIds.mapNotNull { getParcel(it) }
|
val parcels = parcelIds.mapNotNull { getParcel(it) }
|
||||||
if (parcels.isEmpty()) return worktimeLimiter.submit(task)
|
if (parcels.isEmpty()) return workDispatcher.dispatch(task)
|
||||||
if (parcels.any { it.hasBlockVisitors }) throw IllegalArgumentException("This parcel already has a block visitor")
|
if (parcels.any { it.hasBlockVisitors }) throw IllegalArgumentException("This parcel already has a block visitor")
|
||||||
|
|
||||||
val worker = worktimeLimiter.submit(task)
|
val worker = workDispatcher.dispatch(task)
|
||||||
|
|
||||||
for (parcel in parcels) {
|
for (parcel in parcels) {
|
||||||
launch(start = UNDISPATCHED) {
|
launch(start = UNDISPATCHED) {
|
||||||
@@ -277,10 +277,10 @@ class DefaultParcelGenerator(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun swapParcels(parcel1: ParcelId, parcel2: ParcelId): Worker = submitBlockVisitor(parcel1, parcel2) {
|
override fun swapParcels(parcel1: ParcelId, parcel2: ParcelId): Worker = submitBlockVisitor(parcel1, parcel2) {
|
||||||
val schematicOf1 = delegateWork(0.15) { Schematic().apply { load(world, getRegion(parcel1)) } }
|
val schematicOf1 = delegateWork(0.25) { Schematic().apply { load(world, getRegion(parcel1)) } }
|
||||||
val schematicOf2 = delegateWork(0.15) { Schematic().apply { load(world, getRegion(parcel2)) } }
|
val schematicOf2 = delegateWork(0.25) { Schematic().apply { load(world, getRegion(parcel2)) } }
|
||||||
delegateWork(0.35) { with(schematicOf1) { paste(world, getRegion(parcel2).origin) } }
|
delegateWork(0.25) { with(schematicOf1) { paste(world, getRegion(parcel2).origin) } }
|
||||||
delegateWork(0.35) { with(schematicOf2) { paste(world, getRegion(parcel1).origin) } }
|
delegateWork(0.25) { with(schematicOf2) { paste(world, getRegion(parcel1).origin) } }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getParcelsWithOwnerBlockIn(chunk: Chunk): Collection<Vec2i> {
|
override fun getParcelsWithOwnerBlockIn(chunk: Chunk): Collection<Vec2i> {
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ class ParcelProviderImpl(val plugin: ParcelsPlugin) : ParcelProvider {
|
|||||||
else WorldCreator(worldName).generator(generator).createWorld().also { logger.info("Creating world $worldName") }
|
else WorldCreator(worldName).generator(generator).createWorld().also { logger.info("Creating world $worldName") }
|
||||||
|
|
||||||
parcelWorld = ParcelWorldImpl(bukkitWorld, generator, worldOptions.runtime, plugin.storage,
|
parcelWorld = ParcelWorldImpl(bukkitWorld, generator, worldOptions.runtime, plugin.storage,
|
||||||
plugin.globalPrivileges, ::DefaultParcelContainer, plugin, plugin.worktimeLimiter)
|
plugin.globalPrivileges, ::DefaultParcelContainer, plugin, plugin.workDispatcher)
|
||||||
|
|
||||||
if (!worldExists) {
|
if (!worldExists) {
|
||||||
val time = DateTime.now()
|
val time = DateTime.now()
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
package io.dico.parcels2.defaultimpl
|
package io.dico.parcels2.defaultimpl
|
||||||
|
|
||||||
import io.dico.parcels2.*
|
import io.dico.parcels2.*
|
||||||
import io.dico.parcels2.blockvisitor.WorktimeLimiter
|
import io.dico.parcels2.blockvisitor.WorkDispatcher
|
||||||
import io.dico.parcels2.options.RuntimeWorldOptions
|
import io.dico.parcels2.options.RuntimeWorldOptions
|
||||||
import io.dico.parcels2.storage.Storage
|
import io.dico.parcels2.storage.Storage
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
@@ -18,7 +18,7 @@ class ParcelWorldImpl(override val world: World,
|
|||||||
override val globalPrivileges: GlobalPrivilegesManager,
|
override val globalPrivileges: GlobalPrivilegesManager,
|
||||||
containerFactory: ParcelContainerFactory,
|
containerFactory: ParcelContainerFactory,
|
||||||
coroutineScope: CoroutineScope,
|
coroutineScope: CoroutineScope,
|
||||||
worktimeLimiter: WorktimeLimiter)
|
workDispatcher: WorkDispatcher)
|
||||||
: ParcelWorld,
|
: ParcelWorld,
|
||||||
ParcelWorldId,
|
ParcelWorldId,
|
||||||
ParcelContainer, /* missing delegation */
|
ParcelContainer, /* missing delegation */
|
||||||
@@ -39,7 +39,7 @@ class ParcelWorldImpl(override val world: World,
|
|||||||
override val blockManager: ParcelBlockManager
|
override val blockManager: ParcelBlockManager
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val pair = generator.makeParcelLocatorAndBlockManager(id, container, coroutineScope, worktimeLimiter)
|
val pair = generator.makeParcelLocatorAndBlockManager(id, container, coroutineScope, workDispatcher)
|
||||||
locator = pair.first
|
locator = pair.first
|
||||||
blockManager = pair.second
|
blockManager = pair.second
|
||||||
|
|
||||||
|
|||||||
@@ -198,8 +198,8 @@ class ParcelListeners(
|
|||||||
val type = clickedBlock.type
|
val type = clickedBlock.type
|
||||||
|
|
||||||
val interactableClass = Interactables[type]
|
val interactableClass = Interactables[type]
|
||||||
if (interactableClass != null && (parcel.effectiveInteractableConfig.isInteractable(type) || (parcel != null && parcel.canBuild(user)))) {
|
if (interactableClass != null && !parcel.effectiveInteractableConfig.isInteractable(type) && (parcel == null || !parcel.canBuild(user))) {
|
||||||
user.sendParcelMessage(nopermit = true, message = "You cannot interact with ${interactableClass.name} in this parcel")
|
user.sendParcelMessage(nopermit = true, message = "You cannot interact with ${interactableClass.name} here")
|
||||||
event.isCancelled = true
|
event.isCancelled = true
|
||||||
return@l
|
return@l
|
||||||
}
|
}
|
||||||
@@ -595,4 +595,16 @@ class ParcelListeners(
|
|||||||
storage.updatePlayerName(event.player.uuid, event.player.name)
|
storage.updatePlayerName(event.player.uuid, event.player.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to prevent redstone contraptions from breaking while they are being swapped
|
||||||
|
* Might remove if it causes lag
|
||||||
|
*/
|
||||||
|
@ListenerMarker
|
||||||
|
val onBlockRedstoneEvent = RegistratorListener<BlockRedstoneEvent> l@{ event ->
|
||||||
|
val (_, area) = getWorldAndArea(event.block) ?: return@l
|
||||||
|
if (area == null || area.hasBlockVisitors) {
|
||||||
|
event.newCurrent = event.oldCurrent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -26,6 +26,7 @@ data class Vec3i(
|
|||||||
val z: Int
|
val z: Int
|
||||||
) {
|
) {
|
||||||
operator fun plus(o: Vec3i) = Vec3i(x + o.x, y + o.y, z + o.z)
|
operator fun plus(o: Vec3i) = Vec3i(x + o.x, y + o.y, z + o.z)
|
||||||
|
operator fun minus(o: Vec3i) = Vec3i(x - o.x, y - o.y, z - o.z)
|
||||||
infix fun addX(o: Int) = Vec3i(x + o, y, z)
|
infix fun addX(o: Int) = Vec3i(x + o, y, z)
|
||||||
infix fun addY(o: Int) = Vec3i(x, y + o, z)
|
infix fun addY(o: Int) = Vec3i(x, y + o, z)
|
||||||
infix fun addZ(o: Int) = Vec3i(x, y, z + o)
|
infix fun addZ(o: Int) = Vec3i(x, y, z + o)
|
||||||
|
|||||||
32
todo.md
32
todo.md
@@ -6,16 +6,16 @@ Basically all admin commands.
|
|||||||
* ~~setowner~~
|
* ~~setowner~~
|
||||||
* ~~dispose~~
|
* ~~dispose~~
|
||||||
* ~~reset~~
|
* ~~reset~~
|
||||||
* swap
|
* ~~swap~~
|
||||||
* New admin commands that I can't think of right now.
|
* New admin commands that I can't think of right now.
|
||||||
|
|
||||||
Also
|
Also
|
||||||
* ~~setbiome~~
|
* ~~setbiome~~
|
||||||
* random
|
* random
|
||||||
|
|
||||||
Modify home command:
|
~~Modify home command:~~
|
||||||
* ~~Make `:` not be required if prior component cannot be parsed to an int~~
|
* ~~Make `:` not be required if prior component cannot be parsed to an int~~
|
||||||
* Listen for command events that use plotme-style argument, and transform the command
|
* ~~Listen for command events that use plotme-style argument, and transform the command~~
|
||||||
|
|
||||||
~~Add permissions to commands (replace or fix `IContextFilter` from command lib
|
~~Add permissions to commands (replace or fix `IContextFilter` from command lib
|
||||||
to allow inheriting permissions properly).~~
|
to allow inheriting permissions properly).~~
|
||||||
@@ -23,32 +23,34 @@ to allow inheriting permissions properly).~~
|
|||||||
Parcel Options
|
Parcel Options
|
||||||
-
|
-
|
||||||
|
|
||||||
Parcel options apply to any player with `DEFAULT` added status.
|
Parcel options apply to any player with `DEFAULT` added status.
|
||||||
They affect what their permissions might be within the parcel.
|
They affect what their permissions might be within the parcel.
|
||||||
|
|
||||||
Apart from `/p option inputs`, `/p option inventory`, the following might be considered.
|
Apart from `/p option inputs`, `/p option inventory`, the following might be considered.
|
||||||
|
|
||||||
Move existing options to "interact" namespace (`/p o interact`)
|
~~Move existing options to "interact" namespace (`/p o interact`)
|
||||||
|
Add classes for different things you can interact with~~
|
||||||
|
|
||||||
Then,
|
~~Then,~~
|
||||||
* Split `/p option interact inputs` into a list of interactible block types.
|
~~* Split `/p option interact inputs` into a list of interactible block types.~~
|
||||||
The list could include container blocks, merging the existing inventory option.
|
~~The list could include container blocks, merging the existing inventory option.~~
|
||||||
* Players cannot launch projectiles in locations where they can't build.
|
* Players cannot launch projectiles in locations where they can't build.~~
|
||||||
This could become optional.
|
This could become optional.
|
||||||
* Option to control spreading and/or forming of blocks such as grass and ice within the parcel.
|
|
||||||
|
* Option to control spreading and/or forming of blocks such as grass and ice within the parcel.~~
|
||||||
|
|
||||||
Block Management
|
Block Management
|
||||||
-
|
-
|
||||||
~~Update the parcel corner with owner info when a player flies into the parcel (after migrations).
|
~~Update the parcel corner with owner info when a player flies into the parcel (after migrations).
|
||||||
Parcels has a player head in that corner in addition to the sign that PlotMe uses.~~
|
Parcels has a player head in that corner in addition to the sign that PlotMe uses.~~
|
||||||
|
|
||||||
Commands that modify parcel blocks must be kept track of to prevent multiple
|
~~Commands that modify parcel blocks must be kept track of to prevent multiple
|
||||||
from running simultaneously in the same parcel. `hasBlockVisitors` field must be updated.
|
from running simultaneously in the same parcel. `hasBlockVisitors` field must be updated.
|
||||||
In general, spamming the commands must be caught at all cost to avoid lots of lag.
|
In general, spamming the commands must be caught at all cost to avoid lots of lag.~~
|
||||||
|
|
||||||
Swap - schematic is in place, but proper placement order must be enforced to make sure that attachable
|
~~Swap - schematic is in place, but proper placement order must be enforced to make sure that attachable
|
||||||
blocks are placed properly. Alternatively, if a block change method can be found that doesn't
|
blocks are placed properly. Alternatively, if a block change method can be found that doesn't
|
||||||
cause block updates, that would be preferred subject to having good performance.
|
cause block updates, that would be preferred subject to having good performance.~~
|
||||||
|
|
||||||
~~Change `RegionTraversal` to allow traversing different parts of a region in a different order.
|
~~Change `RegionTraversal` to allow traversing different parts of a region in a different order.
|
||||||
This could apply to clearing of plots, for example. It would be better if the bottom 64 (floor height)
|
This could apply to clearing of plots, for example. It would be better if the bottom 64 (floor height)
|
||||||
@@ -79,6 +81,6 @@ Implement a container that doesn't require loading all parcel data on startup (C
|
|||||||
|
|
||||||
~~Update player profiles in the database on join to account for name changes.~~
|
~~Update player profiles in the database on join to account for name changes.~~
|
||||||
|
|
||||||
Store player status on parcel (allowed, default banned) as a number to allow for future additions to this set of possibilities
|
~~Store player status on parcel (allowed, default banned) as a number to allow for future additions to this set of possibilities~~
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user