Archived
0

Make progress

This commit is contained in:
Dico
2018-09-26 07:08:42 +01:00
parent 2225bdae95
commit 520ae530d2
25 changed files with 434 additions and 281 deletions

View File

@@ -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;

View File

@@ -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) {

View File

@@ -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...)}

View File

@@ -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();
} }

View File

@@ -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
* *

View File

@@ -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
* *

View File

@@ -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");

View File

@@ -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;
} }
} }

View File

@@ -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];
} }

View File

@@ -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

View File

@@ -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.")
} }

View File

@@ -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)
}
}
}

View File

@@ -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))
}
}
}
}
}
}
}
*/

View File

@@ -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)
} }

View File

@@ -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
}
*/

View File

@@ -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}"
} }

View File

@@ -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,",

View File

@@ -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)
}
}

View File

@@ -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)
} }
} }

View File

@@ -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> {

View File

@@ -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()

View File

@@ -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

View File

@@ -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
}
}
} }

View File

@@ -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
View File

@@ -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~~