Archived
0

Tweak some command stuff, clear/swap entities

This commit is contained in:
Dico Karssiens
2018-11-17 21:32:43 +00:00
parent 0f196f59c6
commit 5ef2584fdb
25 changed files with 3953 additions and 3554 deletions

View File

@@ -13,7 +13,7 @@ version = "0.2"
plugins { plugins {
java java
kotlin("jvm") version "1.3.0-rc-146" kotlin("jvm") version "1.3.0"
id("com.github.johnrengelman.plugin-shadow") version "2.0.3" id("com.github.johnrengelman.plugin-shadow") version "2.0.3"
} }
@@ -30,10 +30,11 @@ allprojects {
maven("https://dl.bintray.com/kotlin/kotlin-dev/") maven("https://dl.bintray.com/kotlin/kotlin-dev/")
maven("https://dl.bintray.com/kotlin/kotlin-eap/") maven("https://dl.bintray.com/kotlin/kotlin-eap/")
maven("https://dl.bintray.com/kotlin/kotlinx/") maven("https://dl.bintray.com/kotlin/kotlinx/")
maven("http://maven.sk89q.com/repo")
} }
dependencies { dependencies {
val spigotVersion = "1.13.1-R0.1-SNAPSHOT" val spigotVersion = "1.13.2-R0.1-SNAPSHOT"
c.provided("org.bukkit:bukkit:$spigotVersion") { isTransitive = false } c.provided("org.bukkit:bukkit:$spigotVersion") { isTransitive = false }
c.provided("org.spigotmc:spigot-api:$spigotVersion") { isTransitive = false } c.provided("org.spigotmc:spigot-api:$spigotVersion") { isTransitive = false }
@@ -52,13 +53,15 @@ project(":dicore3:dicore3-core") {
} }
} }
val coroutinesCore = kotlinx("coroutines-core:0.26.1-eap13")
project(":dicore3:dicore3-command") { project(":dicore3:dicore3-command") {
apply<KotlinPlatformJvmPlugin>() apply<KotlinPlatformJvmPlugin>()
dependencies { dependencies {
c.kotlinStd(kotlin("stdlib-jdk8")) c.kotlinStd(kotlin("stdlib-jdk8"))
c.kotlinStd(kotlin("reflect")) c.kotlinStd(kotlin("reflect"))
c.kotlinStd(kotlinx("coroutines-core:0.26.1-eap13")) c.kotlinStd(coroutinesCore)
compile(project(":dicore3:dicore3-core")) compile(project(":dicore3:dicore3-core"))
compile("com.thoughtworks.paranamer:paranamer:2.8") compile("com.thoughtworks.paranamer:paranamer:2.8")
@@ -72,12 +75,13 @@ dependencies {
c.kotlinStd(kotlin("stdlib-jdk8")) c.kotlinStd(kotlin("stdlib-jdk8"))
c.kotlinStd(kotlin("reflect")) c.kotlinStd(kotlin("reflect"))
c.kotlinStd(kotlinx("coroutines-core:0.26.1-eap13")) c.kotlinStd(coroutinesCore)
c.kotlinStd("org.jetbrains.kotlinx:atomicfu-common:0.11.7-eap13") c.kotlinStd("org.jetbrains.kotlinx:atomicfu-common:0.11.12")
// not on sk89q maven repo yet // not on sk89q maven repo yet
compileClasspath(files("$rootDir/debug/plugins/worldedit-bukkit-7.0.0-beta-01.jar")) //compileClasspath(files("$rootDir/debug/plugins/worldedit-bukkit-7.0.0-beta-01.jar"))
compileClasspath(files("$rootDir/debug/lib/spigot-1.13.1.jar")) // compileClasspath(files("$rootDir/debug/lib/spigot-1.13.2.jar"))
compileClasspath("com.sk89q.worldedit:worldedit-bukkit:7.0.0-SNAPSHOT")
compile("org.jetbrains.exposed:exposed:0.10.5") { isTransitive = false } compile("org.jetbrains.exposed:exposed:0.10.5") { isTransitive = false }
compile("joda-time:joda-time:2.10") compile("joda-time:joda-time:2.10")
@@ -167,7 +171,6 @@ val ConfigurationContainer.`kotlinStd`: Configuration
get() = findByName("kotlinStd") ?: create("kotlinStd").let { compileClasspath.extendsFrom(it) } get() = findByName("kotlinStd") ?: create("kotlinStd").let { compileClasspath.extendsFrom(it) }
fun Jar.fromFiles(files: Iterable<File>) { fun Jar.fromFiles(files: Iterable<File>) {
return
afterEvaluate { from(*files.map { if (it.isDirectory) it else zipTree(it) }.toTypedArray()) } afterEvaluate { from(*files.map { if (it.isDirectory) it else zipTree(it) }.toTypedArray()) }
} }

View File

@@ -1,275 +1,281 @@
package io.dico.dicore.command; package io.dico.dicore.command;
import io.dico.dicore.command.parameter.ArgumentBuffer; import io.dico.dicore.command.parameter.ArgumentBuffer;
import io.dico.dicore.command.registration.BukkitCommand; import io.dico.dicore.command.registration.BukkitCommand;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import java.util.*; import java.util.*;
public class RootCommandAddress extends ModifiableCommandAddress implements ICommandDispatcher { public class RootCommandAddress extends ModifiableCommandAddress implements ICommandDispatcher {
@Deprecated @Deprecated
public static final RootCommandAddress INSTANCE = new RootCommandAddress(); public static final RootCommandAddress INSTANCE = new RootCommandAddress();
public RootCommandAddress() { public RootCommandAddress() {
} }
@Override @Override
public Command getCommand() { public Command getCommand() {
return null; return null;
} }
@Override @Override
public boolean isRoot() { public boolean isRoot() {
return true; return true;
} }
@Override @Override
public List<String> getNames() { public List<String> getNames() {
return Collections.emptyList(); return Collections.emptyList();
} }
@Override @Override
public ModifiableCommandAddress getParent() { public ModifiableCommandAddress getParent() {
return null; return null;
} }
@Override @Override
public String getMainKey() { public String getMainKey() {
return null; return null;
} }
@Override @Override
public String getAddress() { public String getAddress() {
return ""; return "";
} }
@Override @Override
public void registerToCommandMap(String fallbackPrefix, Map<String, org.bukkit.command.Command> map, EOverridePolicy overridePolicy) { public void registerToCommandMap(String fallbackPrefix, Map<String, org.bukkit.command.Command> map, EOverridePolicy overridePolicy) {
Objects.requireNonNull(overridePolicy); Objects.requireNonNull(overridePolicy);
//debugChildren(this); //debugChildren(this);
Map<String, ChildCommandAddress> children = this.children; Map<String, ChildCommandAddress> children = this.children;
Map<ChildCommandAddress, BukkitCommand> wrappers = new IdentityHashMap<>(); Map<ChildCommandAddress, BukkitCommand> wrappers = new IdentityHashMap<>();
for (ChildCommandAddress address : children.values()) { for (ChildCommandAddress address : children.values()) {
if (!wrappers.containsKey(address)) { if (!wrappers.containsKey(address)) {
wrappers.put(address, new BukkitCommand(address)); wrappers.put(address, new BukkitCommand(address));
} }
} }
for (Map.Entry<String, ChildCommandAddress> entry : children.entrySet()) { for (Map.Entry<String, ChildCommandAddress> entry : children.entrySet()) {
String key = entry.getKey(); String key = entry.getKey();
ChildCommandAddress address = entry.getValue(); ChildCommandAddress address = entry.getValue();
boolean override = overridePolicy == EOverridePolicy.OVERRIDE_ALL; boolean override = overridePolicy == EOverridePolicy.OVERRIDE_ALL;
if (!override && key.equals(address.getMainKey())) { if (!override && key.equals(address.getMainKey())) {
override = overridePolicy == EOverridePolicy.MAIN_KEY_ONLY || overridePolicy == EOverridePolicy.MAIN_AND_FALLBACK; override = overridePolicy == EOverridePolicy.MAIN_KEY_ONLY || overridePolicy == EOverridePolicy.MAIN_AND_FALLBACK;
} }
registerMember(map, key, wrappers.get(address), override); registerMember(map, key, wrappers.get(address), override);
if (fallbackPrefix != null) { if (fallbackPrefix != null) {
key = fallbackPrefix + key; key = fallbackPrefix + key;
override = overridePolicy != EOverridePolicy.OVERRIDE_NONE && overridePolicy != EOverridePolicy.MAIN_KEY_ONLY; override = overridePolicy != EOverridePolicy.OVERRIDE_NONE && overridePolicy != EOverridePolicy.MAIN_KEY_ONLY;
registerMember(map, key, wrappers.get(address), override); registerMember(map, key, wrappers.get(address), override);
} }
} }
} }
private static void debugChildren(ModifiableCommandAddress address) { private static void debugChildren(ModifiableCommandAddress address) {
Collection<String> keys = address.getChildrenMainKeys(); Collection<String> keys = address.getChildrenMainKeys();
for (String key : keys) { for (String key : keys) {
ChildCommandAddress child = address.getChild(key); ChildCommandAddress child = address.getChild(key);
System.out.println(child.getAddress()); System.out.println(child.getAddress());
debugChildren(child); debugChildren(child);
} }
} }
private static void registerMember(Map<String, org.bukkit.command.Command> map, private static void registerMember(Map<String, org.bukkit.command.Command> map,
String key, org.bukkit.command.Command value, boolean override) { String key, org.bukkit.command.Command value, boolean override) {
if (override) { if (override) {
map.put(key, value); map.put(key, value);
} else { } else {
map.putIfAbsent(key, value); map.putIfAbsent(key, value);
} }
} }
@Override @Override
public void unregisterFromCommandMap(Map<String, org.bukkit.command.Command> map) { public void unregisterFromCommandMap(Map<String, org.bukkit.command.Command> map) {
Set<ICommandAddress> children = new HashSet<>(this.children.values()); Set<ICommandAddress> children = new HashSet<>(this.children.values());
Iterator<Map.Entry<String, org.bukkit.command.Command>> iterator = map.entrySet().iterator(); Iterator<Map.Entry<String, org.bukkit.command.Command>> iterator = map.entrySet().iterator();
while (iterator.hasNext()) { while (iterator.hasNext()) {
Map.Entry<String, org.bukkit.command.Command> entry = iterator.next(); Map.Entry<String, org.bukkit.command.Command> entry = iterator.next();
org.bukkit.command.Command cmd = entry.getValue(); org.bukkit.command.Command cmd = entry.getValue();
if (cmd instanceof BukkitCommand && children.contains(((BukkitCommand) cmd).getOrigin())) { if (cmd instanceof BukkitCommand && children.contains(((BukkitCommand) cmd).getOrigin())) {
iterator.remove(); iterator.remove();
} }
} }
} }
@Override @Override
public ModifiableCommandAddress getDeepChild(ArgumentBuffer buffer) { public ModifiableCommandAddress getDeepChild(ArgumentBuffer buffer) {
ModifiableCommandAddress cur = this; ModifiableCommandAddress cur = this;
ChildCommandAddress child; ChildCommandAddress child;
while (buffer.hasNext()) { while (buffer.hasNext()) {
child = cur.getChild(buffer.next()); child = cur.getChild(buffer.next());
if (child == null) { if (child == null) {
buffer.rewind(); buffer.rewind();
return cur; return cur;
} }
cur = child; cur = child;
} }
return cur; return cur;
} }
@Override @Override
public ModifiableCommandAddress getCommandTarget(CommandSender sender, ArgumentBuffer buffer) { public ModifiableCommandAddress getCommandTarget(CommandSender sender, ArgumentBuffer buffer) {
ModifiableCommandAddress cur = this; ModifiableCommandAddress cur = this;
ChildCommandAddress child; ChildCommandAddress child;
while (buffer.hasNext()) { while (buffer.hasNext()) {
child = cur.getChild(buffer.next()); child = cur.getChild(buffer.next());
if (child == null if (child == null
|| (child.hasCommand() && !child.getCommand().isVisibleTo(sender)) || (child.hasCommand() && !child.getCommand().isVisibleTo(sender))
|| (cur.hasCommand() && cur.getCommand().takePrecedenceOverSubcommand(buffer.peekPrevious(), buffer.getUnaffectingCopy()))) { || (cur.hasCommand() && cur.getCommand().takePrecedenceOverSubcommand(buffer.peekPrevious(), buffer.getUnaffectingCopy()))) {
buffer.rewind(); buffer.rewind();
break; break;
} }
cur = child; cur = child;
} }
return cur; return cur;
} }
@Override @Override
public ModifiableCommandAddress getCommandTarget(ExecutionContext context, ArgumentBuffer buffer) throws CommandException { public ModifiableCommandAddress getCommandTarget(ExecutionContext context, ArgumentBuffer buffer) throws CommandException {
CommandSender sender = context.getSender(); CommandSender sender = context.getSender();
ModifiableCommandAddress cur = this; ModifiableCommandAddress cur = this;
ChildCommandAddress child; ChildCommandAddress child;
while (buffer.hasNext()) { while (buffer.hasNext()) {
int cursor = buffer.getCursor(); int cursor = buffer.getCursor();
child = cur.getChild(context, buffer); child = cur.getChild(context, buffer);
if (child == null if (child == null
|| (context.isTabComplete() && !buffer.hasNext()) || (context.isTabComplete() && !buffer.hasNext())
|| (child.hasCommand() && !child.getCommand().isVisibleTo(sender)) || (child.hasCommand() && !child.getCommand().isVisibleTo(sender))
|| (cur.hasCommand() && cur.getCommand().takePrecedenceOverSubcommand(buffer.peekPrevious(), buffer.getUnaffectingCopy()))) { || (cur.hasCommand() && cur.getCommand().takePrecedenceOverSubcommand(buffer.peekPrevious(), buffer.getUnaffectingCopy()))) {
buffer.setCursor(cursor); buffer.setCursor(cursor);
break; break;
} }
cur = child; cur = child;
context.setAddress(child); context.setAddress(child);
if (child.hasCommand() && child.isCommandTrailing()) { if (child.hasCommand() && child.isCommandTrailing()) {
child.getCommand().initializeAndFilterContext(context); child.getCommand().initializeAndFilterContext(context);
child.getCommand().execute(context.getSender(), context); child.getCommand().execute(context.getSender(), context);
} }
} }
return cur; return cur;
} }
@Override @Override
public boolean dispatchCommand(CommandSender sender, String[] command) { public boolean dispatchCommand(CommandSender sender, String[] command) {
return dispatchCommand(sender, new ArgumentBuffer(command)); return dispatchCommand(sender, new ArgumentBuffer(command));
} }
@Override @Override
public boolean dispatchCommand(CommandSender sender, String usedLabel, String[] args) { public boolean dispatchCommand(CommandSender sender, String usedLabel, String[] args) {
return dispatchCommand(sender, new ArgumentBuffer(usedLabel, args)); return dispatchCommand(sender, new ArgumentBuffer(usedLabel, args));
} }
@Override @Override
public boolean dispatchCommand(CommandSender sender, ArgumentBuffer buffer) { public boolean dispatchCommand(CommandSender sender, ArgumentBuffer buffer) {
ExecutionContext context = new ExecutionContext(sender, buffer, false); ExecutionContext context = new ExecutionContext(sender, buffer, false);
ModifiableCommandAddress targetAddress = null; ModifiableCommandAddress targetAddress = null;
try { try {
targetAddress = getCommandTarget(context, buffer); targetAddress = getCommandTarget(context, buffer);
Command target = targetAddress.getCommand(); Command target = targetAddress.getCommand();
if (target == null) { if (target == null) {
if (targetAddress.hasHelpCommand()) { if (targetAddress.hasHelpCommand()) {
target = targetAddress.getHelpCommand().getCommand(); target = targetAddress.getHelpCommand().getCommand();
} else { } else {
return false; return false;
} }
} }
context.setCommand(target); context.setCommand(target);
if (!targetAddress.isCommandTrailing()) { if (!targetAddress.isCommandTrailing()) {
target.initializeAndFilterContext(context); target.initializeAndFilterContext(context);
String message = target.execute(sender, context); String message = target.execute(sender, context);
if (message != null && !message.isEmpty()) { if (message != null && !message.isEmpty()) {
context.sendMessage(EMessageType.RESULT, message); context.sendMessage(EMessageType.RESULT, message);
} }
} }
} catch (Throwable t) { } catch (Throwable t) {
if (targetAddress == null) { if (targetAddress == null) {
targetAddress = this; targetAddress = this;
} }
targetAddress.getChatHandler().handleException(sender, context, t); targetAddress.getChatHandler().handleException(sender, context, t);
} }
return true; return true;
} }
@Override @Override
public List<String> getTabCompletions(CommandSender sender, Location location, String[] args) { public List<String> getTabCompletions(CommandSender sender, Location location, String[] args) {
return getTabCompletions(sender, location, new ArgumentBuffer(args)); return getTabCompletions(sender, location, new ArgumentBuffer(args));
} }
@Override @Override
public List<String> getTabCompletions(CommandSender sender, String usedLabel, Location location, String[] args) { public List<String> getTabCompletions(CommandSender sender, String usedLabel, Location location, String[] args) {
return getTabCompletions(sender, location, new ArgumentBuffer(usedLabel, args)); return getTabCompletions(sender, location, new ArgumentBuffer(usedLabel, args));
} }
@Override @Override
public List<String> getTabCompletions(CommandSender sender, Location location, ArgumentBuffer buffer) { public List<String> getTabCompletions(CommandSender sender, Location location, ArgumentBuffer buffer) {
ExecutionContext context = new ExecutionContext(sender, buffer, true); ExecutionContext context = new ExecutionContext(sender, buffer, true);
long start = System.currentTimeMillis();
try {
ICommandAddress target = getCommandTarget(context, buffer); try {
ICommandAddress target = getCommandTarget(context, buffer);
List<String> out;
if (target.hasCommand()) { List<String> out;
context.setCommand(target.getCommand()); if (target.hasCommand()) {
target.getCommand().initializeAndFilterContext(context); context.setCommand(target.getCommand());
out = target.getCommand().tabComplete(sender, context, location); target.getCommand().initializeAndFilterContext(context);
} else { out = target.getCommand().tabComplete(sender, context, location);
out = Collections.emptyList(); } else {
} out = Collections.emptyList();
}
int cursor = buffer.getCursor();
String input; int cursor = buffer.getCursor();
if (cursor >= buffer.size()) { String input;
input = ""; if (cursor >= buffer.size()) {
} else { input = "";
input = buffer.get(cursor).toLowerCase(); } else {
} input = buffer.get(cursor).toLowerCase();
}
boolean wrapped = false;
for (String child : target.getChildrenMainKeys()) { boolean wrapped = false;
if (child.toLowerCase().startsWith(input)) { for (String child : target.getChildrenMainKeys()) {
if (!wrapped) { if (child.toLowerCase().startsWith(input)) {
out = new ArrayList<>(out); if (!wrapped) {
wrapped = true; out = new ArrayList<>(out);
} wrapped = true;
out.add(child); }
} out.add(child);
} }
}
return out;
return out;
} catch (CommandException ex) {
return Collections.emptyList(); } catch (CommandException ex) {
} return Collections.emptyList();
} finally {
} long duration = System.currentTimeMillis() - start;
} if (duration > 2) {
System.out.println(String.format("Complete took %.3f seconds", duration / 1000.0));
}
}
}
}

View File

@@ -1,276 +1,278 @@
package io.dico.dicore.command.parameter; package io.dico.dicore.command.parameter;
import io.dico.dicore.command.CommandException; import io.dico.dicore.command.CommandException;
import io.dico.dicore.command.ExecutionContext; import io.dico.dicore.command.ExecutionContext;
import java.lang.reflect.Array; import java.lang.reflect.Array;
import java.util.*; import java.util.*;
public class ContextParser { public class ContextParser {
private final ExecutionContext m_context; private final ExecutionContext m_context;
private final ArgumentBuffer m_buffer; private final ArgumentBuffer m_buffer;
private final ParameterList m_paramList; private final ParameterList m_paramList;
private final Parameter<?, ?> m_repeatedParam; private final Parameter<?, ?> m_repeatedParam;
private final List<Parameter<?, ?>> m_indexedParams; private final List<Parameter<?, ?>> m_indexedParams;
private final int m_maxIndex; private final int m_maxIndex;
private final int m_maxRequiredIndex; private final int m_maxRequiredIndex;
private Map<String, Object> m_valueMap; private Map<String, Object> m_valueMap;
private Set<String> m_parsedKeys; private Set<String> m_parsedKeys;
private int m_completionCursor = -1; private int m_completionCursor = -1;
private Parameter<?, ?> m_completionTarget = null; private Parameter<?, ?> m_completionTarget = null;
public ContextParser(ExecutionContext context, public ContextParser(ExecutionContext context,
ParameterList parameterList, ParameterList parameterList,
Map<String, Object> valueMap, Map<String, Object> valueMap,
Set<String> keySet) { Set<String> keySet) {
m_context = context; m_context = context;
m_paramList = parameterList; m_paramList = parameterList;
m_valueMap = valueMap; m_valueMap = valueMap;
m_parsedKeys = keySet; m_parsedKeys = keySet;
m_buffer = context.getBuffer(); m_buffer = context.getBuffer();
m_repeatedParam = m_paramList.getRepeatedParameter(); m_repeatedParam = m_paramList.getRepeatedParameter();
m_indexedParams = m_paramList.getIndexedParameters(); m_indexedParams = m_paramList.getIndexedParameters();
m_maxIndex = m_indexedParams.size() - 1; m_maxIndex = m_indexedParams.size() - 1;
m_maxRequiredIndex = m_paramList.getRequiredCount() - 1; m_maxRequiredIndex = m_paramList.getRequiredCount() - 1;
} }
public ExecutionContext getContext() { public ExecutionContext getContext() {
return m_context; return m_context;
} }
public Map<String, Object> getValueMap() { public Map<String, Object> getValueMap() {
return m_valueMap; return m_valueMap;
} }
public Set<String> getParsedKeys() { public Set<String> getParsedKeys() {
return m_parsedKeys; return m_parsedKeys;
} }
public void parse() throws CommandException { public void parse() throws CommandException {
parseAllParameters(); parseAllParameters();
} }
public int getCompletionCursor() { public int getCompletionCursor() {
if (!m_done) { if (!m_done) {
throw new IllegalStateException(); throw new IllegalStateException();
} }
return m_completionCursor; return m_completionCursor;
} }
public Parameter<?, ?> getCompletionTarget() { public Parameter<?, ?> getCompletionTarget() {
if (!m_done) { if (!m_done) {
throw new IllegalStateException(); throw new IllegalStateException();
} }
return m_completionTarget; return m_completionTarget;
} }
// ################################ // ################################
// # PARSING METHODS # // # PARSING METHODS #
// ################################ // ################################
private boolean m_repeating = false; private boolean m_repeating = false;
private boolean m_done = false; private boolean m_done = false;
private int m_curParamIndex = -1; private int m_curParamIndex = -1;
private Parameter<?, ?> m_curParam = null; private Parameter<?, ?> m_curParam = null;
private List<Object> m_curRepeatingList = null; private List<Object> m_curRepeatingList = null;
private void parseAllParameters() throws CommandException { private void parseAllParameters() throws CommandException {
try { try {
do { do {
prepareStateToParseParam(); prepareStateToParseParam();
if (m_done) break; if (m_done) break;
parseCurParam(); parseCurParam();
} while (!m_done); } while (!m_done);
} finally { } finally {
m_curParam = null; m_curParam = null;
m_curRepeatingList = null; m_curRepeatingList = null;
assignDefaultValuesToUncomputedParams(); assignDefaultValuesToUncomputedParams();
arrayifyRepeatedParamValue(); arrayifyRepeatedParamValue();
}
} m_done = true;
}
private void prepareStateToParseParam() throws CommandException { }
boolean requireInput; private void prepareStateToParseParam() throws CommandException {
if (identifyFlag()) {
m_buffer.advance(); boolean requireInput;
prepareRepeatedParameterIfSet(); if (identifyFlag()) {
requireInput = false; m_buffer.advance();
prepareRepeatedParameterIfSet();
} else if (m_repeating) { requireInput = false;
m_curParam = m_repeatedParam;
requireInput = false; } else if (m_repeating) {
m_curParam = m_repeatedParam;
} else if (m_curParamIndex < m_maxIndex) { requireInput = false;
m_curParamIndex++;
m_curParam = m_indexedParams.get(m_curParamIndex); } else if (m_curParamIndex < m_maxIndex) {
prepareRepeatedParameterIfSet(); m_curParamIndex++;
requireInput = m_curParamIndex <= m_maxRequiredIndex; m_curParam = m_indexedParams.get(m_curParamIndex);
prepareRepeatedParameterIfSet();
} else if (m_buffer.hasNext()) { requireInput = m_curParamIndex <= m_maxRequiredIndex;
throw new CommandException("Too many arguments for /" + m_context.getAddress().getAddress());
} else if (m_buffer.hasNext()) {
} else { throw new CommandException("Too many arguments for /" + m_context.getAddress().getAddress());
m_done = true;
return; } else {
} m_done = true;
return;
if (!m_buffer.hasNext()) { }
if (requireInput) {
reportParameterRequired(m_curParam); if (!m_buffer.hasNext()) {
} if (requireInput) {
reportParameterRequired(m_curParam);
if (m_repeating) { }
m_done = true;
} if (m_repeating) {
} m_done = true;
}
} }
private boolean identifyFlag() { }
String potentialFlag = m_buffer.peekNext();
Parameter<?, ?> target; private boolean identifyFlag() {
if (potentialFlag != null String potentialFlag = m_buffer.peekNext();
&& potentialFlag.startsWith("-") Parameter<?, ?> target;
&& (target = m_paramList.getParameterByName(potentialFlag)) != null if (potentialFlag != null
&& target.isFlag() && potentialFlag.startsWith("-")
&& !m_valueMap.containsKey(potentialFlag) && (target = m_paramList.getParameterByName(potentialFlag)) != null
&& target.isFlag()
// Disabled because it's checked by {@link Parameter#parse(ExecutionContext, ArgumentBuffer)} && !m_valueMap.containsKey(potentialFlag)
// && (target.getFlagPermission() == null || m_context.getSender().hasPermission(target.getFlagPermission()))
) { // Disabled because it's checked by {@link Parameter#parse(ExecutionContext, ArgumentBuffer)}
m_curParam = target; // && (target.getFlagPermission() == null || m_context.getSender().hasPermission(target.getFlagPermission()))
return true; ) {
} m_curParam = target;
return true;
return false; }
}
return false;
private void prepareRepeatedParameterIfSet() throws CommandException { }
if (m_curParam != null && m_curParam == m_repeatedParam) {
private void prepareRepeatedParameterIfSet() throws CommandException {
if (m_curParam.isFlag() && m_curParamIndex < m_maxRequiredIndex) { if (m_curParam != null && m_curParam == m_repeatedParam) {
Parameter<?, ?> requiredParam = m_indexedParams.get(m_curParamIndex + 1);
reportParameterRequired(requiredParam); if (m_curParam.isFlag() && m_curParamIndex < m_maxRequiredIndex) {
} Parameter<?, ?> requiredParam = m_indexedParams.get(m_curParamIndex + 1);
reportParameterRequired(requiredParam);
m_curRepeatingList = new ArrayList<>(); }
assignValue(m_curRepeatingList);
m_repeating = true; m_curRepeatingList = new ArrayList<>();
} assignValue(m_curRepeatingList);
} m_repeating = true;
}
private void reportParameterRequired(Parameter<?, ?> param) throws CommandException { }
throw new CommandException("The argument '" + param.getName() + "' is required");
} private void reportParameterRequired(Parameter<?, ?> param) throws CommandException {
throw new CommandException("The argument '" + param.getName() + "' is required");
private void parseCurParam() throws CommandException { }
if (!m_buffer.hasNext() && !m_curParam.isFlag()) {
assignDefaultValue(); private void parseCurParam() throws CommandException {
return; if (!m_buffer.hasNext() && !m_curParam.isFlag()) {
} assignDefaultValue();
return;
int cursorStart = m_buffer.getCursor(); }
if (m_context.isTabComplete() && "".equals(m_buffer.peekNext())) { int cursorStart = m_buffer.getCursor();
assignAsCompletionTarget(cursorStart);
return; if (m_context.isTabComplete() && "".equals(m_buffer.peekNext())) {
} assignAsCompletionTarget(cursorStart);
return;
Object parseResult; }
try {
parseResult = m_curParam.parse(m_context, m_buffer); Object parseResult;
} catch (CommandException e) { try {
assignAsCompletionTarget(cursorStart); parseResult = m_curParam.parse(m_context, m_buffer);
throw e; } catch (CommandException e) {
} assignAsCompletionTarget(cursorStart);
throw e;
assignValue(parseResult); }
m_parsedKeys.add(m_curParam.getName());
} assignValue(parseResult);
m_parsedKeys.add(m_curParam.getName());
private void assignDefaultValue() throws CommandException { }
assignValue(m_curParam.getDefaultValue(m_context, m_buffer));
} private void assignDefaultValue() throws CommandException {
assignValue(m_curParam.getDefaultValue(m_context, m_buffer));
private void assignAsCompletionTarget(int cursor) { }
m_completionCursor = cursor;
m_completionTarget = m_curParam; private void assignAsCompletionTarget(int cursor) {
m_done = true; m_completionCursor = cursor;
} m_completionTarget = m_curParam;
m_done = true;
private void assignValue(Object value) { }
if (m_repeating) {
m_curRepeatingList.add(value); private void assignValue(Object value) {
} else { if (m_repeating) {
m_valueMap.put(m_curParam.getName(), value); m_curRepeatingList.add(value);
} } else {
} m_valueMap.put(m_curParam.getName(), value);
}
private void assignDefaultValuesToUncomputedParams() throws CommandException { }
// add default values for unset parameters
for (Map.Entry<String, Parameter<?, ?>> entry : m_paramList.getParametersByName().entrySet()) { private void assignDefaultValuesToUncomputedParams() throws CommandException {
String name = entry.getKey(); // add default values for unset parameters
if (!m_valueMap.containsKey(name)) { for (Map.Entry<String, Parameter<?, ?>> entry : m_paramList.getParametersByName().entrySet()) {
if (m_repeatedParam == entry.getValue()) { String name = entry.getKey();
// below value will be turned into an array later if (!m_valueMap.containsKey(name)) {
m_valueMap.put(name, Collections.emptyList()); if (m_repeatedParam == entry.getValue()) {
} else { // below value will be turned into an array later
m_valueMap.put(name, entry.getValue().getDefaultValue(m_context, m_buffer)); m_valueMap.put(name, Collections.emptyList());
} } else {
} m_valueMap.put(name, entry.getValue().getDefaultValue(m_context, m_buffer));
} }
} }
}
private void arrayifyRepeatedParamValue() { }
if (m_repeatedParam != null) {
m_valueMap.computeIfPresent(m_repeatedParam.getName(), (k, v) -> { private void arrayifyRepeatedParamValue() {
List list = (List) v; if (m_repeatedParam != null) {
Class<?> returnType = m_repeatedParam.getType().getReturnType(); m_valueMap.computeIfPresent(m_repeatedParam.getName(), (k, v) -> {
Object array = Array.newInstance(returnType, list.size()); List list = (List) v;
ArraySetter setter = ArraySetter.getSetter(returnType); Class<?> returnType = m_repeatedParam.getType().getReturnType();
for (int i = 0, n = list.size(); i < n; i++) { Object array = Array.newInstance(returnType, list.size());
setter.set(array, i, list.get(i)); ArraySetter setter = ArraySetter.getSetter(returnType);
} for (int i = 0, n = list.size(); i < n; i++) {
setter.set(array, i, list.get(i));
return array; }
});
} return array;
} });
}
private interface ArraySetter { }
void set(Object array, int index, Object value);
private interface ArraySetter {
static ArraySetter getSetter(Class<?> clazz) { void set(Object array, int index, Object value);
if (!clazz.isPrimitive()) {
return (array, index, value) -> ((Object[]) array)[index] = value; static ArraySetter getSetter(Class<?> clazz) {
} if (!clazz.isPrimitive()) {
return (array, index, value) -> ((Object[]) array)[index] = value;
switch (clazz.getSimpleName()) { }
case "boolean":
return (array, index, value) -> ((boolean[]) array)[index] = (boolean) value; switch (clazz.getSimpleName()) {
case "int": case "boolean":
return (array, index, value) -> ((int[]) array)[index] = (int) value; return (array, index, value) -> ((boolean[]) array)[index] = (boolean) value;
case "double": case "int":
return (array, index, value) -> ((double[]) array)[index] = (double) value; return (array, index, value) -> ((int[]) array)[index] = (int) value;
case "long": case "double":
return (array, index, value) -> ((long[]) array)[index] = (long) value; return (array, index, value) -> ((double[]) array)[index] = (double) value;
case "short": case "long":
return (array, index, value) -> ((short[]) array)[index] = (short) value; return (array, index, value) -> ((long[]) array)[index] = (long) value;
case "byte": case "short":
return (array, index, value) -> ((byte[]) array)[index] = (byte) value; return (array, index, value) -> ((short[]) array)[index] = (short) value;
case "float": case "byte":
return (array, index, value) -> ((float[]) array)[index] = (float) value; return (array, index, value) -> ((byte[]) array)[index] = (byte) value;
case "char": case "float":
return (array, index, value) -> ((char[]) array)[index] = (char) value; return (array, index, value) -> ((float[]) array)[index] = (float) value;
case "void": case "char":
default: return (array, index, value) -> ((char[]) array)[index] = (char) value;
throw new InternalError("This should not happen"); case "void":
} default:
} throw new InternalError("This should not happen");
} }
}
} }
}

View File

@@ -0,0 +1,186 @@
package io.dico.dicore.command.registration.reflect;
import io.dico.dicore.command.CommandException;
import io.dico.dicore.command.ExecutionContext;
import io.dico.dicore.exceptions.checkedfunctions.CheckedSupplier;
/**
* Call flags store which extra parameters the target function expects on top of command parameters.
* All 4 possible extra parameters are listed below.
* <p>
* Extra parameters are ordered by the bit that represents them in the call flags.
* They can either be leading or trailing the command's parameters.
*/
public class ReflectiveCallFlags {
/**
* Receiver ({@code this} in some kotlin functions - always first parameter)
*
* @see ICommandInterceptor#getReceiver(io.dico.dicore.command.ExecutionContext, java.lang.reflect.Method, String)
*/
public static final int RECEIVER_BIT = 1 << 0;
/**
* CommandSender
*
* @see org.bukkit.command.CommandSender
*/
public static final int SENDER_BIT = 1 << 1;
/**
* ExecutionContext
*
* @see io.dico.dicore.command.ExecutionContext
*/
public static final int CONTEXT_BIT = 1 << 2;
/**
* Continuation (trailing parameters of kotlin suspended functions)
*
* @see kotlin.coroutines.Continuation
*/
public static final int CONTINUATION_BIT = 1 << 3;
/**
* Mask of extra parameters that trail the command's parameters, instead of leading.
*/
public static final int TRAILING_MASK = CONTINUATION_BIT;
/**
* Check if the call arg is trailing the command's parameters.
*
* @param bit the bit used for the call flag
* @return true if the call arg is trailing the command's parameters
*/
public static boolean isTrailingCallArg(int bit) {
return (bit & TRAILING_MASK) != 0;
}
/**
* Number of call arguments leading the command parameters.
*
* @param flags the call flags
* @return the number of call arguments leading the command parameters
*/
public static int getLeadingCallArgNum(int flags) {
return Integer.bitCount(flags & ~TRAILING_MASK);
}
/**
* Number of call arguments trailing the command parameters.
*
* @param flags the call flags
* @return the number of call arguments trailing the command parameters
*/
public static int getTrailingCallArgNum(int flags) {
return Integer.bitCount(flags & TRAILING_MASK);
}
/**
* Check if the flags contain the call arg.
*
* @param flags the call flags
* @param bit the bit used for the call flag
* @return true if the flags contain the call arg
*/
public static boolean hasCallArg(int flags, int bit) {
return (flags & bit) != 0;
}
/**
* Get the index used for the call arg when calling the reflective function
*
* @param flags the call flags
* @param bit the bit used for the call flag
* @param cmdParameterNum the number of parameters of the command
* @return the index used for the call arg
*/
public static int getCallArgIndex(int flags, int bit, int cmdParameterNum) {
if ((bit & TRAILING_MASK) == 0) {
// Leading.
int preceding = precedingMaskFrom(bit);
int mask = flags & precedingMaskFrom(bit) & ~TRAILING_MASK;
// Count the number of present call args that are leading and precede the given bit
return Integer.bitCount(flags & precedingMaskFrom(bit) & ~TRAILING_MASK);
} else {
// Trailing.
// Count the number of present call args that are leading
// plus the number of present call args that are trailing and precede the given bit
// plus the command's parameters
return Integer.bitCount(flags & ~TRAILING_MASK)
+ Integer.bitCount(flags & precedingMaskFrom(bit) & TRAILING_MASK)
+ cmdParameterNum;
}
}
/**
* Get the mask for all bits trailing the given fromBit
*
* <p>
* For example, if the bit is 00010000
* This function returns 00001111
* <p>
*
* @param fromBit number with the bit set there the ones should stop.
* @return the mask for all bits trailing the given fromBit
*/
private static int precedingMaskFrom(int fromBit) {
int trailingZeros = Integer.numberOfTrailingZeros(fromBit);
if (trailingZeros == 0) return 0;
return -1 >>> -trailingZeros;
}
/**
* Get the object array used to call the function.
*
* @param callFlags the call flags
* @param context the context
* @param parameterOrder the order of parameters in the function
* @param receiverFunction the function that will create the receiver for this call, if applicable
* @return the call args
*/
public static Object[] getCallArgs(
int callFlags,
ExecutionContext context,
String[] parameterOrder,
CheckedSupplier<Object, CommandException> receiverFunction
) throws CommandException {
int leadingParameterNum = getLeadingCallArgNum(callFlags);
int cmdParameterNum = parameterOrder.length;
int trailingParameterNum = getTrailingCallArgNum(callFlags);
Object[] result = new Object[leadingParameterNum + cmdParameterNum + trailingParameterNum];
if (hasCallArg(callFlags, RECEIVER_BIT)) {
int index = getCallArgIndex(callFlags, RECEIVER_BIT, cmdParameterNum);
result[index] = receiverFunction.get();
}
if (hasCallArg(callFlags, SENDER_BIT)) {
int index = getCallArgIndex(callFlags, SENDER_BIT, cmdParameterNum);
result[index] = context.getSender();
}
if (hasCallArg(callFlags, CONTEXT_BIT)) {
int index = getCallArgIndex(callFlags, CONTEXT_BIT, cmdParameterNum);
result[index] = context;
}
if (hasCallArg(callFlags, CONTINUATION_BIT)) {
int index = getCallArgIndex(callFlags, CONTINUATION_BIT, cmdParameterNum);
result[index] = null; // filled in later.
}
for (int i = 0; i < parameterOrder.length; i++) {
String parameterName = parameterOrder[i];
result[leadingParameterNum + i] = context.get(parameterName);
}
return result;
}
}

View File

@@ -1,187 +1,169 @@
package io.dico.dicore.command.registration.reflect; package io.dico.dicore.command.registration.reflect;
import io.dico.dicore.command.*; import io.dico.dicore.command.*;
import io.dico.dicore.command.annotation.Cmd; import io.dico.dicore.command.annotation.Cmd;
import io.dico.dicore.command.annotation.GenerateCommands; import io.dico.dicore.command.annotation.GenerateCommands;
import io.dico.dicore.command.parameter.type.IParameterTypeSelector; import io.dico.dicore.command.parameter.type.IParameterTypeSelector;
import org.bukkit.command.CommandSender; import io.dico.dicore.exceptions.checkedfunctions.CheckedSupplier;
import kotlin.coroutines.CoroutineContext;
import java.lang.reflect.InvocationTargetException; import org.bukkit.command.CommandSender;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public final class ReflectiveCommand extends Command { import java.lang.reflect.Modifier;
private static final int continuationMask = 1 << 3;
private final Cmd cmdAnnotation; public final class ReflectiveCommand extends Command {
private final Method method; private final Cmd cmdAnnotation;
private final Object instance; private final Method method;
private String[] parameterOrder; private final Object instance;
private String[] parameterOrder;
// hasContinuation | hasContext | hasSender | hasReceiver private final int callFlags;
private final int flags;
ReflectiveCommand(IParameterTypeSelector selector, Method method, Object instance) throws CommandParseException {
ReflectiveCommand(IParameterTypeSelector selector, Method method, Object instance) throws CommandParseException { if (!method.isAnnotationPresent(Cmd.class)) {
if (!method.isAnnotationPresent(Cmd.class)) { throw new CommandParseException("No @Cmd present for the method " + method.toGenericString());
throw new CommandParseException("No @Cmd present for the method " + method.toGenericString()); }
} cmdAnnotation = method.getAnnotation(Cmd.class);
cmdAnnotation = method.getAnnotation(Cmd.class);
java.lang.reflect.Parameter[] parameters = method.getParameters();
java.lang.reflect.Parameter[] parameters = method.getParameters();
if (!method.isAccessible()) try {
if (!method.isAccessible()) try { method.setAccessible(true);
method.setAccessible(true); } catch (Exception ex) {
} catch (Exception ex) { throw new CommandParseException("Failed to make method accessible");
throw new CommandParseException("Failed to make method accessible"); }
}
if (!Modifier.isStatic(method.getModifiers())) {
if (!Modifier.isStatic(method.getModifiers())) { if (instance == null) {
if (instance == null) { try {
try { instance = method.getDeclaringClass().newInstance();
instance = method.getDeclaringClass().newInstance(); } catch (Exception ex) {
} catch (Exception ex) { throw new CommandParseException("No instance given for instance method, and failed to create new instance", ex);
throw new CommandParseException("No instance given for instance method, and failed to create new instance", ex); }
} } else if (!method.getDeclaringClass().isInstance(instance)) {
} else if (!method.getDeclaringClass().isInstance(instance)) { throw new CommandParseException("Given instance is not an instance of the method's declaring class");
throw new CommandParseException("Given instance is not an instance of the method's declaring class"); }
} }
}
this.method = method;
this.method = method; this.instance = instance;
this.instance = instance; this.callFlags = ReflectiveRegistration.parseCommandAttributes(selector, method, this, parameters);
this.flags = ReflectiveRegistration.parseCommandAttributes(selector, method, this, parameters); }
}
public Method getMethod() {
public Method getMethod() { return method;
return method; }
}
public Object getInstance() {
public Object getInstance() { return instance;
return instance; }
}
public String getCmdName() {
public String getCmdName() { return cmdAnnotation.value(); } return cmdAnnotation.value();
}
void setParameterOrder(String[] parameterOrder) {
this.parameterOrder = parameterOrder; public int getCallFlags() {
} return callFlags;
}
ICommandAddress getAddress() {
ChildCommandAddress result = new ChildCommandAddress(); void setParameterOrder(String[] parameterOrder) {
result.setCommand(this); this.parameterOrder = parameterOrder;
}
Cmd cmd = cmdAnnotation;
result.getNames().add(cmd.value()); public int getParameterNum() {
for (String alias : cmd.aliases()) { return parameterOrder.length;
result.getNames().add(alias); }
}
result.finalizeNames(); ICommandAddress getAddress() {
ChildCommandAddress result = new ChildCommandAddress();
GenerateCommands generateCommands = method.getAnnotation(GenerateCommands.class); result.setCommand(this);
if (generateCommands != null) {
ReflectiveRegistration.generateCommands(result, generateCommands.value()); Cmd cmd = cmdAnnotation;
} result.getNames().add(cmd.value());
for (String alias : cmd.aliases()) {
return result; result.getNames().add(alias);
} }
result.finalizeNames();
@Override
public String execute(CommandSender sender, ExecutionContext context) throws CommandException { GenerateCommands generateCommands = method.getAnnotation(GenerateCommands.class);
String[] parameterOrder = this.parameterOrder; if (generateCommands != null) {
int extraArgumentCount = Integer.bitCount(flags); ReflectiveRegistration.generateCommands(result, generateCommands.value());
int parameterStartIndex = Integer.bitCount(flags & ~continuationMask); }
Object[] args = new Object[parameterOrder.length + extraArgumentCount]; return result;
}
int i = 0;
@Override
int mask = 1; public String execute(CommandSender sender, ExecutionContext context) throws CommandException {
if ((flags & mask) != 0) {
// Has receiver CheckedSupplier<Object, CommandException> receiverFunction = () -> {
try { try {
args[i++] = ((ICommandInterceptor) instance).getReceiver(context, method, getCmdName()); return ((ICommandInterceptor) instance).getReceiver(context, method, getCmdName());
} catch (Exception ex) { } catch (Exception ex) {
handleException(ex); handleException(ex);
return null; // unreachable return null; // unreachable
} }
} };
mask <<= 1; Object[] callArgs = ReflectiveCallFlags.getCallArgs(callFlags, context, parameterOrder, receiverFunction);
if ((flags & mask) != 0) {
// Has sender if (ReflectiveCallFlags.hasCallArg(callFlags, ReflectiveCallFlags.CONTINUATION_BIT)) {
args[i++] = sender; // If it has a continuation, call as coroutine
} return callAsCoroutine(context, callArgs);
}
mask <<= 1;
if ((flags & mask) != 0) { return callSynchronously(callArgs);
// Has context }
args[i++] = context;
} private boolean isSuspendFunction() {
try {
mask <<= 1; return KotlinReflectiveRegistrationKt.isSuspendFunction(method);
if ((flags & mask) != 0) { } catch (Throwable ex) {
// Has continuation return false;
}
extraArgumentCount--; }
}
public String callSynchronously(Object[] args) throws CommandException {
for (int n = args.length; i < n; i++) { try {
args[i] = context.get(parameterOrder[i - extraArgumentCount]); return getResult(method.invoke(instance, args), null);
} } catch (Exception ex) {
return getResult(null, ex);
if ((flags & mask) != 0) { }
// Since it has continuation, call as coroutine }
return callAsCoroutine(context, args);
} public static String getResult(Object returned, Exception ex) throws CommandException {
if (ex != null) {
return callSynchronously(args); handleException(ex);
} return null; // unreachable
}
private boolean isSuspendFunction() {
try { if (returned instanceof String) {
return KotlinReflectiveRegistrationKt.isSuspendFunction(method); return (String) returned;
} catch (Throwable ex) { }
return false; return null;
} }
}
public static void handleException(Exception ex) throws CommandException {
public String callSynchronously(Object[] args) throws CommandException { if (ex instanceof InvocationTargetException) {
try { if (ex.getCause() instanceof CommandException) {
return getResult(method.invoke(instance, args), null); throw (CommandException) ex.getCause();
} catch (Exception ex) { }
return getResult(null, ex);
} ex.printStackTrace();
} throw new CommandException("An internal error occurred while executing this command.", ex);
}
public static String getResult(Object returned, Exception ex) throws CommandException { if (ex instanceof CommandException) {
if (ex != null) { throw (CommandException) ex;
handleException(ex); }
return null; // unreachable ex.printStackTrace();
} throw new CommandException("An internal error occurred while executing this command.", ex);
}
if (returned instanceof String) {
return (String) returned; private String callAsCoroutine(ExecutionContext executionContext, Object[] args) throws CommandException {
} ICommandInterceptor factory = (ICommandInterceptor) instance;
return null; CoroutineContext coroutineContext = (CoroutineContext) factory.getCoroutineContext(executionContext, method, getCmdName());
} int continuationIndex = ReflectiveCallFlags.getCallArgIndex(callFlags, ReflectiveCallFlags.CONTINUATION_BIT, parameterOrder.length);
return KotlinReflectiveRegistrationKt.callCommandAsCoroutine(executionContext, coroutineContext, continuationIndex, method, instance, args);
public static void handleException(Exception ex) throws CommandException { }
if (ex instanceof InvocationTargetException) {
if (ex.getCause() instanceof CommandException) { }
throw (CommandException) ex.getCause();
}
ex.printStackTrace();
throw new CommandException("An internal error occurred while executing this command.", ex);
}
if (ex instanceof CommandException) {
throw (CommandException) ex;
}
ex.printStackTrace();
throw new CommandException("An internal error occurred while executing this command.", ex);
}
private String callAsCoroutine(ExecutionContext context, Object[] args) {
return KotlinReflectiveRegistrationKt.callAsCoroutine(this, (ICommandInterceptor) instance, context, args);
}
}

View File

@@ -1,415 +1,406 @@
package io.dico.dicore.command.registration.reflect; package io.dico.dicore.command.registration.reflect;
import io.dico.dicore.command.*; import io.dico.dicore.command.*;
import io.dico.dicore.command.annotation.*; import io.dico.dicore.command.annotation.*;
import io.dico.dicore.command.annotation.GroupMatchedCommands.GroupEntry; import io.dico.dicore.command.annotation.GroupMatchedCommands.GroupEntry;
import io.dico.dicore.command.parameter.Parameter; import io.dico.dicore.command.parameter.Parameter;
import io.dico.dicore.command.parameter.ParameterList; import io.dico.dicore.command.parameter.ParameterList;
import io.dico.dicore.command.parameter.type.IParameterTypeSelector; import io.dico.dicore.command.parameter.type.IParameterTypeSelector;
import io.dico.dicore.command.parameter.type.MapBasedParameterTypeSelector; import io.dico.dicore.command.parameter.type.MapBasedParameterTypeSelector;
import io.dico.dicore.command.parameter.type.ParameterType; import io.dico.dicore.command.parameter.type.ParameterType;
import io.dico.dicore.command.parameter.type.ParameterTypes; import io.dico.dicore.command.parameter.type.ParameterTypes;
import io.dico.dicore.command.predef.PredefinedCommand; import io.dico.dicore.command.predef.PredefinedCommand;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.command.ConsoleCommandSender; import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import java.util.*; import java.util.*;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException; import java.util.regex.PatternSyntaxException;
/** import static io.dico.dicore.command.registration.reflect.ReflectiveCallFlags.*;
* Takes care of turning a reflection {@link Method} into a command and more.
*/ /**
public class ReflectiveRegistration { * Takes care of turning a reflection {@link Method} into a command and more.
/** */
* This object provides names of the parameters. public class ReflectiveRegistration {
* Oddly, the AnnotationParanamer extensions require a 'fallback' paranamer to function properly without /**
* requiring ALL parameters to have that flag. This is weird because it should just use the AdaptiveParanamer on an upper level to * This object provides names of the parameters.
* determine the name of each individual flag. Oddly this isn't how it works, so the fallback works the same way as the AdaptiveParanamer does. * Oddly, the AnnotationParanamer extensions require a 'fallback' paranamer to function properly without
* It's just linked instead of using an array for that part. Then we can use an AdaptiveParanamer for the latest fallback, to get bytecode names * requiring ALL parameters to have that flag. This is weird because it should just use the AdaptiveParanamer on an upper level to
* or, finally, to get the Jvm-provided parameter names. * determine the name of each individual flag. Oddly this isn't how it works, so the fallback works the same way as the AdaptiveParanamer does.
*/ * It's just linked instead of using an array for that part. Then we can use an AdaptiveParanamer for the latest fallback, to get bytecode names
//private static final Paranamer paranamer = new CachingParanamer(new BytecodeReadingParanamer()); * or, finally, to get the Jvm-provided parameter names.
@SuppressWarnings("StatementWithEmptyBody") */
private static String[] lookupParameterNames(Method method, java.lang.reflect.Parameter[] parameters, int start) { //private static final Paranamer paranamer = new CachingParanamer(new BytecodeReadingParanamer());
int n = parameters.length; @SuppressWarnings("StatementWithEmptyBody")
String[] out = new String[n - start]; private static String[] lookupParameterNames(Method method, java.lang.reflect.Parameter[] parameters, int start) {
int n = parameters.length;
//String[] bytecode; String[] out = new String[n - start];
//try {
// bytecode = paranamer.lookupParameterNames(method, false); //String[] bytecode;
//} catch (Exception ex) { //try {
// bytecode = new String[0]; // bytecode = paranamer.lookupParameterNames(method, false);
// System.err.println("ReflectiveRegistration.lookupParameterNames failed to read bytecode"); //} catch (Exception ex) {
// //ex.printStackTrace(); // bytecode = new String[0];
//} // System.err.println("ReflectiveRegistration.lookupParameterNames failed to read bytecode");
//int bn = bytecode.length; // //ex.printStackTrace();
//}
for (int i = start; i < n; i++) { //int bn = bytecode.length;
java.lang.reflect.Parameter parameter = parameters[i];
Flag flag = parameter.getAnnotation(Flag.class); for (int i = start; i < n; i++) {
NamedArg namedArg = parameter.getAnnotation(NamedArg.class); java.lang.reflect.Parameter parameter = parameters[i];
Flag flag = parameter.getAnnotation(Flag.class);
boolean isFlag = flag != null; NamedArg namedArg = parameter.getAnnotation(NamedArg.class);
String name;
if (namedArg != null && !(name = namedArg.value()).isEmpty()) { boolean isFlag = flag != null;
} else if (isFlag && !(name = flag.value()).isEmpty()) { String name;
//} else if (i < bn && (name = bytecode[i]) != null && !name.isEmpty()) { if (namedArg != null && !(name = namedArg.value()).isEmpty()) {
} else { } else if (isFlag && !(name = flag.value()).isEmpty()) {
name = parameter.getName(); //} else if (i < bn && (name = bytecode[i]) != null && !name.isEmpty()) {
} } else {
name = parameter.getName();
if (isFlag) { }
name = '-' + name;
} else { if (isFlag) {
int idx = 0; name = '-' + name;
while (name.startsWith("-", idx)) { } else {
idx++; int idx = 0;
} while (name.startsWith("-", idx)) {
name = name.substring(idx); idx++;
} }
name = name.substring(idx);
out[i - start] = name; }
}
out[i - start] = name;
return out; }
}
return out;
public static void parseCommandGroup(ICommandAddress address, Class<?> clazz, Object instance) throws CommandParseException { }
parseCommandGroup(address, ParameterTypes.getSelector(), clazz, instance);
} public static void parseCommandGroup(ICommandAddress address, Class<?> clazz, Object instance) throws CommandParseException {
parseCommandGroup(address, ParameterTypes.getSelector(), clazz, instance);
public static void parseCommandGroup(ICommandAddress address, IParameterTypeSelector selector, Class<?> clazz, Object instance) throws CommandParseException { }
boolean requireStatic = instance == null;
if (!requireStatic && !clazz.isInstance(instance)) { public static void parseCommandGroup(ICommandAddress address, IParameterTypeSelector selector, Class<?> clazz, Object instance) throws CommandParseException {
throw new CommandParseException(); boolean requireStatic = instance == null;
} if (!requireStatic && !clazz.isInstance(instance)) {
throw new CommandParseException();
List<Method> methods = new LinkedList<>(Arrays.asList(clazz.getDeclaredMethods())); }
Iterator<Method> it = methods.iterator(); List<Method> methods = new LinkedList<>(Arrays.asList(clazz.getDeclaredMethods()));
for (Method method; it.hasNext(); ) {
method = it.next(); Iterator<Method> it = methods.iterator();
for (Method method; it.hasNext(); ) {
if (requireStatic && !Modifier.isStatic(method.getModifiers())) { method = it.next();
it.remove();
continue; if (requireStatic && !Modifier.isStatic(method.getModifiers())) {
} it.remove();
continue;
if (method.isAnnotationPresent(CmdParamType.class)) { }
it.remove();
if (method.isAnnotationPresent(CmdParamType.class)) {
if (method.getReturnType() != ParameterType.class || method.getParameterCount() != 0) { it.remove();
throw new CommandParseException("Invalid CmdParamType method: must return ParameterType and take no arguments");
} if (method.getReturnType() != ParameterType.class || method.getParameterCount() != 0) {
throw new CommandParseException("Invalid CmdParamType method: must return ParameterType and take no arguments");
ParameterType<?, ?> type; }
try {
Object inst = Modifier.isStatic(method.getModifiers()) ? null : instance; ParameterType<?, ?> type;
type = (ParameterType<?, ?>) method.invoke(inst); try {
Objects.requireNonNull(type, "ParameterType returned is null"); Object inst = Modifier.isStatic(method.getModifiers()) ? null : instance;
} catch (Exception ex) { type = (ParameterType<?, ?>) method.invoke(inst);
throw new CommandParseException("Error occurred whilst getting ParameterType from CmdParamType method '" + method.toGenericString() + "'", ex); Objects.requireNonNull(type, "ParameterType returned is null");
} } catch (Exception ex) {
throw new CommandParseException("Error occurred whilst getting ParameterType from CmdParamType method '" + method.toGenericString() + "'", ex);
if (selector == ParameterTypes.getSelector()) { }
selector = new MapBasedParameterTypeSelector(true);
} if (selector == ParameterTypes.getSelector()) {
selector = new MapBasedParameterTypeSelector(true);
selector.addType(method.getAnnotation(CmdParamType.class).infolessAlias(), type); }
}
} selector.addType(method.getAnnotation(CmdParamType.class).infolessAlias(), type);
}
GroupMatcherCache groupMatcherCache = new GroupMatcherCache(clazz, address); }
for (Method method : methods) {
if (method.isAnnotationPresent(Cmd.class)) { GroupMatcherCache groupMatcherCache = new GroupMatcherCache(clazz, address);
ICommandAddress parsed = parseCommandMethod(selector, method, instance); for (Method method : methods) {
groupMatcherCache.getGroupFor(method).addChild(parsed); if (method.isAnnotationPresent(Cmd.class)) {
} ICommandAddress parsed = parseCommandMethod(selector, method, instance);
} groupMatcherCache.getGroupFor(method).addChild(parsed);
}
} }
private static final class GroupMatcherCache { }
private ModifiableCommandAddress groupRootAddress;
private GroupEntry[] matchEntries; private static final class GroupMatcherCache {
private Pattern[] patterns; private ModifiableCommandAddress groupRootAddress;
private ModifiableCommandAddress[] addresses; private GroupEntry[] matchEntries;
private Pattern[] patterns;
GroupMatcherCache(Class<?> clazz, ICommandAddress groupRootAddress) throws CommandParseException { private ModifiableCommandAddress[] addresses;
this.groupRootAddress = (ModifiableCommandAddress) groupRootAddress;
GroupMatcherCache(Class<?> clazz, ICommandAddress groupRootAddress) throws CommandParseException {
GroupMatchedCommands groupMatchedCommands = clazz.getAnnotation(GroupMatchedCommands.class); this.groupRootAddress = (ModifiableCommandAddress) groupRootAddress;
GroupEntry[] matchEntries = groupMatchedCommands == null ? new GroupEntry[0] : groupMatchedCommands.value();
GroupMatchedCommands groupMatchedCommands = clazz.getAnnotation(GroupMatchedCommands.class);
Pattern[] patterns = new Pattern[matchEntries.length]; GroupEntry[] matchEntries = groupMatchedCommands == null ? new GroupEntry[0] : groupMatchedCommands.value();
for (int i = 0; i < matchEntries.length; i++) {
GroupEntry matchEntry = matchEntries[i]; Pattern[] patterns = new Pattern[matchEntries.length];
if (matchEntry.group().isEmpty() || matchEntry.regex().isEmpty()) { for (int i = 0; i < matchEntries.length; i++) {
throw new CommandParseException("Empty group or regex in GroupMatchedCommands entry"); GroupEntry matchEntry = matchEntries[i];
} if (matchEntry.group().isEmpty() || matchEntry.regex().isEmpty()) {
try { throw new CommandParseException("Empty group or regex in GroupMatchedCommands entry");
patterns[i] = Pattern.compile(matchEntry.regex()); }
} catch (PatternSyntaxException ex) { try {
throw new CommandParseException(ex); patterns[i] = Pattern.compile(matchEntry.regex());
} } catch (PatternSyntaxException ex) {
} throw new CommandParseException(ex);
}
this.matchEntries = matchEntries; }
this.patterns = patterns;
this.addresses = new ModifiableCommandAddress[this.matchEntries.length]; this.matchEntries = matchEntries;
} this.patterns = patterns;
this.addresses = new ModifiableCommandAddress[this.matchEntries.length];
ModifiableCommandAddress getGroupFor(Method method) { }
String name = method.getName();
ModifiableCommandAddress getGroupFor(Method method) {
GroupEntry[] matchEntries = this.matchEntries; String name = method.getName();
Pattern[] patterns = this.patterns;
ModifiableCommandAddress[] addresses = this.addresses; GroupEntry[] matchEntries = this.matchEntries;
Pattern[] patterns = this.patterns;
for (int i = 0; i < matchEntries.length; i++) { ModifiableCommandAddress[] addresses = this.addresses;
GroupEntry matchEntry = matchEntries[i];
if (patterns[i].matcher(name).matches()) { for (int i = 0; i < matchEntries.length; i++) {
if (addresses[i] == null) { GroupEntry matchEntry = matchEntries[i];
ChildCommandAddress placeholder = new ChildCommandAddress(); if (patterns[i].matcher(name).matches()) {
placeholder.setupAsPlaceholder(matchEntry.group(), matchEntry.groupAliases()); if (addresses[i] == null) {
addresses[i] = placeholder; ChildCommandAddress placeholder = new ChildCommandAddress();
groupRootAddress.addChild(placeholder); placeholder.setupAsPlaceholder(matchEntry.group(), matchEntry.groupAliases());
generateCommands(placeholder, matchEntry.generatedCommands()); addresses[i] = placeholder;
setDescription(placeholder, matchEntry.description(), matchEntry.shortDescription()); groupRootAddress.addChild(placeholder);
} generateCommands(placeholder, matchEntry.generatedCommands());
return addresses[i]; setDescription(placeholder, matchEntry.description(), matchEntry.shortDescription());
} }
} return addresses[i];
}
return groupRootAddress; }
}
return groupRootAddress;
} }
public static ICommandAddress parseCommandMethod(IParameterTypeSelector selector, Method method, Object instance) throws CommandParseException { }
return new ReflectiveCommand(selector, method, instance).getAddress();
} public static ICommandAddress parseCommandMethod(IParameterTypeSelector selector, Method method, Object instance) throws CommandParseException {
return new ReflectiveCommand(selector, method, instance).getAddress();
static int parseCommandAttributes(IParameterTypeSelector selector, Method method, ReflectiveCommand command, java.lang.reflect.Parameter[] parameters) throws CommandParseException { }
ParameterList list = command.getParameterList();
static int parseCommandAttributes(IParameterTypeSelector selector, Method method, ReflectiveCommand command, java.lang.reflect.Parameter[] callParameters) throws CommandParseException {
boolean hasReceiverParameter = false; ParameterList list = command.getParameterList();
boolean hasSenderParameter = false;
boolean hasContextParameter = false; Class<?> senderParameterType = null;
boolean hasContinuationParameter = false; int flags = 0;
int start = 0;
int start = 0; int end = callParameters.length;
int end = parameters.length;
if (callParameters.length > start
Class<?> senderParameterType = null; && command.getInstance() instanceof ICommandInterceptor
&& ICommandReceiver.class.isAssignableFrom(callParameters[start].getType())) {
if (parameters.length > start flags |= RECEIVER_BIT;
&& command.getInstance() instanceof ICommandInterceptor ++start;
&& ICommandReceiver.class.isAssignableFrom(parameters[start].getType())) { }
hasReceiverParameter = true;
start++; if (callParameters.length > start && CommandSender.class.isAssignableFrom(senderParameterType = callParameters[start].getType())) {
} flags |= SENDER_BIT;
++start;
if (parameters.length > start && CommandSender.class.isAssignableFrom(senderParameterType = parameters[start].getType())) { }
hasSenderParameter = true;
start++; if (callParameters.length > start && callParameters[start].getType() == ExecutionContext.class) {
} flags |= CONTEXT_BIT;
++start;
if (parameters.length > start && parameters[start].getType() == ExecutionContext.class) { }
hasContextParameter = true;
start++; if (callParameters.length > start && callParameters[end - 1].getType().getName().equals("kotlin.coroutines.Continuation")) {
} flags |= CONTINUATION_BIT;
--end;
if (parameters.length > start && parameters[end - 1].getType().getName().equals("kotlin.coroutines.Continuation")) { }
hasContinuationParameter = true;
end--; String[] parameterNames = lookupParameterNames(method, callParameters, start);
} for (int i = start, n = end; i < n; i++) {
Parameter<?, ?> parameter = parseParameter(selector, method, callParameters[i], parameterNames[i - start]);
String[] parameterNames = lookupParameterNames(method, parameters, start); list.addParameter(parameter);
for (int i = start, n = end; i < n; i++) { }
Parameter<?, ?> parameter = parseParameter(selector, method, parameters[i], parameterNames[i - start]);
list.addParameter(parameter); command.setParameterOrder(hasCallArg(flags, CONTINUATION_BIT) ? Arrays.copyOfRange(parameterNames, 0, parameterNames.length - 1) : parameterNames);
}
command.setParameterOrder(hasContinuationParameter ? Arrays.copyOfRange(parameterNames, 0, parameterNames.length - 1) : parameterNames); RequirePermissions cmdPermissions = method.getAnnotation(RequirePermissions.class);
if (cmdPermissions != null) {
RequirePermissions cmdPermissions = method.getAnnotation(RequirePermissions.class); for (String permission : cmdPermissions.value()) {
if (cmdPermissions != null) { command.addContextFilter(IContextFilter.permission(permission));
for (String permission : cmdPermissions.value()) { }
command.addContextFilter(IContextFilter.permission(permission));
} if (cmdPermissions.inherit()) {
command.addContextFilter(IContextFilter.INHERIT_PERMISSIONS);
if (cmdPermissions.inherit()) { }
command.addContextFilter(IContextFilter.INHERIT_PERMISSIONS); } else {
} command.addContextFilter(IContextFilter.INHERIT_PERMISSIONS);
} else { }
command.addContextFilter(IContextFilter.INHERIT_PERMISSIONS);
} RequireParameters reqPar = method.getAnnotation(RequireParameters.class);
if (reqPar != null) {
RequireParameters reqPar = method.getAnnotation(RequireParameters.class); list.setRequiredCount(reqPar.value() < 0 ? Integer.MAX_VALUE : reqPar.value());
if (reqPar != null) { } else {
list.setRequiredCount(reqPar.value() < 0 ? Integer.MAX_VALUE : reqPar.value()); list.setRequiredCount(list.getIndexedParameters().size());
} else { }
list.setRequiredCount(list.getIndexedParameters().size());
} /*
PreprocessArgs preprocessArgs = method.getAnnotation(PreprocessArgs.class);
/* if (preprocessArgs != null) {
PreprocessArgs preprocessArgs = method.getAnnotation(PreprocessArgs.class); IArgumentPreProcessor preProcessor = IArgumentPreProcessor.mergeOnTokens(preprocessArgs.tokens(), preprocessArgs.escapeChar());
if (preprocessArgs != null) { list.setArgumentPreProcessor(preProcessor);
IArgumentPreProcessor preProcessor = IArgumentPreProcessor.mergeOnTokens(preprocessArgs.tokens(), preprocessArgs.escapeChar()); }*/
list.setArgumentPreProcessor(preProcessor);
}*/ Desc desc = method.getAnnotation(Desc.class);
if (desc != null) {
Desc desc = method.getAnnotation(Desc.class); String[] array = desc.value();
if (desc != null) { if (array.length == 0) {
String[] array = desc.value(); command.setDescription(desc.shortVersion());
if (array.length == 0) { } else {
command.setDescription(desc.shortVersion()); command.setDescription(array);
} else { }
command.setDescription(array); } else {
} command.setDescription();
} else { }
command.setDescription();
} boolean hasSenderParameter = hasCallArg(flags, SENDER_BIT);
if (hasSenderParameter && Player.class.isAssignableFrom(senderParameterType)) {
if (hasSenderParameter && Player.class.isAssignableFrom(senderParameterType)) { command.addContextFilter(IContextFilter.PLAYER_ONLY);
command.addContextFilter(IContextFilter.PLAYER_ONLY); } else if (hasSenderParameter && ConsoleCommandSender.class.isAssignableFrom(senderParameterType)) {
} else if (hasSenderParameter && ConsoleCommandSender.class.isAssignableFrom(senderParameterType)) { command.addContextFilter(IContextFilter.CONSOLE_ONLY);
command.addContextFilter(IContextFilter.CONSOLE_ONLY); } else if (method.isAnnotationPresent(RequirePlayer.class)) {
} else if (method.isAnnotationPresent(RequirePlayer.class)) { command.addContextFilter(IContextFilter.PLAYER_ONLY);
command.addContextFilter(IContextFilter.PLAYER_ONLY); } else if (method.isAnnotationPresent(RequireConsole.class)) {
} else if (method.isAnnotationPresent(RequireConsole.class)) { command.addContextFilter(IContextFilter.CONSOLE_ONLY);
command.addContextFilter(IContextFilter.CONSOLE_ONLY); }
}
list.setRepeatFinalParameter(callParameters.length > start && callParameters[callParameters.length - 1].isVarArgs());
list.setRepeatFinalParameter(parameters.length > start && parameters[parameters.length - 1].isVarArgs()); list.setFinalParameterMayBeFlag(true);
list.setFinalParameterMayBeFlag(true);
return flags;
int flags = 0; }
if (hasContinuationParameter) flags |= 1;
flags <<= 1; public static int parseCommandAttributes(IParameterTypeSelector selector, Method method, ReflectiveCommand command) throws CommandParseException {
if (hasContextParameter) flags |= 1; return parseCommandAttributes(selector, method, command, method.getParameters());
flags <<= 1; }
if (hasSenderParameter) flags |= 1;
flags <<= 1; public static Parameter<?, ?> parseParameter(IParameterTypeSelector selector, Method method, java.lang.reflect.Parameter parameter, String name) throws CommandParseException {
if (hasReceiverParameter) flags |= 1; Class<?> type = parameter.getType();
return flags; if (parameter.isVarArgs()) {
} type = type.getComponentType();
}
public static int parseCommandAttributes(IParameterTypeSelector selector, Method method, ReflectiveCommand command) throws CommandParseException {
return parseCommandAttributes(selector, method, command, method.getParameters()); Annotation[] annotations = parameter.getAnnotations();
} Flag flag = null;
Annotation typeAnnotation = null;
public static Parameter<?, ?> parseParameter(IParameterTypeSelector selector, Method method, java.lang.reflect.Parameter parameter, String name) throws CommandParseException { Desc desc = null;
Class<?> type = parameter.getType();
if (parameter.isVarArgs()) { for (Annotation annotation : annotations) {
type = type.getComponentType(); //noinspection StatementWithEmptyBody
} if (annotation instanceof NamedArg) {
// do nothing
Annotation[] annotations = parameter.getAnnotations(); } else if (annotation instanceof Flag) {
Flag flag = null; if (flag != null) {
Annotation typeAnnotation = null; throw new CommandParseException("Multiple flags for the same parameter");
Desc desc = null; }
flag = (Flag) annotation;
for (Annotation annotation : annotations) { } else if (annotation instanceof Desc) {
//noinspection StatementWithEmptyBody if (desc != null) {
if (annotation instanceof NamedArg) { throw new CommandParseException("Multiple descriptions for the same parameter");
// do nothing }
} else if (annotation instanceof Flag) { desc = (Desc) annotation;
if (flag != null) { } else {
throw new CommandParseException("Multiple flags for the same parameter"); if (typeAnnotation != null) {
} throw new CommandParseException("Multiple parameter type annotations for the same parameter");
flag = (Flag) annotation; }
} else if (annotation instanceof Desc) { typeAnnotation = annotation;
if (desc != null) { }
throw new CommandParseException("Multiple descriptions for the same parameter"); }
}
desc = (Desc) annotation; if (flag == null && name.startsWith("-")) {
} else { throw new CommandParseException("Non-flag parameter's name starts with -");
if (typeAnnotation != null) { } else if (flag != null && !name.startsWith("-")) {
throw new CommandParseException("Multiple parameter type annotations for the same parameter"); throw new CommandParseException("Flag parameter's name doesn't start with -");
} }
typeAnnotation = annotation;
} ParameterType<Object, Object> parameterType = selector.selectAny(type, typeAnnotation == null ? null : typeAnnotation.getClass());
} if (parameterType == null) {
throw new CommandParseException("IParameter type not found for parameter " + name + " in method " + method.toString());
if (flag == null && name.startsWith("-")) { }
throw new CommandParseException("Non-flag parameter's name starts with -");
} else if (flag != null && !name.startsWith("-")) { Object parameterInfo;
throw new CommandParseException("Flag parameter's name doesn't start with -"); if (typeAnnotation == null) {
} parameterInfo = null;
} else try {
ParameterType<Object, Object> parameterType = selector.selectAny(type, typeAnnotation == null ? null : typeAnnotation.getClass()); parameterInfo = parameterType.getParameterConfig() == null ? null : parameterType.getParameterConfig().getParameterInfo(typeAnnotation);
if (parameterType == null) { } catch (Exception ex) {
throw new CommandParseException("IParameter type not found for parameter " + name + " in method " + method.toString()); throw new CommandParseException("Invalid parameter config", ex);
} }
Object parameterInfo; String descString = desc == null ? null : CommandAnnotationUtils.getShortDescription(desc);
if (typeAnnotation == null) {
parameterInfo = null; try {
} else try { //noinspection unchecked
parameterInfo = parameterType.getParameterConfig() == null ? null : parameterType.getParameterConfig().getParameterInfo(typeAnnotation); String flagPermission = flag == null || flag.permission().isEmpty() ? null : flag.permission();
} catch (Exception ex) { return new Parameter<>(name, descString, parameterType, parameterInfo, type.isPrimitive(), name.startsWith("-"), flagPermission);
throw new CommandParseException("Invalid parameter config", ex); } catch (Exception ex) {
} throw new CommandParseException("Invalid parameter", ex);
}
String descString = desc == null ? null : CommandAnnotationUtils.getShortDescription(desc); }
try { public static void generateCommands(ICommandAddress address, String[] input) {
//noinspection unchecked for (String value : input) {
String flagPermission = flag == null || flag.permission().isEmpty() ? null : flag.permission(); Consumer<ICommandAddress> consumer = PredefinedCommand.getPredefinedCommandGenerator(value);
return new Parameter<>(name, descString, parameterType, parameterInfo, type.isPrimitive(), name.startsWith("-"), flagPermission); if (consumer == null) {
} catch (Exception ex) { System.out.println("[Command Warning] generated command '" + value + "' could not be found");
throw new CommandParseException("Invalid parameter", ex); } else {
} consumer.accept(address);
} }
}
public static void generateCommands(ICommandAddress address, String[] input) { }
for (String value : input) {
Consumer<ICommandAddress> consumer = PredefinedCommand.getPredefinedCommandGenerator(value); /*
if (consumer == null) { Desired format
System.out.println("[Command Warning] generated command '" + value + "' could not be found");
} else { @Cmd({"tp", "tpto"})
consumer.accept(address); @RequirePermissions("teleport.self")
} public (static) String|void onCommand(Player sender, Player target, @Flag("force", permission = "teleport.self.force") boolean force) {
} Validate.isTrue(force || !hasTpToggledOff(target), "Target has teleportation disabled. Use -force to ignore");
} sender.teleport(target);
//return
/* }
Desired format
parser needs to:
@Cmd({"tp", "tpto"}) - see the @Cmd and create a CommandTree for it
@RequirePermissions("teleport.self") - see that it must be a Player executing the command
public (static) String|void onCommand(Player sender, Player target, @Flag("force", permission = "teleport.self.force") boolean force) { - add an indexed IParameter for a Player type
Validate.isTrue(force || !hasTpToggledOff(target), "Target has teleportation disabled. Use -force to ignore"); - add a flag parameter named force, that consumes no arguments.
sender.teleport(target); - see that setting the force flag requires a permission
//return */
}
private static void setDescription(ICommandAddress address, String[] array, String shortVersion) {
parser needs to: if (!address.hasCommand()) {
- see the @Cmd and create a CommandTree for it return;
- see that it must be a Player executing the command }
- add an indexed IParameter for a Player type
- add a flag parameter named force, that consumes no arguments. if (array.length == 0) {
- see that setting the force flag requires a permission address.getCommand().setDescription(shortVersion);
*/ } else {
address.getCommand().setDescription(array);
private static void setDescription(ICommandAddress address, String[] array, String shortVersion) { }
if (!address.hasCommand()) {
return; }
}
}
if (array.length == 0) {
address.getCommand().setDescription(shortVersion);
} else {
address.getCommand().setDescription(array);
}
}
}

View File

@@ -1,66 +1,69 @@
package io.dico.dicore.command.registration.reflect package io.dico.dicore.command.registration.reflect
import io.dico.dicore.command.* import io.dico.dicore.command.*
import kotlinx.coroutines.CoroutineStart.UNDISPATCHED import kotlinx.coroutines.CoroutineStart.UNDISPATCHED
import kotlinx.coroutines.Deferred import kotlinx.coroutines.Deferred
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async import kotlinx.coroutines.async
import java.lang.reflect.Method import java.lang.reflect.Method
import java.util.concurrent.CancellationException import java.util.concurrent.CancellationException
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.intrinsics.intercepted import kotlin.coroutines.intrinsics.intercepted
import kotlin.coroutines.intrinsics.suspendCoroutineUninterceptedOrReturn import kotlin.coroutines.intrinsics.suspendCoroutineUninterceptedOrReturn
import kotlin.reflect.jvm.kotlinFunction import kotlin.reflect.jvm.kotlinFunction
fun isSuspendFunction(method: Method): Boolean { fun isSuspendFunction(method: Method): Boolean {
val func = method.kotlinFunction ?: return false val func = method.kotlinFunction ?: return false
return func.isSuspend return func.isSuspend
} }
fun callAsCoroutine( @Throws(CommandException::class)
command: ReflectiveCommand, fun callCommandAsCoroutine(
factory: ICommandInterceptor, executionContext: ExecutionContext,
context: ExecutionContext, coroutineContext: CoroutineContext,
args: Array<Any?> continuationIndex: Int,
): String? { method: Method,
val coroutineContext = factory.getCoroutineContext(context, command.method, command.cmdName) as CoroutineContext instance: Any?,
args: Array<Any?>
// UNDISPATCHED causes the handler to run until the first suspension point on the current thread, ): String? {
// meaning command handlers that don't have suspension points will run completely synchronously.
// Tasks that take time to compute should suspend the coroutine and resume on another thread. // UNDISPATCHED causes the handler to run until the first suspension point on the current thread,
val job = GlobalScope.async(context = coroutineContext, start = UNDISPATCHED) { // meaning command handlers that don't have suspension points will run completely synchronously.
suspendCoroutineUninterceptedOrReturn<Any?> { cont -> // Tasks that take time to compute should suspend the coroutine and resume on another thread.
command.method.invoke(command.instance, *args, cont.intercepted()) val job = GlobalScope.async(context = coroutineContext, start = UNDISPATCHED) {
} suspendCoroutineUninterceptedOrReturn<Any?> { cont ->
} args[continuationIndex] = cont.intercepted()
method.invoke(instance, *args)
if (job.isCompleted) { }
return job.getResult() }
}
if (job.isCompleted) {
job.invokeOnCompletion { return job.getResult()
val chatHandler = context.address.chatHandler }
try {
val result = job.getResult() job.invokeOnCompletion {
chatHandler.sendMessage(context.sender, EMessageType.RESULT, result) val chatHandler = executionContext.address.chatHandler
} catch (ex: Throwable) { try {
chatHandler.handleException(context.sender, context, ex) val result = job.getResult()
} chatHandler.sendMessage(executionContext.sender, EMessageType.RESULT, result)
} } catch (ex: Throwable) {
chatHandler.handleException(executionContext.sender, executionContext, ex)
return null }
} }
@Throws(CommandException::class) return null
private fun Deferred<Any?>.getResult(): String? { }
getCompletionExceptionOrNull()?.let { ex ->
if (ex is CancellationException) { @Throws(CommandException::class)
System.err.println("An asynchronously dispatched command was cancelled unexpectedly") private fun Deferred<Any?>.getResult(): String? {
ex.printStackTrace() getCompletionExceptionOrNull()?.let { ex ->
throw CommandException("The command was cancelled unexpectedly (see console)") if (ex is CancellationException) {
} System.err.println("An asynchronously dispatched command was cancelled unexpectedly")
if (ex is Exception) return ReflectiveCommand.getResult(null, ex) ex.printStackTrace()
throw ex throw CommandException("The command was cancelled unexpectedly (see console)")
} }
return ReflectiveCommand.getResult(getCompleted(), null) if (ex is Exception) return ReflectiveCommand.getResult(null, ex)
} throw ex
}
return ReflectiveCommand.getResult(getCompleted(), null)
}

View File

@@ -1,337 +1,368 @@
package io.dico.parcels2 package io.dico.parcels2
import io.dico.parcels2.util.math.clampMin import io.dico.parcels2.util.PluginAware
import kotlinx.coroutines.CancellationException import io.dico.parcels2.util.math.clampMin
import kotlinx.coroutines.CoroutineScope import io.dico.parcels2.util.scheduleRepeating
import kotlinx.coroutines.CoroutineStart.LAZY import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Job as CoroutineJob import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch import kotlinx.coroutines.CoroutineStart.LAZY
import org.bukkit.scheduler.BukkitTask import kotlinx.coroutines.Job as CoroutineJob
import java.lang.System.currentTimeMillis import kotlinx.coroutines.launch
import java.util.LinkedList import org.bukkit.scheduler.BukkitTask
import kotlin.coroutines.Continuation import java.lang.System.currentTimeMillis
import kotlin.coroutines.intrinsics.COROUTINE_SUSPENDED import java.util.LinkedList
import kotlin.coroutines.intrinsics.suspendCoroutineUninterceptedOrReturn import kotlin.coroutines.Continuation
import kotlin.coroutines.resume import kotlin.coroutines.intrinsics.COROUTINE_SUSPENDED
import kotlin.coroutines.intrinsics.suspendCoroutineUninterceptedOrReturn
typealias JobFunction = suspend JobScope.() -> Unit import kotlin.coroutines.resume
typealias JobUpdateLister = Job.(Double, Long) -> Unit
typealias JobFunction = suspend JobScope.() -> Unit
data class TickJobtimeOptions(var jobTime: Int, var tickInterval: Int) typealias JobUpdateLister = Job.(Double, Long) -> Unit
interface JobDispatcher { data class TickJobtimeOptions(var jobTime: Int, var tickInterval: Int)
/**
* Submit a [function] that should be run synchronously, but limited such that it does not stall the server interface JobDispatcher {
*/ /**
fun dispatch(function: JobFunction): Job * Submit a [function] that should be run synchronously, but limited such that it does not stall the server
*/
/** fun dispatch(function: JobFunction): Job
* Get a list of all jobs
*/ /**
val jobs: List<Job> * Get a list of all jobs
*/
/** val jobs: List<Job>
* Attempts to complete any remaining tasks immediately, without suspension.
*/ /**
fun completeAllTasks() * Attempts to complete any remaining tasks immediately, without suspension.
} */
fun completeAllTasks()
interface JobAndScopeMembersUnion { }
/**
* The time that elapsed since this job was dispatched, in milliseconds interface JobAndScopeMembersUnion {
*/ /**
val elapsedTime: Long * The time that elapsed since this job was dispatched, in milliseconds
*/
/** val elapsedTime: Long
* A value indicating the progress of this job, in the range 0.0 <= progress <= 1.0
* with no guarantees to its accuracy. /**
*/ * A value indicating the progress of this job, in the range 0.0 <= progress <= 1.0
val progress: Double * with no guarantees to its accuracy.
} */
val progress: Double
interface Job : JobAndScopeMembersUnion { }
/**
* The coroutine associated with this job interface Job : JobAndScopeMembersUnion {
*/ /**
val coroutine: CoroutineJob * The coroutine associated with this job
*/
/** val coroutine: CoroutineJob
* true if this job has completed
*/ /**
val isComplete: Boolean * true if this job has completed
*/
/** val isComplete: Boolean
* If an exception was thrown during the execution of this task,
* returns that exception. Returns null otherwise. /**
*/ * If an exception was thrown during the execution of this task,
val completionException: Throwable? * returns that exception. Returns null otherwise.
*/
/** val completionException: Throwable?
* Calls the given [block] whenever the progress of this job is updated,
* if [minInterval] milliseconds expired since the last call. /**
* The first call occurs after at least [minDelay] milliseconds in a likewise manner. * Calls the given [block] whenever the progress of this job is updated,
* Repeated invocations of this method result in an [IllegalStateException] * if [minInterval] milliseconds expired since the last call.
* * The first call occurs after at least [minDelay] milliseconds in a likewise manner.
* if [asCompletionListener] is true, [onCompleted] is called with the same [block] * Repeated invocations of this method result in an [IllegalStateException]
*/ *
fun onProgressUpdate(minDelay: Int, minInterval: Int, asCompletionListener: Boolean = true, block: JobUpdateLister): Job * if [asCompletionListener] is true, [onCompleted] is called with the same [block]
*/
/** fun onProgressUpdate(
* Calls the given [block] when this job completes, with the progress value 1.0. minDelay: Int,
* Multiple listeners may be registered to this function. minInterval: Int,
*/ asCompletionListener: Boolean = true,
fun onCompleted(block: JobUpdateLister): Job block: JobUpdateLister
): Job
/**
* Await completion of this job /**
*/ * Calls the given [block] when this job completes, with the progress value 1.0.
suspend fun awaitCompletion() * Multiple listeners may be registered to this function.
} */
fun onCompleted(block: JobUpdateLister): Job
interface JobScope : JobAndScopeMembersUnion {
/** /**
* A task should call this frequently during its execution, such that the timer can suspend it when necessary. * Await completion of this job
*/ */
suspend fun markSuspensionPoint() suspend fun awaitCompletion()
}
/**
* A task should call this method to indicate its progress interface JobScope : JobAndScopeMembersUnion {
*/ /**
fun setProgress(progress: Double) * A task should call this frequently during its execution, such that the timer can suspend it when necessary.
*/
/** suspend fun markSuspensionPoint()
* Indicate that this job is complete
*/ /**
fun markComplete() = setProgress(1.0) * A task should call this method to indicate its progress
*/
/** fun setProgress(progress: Double)
* Get a [JobScope] that is responsible for [portion] part of the progress
* If [portion] is negative, the remaining progress is used /**
*/ * Indicate that this job is complete
fun delegateProgress(portion: Double = -1.0): JobScope */
} fun markComplete() = setProgress(1.0)
inline fun <T> JobScope.delegateWork(portion: Double = -1.0, block: JobScope.() -> T): T { /**
delegateProgress(portion).apply { * Get a [JobScope] that is responsible for [portion] part of the progress
val result = block() * If [portion] is negative, the remaining progress is used
markComplete() */
return result fun delegateProgress(portion: Double = -1.0): JobScope
} }
}
inline fun <T> JobScope.delegateWork(portion: Double = -1.0, block: JobScope.() -> T): T {
interface JobInternal : Job, JobScope { delegateProgress(portion).apply {
/** val result = block()
* Start or resumes the execution of this job markComplete()
* and returns true if the job completed return result
* }
* [worktime] is the maximum amount of time, in milliseconds, }
* that this job may run for until suspension.
* interface JobInternal : Job, JobScope {
* If [worktime] is not positive, the job will complete /**
* without suspension and this method will always return true. * Start or resumes the execution of this job
*/ * and returns true if the job completed
fun resume(worktime: Long): Boolean *
} * [worktime] is the maximum amount of time, in milliseconds,
* that this job may run for until suspension.
/** *
* An object that controls one or more jobs, ensuring that they don't stall the server too much. * If [worktime] is not positive, the job will complete
* There is a configurable maxiumum amount of milliseconds that can be allocated to all jobs together in each server tick * without suspension and this method will always return true.
* This object attempts to split that maximum amount of milliseconds equally between all jobs */
*/ fun resume(worktime: Long): Boolean
class BukkitJobDispatcher(private val plugin: ParcelsPlugin, var options: TickJobtimeOptions) : JobDispatcher { }
// The currently registered bukkit scheduler task
private var bukkitTask: BukkitTask? = null /**
// The jobs. * An object that controls one or more jobs, ensuring that they don't stall the server too much.
private val _jobs = LinkedList<JobInternal>() * There is a configurable maxiumum amount of milliseconds that can be allocated to all jobs together in each server tick
override val jobs: List<Job> = _jobs * This object attempts to split that maximum amount of milliseconds equally between all jobs
*/
override fun dispatch(function: JobFunction): Job { class BukkitJobDispatcher(
val job: JobInternal = JobImpl(plugin, function) private val plugin: PluginAware,
private val scope: CoroutineScope,
if (bukkitTask == null) { var options: TickJobtimeOptions
val completed = job.resume(options.jobTime.toLong()) ) : JobDispatcher {
if (completed) return job // The currently registered bukkit scheduler task
bukkitTask = plugin.scheduleRepeating(0, options.tickInterval) { tickCoroutineJobs() } private var bukkitTask: BukkitTask? = null
} // The jobs.
_jobs.addFirst(job) private val _jobs = LinkedList<JobInternal>()
return job override val jobs: List<Job> = _jobs
}
override fun dispatch(function: JobFunction): Job {
private fun tickCoroutineJobs() { val job: JobInternal = JobImpl(scope, function)
val jobs = _jobs
if (jobs.isEmpty()) return if (bukkitTask == null) {
val tickStartTime = System.currentTimeMillis() val completed = job.resume(options.jobTime.toLong())
if (completed) return job
val iterator = jobs.listIterator(index = 0) bukkitTask = plugin.scheduleRepeating(options.tickInterval) { tickJobs() }
while (iterator.hasNext()) { }
val time = System.currentTimeMillis() _jobs.addFirst(job)
val timeElapsed = time - tickStartTime return job
val timeLeft = options.jobTime - timeElapsed }
if (timeLeft <= 0) return
private fun tickJobs() {
val count = jobs.size - iterator.nextIndex() val jobs = _jobs
val timePerJob = (timeLeft + count - 1) / count if (jobs.isEmpty()) return
val job = iterator.next() val tickStartTime = System.currentTimeMillis()
val completed = job.resume(timePerJob)
if (completed) { val iterator = jobs.listIterator(index = 0)
iterator.remove() while (iterator.hasNext()) {
} val time = System.currentTimeMillis()
} val timeElapsed = time - tickStartTime
val timeLeft = options.jobTime - timeElapsed
if (jobs.isEmpty()) { if (timeLeft <= 0) return
bukkitTask?.cancel()
bukkitTask = null val count = jobs.size - iterator.nextIndex()
} val timePerJob = (timeLeft + count - 1) / count
} val job = iterator.next()
val completed = job.resume(timePerJob)
override fun completeAllTasks() { if (completed) {
_jobs.forEach { iterator.remove()
it.resume(-1) }
} }
_jobs.clear()
bukkitTask?.cancel() if (jobs.isEmpty()) {
bukkitTask = null bukkitTask?.cancel()
} bukkitTask = null
}
} }
private class JobImpl(scope: CoroutineScope, task: JobFunction) : JobInternal { override fun completeAllTasks() {
override val coroutine: CoroutineJob = scope.launch(start = LAZY) { task() } _jobs.forEach {
it.resume(-1)
private var continuation: Continuation<Unit>? = null }
private var nextSuspensionTime: Long = 0L _jobs.clear()
private var completeForcefully = false bukkitTask?.cancel()
private var isStarted = false bukkitTask = null
}
override val elapsedTime
get() = }
if (coroutine.isCompleted) startTimeOrElapsedTime
else currentTimeMillis() - startTimeOrElapsedTime private class JobImpl(scope: CoroutineScope, task: JobFunction) : JobInternal {
override val coroutine: CoroutineJob = scope.launch(start = LAZY) { task() }
override val isComplete get() = coroutine.isCompleted
private var continuation: Continuation<Unit>? = null
private var _progress = 0.0 private var nextSuspensionTime: Long = 0L
override val progress get() = _progress private var completeForcefully = false
override var completionException: Throwable? = null; private set private var isStarted = false
private var startTimeOrElapsedTime: Long = 0L // startTime before completed, elapsed time otherwise override val elapsedTime
private var onProgressUpdate: JobUpdateLister? = null get() =
private var progressUpdateInterval: Int = 0 if (coroutine.isCompleted) startTimeOrElapsedTime
private var lastUpdateTime: Long = 0L else currentTimeMillis() - startTimeOrElapsedTime
private var onCompleted: JobUpdateLister? = null
override val isComplete get() = coroutine.isCompleted
init {
coroutine.invokeOnCompletion { exception -> private var _progress = 0.0
// report any error that occurred override val progress get() = _progress
completionException = exception?.also { override var completionException: Throwable? = null; private set
if (it !is CancellationException)
logger.error("JobFunction generated an exception", it) private var startTimeOrElapsedTime: Long = 0L // startTime before completed, elapsed time otherwise
} private var onProgressUpdate: JobUpdateLister? = null
private var progressUpdateInterval: Int = 0
// convert to elapsed time here private var lastUpdateTime: Long = 0L
startTimeOrElapsedTime = System.currentTimeMillis() - startTimeOrElapsedTime private var onCompleted: JobUpdateLister? = null
onCompleted?.let { it(1.0, elapsedTime) }
init {
onCompleted = null coroutine.invokeOnCompletion { exception ->
onProgressUpdate = { prog, el -> } // report any error that occurred
} completionException = exception?.also {
} if (it !is CancellationException)
logger.error("JobFunction generated an exception", it)
override fun onProgressUpdate(minDelay: Int, minInterval: Int, asCompletionListener: Boolean, block: JobUpdateLister): Job { }
onProgressUpdate?.let { throw IllegalStateException() }
if (asCompletionListener) onCompleted(block) // convert to elapsed time here
if (isComplete) return this startTimeOrElapsedTime = System.currentTimeMillis() - startTimeOrElapsedTime
onProgressUpdate = block onCompleted?.let { it(1.0, elapsedTime) }
progressUpdateInterval = minInterval
lastUpdateTime = System.currentTimeMillis() + minDelay - minInterval onCompleted = null
onProgressUpdate = { prog, el -> }
return this }
} }
override fun onCompleted(block: JobUpdateLister): Job { override fun onProgressUpdate(
if (isComplete) { minDelay: Int,
block(1.0, startTimeOrElapsedTime) minInterval: Int,
return this asCompletionListener: Boolean,
} block: JobUpdateLister
): Job {
val cur = onCompleted onProgressUpdate?.let { throw IllegalStateException() }
onCompleted = if (cur == null) { if (asCompletionListener) onCompleted(block)
block if (isComplete) return this
} else { onProgressUpdate = block
fun Job.(prog: Double, el: Long) { progressUpdateInterval = minInterval
cur(prog, el) lastUpdateTime = System.currentTimeMillis() + minDelay - minInterval
block(prog, el)
} return this
} }
return this
} override fun onCompleted(block: JobUpdateLister): Job {
if (isComplete) {
override suspend fun markSuspensionPoint() { block(1.0, startTimeOrElapsedTime)
if (System.currentTimeMillis() >= nextSuspensionTime && !completeForcefully) return this
suspendCoroutineUninterceptedOrReturn { cont: Continuation<Unit> -> }
continuation = cont
COROUTINE_SUSPENDED val cur = onCompleted
} onCompleted = if (cur == null) {
} block
} else {
override fun setProgress(progress: Double) { fun Job.(prog: Double, el: Long) {
this._progress = progress cur(prog, el)
val onProgressUpdate = onProgressUpdate ?: return block(prog, el)
val time = System.currentTimeMillis() }
if (time > lastUpdateTime + progressUpdateInterval) { }
onProgressUpdate(progress, elapsedTime) return this
lastUpdateTime = time }
}
} override suspend fun markSuspensionPoint() {
if (System.currentTimeMillis() >= nextSuspensionTime && !completeForcefully)
override fun resume(worktime: Long): Boolean { suspendCoroutineUninterceptedOrReturn { cont: Continuation<Unit> ->
if (isComplete) return true continuation = cont
COROUTINE_SUSPENDED
if (worktime > 0) { }
nextSuspensionTime = currentTimeMillis() + worktime }
} else {
completeForcefully = true override fun setProgress(progress: Double) {
} this._progress = progress
val onProgressUpdate = onProgressUpdate ?: return
if (isStarted) { val time = System.currentTimeMillis()
continuation?.let { if (time > lastUpdateTime + progressUpdateInterval) {
continuation = null onProgressUpdate(progress, elapsedTime)
it.resume(Unit) lastUpdateTime = time
return continuation == null }
} }
return true
} override fun resume(worktime: Long): Boolean {
if (isComplete) return true
isStarted = true
startTimeOrElapsedTime = System.currentTimeMillis() if (worktime > 0) {
coroutine.start() nextSuspensionTime = currentTimeMillis() + worktime
} else {
return continuation == null completeForcefully = true
} }
override suspend fun awaitCompletion() { if (isStarted) {
coroutine.join() continuation?.let {
} continuation = null
private fun delegateProgress(curPortion: Double, portion: Double): JobScope = wrapExternalCall {
DelegateScope(progress, curPortion * (if (portion < 0) 1.0 - progress else portion).clampMin(0.0)) it.resume(Unit)
}
override fun delegateProgress(portion: Double): JobScope = delegateProgress(1.0, portion)
return continuation == null
private inner class DelegateScope(val progressStart: Double, val portion: Double) : JobScope { }
override val elapsedTime: Long return true
get() = this@JobImpl.elapsedTime }
override suspend fun markSuspensionPoint() = isStarted = true
this@JobImpl.markSuspensionPoint() startTimeOrElapsedTime = System.currentTimeMillis()
override val progress: Double wrapExternalCall {
get() = (this@JobImpl.progress - progressStart) / portion coroutine.start()
}
override fun setProgress(progress: Double) =
this@JobImpl.setProgress(progressStart + progress * portion) return continuation == null
}
override fun delegateProgress(portion: Double): JobScope =
this@JobImpl.delegateProgress(this.portion, portion) private inline fun wrapExternalCall(block: () -> Unit) {
} try {
} block()
} catch (ex: Throwable) {
logger.error("Job $coroutine generated an exception", ex)
}
}
override suspend fun awaitCompletion() {
coroutine.join()
}
private fun delegateProgress(curPortion: Double, portion: Double): JobScope =
DelegateScope(this, progress, curPortion * (if (portion < 0) 1.0 - progress else portion).clampMin(0.0))
override fun delegateProgress(portion: Double): JobScope = delegateProgress(1.0, portion)
private class DelegateScope(val parent: JobImpl, val progressStart: Double, val portion: Double) : JobScope {
override val elapsedTime: Long
get() = parent.elapsedTime
override suspend fun markSuspensionPoint() =
parent.markSuspensionPoint()
override val progress: Double
get() = (parent.progress - progressStart) / portion
override fun setProgress(progress: Double) =
parent.setProgress(progressStart + progress * portion)
override fun delegateProgress(portion: Double): JobScope =
parent.delegateProgress(this.portion, portion)
}
}

View File

@@ -1,103 +1,102 @@
package io.dico.parcels2 package io.dico.parcels2
import io.dico.parcels2.blockvisitor.RegionTraverser import io.dico.parcels2.blockvisitor.RegionTraverser
import io.dico.parcels2.util.math.Region import io.dico.parcels2.util.math.Region
import io.dico.parcels2.util.math.Vec2i import io.dico.parcels2.util.math.Vec2i
import io.dico.parcels2.util.math.Vec3i import io.dico.parcels2.util.math.Vec3i
import io.dico.parcels2.util.math.get import io.dico.parcels2.util.math.get
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import org.bukkit.Chunk import org.bukkit.Chunk
import org.bukkit.Location import org.bukkit.Location
import org.bukkit.Material import org.bukkit.Material
import org.bukkit.World import org.bukkit.World
import org.bukkit.block.Biome import org.bukkit.block.Biome
import org.bukkit.block.Block import org.bukkit.block.Block
import org.bukkit.block.BlockFace import org.bukkit.block.BlockFace
import org.bukkit.entity.Entity import org.bukkit.entity.Entity
import org.bukkit.generator.BlockPopulator import org.bukkit.generator.BlockPopulator
import org.bukkit.generator.ChunkGenerator import org.bukkit.generator.ChunkGenerator
import java.util.Random import java.util.Random
abstract class ParcelGenerator : ChunkGenerator() { abstract class ParcelGenerator : ChunkGenerator() {
abstract val worldName: String abstract val worldName: String
abstract val world: World abstract val world: World
abstract override fun generateChunkData(world: World?, random: Random?, chunkX: Int, chunkZ: Int, biome: BiomeGrid?): ChunkData abstract override fun generateChunkData(world: World?, random: Random?, chunkX: Int, chunkZ: Int, biome: BiomeGrid?): ChunkData
abstract fun populate(world: World?, random: Random?, chunk: Chunk?) abstract fun populate(world: World?, random: Random?, chunk: Chunk?)
abstract override fun getFixedSpawnLocation(world: World?, random: Random?): Location abstract override fun getFixedSpawnLocation(world: World?, random: Random?): Location
override fun getDefaultPopulators(world: World?): MutableList<BlockPopulator> { override fun getDefaultPopulators(world: World?): MutableList<BlockPopulator> {
return mutableListOf(object : BlockPopulator() { return mutableListOf(object : BlockPopulator() {
override fun populate(world: World?, random: Random?, chunk: Chunk?) { override fun populate(world: World?, random: Random?, chunk: Chunk?) {
this@ParcelGenerator.populate(world, random, chunk) this@ParcelGenerator.populate(world, random, chunk)
} }
}) })
} }
abstract fun makeParcelLocatorAndBlockManager( abstract fun makeParcelLocatorAndBlockManager(
parcelProvider: ParcelProvider, parcelProvider: ParcelProvider,
container: ParcelContainer, container: ParcelContainer,
coroutineScope: CoroutineScope, coroutineScope: CoroutineScope,
jobDispatcher: JobDispatcher jobDispatcher: JobDispatcher
): Pair<ParcelLocator, ParcelBlockManager> ): Pair<ParcelLocator, ParcelBlockManager>
} }
interface ParcelBlockManager { interface ParcelBlockManager {
val world: World val world: World
val jobDispatcher: JobDispatcher val jobDispatcher: JobDispatcher
val parcelTraverser: RegionTraverser val parcelTraverser: RegionTraverser
fun getRegionOrigin(parcel: ParcelId) = getRegion(parcel).origin.toVec2i() fun getRegionOrigin(parcel: ParcelId) = getRegion(parcel).origin.toVec2i()
fun getHomeLocation(parcel: ParcelId): Location fun getHomeLocation(parcel: ParcelId): Location
fun getRegion(parcel: ParcelId): Region fun getRegion(parcel: ParcelId): Region
fun getEntities(parcel: ParcelId): Collection<Entity> fun getEntities(region: Region): Collection<Entity>
fun isParcelInfoSectionLoaded(parcel: ParcelId): Boolean fun isParcelInfoSectionLoaded(parcel: ParcelId): Boolean
fun updateParcelInfo(parcel: ParcelId, owner: PlayerProfile?) fun updateParcelInfo(parcel: ParcelId, owner: PlayerProfile?)
fun getParcelForInfoBlockInteraction(block: Vec3i, type: Material, face: BlockFace): Parcel? fun getParcelForInfoBlockInteraction(block: Vec3i, type: Material, face: BlockFace): Parcel?
fun setBiome(parcel: ParcelId, biome: Biome): Job? fun setBiome(parcel: ParcelId, biome: Biome): Job?
fun clearParcel(parcel: ParcelId): Job? fun clearParcel(parcel: ParcelId): Job?
/** /**
* Used to update owner blocks in the corner of the parcel * Used to update owner blocks in the corner of the parcel
*/ */
fun getParcelsWithOwnerBlockIn(chunk: Chunk): Collection<Vec2i> fun getParcelsWithOwnerBlockIn(chunk: Chunk): Collection<Vec2i>
} }
inline fun ParcelBlockManager.tryDoBlockOperation( inline fun ParcelBlockManager.tryDoBlockOperation(
parcelProvider: ParcelProvider, parcelProvider: ParcelProvider,
parcel: ParcelId, parcel: ParcelId,
traverser: RegionTraverser, traverser: RegionTraverser,
crossinline operation: suspend JobScope.(Block) -> Unit crossinline operation: suspend JobScope.(Block) -> Unit
) = parcelProvider.trySubmitBlockVisitor(Permit(), arrayOf(parcel)) { ) = parcelProvider.trySubmitBlockVisitor(Permit(), arrayOf(parcel)) {
val region = getRegion(parcel) val region = getRegion(parcel)
val blockCount = region.blockCount.toDouble() val blockCount = region.blockCount.toDouble()
val blocks = traverser.traverseRegion(region) val blocks = traverser.traverseRegion(region)
for ((index, vec) in blocks.withIndex()) { for ((index, vec) in blocks.withIndex()) {
markSuspensionPoint() markSuspensionPoint()
operation(world[vec]) operation(world[vec])
setProgress((index + 1) / blockCount) setProgress((index + 1) / blockCount)
} }
} }
abstract class ParcelBlockManagerBase : ParcelBlockManager { abstract class ParcelBlockManagerBase : ParcelBlockManager {
override fun getEntities(parcel: ParcelId): Collection<Entity> { override fun getEntities(region: Region): Collection<Entity> {
val region = getRegion(parcel) val center = region.center
val center = region.center val centerLoc = Location(world, center.x, center.y, center.z)
val centerLoc = Location(world, center.x, center.y, center.z) val centerDist = (center - region.origin).add(0.2, 0.2, 0.2)
val centerDist = (center - region.origin).add(0.2, 0.2, 0.2) return world.getNearbyEntities(centerLoc, centerDist.x, centerDist.y, centerDist.z)
return world.getNearbyEntities(centerLoc, centerDist.x, centerDist.y, centerDist.z) }
}
}
}

View File

@@ -1,103 +1,103 @@
package io.dico.parcels2 package io.dico.parcels2
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 io.dico.parcels2.util.math.Vec2i import io.dico.parcels2.util.math.Vec2i
import io.dico.parcels2.util.math.floor import io.dico.parcels2.util.math.floor
import org.bukkit.Location import org.bukkit.Location
import org.bukkit.World import org.bukkit.World
import org.bukkit.block.Block import org.bukkit.block.Block
import org.bukkit.entity.Entity import org.bukkit.entity.Entity
import org.joda.time.DateTime import org.joda.time.DateTime
import java.lang.IllegalStateException import java.lang.IllegalStateException
import java.util.UUID import java.util.UUID
class Permit class Permit
interface ParcelProvider { interface ParcelProvider {
val worlds: Map<String, ParcelWorld> val worlds: Map<String, ParcelWorld>
fun getWorldById(id: ParcelWorldId): ParcelWorld? fun getWorldById(id: ParcelWorldId): ParcelWorld?
fun getParcelById(id: ParcelId): Parcel? fun getParcelById(id: ParcelId): Parcel?
fun getWorld(name: String): ParcelWorld? fun getWorld(name: String): ParcelWorld?
fun getWorld(world: World): ParcelWorld? = getWorld(world.name) fun getWorld(world: World): ParcelWorld? = getWorld(world.name)
fun getWorld(block: Block): ParcelWorld? = getWorld(block.world) fun getWorld(block: Block): ParcelWorld? = getWorld(block.world)
fun getWorld(loc: Location): ParcelWorld? = getWorld(loc.world) fun getWorld(loc: Location): ParcelWorld? = getWorld(loc.world)
fun getWorld(entity: Entity): ParcelWorld? = getWorld(entity.location) fun getWorld(entity: Entity): ParcelWorld? = getWorld(entity.location)
fun getParcelAt(worldName: String, x: Int, z: Int): Parcel? = getWorld(worldName)?.locator?.getParcelAt(x, z) fun getParcelAt(worldName: String, x: Int, z: Int): Parcel? = getWorld(worldName)?.locator?.getParcelAt(x, z)
fun getParcelAt(world: World, x: Int, z: Int): Parcel? = getParcelAt(world.name, x, z) fun getParcelAt(world: World, x: Int, z: Int): Parcel? = getParcelAt(world.name, x, z)
fun getParcelAt(world: World, vec: Vec2i): Parcel? = getParcelAt(world, vec.x, vec.z) fun getParcelAt(world: World, vec: Vec2i): Parcel? = getParcelAt(world, vec.x, vec.z)
fun getParcelAt(loc: Location): Parcel? = getParcelAt(loc.world, loc.x.floor(), loc.z.floor()) fun getParcelAt(loc: Location): Parcel? = getParcelAt(loc.world, loc.x.floor(), loc.z.floor())
fun getParcelAt(entity: Entity): Parcel? = getParcelAt(entity.location) fun getParcelAt(entity: Entity): Parcel? = getParcelAt(entity.location)
fun getParcelAt(block: Block): Parcel? = getParcelAt(block.world, block.x, block.z) fun getParcelAt(block: Block): Parcel? = getParcelAt(block.world, block.x, block.z)
fun getWorldGenerator(worldName: String): ParcelGenerator? fun getWorldGenerator(worldName: String): ParcelGenerator?
fun loadWorlds() fun loadWorlds()
fun acquireBlockVisitorPermit(parcelId: ParcelId, with: Permit): Boolean fun acquireBlockVisitorPermit(parcelId: ParcelId, with: Permit): Boolean
@Throws(IllegalStateException::class) @Throws(IllegalStateException::class)
fun releaseBlockVisitorPermit(parcelId: ParcelId, with: Permit) fun releaseBlockVisitorPermit(parcelId: ParcelId, with: Permit)
fun trySubmitBlockVisitor(permit: Permit, parcelIds: Array<out ParcelId>, function: JobFunction): Job? fun trySubmitBlockVisitor(permit: Permit, parcelIds: Array<out ParcelId>, function: JobFunction): Job?
fun swapParcels(parcelId1: ParcelId, parcelId2: ParcelId): Job? fun swapParcels(parcelId1: ParcelId, parcelId2: ParcelId): Job?
} }
interface ParcelLocator { interface ParcelLocator {
val world: World val world: World
fun getParcelIdAt(x: Int, z: Int): ParcelId? fun getParcelIdAt(x: Int, z: Int): ParcelId?
fun getParcelAt(x: Int, z: Int): Parcel? fun getParcelAt(x: Int, z: Int): Parcel?
fun getParcelAt(vec: Vec2i): Parcel? = getParcelAt(vec.x, vec.z) fun getParcelAt(vec: Vec2i): Parcel? = getParcelAt(vec.x, vec.z)
fun getParcelAt(loc: Location): Parcel? = getParcelAt(loc.x.floor(), loc.z.floor()).takeIf { loc.world == world } fun getParcelAt(loc: Location): Parcel? = getParcelAt(loc.x.floor(), loc.z.floor()).takeIf { loc.world == world }
fun getParcelAt(entity: Entity): Parcel? = getParcelAt(entity.location).takeIf { entity.world == world } fun getParcelAt(entity: Entity): Parcel? = getParcelAt(entity.location).takeIf { entity.world == world }
fun getParcelAt(block: Block): Parcel? = getParcelAt(block.x, block.z).takeIf { block.world == world } fun getParcelAt(block: Block): Parcel? = getParcelAt(block.x, block.z).takeIf { block.world == world }
} }
typealias ParcelContainerFactory = (ParcelWorld) -> ParcelContainer typealias ParcelContainerFactory = (ParcelWorld) -> ParcelContainer
interface ParcelContainer { interface ParcelContainer {
fun getParcelById(x: Int, z: Int): Parcel? fun getParcelById(x: Int, z: Int): Parcel?
fun getParcelById(id: Vec2i): Parcel? = getParcelById(id.x, id.z) fun getParcelById(id: Vec2i): Parcel? = getParcelById(id.x, id.z)
fun getParcelById(id: ParcelId): Parcel? fun getParcelById(id: ParcelId): Parcel?
fun nextEmptyParcel(): Parcel? suspend fun nextEmptyParcel(): Parcel?
} }
interface ParcelWorld : ParcelLocator, ParcelContainer { interface ParcelWorld : ParcelLocator, ParcelContainer {
val id: ParcelWorldId val id: ParcelWorldId
val name: String val name: String
val uid: UUID? val uid: UUID?
val options: RuntimeWorldOptions val options: RuntimeWorldOptions
val generator: ParcelGenerator val generator: ParcelGenerator
val storage: Storage val storage: Storage
val container: ParcelContainer val container: ParcelContainer
val locator: ParcelLocator val locator: ParcelLocator
val blockManager: ParcelBlockManager val blockManager: ParcelBlockManager
val globalPrivileges: GlobalPrivilegesManager val globalPrivileges: GlobalPrivilegesManager
val creationTime: DateTime? val creationTime: DateTime?
} }

View File

@@ -1,153 +1,154 @@
package io.dico.parcels2 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.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
import io.dico.parcels2.listener.ParcelEntityTracker import io.dico.parcels2.listener.ParcelEntityTracker
import io.dico.parcels2.listener.ParcelListeners import io.dico.parcels2.listener.ParcelListeners
import io.dico.parcels2.listener.WorldEditListener import io.dico.parcels2.listener.WorldEditListener
import io.dico.parcels2.options.Options import io.dico.parcels2.options.Options
import io.dico.parcels2.options.optionsMapper import io.dico.parcels2.options.optionsMapper
import io.dico.parcels2.storage.Storage import io.dico.parcels2.storage.Storage
import io.dico.parcels2.util.MainThreadDispatcher import io.dico.parcels2.util.MainThreadDispatcher
import io.dico.parcels2.util.PluginScheduler import io.dico.parcels2.util.PluginAware
import io.dico.parcels2.util.ext.tryCreate import io.dico.parcels2.util.ext.tryCreate
import io.dico.parcels2.util.isServerThread import io.dico.parcels2.util.isServerThread
import kotlinx.coroutines.CoroutineScope import io.dico.parcels2.util.scheduleRepeating
import org.bukkit.Bukkit import kotlinx.coroutines.CoroutineScope
import org.bukkit.generator.ChunkGenerator import org.bukkit.Bukkit
import org.bukkit.plugin.Plugin import org.bukkit.generator.ChunkGenerator
import org.bukkit.plugin.java.JavaPlugin import org.bukkit.plugin.Plugin
import org.slf4j.Logger import org.bukkit.plugin.java.JavaPlugin
import org.slf4j.LoggerFactory import org.slf4j.Logger
import java.io.File import org.slf4j.LoggerFactory
import kotlin.coroutines.CoroutineContext import java.io.File
import kotlin.coroutines.CoroutineContext
val logger: Logger = LoggerFactory.getLogger("ParcelsPlugin")
private inline val plogger get() = logger val logger: Logger = LoggerFactory.getLogger("ParcelsPlugin")
private inline val plogger get() = logger
class ParcelsPlugin : JavaPlugin(), CoroutineScope, PluginScheduler {
lateinit var optionsFile: File; private set class ParcelsPlugin : JavaPlugin(), CoroutineScope, PluginAware {
lateinit var options: Options; private set lateinit var optionsFile: File; private set
lateinit var parcelProvider: ParcelProvider; private set lateinit var options: Options; private set
lateinit var storage: Storage; private set lateinit var parcelProvider: ParcelProvider; private set
lateinit var globalPrivileges: GlobalPrivilegesManager; private set lateinit var storage: Storage; private set
lateinit var globalPrivileges: GlobalPrivilegesManager; private set
val registrator = Registrator(this)
lateinit var entityTracker: ParcelEntityTracker; private set val registrator = Registrator(this)
private var listeners: ParcelListeners? = null lateinit var entityTracker: ParcelEntityTracker; private set
private var cmdDispatcher: ICommandDispatcher? = null private var listeners: ParcelListeners? = null
private var cmdDispatcher: ICommandDispatcher? = null
override val coroutineContext: CoroutineContext = MainThreadDispatcher(this)
override val plugin: Plugin get() = this override val coroutineContext: CoroutineContext = MainThreadDispatcher(this)
val jobDispatcher: JobDispatcher by lazy { BukkitJobDispatcher(this, options.tickJobtime) } override val plugin: Plugin get() = this
val jobDispatcher: JobDispatcher by lazy { BukkitJobDispatcher(this, this, options.tickJobtime) }
override fun onEnable() {
plogger.info("Is server thread: ${isServerThread()}") override fun onEnable() {
plogger.info("Debug enabled: ${plogger.isDebugEnabled}") plogger.info("Is server thread: ${isServerThread()}")
plogger.debug(System.getProperty("user.dir")) plogger.info("Debug enabled: ${plogger.isDebugEnabled}")
if (!init()) { plogger.debug(System.getProperty("user.dir"))
Bukkit.getPluginManager().disablePlugin(this) if (!init()) {
} Bukkit.getPluginManager().disablePlugin(this)
} }
}
override fun onDisable() {
val hasWorkers = jobDispatcher.jobs.isNotEmpty() override fun onDisable() {
if (hasWorkers) { val hasWorkers = jobDispatcher.jobs.isNotEmpty()
plogger.warn("Parcels is attempting to complete all ${jobDispatcher.jobs.size} remaining jobs before shutdown...") if (hasWorkers) {
} plogger.warn("Parcels is attempting to complete all ${jobDispatcher.jobs.size} remaining jobs before shutdown...")
jobDispatcher.completeAllTasks() }
if (hasWorkers) { jobDispatcher.completeAllTasks()
plogger.info("Parcels has completed the remaining jobs.") if (hasWorkers) {
} plogger.info("Parcels has completed the remaining jobs.")
}
cmdDispatcher?.unregisterFromCommandMap()
} cmdDispatcher?.unregisterFromCommandMap()
}
private fun init(): Boolean {
optionsFile = File(dataFolder, "options.yml") private fun init(): Boolean {
options = Options() optionsFile = File(dataFolder, "options.yml")
parcelProvider = ParcelProviderImpl(this) options = Options()
parcelProvider = ParcelProviderImpl(this)
try {
if (!loadOptions()) return false try {
if (!loadOptions()) return false
try {
storage = options.storage.newInstance() try {
storage.init() storage = options.storage.newInstance()
} catch (ex: Exception) { storage.init()
plogger.error("Failed to connect to database", ex) } catch (ex: Exception) {
return false plogger.error("Failed to connect to database", ex)
} return false
}
globalPrivileges = GlobalPrivilegesManagerImpl(this)
entityTracker = ParcelEntityTracker(parcelProvider) globalPrivileges = GlobalPrivilegesManagerImpl(this)
} catch (ex: Exception) { entityTracker = ParcelEntityTracker(parcelProvider)
plogger.error("Error loading options", ex) } catch (ex: Exception) {
return false plogger.error("Error loading options", ex)
} return false
}
registerListeners()
registerCommands() registerListeners()
registerCommands()
parcelProvider.loadWorlds()
return true parcelProvider.loadWorlds()
} return true
}
fun loadOptions(): Boolean {
when { fun loadOptions(): Boolean {
optionsFile.exists() -> optionsMapper.readerForUpdating(options).readValue<Options>(optionsFile) when {
else -> run { optionsFile.exists() -> optionsMapper.readerForUpdating(options).readValue<Options>(optionsFile)
options.addWorld("parcels") else -> run {
if (saveOptions()) { options.addWorld("parcels")
plogger.warn("Created options file with a world template. Please review it before next start.") if (saveOptions()) {
} else { plogger.warn("Created options file with a world template. Please review it before next start.")
plogger.error("Failed to save options file ${optionsFile.canonicalPath}") } else {
} plogger.error("Failed to save options file ${optionsFile.canonicalPath}")
return false }
} return false
} }
return true }
} return true
}
fun saveOptions(): Boolean {
if (optionsFile.tryCreate()) { fun saveOptions(): Boolean {
try { if (optionsFile.tryCreate()) {
optionsMapper.writeValue(optionsFile, options) try {
} catch (ex: Throwable) { optionsMapper.writeValue(optionsFile, options)
optionsFile.delete() } catch (ex: Throwable) {
throw ex optionsFile.delete()
} throw ex
return true }
} return true
return false }
} return false
}
override fun getDefaultWorldGenerator(worldName: String, generatorId: String?): ChunkGenerator? {
return parcelProvider.getWorldGenerator(worldName) override fun getDefaultWorldGenerator(worldName: String, generatorId: String?): ChunkGenerator? {
} return parcelProvider.getWorldGenerator(worldName)
}
private fun registerCommands() {
cmdDispatcher = getParcelCommands(this).apply { private fun registerCommands() {
registerToCommandMap("parcels:", EOverridePolicy.FALLBACK_ONLY) cmdDispatcher = getParcelCommands(this).apply {
} registerToCommandMap("parcels:", EOverridePolicy.FALLBACK_ONLY)
} }
}
private fun registerListeners() {
if (listeners == null) { private fun registerListeners() {
listeners = ParcelListeners(parcelProvider, entityTracker, storage) if (listeners == null) {
registrator.registerListeners(listeners!!) listeners = ParcelListeners(parcelProvider, entityTracker, storage)
registrator.registerListeners(listeners!!)
val worldEditPlugin = server.pluginManager.getPlugin("WorldEdit")
if (worldEditPlugin != null) { val worldEditPlugin = server.pluginManager.getPlugin("WorldEdit")
WorldEditListener.register(this, worldEditPlugin) if (worldEditPlugin != null) {
} WorldEditListener.register(this, worldEditPlugin)
} }
}
scheduleRepeating(100, 5, entityTracker::tick)
} scheduleRepeating(5, delay = 100, task = entityTracker::tick)
}
} }

View File

@@ -1,184 +1,208 @@
@file:Suppress("unused", "UsePropertyAccessSyntax", "DEPRECATION") @file:Suppress("unused", "UsePropertyAccessSyntax", "DEPRECATION")
package io.dico.parcels2 package io.dico.parcels2
import io.dico.parcels2.storage.Storage import io.dico.parcels2.storage.Storage
import io.dico.parcels2.util.ext.PLAYER_NAME_PLACEHOLDER import io.dico.parcels2.util.checkPlayerNameValid
import io.dico.parcels2.util.ext.isValid import io.dico.parcels2.util.ext.PLAYER_NAME_PLACEHOLDER
import io.dico.parcels2.util.ext.uuid import io.dico.parcels2.util.ext.isValid
import io.dico.parcels2.util.getOfflinePlayer import io.dico.parcels2.util.ext.uuid
import io.dico.parcels2.util.getPlayerName import io.dico.parcels2.util.getOfflinePlayer
import org.bukkit.Bukkit import io.dico.parcels2.util.getPlayerName
import org.bukkit.OfflinePlayer import io.dico.parcels2.util.isPlayerNameValid
import java.util.UUID import org.bukkit.Bukkit
import org.bukkit.OfflinePlayer
interface PlayerProfile { import java.util.UUID
val uuid: UUID? get() = null
val name: String? interface PlayerProfile {
val nameOrBukkitName: String? val uuid: UUID? get() = null
val notNullName: String val name: String?
val isStar: Boolean get() = this is Star val nameOrBukkitName: String?
val exists: Boolean get() = this is RealImpl val notNullName: String
val isStar: Boolean get() = this is Star
fun matches(player: OfflinePlayer, allowNameMatch: Boolean = false): Boolean val exists: Boolean get() = this is RealImpl
fun equals(other: PlayerProfile): Boolean fun matches(player: OfflinePlayer, allowNameMatch: Boolean = false): Boolean
override fun equals(other: Any?): Boolean fun equals(other: PlayerProfile): Boolean
override fun hashCode(): Int
override fun equals(other: Any?): Boolean
val isFake: Boolean get() = this is Fake override fun hashCode(): Int
val isReal: Boolean get() = this is Real
val isFake: Boolean get() = this is Fake
companion object { val isReal: Boolean get() = this is Real
fun safe(uuid: UUID?, name: String?): PlayerProfile? {
if (uuid != null) return Real(uuid, name) companion object {
if (name != null) return invoke(name) fun safe(uuid: UUID?, name: String?): PlayerProfile? {
return null if (uuid != null) return Real(uuid, if (name != null && !isPlayerNameValid(name)) null else name)
} if (name != null) return invoke(name)
return null
operator fun invoke(uuid: UUID?, name: String?): PlayerProfile { }
return safe(uuid, name) ?: throw IllegalArgumentException("One of uuid and name must not be null")
} operator fun invoke(uuid: UUID?, name: String?): PlayerProfile {
return safe(uuid, name) ?: throw IllegalArgumentException("One of uuid and name must not be null")
operator fun invoke(uuid: UUID): Real { }
if (uuid == Star.uuid) return Star
return RealImpl(uuid, null) operator fun invoke(uuid: UUID): Real {
} if (uuid == Star.uuid) return Star
return RealImpl(uuid, null)
operator fun invoke(name: String): PlayerProfile { }
if (name == Star.name) return Star
return Fake(name) operator fun invoke(name: String): PlayerProfile {
} if (name equalsIgnoreCase Star.name) return Star
return Fake(name)
operator fun invoke(player: OfflinePlayer): PlayerProfile { }
return if (player.isValid) Real(player.uuid, player.name) else Fake(player.name)
} operator fun invoke(player: OfflinePlayer): PlayerProfile {
return if (player.isValid) Real(player.uuid, player.name) else Fake(player.name)
fun nameless(player: OfflinePlayer): Real { }
if (!player.isValid) throw IllegalArgumentException("The given OfflinePlayer is not valid")
return RealImpl(player.uuid, null) fun nameless(player: OfflinePlayer): Real {
} if (!player.isValid) throw IllegalArgumentException("The given OfflinePlayer is not valid")
return RealImpl(player.uuid, null)
fun byName(input: String, allowReal: Boolean = true, allowFake: Boolean = false): PlayerProfile { }
if (!allowReal) {
if (!allowFake) throw IllegalArgumentException("at least one of allowReal and allowFake must be true") fun byName(input: String, allowReal: Boolean = true, allowFake: Boolean = false): PlayerProfile? {
return Fake(input) if (!allowReal) {
} if (!allowFake) throw IllegalArgumentException("at least one of allowReal and allowFake must be true")
return Fake(input)
if (input == Star.name) return Star }
return getOfflinePlayer(input)?.let { PlayerProfile(it) } ?: Unresolved(input) if (!isPlayerNameValid(input)) {
} if (!allowFake) return null
} return Fake(input)
}
interface Real : PlayerProfile {
override val uuid: UUID if (input == Star.name) return Star
override val nameOrBukkitName: String?
// If a player is online, their name is prioritized to get name changes right immediately return getOfflinePlayer(input)?.let { PlayerProfile(it) } ?: Unresolved(input)
get() = Bukkit.getPlayer(uuid)?.name ?: name ?: getPlayerName(uuid) }
override val notNullName: String }
get() = nameOrBukkitName ?: PLAYER_NAME_PLACEHOLDER
interface Real : PlayerProfile {
val player: OfflinePlayer? get() = getOfflinePlayer(uuid) override val uuid: UUID
override val nameOrBukkitName: String?
override fun matches(player: OfflinePlayer, allowNameMatch: Boolean): Boolean { // If a player is online, their name is prioritized to get name changes right immediately
return uuid == player.uuid || (allowNameMatch && name?.let { it == player.name } == true) get() = Bukkit.getPlayer(uuid)?.name ?: name ?: getPlayerName(uuid)
} override val notNullName: String
get() = nameOrBukkitName ?: PLAYER_NAME_PLACEHOLDER
override fun equals(other: PlayerProfile): Boolean {
return other is Real && uuid == other.uuid val player: OfflinePlayer? get() = getOfflinePlayer(uuid)
}
override fun matches(player: OfflinePlayer, allowNameMatch: Boolean): Boolean {
companion object { return uuid == player.uuid || (allowNameMatch && name?.let { it == player.name } == true)
fun byName(name: String): PlayerProfile { }
if (name == Star.name) return Star
return Unresolved(name) override fun equals(other: PlayerProfile): Boolean {
} return other is Real && uuid == other.uuid
}
operator fun invoke(uuid: UUID, name: String?): Real {
if (name == Star.name || uuid == Star.uuid) return Star companion object {
return RealImpl(uuid, name) fun byName(name: String): PlayerProfile {
} if (name equalsIgnoreCase Star.name) return Star
return Unresolved(name)
fun safe(uuid: UUID?, name: String?): Real? { }
if (name == Star.name || uuid == Star.uuid) return Star
if (uuid == null) return null operator fun invoke(uuid: UUID, name: String?): Real {
return RealImpl(uuid, name) if (name equalsIgnoreCase Star.name || uuid == Star.uuid) return Star
} return RealImpl(uuid, name)
}
}
} fun safe(uuid: UUID?, name: String?): Real? {
if (name equalsIgnoreCase Star.name || uuid == Star.uuid) return Star
object Star : BaseImpl(), Real { if (uuid == null) return null
override val name get() = "*" return RealImpl(uuid, if (name != null && !isPlayerNameValid(name)) null else name)
override val nameOrBukkitName get() = name }
override val notNullName get() = name
}
// hopefully nobody will have this random UUID :) }
override val uuid: UUID = UUID.fromString("7d09c4c6-117d-4f36-9778-c4d24618cee1")
object Star : BaseImpl(), Real {
override fun matches(player: OfflinePlayer, allowNameMatch: Boolean): Boolean { override val name get() = "*"
return true override val nameOrBukkitName get() = name
} override val notNullName get() = name
override fun toString() = "Star" // hopefully nobody will have this random UUID :)
} override val uuid: UUID = UUID.fromString("7d09c4c6-117d-4f36-9778-c4d24618cee1")
abstract class NameOnly(override val name: String) : BaseImpl() { override fun matches(player: OfflinePlayer, allowNameMatch: Boolean): Boolean {
override val notNullName get() = name return true
override val nameOrBukkitName: String get() = name }
override fun matches(player: OfflinePlayer, allowNameMatch: Boolean): Boolean { override fun toString() = "Star"
return allowNameMatch && player.name == name }
}
abstract class NameOnly(override val name: String) : BaseImpl() {
override fun toString() = "${javaClass.simpleName}($name)" override val notNullName get() = name
} override val nameOrBukkitName: String get() = name
class Fake(name: String) : NameOnly(name) { override fun matches(player: OfflinePlayer, allowNameMatch: Boolean): Boolean {
override fun equals(other: PlayerProfile): Boolean { return allowNameMatch && player.name equalsIgnoreCase name
return other is Fake && other.name == name }
}
} override fun toString() = "${javaClass.simpleName}($name)"
}
class Unresolved(name: String) : NameOnly(name) {
override fun equals(other: PlayerProfile): Boolean { class Fake(name: String) : NameOnly(name) {
return other is Unresolved && name == other.name override fun equals(other: PlayerProfile): Boolean {
} return other is Fake && other.name equalsIgnoreCase name
}
suspend fun tryResolveSuspendedly(storage: Storage): Real? { }
return storage.getPlayerUuidForName(name).await()?.let { resolve(it) }
} class Unresolved(name: String) : NameOnly(name) {
init {
fun resolve(uuid: UUID): Real { checkPlayerNameValid(name)
return RealImpl(uuid, name) }
}
override fun equals(other: PlayerProfile): Boolean {
fun throwException(): Nothing { return other is Unresolved && name equalsIgnoreCase other.name
throw IllegalArgumentException("A UUID for the player $name can not be found") }
}
} suspend fun tryResolveSuspendedly(storage: Storage): Real? {
return storage.getPlayerUuidForName(name).await()?.let { resolve(it) }
abstract class BaseImpl : PlayerProfile { }
override fun equals(other: Any?): Boolean {
return this === other || (other is PlayerProfile && equals(other)) fun resolve(uuid: UUID): Real {
} return RealImpl(uuid, name)
}
override fun hashCode(): Int {
return uuid?.hashCode() ?: name!!.hashCode() fun throwException(): Nothing {
} throw IllegalArgumentException("A UUID for the player $name can not be found")
} }
}
private class RealImpl(override val uuid: UUID, override val name: String?) : BaseImpl(), Real {
override fun toString() = "Real($notNullName)" abstract class BaseImpl : PlayerProfile {
} override fun equals(other: Any?): Boolean {
return this === other || (other is PlayerProfile && equals(other))
} }
suspend fun PlayerProfile.resolved(storage: Storage, resolveToFake: Boolean = false): PlayerProfile? = override fun hashCode(): Int {
when (this) { return uuid?.hashCode() ?: name!!.hashCode()
is PlayerProfile.Unresolved -> tryResolveSuspendedly(storage) }
?: if (resolveToFake) PlayerProfile.Fake(name) else null }
else -> this
private class RealImpl(override val uuid: UUID, override val name: String?) : BaseImpl(), Real {
init {
name?.let { checkPlayerNameValid(it) }
}
override fun toString() = "Real($notNullName)"
}
}
private infix fun String?.equalsIgnoreCase(other: String): Boolean {
if (this == null) return false
if (length != other.length) return false
repeat(length) { i ->
if (this[i].toLowerCase() != other[i].toLowerCase()) return false
}
return true
}
suspend fun PlayerProfile.resolved(storage: Storage, resolveToFake: Boolean = false): PlayerProfile? =
when (this) {
is PlayerProfile.Unresolved -> tryResolveSuspendedly(storage)
?: if (resolveToFake) PlayerProfile.Fake(name) else null
else -> this
} }

View File

@@ -0,0 +1,38 @@
package io.dico.parcels2.blockvisitor
import io.dico.parcels2.util.math.Vec3d
import org.bukkit.Location
import org.bukkit.World
import org.bukkit.entity.Entity
import org.bukkit.entity.Minecart
/*
open class EntityCopy<T : Entity>(entity: T) {
val type = entity.type
@Suppress("UNCHECKED_CAST")
fun spawn(world: World, position: Vec3d): T {
val entity = world.spawnEntity(Location(null, position.x, position.y, position.z), type) as T
setAttributes(entity)
return entity
}
open fun setAttributes(entity: T) {}
}
open class MinecartCopy<T : Minecart>(entity: T) : EntityCopy<T>(entity) {
val damage = entity.damage
val maxSpeed = entity.maxSpeed
val isSlowWhenEmpty = entity.isSlowWhenEmpty
val flyingVelocityMod = entity.flyingVelocityMod
val derailedVelocityMod = entity.derailedVelocityMod
val displayBlockData = entity.displayBlockData
val displayBlockOffset = entity.displayBlockOffset
override fun setAttributes(entity: T) {
super.setAttributes(entity)
entity.damage = damage
entity.displayBlockData = displayBlockData
entity.displayBlockOffset = displayBlockOffset
}
}*/

View File

@@ -1,83 +1,90 @@
package io.dico.parcels2.command package io.dico.parcels2.command
import io.dico.dicore.command.CommandException import io.dico.dicore.command.CommandException
import io.dico.dicore.command.parameter.ArgumentBuffer import io.dico.dicore.command.parameter.ArgumentBuffer
import io.dico.dicore.command.parameter.Parameter import io.dico.dicore.command.parameter.Parameter
import io.dico.dicore.command.parameter.type.ParameterConfig import io.dico.dicore.command.parameter.type.ParameterConfig
import io.dico.dicore.command.parameter.type.ParameterType import io.dico.dicore.command.parameter.type.ParameterType
import io.dico.parcels2.* import io.dico.parcels2.*
import io.dico.parcels2.command.ProfileKind.Companion.ANY import io.dico.parcels2.command.ProfileKind.Companion.FAKE
import io.dico.parcels2.command.ProfileKind.Companion.FAKE import io.dico.parcels2.command.ProfileKind.Companion.REAL
import io.dico.parcels2.command.ProfileKind.Companion.REAL import org.bukkit.Location
import org.bukkit.Location import org.bukkit.command.CommandSender
import org.bukkit.command.CommandSender import org.bukkit.entity.Player
import org.bukkit.entity.Player
fun invalidInput(parameter: Parameter<*, *>, message: String): Nothing {
fun invalidInput(parameter: Parameter<*, *>, message: String): Nothing { throw CommandException("invalid input for ${parameter.name}: $message")
throw CommandException("invalid input for ${parameter.name}: $message") }
}
fun ParcelProvider.getTargetWorld(input: String?, sender: CommandSender, parameter: Parameter<*, *>): ParcelWorld {
fun ParcelProvider.getTargetWorld(input: String?, sender: CommandSender, parameter: Parameter<*, *>): ParcelWorld { val worldName = input
val worldName = input ?.takeUnless { it.isEmpty() }
?.takeUnless { it.isEmpty() } ?: (sender as? Player)?.world?.name
?: (sender as? Player)?.world?.name ?: invalidInput(parameter, "console cannot omit the world name")
?: invalidInput(parameter, "console cannot omit the world name")
return getWorld(worldName)
return getWorld(worldName) ?: invalidInput(parameter, "$worldName is not a parcel world")
?: invalidInput(parameter, "$worldName is not a parcel world") }
}
class ParcelParameterType(val parcelProvider: ParcelProvider) : ParameterType<Parcel, Void>(Parcel::class.java) {
class ParcelParameterType(val parcelProvider: ParcelProvider) : ParameterType<Parcel, Void>(Parcel::class.java) { val regex = Regex.fromLiteral("((.+)->)?([0-9]+):([0-9]+)")
val regex = Regex.fromLiteral("((.+)->)?([0-9]+):([0-9]+)")
override fun parse(parameter: Parameter<Parcel, Void>, sender: CommandSender, buffer: ArgumentBuffer): Parcel {
override fun parse(parameter: Parameter<Parcel, Void>, sender: CommandSender, buffer: ArgumentBuffer): Parcel { val matchResult = regex.matchEntire(buffer.next()!!)
val matchResult = regex.matchEntire(buffer.next()!!) ?: invalidInput(parameter, "must match (w->)?a:b (/${regex.pattern}/)")
?: invalidInput(parameter, "must match (w->)?a:b (/${regex.pattern}/)")
val world = parcelProvider.getTargetWorld(matchResult.groupValues[2], sender, parameter)
val world = parcelProvider.getTargetWorld(matchResult.groupValues[2], sender, parameter)
val x = matchResult.groupValues[3].toIntOrNull()
val x = matchResult.groupValues[3].toIntOrNull() ?: invalidInput(parameter, "couldn't parse int")
?: invalidInput(parameter, "couldn't parse int")
val z = matchResult.groupValues[4].toIntOrNull()
val z = matchResult.groupValues[4].toIntOrNull() ?: invalidInput(parameter, "couldn't parse int")
?: invalidInput(parameter, "couldn't parse int")
return world.getParcelById(x, z)
return world.getParcelById(x, z) ?: invalidInput(parameter, "parcel id is out of range")
?: invalidInput(parameter, "parcel id is out of range") }
}
}
}
annotation class ProfileKind(val kind: Int) {
annotation class ProfileKind(val kind: Int) { companion object : ParameterConfig<ProfileKind, Int>(ProfileKind::class.java) {
companion object : ParameterConfig<ProfileKind, Int>(ProfileKind::class.java) { const val REAL = 1
const val REAL = 1 const val FAKE = 2
const val FAKE = 2 const val ANY = REAL or FAKE
const val ANY = REAL or FAKE const val ALLOW_INVALID = 4
override fun toParameterInfo(annotation: ProfileKind): Int { override fun toParameterInfo(annotation: ProfileKind): Int {
return annotation.kind return annotation.kind
} }
} }
} }
class ProfileParameterType : ParameterType<PlayerProfile, Int>(PlayerProfile::class.java, ProfileKind) { class ProfileParameterType : ParameterType<PlayerProfile, Int>(PlayerProfile::class.java, ProfileKind) {
override fun parse(parameter: Parameter<PlayerProfile, Int>, sender: CommandSender, buffer: ArgumentBuffer): PlayerProfile { override fun parse(parameter: Parameter<PlayerProfile, Int>, sender: CommandSender, buffer: ArgumentBuffer): PlayerProfile? {
val info = parameter.paramInfo ?: REAL val info = parameter.paramInfo ?: REAL
val allowReal = (info and REAL) != 0 val allowReal = (info and REAL) != 0
val allowFake = (info and FAKE) != 0 val allowFake = (info and FAKE) != 0
val input = buffer.next()!! val input = buffer.next()!!
return PlayerProfile.byName(input, allowReal, allowFake)
} val profile = PlayerProfile.byName(input, allowReal, allowFake)
override fun complete( if (profile == null && (info and ProfileKind.ALLOW_INVALID) == 0) {
parameter: Parameter<PlayerProfile, Int>, invalidInput(parameter, "\'$input\' is not a valid player name")
sender: CommandSender, }
location: Location?,
buffer: ArgumentBuffer return profile
): MutableList<String> { }
logger.info("Completing PlayerProfile: ${buffer.next()}")
return super.complete(parameter, sender, location, buffer) override fun complete(
} parameter: Parameter<PlayerProfile, Int>,
} sender: CommandSender,
location: Location?,
buffer: ArgumentBuffer
): MutableList<String> {
logger.info("Completing PlayerProfile: ${buffer.next()}")
return super.complete(parameter, sender, location, buffer)
}
}

View File

@@ -1,191 +1,215 @@
package io.dico.parcels2.command package io.dico.parcels2.command
import io.dico.dicore.command.parameter.ArgumentBuffer import io.dico.dicore.command.parameter.ArgumentBuffer
import io.dico.dicore.command.parameter.Parameter import io.dico.dicore.command.parameter.Parameter
import io.dico.dicore.command.parameter.type.ParameterConfig import io.dico.dicore.command.parameter.type.ParameterConfig
import io.dico.dicore.command.parameter.type.ParameterType import io.dico.dicore.command.parameter.type.ParameterType
import io.dico.parcels2.Parcel import io.dico.parcels2.Parcel
import io.dico.parcels2.ParcelProvider import io.dico.parcels2.ParcelProvider
import io.dico.parcels2.ParcelWorld import io.dico.parcels2.ParcelWorld
import io.dico.parcels2.PlayerProfile import io.dico.parcels2.PlayerProfile
import io.dico.parcels2.command.ParcelTarget.TargetKind.Companion.DEFAULT_KIND import io.dico.parcels2.command.ParcelTarget.TargetKind.Companion.DEFAULT_KIND
import io.dico.parcels2.command.ParcelTarget.TargetKind.Companion.ID import io.dico.parcels2.command.ParcelTarget.TargetKind.Companion.ID
import io.dico.parcels2.command.ParcelTarget.TargetKind.Companion.OWNER import io.dico.parcels2.command.ParcelTarget.TargetKind.Companion.OWNER
import io.dico.parcels2.command.ParcelTarget.TargetKind.Companion.OWNER_FAKE import io.dico.parcels2.command.ParcelTarget.TargetKind.Companion.OWNER_FAKE
import io.dico.parcels2.command.ParcelTarget.TargetKind.Companion.OWNER_REAL import io.dico.parcels2.command.ParcelTarget.TargetKind.Companion.OWNER_REAL
import io.dico.parcels2.command.ParcelTarget.TargetKind.Companion.PREFER_OWNED_FOR_DEFAULT import io.dico.parcels2.command.ParcelTarget.TargetKind.Companion.PREFER_OWNED_FOR_DEFAULT
import io.dico.parcels2.command.ParcelTarget.TargetKind.Companion.REAL import io.dico.parcels2.command.ParcelTarget.TargetKind.Companion.REAL
import io.dico.parcels2.storage.Storage import io.dico.parcels2.storage.Storage
import io.dico.parcels2.util.math.Vec2i import io.dico.parcels2.util.math.Vec2i
import io.dico.parcels2.util.math.floor import io.dico.parcels2.util.math.floor
import org.bukkit.command.CommandSender import org.bukkit.command.CommandSender
import org.bukkit.entity.Player import org.bukkit.entity.Player
sealed class ParcelTarget(val world: ParcelWorld, val parsedKind: Int, val isDefault: Boolean) { sealed class ParcelTarget(val world: ParcelWorld, val parsedKind: Int, val isDefault: Boolean) {
abstract suspend fun getParcelSuspend(storage: Storage): Parcel? abstract suspend fun getParcelSuspend(storage: Storage): Parcel?
class ByID(world: ParcelWorld, val id: Vec2i?, parsedKind: Int, isDefault: Boolean) : ParcelTarget(world, parsedKind, isDefault) { class ByID(world: ParcelWorld, val id: Vec2i?, parsedKind: Int, isDefault: Boolean) :
override suspend fun getParcelSuspend(storage: Storage): Parcel? = getParcel() ParcelTarget(world, parsedKind, isDefault) {
fun getParcel() = id?.let { world.getParcelById(it) } override suspend fun getParcelSuspend(storage: Storage): Parcel? = getParcel()
val isPath: Boolean get() = id == null fun getParcel() = id?.let { world.getParcelById(it) }
} val isPath: Boolean get() = id == null
}
class ByOwner(
world: ParcelWorld, class ByOwner(
owner: PlayerProfile, world: ParcelWorld,
val index: Int, owner: PlayerProfile,
parsedKind: Int, val index: Int,
isDefault: Boolean, parsedKind: Int,
val onResolveFailure: (() -> Unit)? = null isDefault: Boolean,
) : ParcelTarget(world, parsedKind, isDefault) { val onResolveFailure: (() -> Unit)? = null
init { ) : ParcelTarget(world, parsedKind, isDefault) {
if (index < 0) throw IllegalArgumentException("Invalid parcel home index: $index") init {
} if (index < 0) throw IllegalArgumentException("Invalid parcel home index: $index")
}
var owner = owner; private set
var owner = owner; private set
suspend fun resolveOwner(storage: Storage): Boolean {
val owner = owner suspend fun resolveOwner(storage: Storage): Boolean {
if (owner is PlayerProfile.Unresolved) { val owner = owner
this.owner = owner.tryResolveSuspendedly(storage) ?: if (parsedKind and OWNER_FAKE != 0) PlayerProfile.Fake(owner.name) if (owner is PlayerProfile.Unresolved) {
else run { onResolveFailure?.invoke(); return false } this.owner = owner.tryResolveSuspendedly(storage) ?: if (parsedKind and OWNER_FAKE != 0) PlayerProfile.Fake(owner.name)
} else run { onResolveFailure?.invoke(); return false }
return true }
} return true
}
override suspend fun getParcelSuspend(storage: Storage): Parcel? {
onResolveFailure?.let { resolveOwner(storage) } override suspend fun getParcelSuspend(storage: Storage): Parcel? {
onResolveFailure?.let { resolveOwner(storage) }
val ownedParcelsSerialized = storage.getOwnedParcels(owner).await()
val ownedParcels = ownedParcelsSerialized val ownedParcelsSerialized = storage.getOwnedParcels(owner).await()
.filter { it.worldId.equals(world.id) } val ownedParcels = ownedParcelsSerialized
.map { world.getParcelById(it.x, it.z) } .filter { it.worldId.equals(world.id) }
.map { world.getParcelById(it.x, it.z) }
return ownedParcels.getOrNull(index)
} return ownedParcels.getOrNull(index)
} }
}
annotation class TargetKind(val kind: Int) {
companion object : ParameterConfig<TargetKind, Int>(TargetKind::class.java) { annotation class TargetKind(val kind: Int) {
const val ID = 1 // ID companion object : ParameterConfig<TargetKind, Int>(TargetKind::class.java) {
const val OWNER_REAL = 2 // an owner backed by a UUID const val ID = 1 // ID
const val OWNER_FAKE = 4 // an owner not backed by a UUID const val OWNER_REAL = 2 // an owner backed by a UUID
const val OWNER_FAKE = 4 // an owner not backed by a UUID
const val OWNER = OWNER_REAL or OWNER_FAKE // any owner
const val ANY = ID or OWNER_REAL or OWNER_FAKE // any const val OWNER = OWNER_REAL or OWNER_FAKE // any owner
const val REAL = ID or OWNER_REAL // no owner not backed by a UUID const val ANY = ID or OWNER_REAL or OWNER_FAKE // any
const val REAL = ID or OWNER_REAL // no owner not backed by a UUID
const val DEFAULT_KIND = REAL
const val DEFAULT_KIND = REAL
const val PREFER_OWNED_FOR_DEFAULT = 8 // if the kind can be ID and OWNER_REAL, prefer OWNER_REAL for default
// instead of parcel that the player is in const val PREFER_OWNED_FOR_DEFAULT =
8 // if the kind can be ID and OWNER_REAL, prefer OWNER_REAL for default
override fun toParameterInfo(annotation: TargetKind): Int { // instead of parcel that the player is in
return annotation.kind
} override fun toParameterInfo(annotation: TargetKind): Int {
} return annotation.kind
} }
}
class PType(val parcelProvider: ParcelProvider, val parcelAddress: SpecialCommandAddress? = null) : }
ParameterType<ParcelTarget, Int>(ParcelTarget::class.java, TargetKind) {
class PType(val parcelProvider: ParcelProvider, val parcelAddress: SpecialCommandAddress? = null) :
override fun parse(parameter: Parameter<ParcelTarget, Int>, sender: CommandSender, buffer: ArgumentBuffer): ParcelTarget { ParameterType<ParcelTarget, Int>(ParcelTarget::class.java, TargetKind) {
var input = buffer.next()!!
val worldString = input.substringBefore("/", missingDelimiterValue = "") override fun parse(
input = input.substringAfter("/") parameter: Parameter<ParcelTarget, Int>,
sender: CommandSender,
val world = if (worldString.isEmpty()) { buffer: ArgumentBuffer
val player = requirePlayer(sender, parameter, "the world") ): ParcelTarget {
parcelProvider.getWorld(player.world) var input = buffer.next()!!
?: invalidInput(parameter, "You cannot omit the world if you're not in a parcel world") val worldString = input.substringBefore("/", missingDelimiterValue = "")
} else { input = input.substringAfter("/")
parcelProvider.getWorld(worldString) ?: invalidInput(parameter, "$worldString is not a parcel world")
} val world = if (worldString.isEmpty()) {
val player = requirePlayer(sender, parameter, "the world")
val kind = parameter.paramInfo ?: DEFAULT_KIND parcelProvider.getWorld(player.world)
if (input.contains(',')) { ?: invalidInput(parameter, "You cannot omit the world if you're not in a parcel world")
if (kind and ID == 0) invalidInput(parameter, "You must specify a parcel by OWNER, that is, an owner and index") } else {
return ByID(world, getId(parameter, input), kind, false) parcelProvider.getWorld(worldString) ?: invalidInput(parameter, "$worldString is not a parcel world")
} }
if (kind and OWNER == 0) invalidInput(parameter, "You must specify a parcel by ID, that is, the x and z component separated by a comma") val kind = parameter.paramInfo ?: DEFAULT_KIND
val (owner, index) = getHomeIndex(parameter, kind, sender, input) if (input.contains(',')) {
return ByOwner(world, owner, index, kind, false, onResolveFailure = { invalidInput(parameter, "The player $input does not exist") }) if (kind and ID == 0) invalidInput(parameter,
} "You must specify a parcel by OWNER, that is, an owner and index")
return ByID(world, getId(parameter, input), kind, false)
private fun getId(parameter: Parameter<*, *>, input: String): Vec2i { }
val x = input.substringBefore(',').run {
toIntOrNull() ?: invalidInput(parameter, "ID(x) must be an integer, $this is not an integer") if (kind and OWNER == 0) invalidInput(parameter,
} "You must specify a parcel by ID, that is, the x and z component separated by a comma")
val z = input.substringAfter(',').run { val (owner, index) = getHomeIndex(parameter, kind, sender, input)
toIntOrNull() ?: invalidInput(parameter, "ID(z) must be an integer, $this is not an integer") return ByOwner(world,
} owner,
return Vec2i(x, z) index,
} kind,
false,
private fun getHomeIndex(parameter: Parameter<*, *>, kind: Int, sender: CommandSender, input: String): Pair<PlayerProfile, Int> { onResolveFailure = { invalidInput(parameter, "The player $input does not exist") })
val splitIdx = input.indexOf(':') }
val ownerString: String
val index: Int? private fun getId(parameter: Parameter<*, *>, input: String): Vec2i {
val x = input.substringBefore(',').run {
val speciallyParsedIndex = parcelAddress?.speciallyParsedIndex toIntOrNull() ?: invalidInput(parameter, "ID(x) must be an integer, $this is not an integer")
}
if (splitIdx == -1) { val z = input.substringAfter(',').run {
toIntOrNull() ?: invalidInput(parameter, "ID(z) must be an integer, $this is not an integer")
if (speciallyParsedIndex == null) { }
// just the index. return Vec2i(x, z)
index = input.toIntOrNull() }
ownerString = if (index == null) input else ""
} else { private fun getHomeIndex(
// just the owner. parameter: Parameter<*, *>,
index = speciallyParsedIndex kind: Int,
ownerString = input sender: CommandSender,
} input: String
): Pair<PlayerProfile, Int> {
} else { val splitIdx = input.indexOf(':')
if (speciallyParsedIndex != null) { val ownerString: String
invalidInput(parameter, "Duplicate home index") val index: Int?
}
val speciallyParsedIndex = parcelAddress?.speciallyParsedIndex
ownerString = input.substring(0, splitIdx)
if (splitIdx == -1) {
val indexString = input.substring(splitIdx + 1)
index = indexString.toIntOrNull() if (speciallyParsedIndex == null) {
?: invalidInput(parameter, "The home index must be an integer, $indexString is not an integer") // just the index.
} index = input.toIntOrNull()
ownerString = if (index == null) input else ""
val owner = if (ownerString.isEmpty()) } else {
PlayerProfile(requirePlayer(sender, parameter, "the player")) // just the owner.
else index = speciallyParsedIndex
PlayerProfile.byName(ownerString, allowReal = kind and OWNER_REAL != 0, allowFake = kind and OWNER_FAKE != 0) ownerString = input
}
return owner to (index ?: 0)
} } else {
if (speciallyParsedIndex != null) {
private fun requirePlayer(sender: CommandSender, parameter: Parameter<*, *>, objName: String): Player { invalidInput(parameter, "Duplicate home index")
if (sender !is Player) invalidInput(parameter, "console cannot omit the $objName") }
return sender
} ownerString = input.substring(0, splitIdx)
override fun getDefaultValue(parameter: Parameter<ParcelTarget, Int>, sender: CommandSender, buffer: ArgumentBuffer): ParcelTarget? { val indexString = input.substring(splitIdx + 1)
val kind = parameter.paramInfo ?: DEFAULT_KIND index = indexString.toIntOrNull()
val useLocation = when { ?: invalidInput(parameter, "The home index must be an integer, $indexString is not an integer")
kind and REAL == REAL -> kind and PREFER_OWNED_FOR_DEFAULT == 0 }
kind and ID != 0 -> true
kind and OWNER_REAL != 0 -> false val owner = (if (ownerString.isEmpty())
else -> return null PlayerProfile(requirePlayer(sender, parameter, "the player"))
} else
PlayerProfile.byName(ownerString, allowReal = kind and OWNER_REAL != 0, allowFake = kind and OWNER_FAKE != 0))
val player = requirePlayer(sender, parameter, "the parcel") ?: invalidInput(parameter, "\'$ownerString\' is not a valid player name")
val world = parcelProvider.getWorld(player.world) ?: invalidInput(parameter, "You must be in a parcel world to omit the parcel")
if (useLocation) { return owner to (index ?: 0)
val id = player.location.let { world.getParcelIdAt(it.x.floor(), it.z.floor())?.pos } }
return ByID(world, id, kind, true)
} private fun requirePlayer(sender: CommandSender, parameter: Parameter<*, *>, objName: String): Player {
if (sender !is Player) invalidInput(parameter, "console cannot omit the $objName")
return ByOwner(world, PlayerProfile(player), parcelAddress?.speciallyParsedIndex ?: 0, kind, true) return sender
} }
}
override fun getDefaultValue(
} parameter: Parameter<ParcelTarget, Int>,
sender: CommandSender,
buffer: ArgumentBuffer
): ParcelTarget? {
val kind = parameter.paramInfo ?: DEFAULT_KIND
val useLocation = when {
kind and REAL == REAL -> kind and PREFER_OWNED_FOR_DEFAULT == 0
kind and ID != 0 -> true
kind and OWNER_REAL != 0 -> false
else -> return null
}
val player = requirePlayer(sender, parameter, "the parcel")
val world = parcelProvider.getWorld(player.world) ?: invalidInput(parameter,
"You must be in a parcel world to omit the parcel")
if (useLocation) {
val id = player.location.let { world.getParcelIdAt(it.x.floor(), it.z.floor())?.pos }
return ByID(world, id, kind, true)
}
return ByOwner(world, PlayerProfile(player), parcelAddress?.speciallyParsedIndex ?: 0, kind, true)
}
}
}

View File

@@ -1,378 +1,391 @@
package io.dico.parcels2.defaultimpl package io.dico.parcels2.defaultimpl
import io.dico.parcels2.* import io.dico.parcels2.*
import io.dico.parcels2.blockvisitor.RegionTraverser import io.dico.parcels2.blockvisitor.RegionTraverser
import io.dico.parcels2.options.DefaultGeneratorOptions import io.dico.parcels2.options.DefaultGeneratorOptions
import io.dico.parcels2.util.math.* import io.dico.parcels2.util.math.*
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import org.bukkit.* import org.bukkit.*
import org.bukkit.block.Biome import org.bukkit.block.Biome
import org.bukkit.block.BlockFace import org.bukkit.block.BlockFace
import org.bukkit.block.Skull import org.bukkit.block.Skull
import org.bukkit.block.data.type.Slab import org.bukkit.block.data.type.Slab
import org.bukkit.block.data.type.WallSign import org.bukkit.block.data.type.WallSign
import java.util.Random import org.bukkit.entity.Player
import java.util.Random
private val airType = Bukkit.createBlockData(Material.AIR)
private val airType = Bukkit.createBlockData(Material.AIR)
private const val chunkSize = 16
private const val chunkSize = 16
class DefaultParcelGenerator(
override val worldName: String, class DefaultParcelGenerator(
private val o: DefaultGeneratorOptions override val worldName: String,
) : ParcelGenerator() { private val o: DefaultGeneratorOptions
private var _world: World? = null ) : ParcelGenerator() {
override val world: World private var _world: World? = null
get() { override val world: World
if (_world == null) { get() {
val world = Bukkit.getWorld(worldName) if (_world == null) {
maxHeight = world.maxHeight val world = Bukkit.getWorld(worldName)
_world = world maxHeight = world.maxHeight
return world _world = world
} return world
return _world!! }
} return _world!!
}
private var maxHeight = 0
val sectionSize = o.parcelSize + o.pathSize private var maxHeight = 0
val pathOffset = (if (o.pathSize % 2 == 0) o.pathSize + 2 else o.pathSize + 1) / 2 val sectionSize = o.parcelSize + o.pathSize
val makePathMain = o.pathSize > 2 val pathOffset = (if (o.pathSize % 2 == 0) o.pathSize + 2 else o.pathSize + 1) / 2
val makePathAlt = o.pathSize > 4 val makePathMain = o.pathSize > 2
val makePathAlt = o.pathSize > 4
private inline fun <T> generate(
chunkX: Int, private inline fun <T> generate(
chunkZ: Int, chunkX: Int,
floor: T, wall: chunkZ: Int,
T, pathMain: T, floor: T, wall:
pathAlt: T, T, pathMain: T,
fill: T, pathAlt: T,
setter: (Int, Int, Int, T) -> Unit fill: T,
) { setter: (Int, Int, Int, T) -> Unit
) {
val floorHeight = o.floorHeight
val parcelSize = o.parcelSize val floorHeight = o.floorHeight
val sectionSize = sectionSize val parcelSize = o.parcelSize
val pathOffset = pathOffset val sectionSize = sectionSize
val makePathMain = makePathMain val pathOffset = pathOffset
val makePathAlt = makePathAlt val makePathMain = makePathMain
val makePathAlt = makePathAlt
// parcel bottom x and z
// umod is unsigned %: the result is always >= 0 // parcel bottom x and z
val pbx = ((chunkX shl 4) - o.offsetX) umod sectionSize // umod is unsigned %: the result is always >= 0
val pbz = ((chunkZ shl 4) - o.offsetZ) umod sectionSize val pbx = ((chunkX shl 4) - o.offsetX) umod sectionSize
val pbz = ((chunkZ shl 4) - o.offsetZ) umod sectionSize
var curHeight: Int
var x: Int var curHeight: Int
var z: Int var x: Int
for (cx in 0..15) { var z: Int
for (cz in 0..15) { for (cx in 0..15) {
x = (pbx + cx) % sectionSize - pathOffset for (cz in 0..15) {
z = (pbz + cz) % sectionSize - pathOffset x = (pbx + cx) % sectionSize - pathOffset
curHeight = floorHeight z = (pbz + cz) % sectionSize - pathOffset
curHeight = floorHeight
val type = when {
(x in 0 until parcelSize && z in 0 until parcelSize) -> floor val type = when {
(x in -1..parcelSize && z in -1..parcelSize) -> { (x in 0 until parcelSize && z in 0 until parcelSize) -> floor
curHeight++ (x in -1..parcelSize && z in -1..parcelSize) -> {
wall curHeight++
} wall
(makePathAlt && x in -2 until parcelSize + 2 && z in -2 until parcelSize + 2) -> pathAlt }
(makePathMain) -> pathMain (makePathAlt && x in -2 until parcelSize + 2 && z in -2 until parcelSize + 2) -> pathAlt
else -> { (makePathMain) -> pathMain
curHeight++ else -> {
wall curHeight++
} wall
} }
}
for (y in 0 until curHeight) {
setter(cx, y, cz, fill) for (y in 0 until curHeight) {
} setter(cx, y, cz, fill)
setter(cx, curHeight, cz, type) }
} setter(cx, curHeight, cz, type)
} }
} }
}
override fun generateChunkData(world: World?, random: Random?, chunkX: Int, chunkZ: Int, biome: BiomeGrid?): ChunkData {
val out = Bukkit.createChunkData(world) override fun generateChunkData(world: World?, random: Random?, chunkX: Int, chunkZ: Int, biome: BiomeGrid?): ChunkData {
generate(chunkX, chunkZ, o.floorType, o.wallType, o.pathMainType, o.pathAltType, o.fillType) { x, y, z, type -> val out = Bukkit.createChunkData(world)
out.setBlock(x, y, z, type) generate(chunkX, chunkZ, o.floorType, o.wallType, o.pathMainType, o.pathAltType, o.fillType) { x, y, z, type ->
} out.setBlock(x, y, z, type)
return out }
} return out
}
override fun populate(world: World?, random: Random?, chunk: Chunk?) {
// do nothing override fun populate(world: World?, random: Random?, chunk: Chunk?) {
} // do nothing
}
override fun getFixedSpawnLocation(world: World?, random: Random?): Location {
val fix = if (o.parcelSize.even) 0.5 else 0.0 override fun getFixedSpawnLocation(world: World?, random: Random?): Location {
return Location(world, o.offsetX + fix, o.floorHeight + 1.0, o.offsetZ + fix) val fix = if (o.parcelSize.even) 0.5 else 0.0
} return Location(world, o.offsetX + fix, o.floorHeight + 1.0, o.offsetZ + fix)
}
override fun makeParcelLocatorAndBlockManager(
parcelProvider: ParcelProvider, override fun makeParcelLocatorAndBlockManager(
container: ParcelContainer, parcelProvider: ParcelProvider,
coroutineScope: CoroutineScope, container: ParcelContainer,
jobDispatcher: JobDispatcher coroutineScope: CoroutineScope,
): Pair<ParcelLocator, ParcelBlockManager> { jobDispatcher: JobDispatcher
val impl = ParcelLocatorAndBlockManagerImpl(parcelProvider, container, coroutineScope, jobDispatcher) ): Pair<ParcelLocator, ParcelBlockManager> {
return impl to impl val impl = ParcelLocatorAndBlockManagerImpl(parcelProvider, container, coroutineScope, jobDispatcher)
} return impl to impl
}
private inline fun <T> convertBlockLocationToId(x: Int, z: Int, mapper: (Int, Int) -> T): T? {
val sectionSize = sectionSize private inline fun <T> convertBlockLocationToId(x: Int, z: Int, mapper: (Int, Int) -> T): T? {
val parcelSize = o.parcelSize val sectionSize = sectionSize
val absX = x - o.offsetX - pathOffset val parcelSize = o.parcelSize
val absZ = z - o.offsetZ - pathOffset val absX = x - o.offsetX - pathOffset
val modX = absX umod sectionSize val absZ = z - o.offsetZ - pathOffset
val modZ = absZ umod sectionSize val modX = absX umod sectionSize
if (modX in 0 until parcelSize && modZ in 0 until parcelSize) { val modZ = absZ umod sectionSize
return mapper((absX - modX) / sectionSize + 1, (absZ - modZ) / sectionSize + 1) if (modX in 0 until parcelSize && modZ in 0 until parcelSize) {
} return mapper((absX - modX) / sectionSize + 1, (absZ - modZ) / sectionSize + 1)
return null }
} return null
}
@Suppress("DEPRECATION")
private inner class ParcelLocatorAndBlockManagerImpl( @Suppress("DEPRECATION")
val parcelProvider: ParcelProvider, private inner class ParcelLocatorAndBlockManagerImpl(
val container: ParcelContainer, val parcelProvider: ParcelProvider,
coroutineScope: CoroutineScope, val container: ParcelContainer,
override val jobDispatcher: JobDispatcher coroutineScope: CoroutineScope,
) : ParcelBlockManagerBase(), ParcelLocator, CoroutineScope by coroutineScope { override val jobDispatcher: JobDispatcher
) : ParcelBlockManagerBase(), ParcelLocator, CoroutineScope by coroutineScope {
override val world: World get() = this@DefaultParcelGenerator.world
val worldId = parcelProvider.getWorld(world)?.id ?: ParcelWorldId(world) override val world: World get() = this@DefaultParcelGenerator.world
override val parcelTraverser: RegionTraverser = RegionTraverser.convergingTo(o.floorHeight) val worldId = parcelProvider.getWorld(world)?.id ?: ParcelWorldId(world)
override val parcelTraverser: RegionTraverser = RegionTraverser.convergingTo(o.floorHeight)
private val cornerWallType = when {
o.wallType is Slab -> (o.wallType.clone() as Slab).apply { type = Slab.Type.DOUBLE } private val cornerWallType = when {
o.wallType.material.name.endsWith("CARPET") -> { o.wallType is Slab -> (o.wallType.clone() as Slab).apply { type = Slab.Type.DOUBLE }
Bukkit.createBlockData(Material.getMaterial(o.wallType.material.name.substringBefore("CARPET") + "WOOL")) o.wallType.material.name.endsWith("CARPET") -> {
} Bukkit.createBlockData(Material.getMaterial(o.wallType.material.name.substringBefore("CARPET") + "WOOL"))
else -> null }
} else -> null
}
override fun getParcelAt(x: Int, z: Int): Parcel? {
return convertBlockLocationToId(x, z, container::getParcelById) override fun getParcelAt(x: Int, z: Int): Parcel? {
} return convertBlockLocationToId(x, z, container::getParcelById)
}
override fun getParcelIdAt(x: Int, z: Int): ParcelId? {
return convertBlockLocationToId(x, z) { idx, idz -> ParcelId(worldId, idx, idz) } override fun getParcelIdAt(x: Int, z: Int): ParcelId? {
} return convertBlockLocationToId(x, z) { idx, idz -> ParcelId(worldId, idx, idz) }
}
private fun checkParcelId(parcel: ParcelId): ParcelId {
if (!parcel.worldId.equals(worldId)) { private fun checkParcelId(parcel: ParcelId): ParcelId {
throw IllegalArgumentException() if (!parcel.worldId.equals(worldId)) {
} throw IllegalArgumentException()
return parcel }
} return parcel
}
override fun getRegionOrigin(parcel: ParcelId): Vec2i {
checkParcelId(parcel) override fun getRegionOrigin(parcel: ParcelId): Vec2i {
return Vec2i( checkParcelId(parcel)
sectionSize * (parcel.x - 1) + pathOffset + o.offsetX, return Vec2i(
sectionSize * (parcel.z - 1) + pathOffset + o.offsetZ sectionSize * (parcel.x - 1) + pathOffset + o.offsetX,
) sectionSize * (parcel.z - 1) + pathOffset + o.offsetZ
} )
}
override fun getRegion(parcel: ParcelId): Region {
val origin = getRegionOrigin(parcel) override fun getRegion(parcel: ParcelId): Region {
return Region( val origin = getRegionOrigin(parcel)
Vec3i(origin.x, 0, origin.z), return Region(
Vec3i(o.parcelSize, maxHeight, o.parcelSize) Vec3i(origin.x, 0, origin.z),
) Vec3i(o.parcelSize, maxHeight, o.parcelSize)
} )
}
override fun getHomeLocation(parcel: ParcelId): Location {
val origin = getRegionOrigin(parcel) override fun getHomeLocation(parcel: ParcelId): Location {
val x = origin.x + (o.parcelSize - 1) / 2.0 val origin = getRegionOrigin(parcel)
val z = origin.z - 2 val x = origin.x + (o.parcelSize - 1) / 2.0
return Location(world, x + 0.5, o.floorHeight + 1.0, z + 0.5, 0F, 0F) val z = origin.z - 2
} return Location(world, x + 0.5, o.floorHeight + 1.0, z + 0.5, 0F, 0F)
}
override fun getParcelForInfoBlockInteraction(block: Vec3i, type: Material, face: BlockFace): Parcel? {
if (block.y != o.floorHeight + 1) return null override fun getParcelForInfoBlockInteraction(block: Vec3i, type: Material, face: BlockFace): Parcel? {
if (block.y != o.floorHeight + 1) return null
val expectedParcelOrigin = when (type) {
Material.WALL_SIGN -> Vec2i(block.x + 1, block.z + 2) val expectedParcelOrigin = when (type) {
o.wallType.material, cornerWallType?.material -> { Material.WALL_SIGN -> Vec2i(block.x + 1, block.z + 2)
if (face != BlockFace.NORTH || world[block + Vec3i.convert(BlockFace.NORTH)].type == Material.WALL_SIGN) { o.wallType.material, cornerWallType?.material -> {
return null if (face != BlockFace.NORTH || world[block + Vec3i.convert(BlockFace.NORTH)].type == Material.WALL_SIGN) {
} return null
}
Vec2i(block.x + 1, block.z + 1)
} Vec2i(block.x + 1, block.z + 1)
else -> return null }
} else -> return null
}
return getParcelAt(expectedParcelOrigin.x, expectedParcelOrigin.z)
?.takeIf { expectedParcelOrigin == getRegionOrigin(it.id) } return getParcelAt(expectedParcelOrigin.x, expectedParcelOrigin.z)
?.also { parcel -> ?.takeIf { expectedParcelOrigin == getRegionOrigin(it.id) }
if (type != Material.WALL_SIGN && parcel.owner != null) { ?.also { parcel ->
updateParcelInfo(parcel.id, parcel.owner) if (type != Material.WALL_SIGN && parcel.owner != null) {
parcel.isOwnerSignOutdated = false updateParcelInfo(parcel.id, parcel.owner)
} parcel.isOwnerSignOutdated = false
} }
} }
}
override fun isParcelInfoSectionLoaded(parcel: ParcelId): Boolean {
val wallBlockChunk = getRegionOrigin(parcel).add(-1, -1).toChunk() override fun isParcelInfoSectionLoaded(parcel: ParcelId): Boolean {
return world.isChunkLoaded(wallBlockChunk.x, wallBlockChunk.z) val wallBlockChunk = getRegionOrigin(parcel).add(-1, -1).toChunk()
} return world.isChunkLoaded(wallBlockChunk.x, wallBlockChunk.z)
}
override fun updateParcelInfo(parcel: ParcelId, owner: PlayerProfile?) {
val b = getRegionOrigin(parcel) override fun updateParcelInfo(parcel: ParcelId, owner: PlayerProfile?) {
val b = getRegionOrigin(parcel)
val wallBlock = world.getBlockAt(b.x - 1, o.floorHeight + 1, b.z - 1)
val signBlock = world.getBlockAt(b.x - 1, o.floorHeight + 1, b.z - 2) val wallBlock = world.getBlockAt(b.x - 1, o.floorHeight + 1, b.z - 1)
val skullBlock = world.getBlockAt(b.x - 1, o.floorHeight + 2, b.z - 1) val signBlock = world.getBlockAt(b.x - 1, o.floorHeight + 1, b.z - 2)
val skullBlock = world.getBlockAt(b.x - 1, o.floorHeight + 2, b.z - 1)
if (owner == null) {
wallBlock.blockData = o.wallType if (owner == null) {
signBlock.type = Material.AIR wallBlock.blockData = o.wallType
skullBlock.type = Material.AIR signBlock.type = Material.AIR
skullBlock.type = Material.AIR
} else {
cornerWallType?.let { wallBlock.blockData = it } } else {
signBlock.blockData = (Bukkit.createBlockData(Material.WALL_SIGN) as WallSign).apply { facing = BlockFace.NORTH } cornerWallType?.let { wallBlock.blockData = it }
signBlock.blockData = (Bukkit.createBlockData(Material.WALL_SIGN) as WallSign).apply { facing = BlockFace.NORTH }
val sign = signBlock.state as org.bukkit.block.Sign
sign.setLine(0, "${parcel.x},${parcel.z}") val sign = signBlock.state as org.bukkit.block.Sign
sign.setLine(2, owner.name ?: "") sign.setLine(0, "${parcel.x},${parcel.z}")
sign.update() sign.setLine(2, owner.name ?: "")
sign.update()
skullBlock.type = Material.AIR
skullBlock.type = Material.PLAYER_HEAD skullBlock.type = Material.AIR
val skull = skullBlock.state as Skull skullBlock.type = Material.PLAYER_HEAD
if (owner is PlayerProfile.Real) { val skull = skullBlock.state as Skull
skull.owningPlayer = Bukkit.getOfflinePlayer(owner.uuid) if (owner is PlayerProfile.Real) {
skull.owningPlayer = Bukkit.getOfflinePlayer(owner.uuid)
} else if (!skull.setOwner(owner.name)) {
skullBlock.type = Material.AIR } else if (!skull.setOwner(owner.name)) {
return skullBlock.type = Material.AIR
} return
}
skull.rotation = BlockFace.SOUTH
skull.update() skull.rotation = BlockFace.SOUTH
} skull.update()
} }
}
private fun trySubmitBlockVisitor(vararg parcels: ParcelId, function: JobFunction): Job? {
parcels.forEach { checkParcelId(it) } private fun trySubmitBlockVisitor(vararg parcels: ParcelId, function: JobFunction): Job? {
return parcelProvider.trySubmitBlockVisitor(Permit(), parcels, function) parcels.forEach { checkParcelId(it) }
} return parcelProvider.trySubmitBlockVisitor(Permit(), parcels, function)
}
override fun setBiome(parcel: ParcelId, biome: Biome) = trySubmitBlockVisitor(checkParcelId(parcel)) {
val world = world override fun setBiome(parcel: ParcelId, biome: Biome) = trySubmitBlockVisitor(checkParcelId(parcel)) {
val b = getRegionOrigin(parcel) val world = world
val parcelSize = o.parcelSize val b = getRegionOrigin(parcel)
for (x in b.x until b.x + parcelSize) { val parcelSize = o.parcelSize
for (z in b.z until b.z + parcelSize) { for (x in b.x until b.x + parcelSize) {
markSuspensionPoint() for (z in b.z until b.z + parcelSize) {
world.setBiome(x, z, biome) markSuspensionPoint()
} world.setBiome(x, z, biome)
} }
} }
}
override fun clearParcel(parcel: ParcelId) = trySubmitBlockVisitor(checkParcelId(parcel)) {
val region = getRegion(parcel) override fun clearParcel(parcel: ParcelId) = trySubmitBlockVisitor(checkParcelId(parcel)) {
val blocks = parcelTraverser.traverseRegion(region) val region = getRegion(parcel)
val blockCount = region.blockCount.toDouble() val blocks = parcelTraverser.traverseRegion(region)
val world = world val blockCount = region.blockCount.toDouble()
val floorHeight = o.floorHeight val world = world
val airType = airType val floorHeight = o.floorHeight
val floorType = o.floorType val airType = airType
val fillType = o.fillType val floorType = o.floorType
val fillType = o.fillType
for ((index, vec) in blocks.withIndex()) {
markSuspensionPoint() delegateWork(0.95) {
val y = vec.y for ((index, vec) in blocks.withIndex()) {
val blockType = when { markSuspensionPoint()
y > floorHeight -> airType val y = vec.y
y == floorHeight -> floorType val blockType = when {
else -> fillType y > floorHeight -> airType
} y == floorHeight -> floorType
world[vec].blockData = blockType else -> fillType
setProgress((index + 1) / blockCount) }
} world[vec].blockData = blockType
} setProgress((index + 1) / blockCount)
}
override fun getParcelsWithOwnerBlockIn(chunk: Chunk): Collection<Vec2i> { }
/*
* Get the offsets for the world out of the way delegateWork {
* to simplify the calculation that follows. val entities = getEntities(region)
*/ for ((index, entity) in entities.withIndex()) {
if (entity is Player) continue
val x = chunk.x.shl(4) - (o.offsetX + pathOffset) entity.remove()
val z = chunk.z.shl(4) - (o.offsetZ + pathOffset) setProgress((index + 1) / entities.size.toDouble())
}
/* Locations of wall corners (where owner blocks are placed) are defined as: }
*
* x umod sectionSize == sectionSize-1 }
*
* This check needs to be made for all 16 slices of the chunk in 2 dimensions override fun getParcelsWithOwnerBlockIn(chunk: Chunk): Collection<Vec2i> {
* How to optimize this? /*
* Let's take the expression * Get the offsets for the world out of the way
* * to simplify the calculation that follows.
* x umod sectionSize */
*
* And call it modX val x = chunk.x.shl(4) - (o.offsetX + pathOffset)
* x can be shifted (chunkSize -1) times to attempt to get a modX of 0. val z = chunk.z.shl(4) - (o.offsetZ + pathOffset)
* This means that if the modX is 1, and sectionSize == (chunkSize-1), there would be a match at the last shift.
* To check that there are any matches, we can see if the following holds: /* Locations of wall corners (where owner blocks are placed) are defined as:
* *
* modX >= ((sectionSize-1) - (chunkSize-1)) * x umod sectionSize == sectionSize-1
* *
* Which can be simplified to: * This check needs to be made for all 16 slices of the chunk in 2 dimensions
* modX >= sectionSize - chunkSize * How to optimize this?
* * Let's take the expression
* if sectionSize == chunkSize, this expression can be simplified to *
* modX >= 0 * x umod sectionSize
* which is always true. This is expected. *
* To get the total number of matches on a dimension, we can evaluate the following: * And call it modX
* * x can be shifted (chunkSize -1) times to attempt to get a modX of 0.
* (modX - (sectionSize - chunkSize) + sectionSize) / sectionSize * This means that if the modX is 1, and sectionSize == (chunkSize-1), there would be a match at the last shift.
* * To check that there are any matches, we can see if the following holds:
* We add sectionSize to the lhs because, if the other part of the lhs is 0, we need at least 1. *
* This can be simplified to: * modX >= ((sectionSize-1) - (chunkSize-1))
* *
* (modX + chunkSize) / sectionSize * Which can be simplified to:
*/ * modX >= sectionSize - chunkSize
*
val sectionSize = sectionSize * if sectionSize == chunkSize, this expression can be simplified to
* modX >= 0
val modX = x umod sectionSize * which is always true. This is expected.
val matchesOnDimensionX = (modX + chunkSize) / sectionSize * To get the total number of matches on a dimension, we can evaluate the following:
if (matchesOnDimensionX <= 0) return emptyList() *
* (modX - (sectionSize - chunkSize) + sectionSize) / sectionSize
val modZ = z umod sectionSize *
val matchesOnDimensionZ = (modZ + chunkSize) / sectionSize * We add sectionSize to the lhs because, if the other part of the lhs is 0, we need at least 1.
if (matchesOnDimensionZ <= 0) return emptyList() * This can be simplified to:
*
/* * (modX + chunkSize) / sectionSize
* Now we need to find the first id within the matches, */
* and then return the subsequent matches in a rectangle following it.
* val sectionSize = sectionSize
* On each dimension, get the distance to the first match, which is equal to (sectionSize-1 - modX)
* and add it to the coordinate value val modX = x umod sectionSize
*/ val matchesOnDimensionX = (modX + chunkSize) / sectionSize
val firstX = x + (sectionSize - 1 - modX) if (matchesOnDimensionX <= 0) return emptyList()
val firstZ = z + (sectionSize - 1 - modZ)
val modZ = z umod sectionSize
val firstIdX = (firstX + 1) / sectionSize + 1 val matchesOnDimensionZ = (modZ + chunkSize) / sectionSize
val firstIdZ = (firstZ + 1) / sectionSize + 1 if (matchesOnDimensionZ <= 0) return emptyList()
if (matchesOnDimensionX == 1 && matchesOnDimensionZ == 1) { /*
// fast-path optimization * Now we need to find the first id within the matches,
return listOf(Vec2i(firstIdX, firstIdZ)) * and then return the subsequent matches in a rectangle following it.
} *
* On each dimension, get the distance to the first match, which is equal to (sectionSize-1 - modX)
return (0 until matchesOnDimensionX).flatMap { idOffsetX -> * and add it to the coordinate value
(0 until matchesOnDimensionZ).map { idOffsetZ -> Vec2i(firstIdX + idOffsetX, firstIdZ + idOffsetZ) } */
} val firstX = x + (sectionSize - 1 - modX)
} val firstZ = z + (sectionSize - 1 - modZ)
} val firstIdX = (firstX + 1) / sectionSize + 1
val firstIdZ = (firstZ + 1) / sectionSize + 1
if (matchesOnDimensionX == 1 && matchesOnDimensionZ == 1) {
// fast-path optimization
return listOf(Vec2i(firstIdX, firstIdZ))
}
return (0 until matchesOnDimensionX).flatMap { idOffsetX ->
(0 until matchesOnDimensionZ).map { idOffsetZ -> Vec2i(firstIdX + idOffsetX, firstIdZ + idOffsetZ) }
}
}
}
} }

View File

@@ -1,223 +1,284 @@
package io.dico.parcels2.defaultimpl package io.dico.parcels2.defaultimpl
import io.dico.parcels2.* import io.dico.parcels2.*
import io.dico.parcels2.blockvisitor.Schematic import io.dico.parcels2.blockvisitor.Schematic
import io.dico.parcels2.util.schedule import io.dico.parcels2.util.math.Region
import kotlinx.coroutines.Dispatchers import io.dico.parcels2.util.math.Vec3d
import kotlinx.coroutines.GlobalScope import io.dico.parcels2.util.math.Vec3i
import kotlinx.coroutines.launch import io.dico.parcels2.util.schedule
import org.bukkit.Bukkit import kotlinx.coroutines.Dispatchers
import org.bukkit.WorldCreator import kotlinx.coroutines.GlobalScope
import org.joda.time.DateTime import kotlinx.coroutines.launch
import org.bukkit.Bukkit
class ParcelProviderImpl(val plugin: ParcelsPlugin) : ParcelProvider { import org.bukkit.World
inline val options get() = plugin.options import org.bukkit.WorldCreator
override val worlds: Map<String, ParcelWorld> get() = _worlds import org.bukkit.entity.Entity
private val _worlds: MutableMap<String, ParcelWorld> = hashMapOf() import org.bukkit.util.Vector
private val _generators: MutableMap<String, ParcelGenerator> = hashMapOf() import org.joda.time.DateTime
private var _worldsLoaded = false
private var _dataIsLoaded = false class ParcelProviderImpl(val plugin: ParcelsPlugin) : ParcelProvider {
inline val options get() = plugin.options
// disabled while !_dataIsLoaded. getParcelById() will work though for data loading. override val worlds: Map<String, ParcelWorld> get() = _worlds
override fun getWorld(name: String): ParcelWorld? = _worlds[name]?.takeIf { _dataIsLoaded } private val _worlds: MutableMap<String, ParcelWorld> = hashMapOf()
private val _generators: MutableMap<String, ParcelGenerator> = hashMapOf()
override fun getWorldById(id: ParcelWorldId): ParcelWorld? { private var _worldsLoaded = false
if (id is ParcelWorld) return id private var _dataIsLoaded = false
return _worlds[id.name] ?: id.bukkitWorld?.let { getWorld(it) }
} // disabled while !_dataIsLoaded. getParcelById() will work though for data loading.
override fun getWorld(name: String): ParcelWorld? = _worlds[name]?.takeIf { _dataIsLoaded }
override fun getParcelById(id: ParcelId): Parcel? {
if (id is Parcel) return id override fun getWorldById(id: ParcelWorldId): ParcelWorld? {
return getWorldById(id.worldId)?.container?.getParcelById(id.x, id.z) if (id is ParcelWorld) return id
} return _worlds[id.name] ?: id.bukkitWorld?.let { getWorld(it) }
}
override fun getWorldGenerator(worldName: String): ParcelGenerator? {
return _worlds[worldName]?.generator override fun getParcelById(id: ParcelId): Parcel? {
?: _generators[worldName] if (id is Parcel) return id
?: options.worlds[worldName]?.generator?.newInstance(worldName)?.also { _generators[worldName] = it } return getWorldById(id.worldId)?.container?.getParcelById(id.x, id.z)
} }
override fun loadWorlds() { override fun getWorldGenerator(worldName: String): ParcelGenerator? {
if (_worldsLoaded) throw IllegalStateException() return _worlds[worldName]?.generator
_worldsLoaded = true ?: _generators[worldName]
loadWorlds0() ?: options.worlds[worldName]?.generator?.newInstance(worldName)?.also { _generators[worldName] = it }
} }
private fun loadWorlds0() { override fun loadWorlds() {
if (Bukkit.getWorlds().isEmpty()) { if (_worldsLoaded) throw IllegalStateException()
plugin.schedule(::loadWorlds0) _worldsLoaded = true
plugin.logger.warning("Scheduling to load worlds in the next tick because no bukkit worlds are loaded yet") loadWorlds0()
return }
}
private fun loadWorlds0() {
val newlyCreatedWorlds = mutableListOf<ParcelWorld>() if (Bukkit.getWorlds().isEmpty()) {
for ((worldName, worldOptions) in options.worlds.entries) { plugin.schedule { loadWorlds0() }
var parcelWorld = _worlds[worldName] plugin.logger.warning("Scheduling to load worlds in the next tick because no bukkit worlds are loaded yet")
if (parcelWorld != null) continue return
}
val generator: ParcelGenerator = getWorldGenerator(worldName)!!
val worldExists = Bukkit.getWorld(worldName) != null val newlyCreatedWorlds = mutableListOf<ParcelWorld>()
val bukkitWorld = for ((worldName, worldOptions) in options.worlds.entries) {
if (worldExists) Bukkit.getWorld(worldName)!! var parcelWorld = _worlds[worldName]
else { if (parcelWorld != null) continue
logger.info("Creating world $worldName")
WorldCreator(worldName).generator(generator).createWorld() val generator: ParcelGenerator = getWorldGenerator(worldName)!!
} val worldExists = Bukkit.getWorld(worldName) != null
val bukkitWorld =
parcelWorld = ParcelWorldImpl(plugin, bukkitWorld, generator, worldOptions.runtime,::DefaultParcelContainer) if (worldExists) Bukkit.getWorld(worldName)!!
else {
if (!worldExists) { logger.info("Creating world $worldName")
val time = DateTime.now() WorldCreator(worldName).generator(generator).createWorld()
plugin.storage.setWorldCreationTime(parcelWorld.id, time) }
parcelWorld.creationTime = time
newlyCreatedWorlds.add(parcelWorld) parcelWorld =
} else { ParcelWorldImpl(plugin, bukkitWorld, generator, worldOptions.runtime, ::DefaultParcelContainer)
GlobalScope.launch(context = Dispatchers.Unconfined) {
parcelWorld.creationTime = plugin.storage.getWorldCreationTime(parcelWorld.id).await() ?: DateTime.now() if (!worldExists) {
} val time = DateTime.now()
} plugin.storage.setWorldCreationTime(parcelWorld.id, time)
parcelWorld.creationTime = time
_worlds[worldName] = parcelWorld newlyCreatedWorlds.add(parcelWorld)
} } else {
GlobalScope.launch(context = Dispatchers.Unconfined) {
loadStoredData(newlyCreatedWorlds.toSet()) parcelWorld.creationTime = plugin.storage.getWorldCreationTime(parcelWorld.id).await() ?:
} DateTime.now()
}
private fun loadStoredData(newlyCreatedWorlds: Collection<ParcelWorld> = emptyList()) { }
plugin.launch(Dispatchers.Default) {
val migration = plugin.options.migration _worlds[worldName] = parcelWorld
if (migration.enabled) { }
migration.instance?.newInstance()?.apply {
logger.warn("Migrating database now...") loadStoredData(newlyCreatedWorlds.toSet())
migrateTo(plugin.storage).join() }
logger.warn("Migration completed")
private fun loadStoredData(newlyCreatedWorlds: Collection<ParcelWorld> = emptyList()) {
if (migration.disableWhenComplete) { plugin.launch {
migration.enabled = false val migration = plugin.options.migration
plugin.saveOptions() if (migration.enabled) {
} migration.instance?.newInstance()?.apply {
} logger.warn("Migrating database now...")
} migrateTo(plugin.storage).join()
logger.warn("Migration completed")
logger.info("Loading all parcel data...")
if (migration.disableWhenComplete) {
val job1 = launch { migration.enabled = false
val channel = plugin.storage.transmitAllParcelData() plugin.saveOptions()
while (true) { }
val (id, data) = channel.receiveOrNull() ?: break }
val parcel = getParcelById(id) ?: continue }
data?.let { parcel.copyData(it, callerIsDatabase = true) }
} logger.info("Loading all parcel data...")
}
val job1 = launch {
val channel2 = plugin.storage.transmitAllGlobalPrivileges() val channel = plugin.storage.transmitAllParcelData()
while (true) { while (true) {
val (profile, data) = channel2.receiveOrNull() ?: break val (id, data) = channel.receiveOrNull() ?: break
if (profile !is PrivilegeKey) { val parcel = getParcelById(id) ?: continue
logger.error("Received profile that is not a privilege key: ${profile.javaClass}, $profile") data?.let { parcel.copyData(it, callerIsDatabase = true) }
continue }
} }
(plugin.globalPrivileges[profile] as PrivilegesHolder).copyPrivilegesFrom(data)
} val channel2 = plugin.storage.transmitAllGlobalPrivileges()
while (true) {
job1.join() val (profile, data) = channel2.receiveOrNull() ?: break
if (profile !is PrivilegeKey) {
logger.info("Loading data completed") logger.error("Received profile that is not a privilege key: ${profile.javaClass}, $profile")
_dataIsLoaded = true continue
} }
} (plugin.globalPrivileges[profile] as PrivilegesHolder).copyPrivilegesFrom(data)
}
override fun acquireBlockVisitorPermit(parcelId: ParcelId, with: Permit): Boolean {
val parcel = getParcelById(parcelId) as? ParcelImpl ?: return true job1.join()
return parcel.acquireBlockVisitorPermit(with)
} logger.info("Loading data completed")
_dataIsLoaded = true
override fun releaseBlockVisitorPermit(parcelId: ParcelId, with: Permit) { }
val parcel = getParcelById(parcelId) as? ParcelImpl ?: return }
parcel.releaseBlockVisitorPermit(with)
} override fun acquireBlockVisitorPermit(parcelId: ParcelId, with: Permit): Boolean {
val parcel = getParcelById(parcelId) as? ParcelImpl ?: return true
override fun trySubmitBlockVisitor(permit: Permit, vararg parcelIds: ParcelId, function: JobFunction): Job? { return parcel.acquireBlockVisitorPermit(with)
val withPermit = parcelIds.filter { acquireBlockVisitorPermit(it, permit) } }
if (withPermit.size != parcelIds.size) {
withPermit.forEach { releaseBlockVisitorPermit(it, permit) } override fun releaseBlockVisitorPermit(parcelId: ParcelId, with: Permit) {
return null val parcel = getParcelById(parcelId) as? ParcelImpl ?: return
} parcel.releaseBlockVisitorPermit(with)
}
val job = plugin.jobDispatcher.dispatch(function)
override fun trySubmitBlockVisitor(permit: Permit, vararg parcelIds: ParcelId, function: JobFunction): Job? {
plugin.launch { val withPermit = parcelIds.filter { acquireBlockVisitorPermit(it, permit) }
job.awaitCompletion() if (withPermit.size != parcelIds.size) {
withPermit.forEach { releaseBlockVisitorPermit(it, permit) } withPermit.forEach { releaseBlockVisitorPermit(it, permit) }
} return null
}
return job
} val job = plugin.jobDispatcher.dispatch(function)
override fun swapParcels(parcelId1: ParcelId, parcelId2: ParcelId): Job? { plugin.launch {
val blockManager1 = getWorldById(parcelId1.worldId)?.blockManager ?: return null job.awaitCompletion()
val blockManager2 = getWorldById(parcelId2.worldId)?.blockManager ?: return null withPermit.forEach { releaseBlockVisitorPermit(it, permit) }
}
return trySubmitBlockVisitor(Permit(), parcelId1, parcelId2) {
var region1 = blockManager1.getRegion(parcelId1) return job
var region2 = blockManager2.getRegion(parcelId2) }
val size = region1.size.clampMax(region2.size) override fun swapParcels(parcelId1: ParcelId, parcelId2: ParcelId): Job? {
if (size != region1.size) { val world1 = getWorldById(parcelId1.worldId) ?: return null
region1 = region1.withSize(size) val world2 = getWorldById(parcelId2.worldId) ?: return null
region2 = region2.withSize(size) val blockManager1 = world1.blockManager
} val blockManager2 = world2.blockManager
val schematicOf1 = delegateWork(0.25) { Schematic().apply { load(blockManager1.world, region1) } } class CopyTarget(val world: World, val region: Region)
val schematicOf2 = delegateWork(0.25) { Schematic().apply { load(blockManager2.world, region2) } } class CopySource(val origin: Vec3i, val schematic: Schematic, val entities: Collection<Entity>)
delegateWork(0.25) { with(schematicOf1) { paste(blockManager2.world, region2.origin) } }
delegateWork(0.25) { with(schematicOf2) { paste(blockManager1.world, region1.origin) } } suspend fun JobScope.copy(source: CopySource, target: CopyTarget) {
} with(source.schematic) { paste(target.world, target.region.origin) }
}
for (entity in source.entities) {
/* entity.velocity = Vector(0, 0, 0)
fun loadWorlds(options: Options) { val location = entity.location
for ((worldName, worldOptions) in options.worlds.entries) { location.world = target.world
val world: ParcelWorld val coords = target.region.origin + (Vec3d(entity.location) - source.origin)
try { coords.copyInto(location)
entity.teleport(location)
world = ParcelWorldImpl( }
worldName, }
worldOptions,
worldOptions.generator.newGenerator(this, worldName), return trySubmitBlockVisitor(Permit(), parcelId1, parcelId2) {
plugin.storage, val temporaryParcel = world1.nextEmptyParcel()
plugin.globalPrivileges, ?: world2.nextEmptyParcel()
::DefaultParcelContainer) ?: return@trySubmitBlockVisitor
} catch (ex: Exception) { var region1 = blockManager1.getRegion(parcelId1)
ex.printStackTrace() var region2 = blockManager2.getRegion(parcelId2)
continue
} val size = region1.size.clampMax(region2.size)
if (size != region1.size) {
_worlds[worldName] = world region1 = region1.withSize(size)
} region2 = region2.withSize(size)
}
plugin.functionHelper.schedule(10) {
println("Parcels generating parcelProvider now") // Teleporting entities safely requires a different approach:
for ((name, world) in _worlds) { // * Copy schematic1 into temporary location
if (Bukkit.getWorld(name) == null) { // * Teleport entities1 into temporary location
val bworld = WorldCreator(name).generator(world.generator).createWorld() // * Copy schematic2 into parcel1
val spawn = world.generator.getFixedSpawnLocation(bworld, null) // * Teleport entities2 into parcel1
bworld.setSpawnLocation(spawn.x.floor(), spawn.y.floor(), spawn.z.floor()) // * Copy schematic1 into parcel2
} // * Teleport entities1 into parcel2
} // * Clear temporary location
val channel = plugin.storage.transmitAllParcelData() lateinit var source1: CopySource
val job = plugin.functionHelper.launchLazilyOnMainThread { lateinit var source2: CopySource
do {
val pair = channel.receiveOrNull() ?: break delegateWork(0.30) {
val parcel = getParcelById(pair.first) ?: continue val schematicOf1 = delegateWork(0.50) { Schematic().apply { load(blockManager1.world, region1) } }
pair.second?.let { parcel.copyDataIgnoringDatabase(it) } val schematicOf2 = delegateWork(0.50) { Schematic().apply { load(blockManager2.world, region2) } }
} while (true)
} source1 = CopySource(region1.origin, schematicOf1, blockManager1.getEntities(region1))
job.start() source2 = CopySource(region2.origin, schematicOf2, blockManager2.getEntities(region2))
} }
} val target1 = CopyTarget(blockManager1.world, region1)
*/ val target2 = CopyTarget(blockManager2.world, region2)
val targetTemp = CopyTarget(
temporaryParcel.world.world,
temporaryParcel.world.blockManager.getRegion(temporaryParcel.id)
)
delegateWork {
delegateWork(1.0 / 3.0) { copy(source1, targetTemp) }
delegateWork(1.0 / 3.0) { copy(source2, target1) }
delegateWork(1.0 / 3.0) { copy(source1, target2) }
}
// Separate job. Whatever
temporaryParcel.world.blockManager.clearParcel(temporaryParcel.id)
}
}
/*
fun loadWorlds(options: Options) {
for ((worldName, worldOptions) in options.worlds.entries) {
val world: ParcelWorld
try {
world = ParcelWorldImpl(
worldName,
worldOptions,
worldOptions.generator.newGenerator(this, worldName),
plugin.storage,
plugin.globalPrivileges,
::DefaultParcelContainer)
} catch (ex: Exception) {
ex.printStackTrace()
continue
}
_worlds[worldName] = world
}
plugin.functionHelper.schedule(10) {
println("Parcels generating parcelProvider now")
for ((name, world) in _worlds) {
if (Bukkit.getWorld(name) == null) {
val bworld = WorldCreator(name).generator(world.generator).createWorld()
val spawn = world.generator.getFixedSpawnLocation(bworld, null)
bworld.setSpawnLocation(spawn.x.floor(), spawn.y.floor(), spawn.z.floor())
}
}
val channel = plugin.storage.transmitAllParcelData()
val job = plugin.functionHelper.launchLazilyOnMainThread {
do {
val pair = channel.receiveOrNull() ?: break
val parcel = getParcelById(pair.first) ?: continue
pair.second?.let { parcel.copyDataIgnoringDatabase(it) }
} while (true)
}
job.start()
}
}
*/
} }

View File

@@ -1,282 +1,284 @@
@file:Suppress("NOTHING_TO_INLINE", "PARAMETER_NAME_CHANGED_ON_OVERRIDE", "LocalVariableName", "UNUSED_EXPRESSION") @file:Suppress("NOTHING_TO_INLINE", "PARAMETER_NAME_CHANGED_ON_OVERRIDE", "LocalVariableName", "UNUSED_EXPRESSION")
package io.dico.parcels2.storage.exposed package io.dico.parcels2.storage.exposed
import com.zaxxer.hikari.HikariDataSource import com.zaxxer.hikari.HikariDataSource
import io.dico.parcels2.* import io.dico.parcels2.*
import io.dico.parcels2.PlayerProfile.Star.name import io.dico.parcels2.PlayerProfile.Star.name
import io.dico.parcels2.storage.* import io.dico.parcels2.storage.*
import io.dico.parcels2.util.math.clampMax import io.dico.parcels2.util.math.clampMax
import io.dico.parcels2.util.ext.synchronized import io.dico.parcels2.util.ext.synchronized
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.channels.ArrayChannel import kotlinx.coroutines.channels.ArrayChannel
import kotlinx.coroutines.channels.LinkedListChannel import kotlinx.coroutines.channels.LinkedListChannel
import kotlinx.coroutines.channels.ReceiveChannel import kotlinx.coroutines.channels.ReceiveChannel
import kotlinx.coroutines.channels.SendChannel import kotlinx.coroutines.channels.SendChannel
import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SchemaUtils.create import org.jetbrains.exposed.sql.SchemaUtils.create
import org.jetbrains.exposed.sql.transactions.transaction import org.jetbrains.exposed.sql.transactions.transaction
import org.jetbrains.exposed.sql.vendors.DatabaseDialect import org.jetbrains.exposed.sql.vendors.DatabaseDialect
import org.joda.time.DateTime import org.joda.time.DateTime
import java.util.UUID import java.util.UUID
import javax.sql.DataSource import javax.sql.DataSource
class ExposedDatabaseException(message: String? = null) : Exception(message) class ExposedDatabaseException(message: String? = null) : Exception(message)
class ExposedBacking(private val dataSourceFactory: () -> DataSource, val poolSize: Int) : Backing, CoroutineScope { class ExposedBacking(private val dataSourceFactory: () -> DataSource, val poolSize: Int) : Backing, CoroutineScope {
override val name get() = "Exposed" override val name get() = "Exposed"
override val coroutineContext = Job() + newFixedThreadPoolContext(poolSize, "Parcels StorageThread") override val coroutineContext = Job() + newFixedThreadPoolContext(poolSize, "Parcels StorageThread")
private var dataSource: DataSource? = null private var dataSource: DataSource? = null
private var database: Database? = null private var database: Database? = null
private var isShutdown: Boolean = false private var isShutdown: Boolean = false
override val isConnected get() = database != null override val isConnected get() = database != null
override fun launchJob(job: Backing.() -> Unit): Job = launch { transaction { job() } } override fun launchJob(job: Backing.() -> Unit): Job = launch { transaction { job() } }
override fun <T> launchFuture(future: Backing.() -> T): Deferred<T> = async { transaction { future() } } override fun <T> launchFuture(future: Backing.() -> T): Deferred<T> = async { transaction { future() } }
override fun <T> openChannel(future: Backing.(SendChannel<T>) -> Unit): ReceiveChannel<T> { override fun <T> openChannel(future: Backing.(SendChannel<T>) -> Unit): ReceiveChannel<T> {
val channel = LinkedListChannel<T>() val channel = LinkedListChannel<T>()
launchJob { future(channel) } launchJob { future(channel) }
return channel return channel
} }
override fun <T> openChannelForWriting(action: Backing.(T) -> Unit): SendChannel<T> { override fun <T> openChannelForWriting(action: Backing.(T) -> Unit): SendChannel<T> {
val channel = ArrayChannel<T>(poolSize * 2) val channel = ArrayChannel<T>(poolSize * 2)
repeat(poolSize.clampMax(3)) { repeat(poolSize.clampMax(3)) {
launch { launch {
try { try {
while (true) { while (true) {
action(channel.receive()) action(channel.receive())
} }
} catch (ex: Exception) { } catch (ex: Exception) {
// channel closed // channel closed
} }
} }
} }
return channel return channel
} }
private fun <T> transaction(statement: Transaction.() -> T) = transaction(database!!, statement) private fun <T> transaction(statement: Transaction.() -> T) = transaction(database!!, statement)
companion object { companion object {
init { init {
Database.registerDialect("mariadb") { Database.registerDialect("mariadb") {
Class.forName("org.jetbrains.exposed.sql.vendors.MysqlDialect").newInstance() as DatabaseDialect Class.forName("org.jetbrains.exposed.sql.vendors.MysqlDialect").newInstance() as DatabaseDialect
} }
} }
} }
override fun init() { override fun init() {
synchronized { synchronized {
if (isShutdown || isConnected) throw IllegalStateException() if (isShutdown || isConnected) throw IllegalStateException()
dataSource = dataSourceFactory() val dataSource = dataSourceFactory()
database = Database.connect(dataSource!!) this.dataSource = dataSource
transaction(database!!) { val database = Database.connect(dataSource)
create(WorldsT, ProfilesT, ParcelsT, ParcelOptionsT, PrivilegesLocalT, PrivilegesGlobalT) this.database = database
} transaction(database) {
} create(WorldsT, ProfilesT, ParcelsT, ParcelOptionsT, PrivilegesLocalT, PrivilegesGlobalT)
} }
}
override fun shutdown() { }
synchronized {
if (isShutdown) throw IllegalStateException() override fun shutdown() {
isShutdown = true synchronized {
coroutineContext[Job]!!.cancel(CancellationException("ExposedBacking shutdown")) if (isShutdown) throw IllegalStateException()
dataSource?.let { isShutdown = true
(it as? HikariDataSource)?.close() coroutineContext.cancel(CancellationException("ExposedBacking shutdown"))
} dataSource?.let {
database = null (it as? HikariDataSource)?.close()
} }
} database = null
}
@Suppress("RedundantObjectTypeCheck") }
private fun PlayerProfile.toOwnerProfile(): PlayerProfile {
if (this is PlayerProfile.Star) return PlayerProfile.Fake(name) @Suppress("RedundantObjectTypeCheck")
return this private fun PlayerProfile.toOwnerProfile(): PlayerProfile {
} if (this is PlayerProfile.Star) return PlayerProfile.Fake(name)
return this
private fun PlayerProfile.Unresolved.toResolvedProfile(): PlayerProfile.Real { }
return resolve(getPlayerUuidForName(name) ?: throwException())
} private fun PlayerProfile.Unresolved.toResolvedProfile(): PlayerProfile.Real {
return resolve(getPlayerUuidForName(name) ?: throwException())
private fun PlayerProfile.toResolvedProfile(): PlayerProfile { }
if (this is PlayerProfile.Unresolved) return toResolvedProfile()
return this private fun PlayerProfile.toResolvedProfile(): PlayerProfile {
} if (this is PlayerProfile.Unresolved) return toResolvedProfile()
return this
private fun PlayerProfile.toRealProfile(): PlayerProfile.Real = when (this) { }
is PlayerProfile.Real -> this
is PlayerProfile.Fake -> throw IllegalArgumentException("Fake profiles are not accepted") private fun PlayerProfile.toRealProfile(): PlayerProfile.Real = when (this) {
is PlayerProfile.Unresolved -> toResolvedProfile() is PlayerProfile.Real -> this
else -> throw InternalError("Case should not be reached") is PlayerProfile.Fake -> throw IllegalArgumentException("Fake profiles are not accepted")
} is PlayerProfile.Unresolved -> toResolvedProfile()
else -> throw InternalError("Case should not be reached")
}
override fun getWorldCreationTime(worldId: ParcelWorldId): DateTime? {
return WorldsT.getWorldCreationTime(worldId)
} override fun getWorldCreationTime(worldId: ParcelWorldId): DateTime? {
return WorldsT.getWorldCreationTime(worldId)
override fun setWorldCreationTime(worldId: ParcelWorldId, time: DateTime) { }
WorldsT.setWorldCreationTime(worldId, time)
} override fun setWorldCreationTime(worldId: ParcelWorldId, time: DateTime) {
WorldsT.setWorldCreationTime(worldId, time)
override fun getPlayerUuidForName(name: String): UUID? { }
return ProfilesT.slice(ProfilesT.uuid).select { ProfilesT.name.upperCase() eq name.toUpperCase() }
.firstOrNull()?.let { it[ProfilesT.uuid]?.toUUID() } override fun getPlayerUuidForName(name: String): UUID? {
} return ProfilesT.slice(ProfilesT.uuid).select { ProfilesT.name.upperCase() eq name.toUpperCase() }
.firstOrNull()?.let { it[ProfilesT.uuid]?.toUUID() }
override fun updatePlayerName(uuid: UUID, name: String) { }
val binaryUuid = uuid.toByteArray()
ProfilesT.upsert(ProfilesT.uuid) { override fun updatePlayerName(uuid: UUID, name: String) {
it[ProfilesT.uuid] = binaryUuid val binaryUuid = uuid.toByteArray()
it[ProfilesT.name] = name ProfilesT.upsert(ProfilesT.uuid) {
} it[ProfilesT.uuid] = binaryUuid
} it[ProfilesT.name] = name
}
override fun transmitParcelData(channel: SendChannel<DataPair>, parcels: Sequence<ParcelId>) { }
for (parcel in parcels) {
val data = readParcelData(parcel) override fun transmitParcelData(channel: SendChannel<DataPair>, parcels: Sequence<ParcelId>) {
channel.offer(parcel to data) for (parcel in parcels) {
} val data = readParcelData(parcel)
channel.close() channel.offer(parcel to data)
} }
channel.close()
override fun transmitAllParcelData(channel: SendChannel<DataPair>) { }
ParcelsT.selectAll().forEach { row ->
val parcel = ParcelsT.getItem(row) ?: return@forEach override fun transmitAllParcelData(channel: SendChannel<DataPair>) {
val data = rowToParcelData(row) ParcelsT.selectAll().forEach { row ->
channel.offer(parcel to data) val parcel = ParcelsT.getItem(row) ?: return@forEach
} val data = rowToParcelData(row)
channel.close() channel.offer(parcel to data)
} }
channel.close()
override fun readParcelData(parcel: ParcelId): ParcelDataHolder? { }
val row = ParcelsT.getRow(parcel) ?: return null
return rowToParcelData(row) override fun readParcelData(parcel: ParcelId): ParcelDataHolder? {
} val row = ParcelsT.getRow(parcel) ?: return null
return rowToParcelData(row)
override fun getOwnedParcels(user: PlayerProfile): List<ParcelId> { }
val user_id = ProfilesT.getId(user.toOwnerProfile()) ?: return emptyList()
return ParcelsT.select { ParcelsT.owner_id eq user_id } override fun getOwnedParcels(user: PlayerProfile): List<ParcelId> {
.orderBy(ParcelsT.claim_time, isAsc = true) val user_id = ProfilesT.getId(user.toOwnerProfile()) ?: return emptyList()
.mapNotNull(ParcelsT::getItem) return ParcelsT.select { ParcelsT.owner_id eq user_id }
.toList() .orderBy(ParcelsT.claim_time, isAsc = true)
} .mapNotNull(ParcelsT::getItem)
.toList()
override fun setParcelData(parcel: ParcelId, data: ParcelDataHolder?) { }
if (data == null) {
transaction { override fun setParcelData(parcel: ParcelId, data: ParcelDataHolder?) {
ParcelsT.getId(parcel)?.let { id -> if (data == null) {
ParcelsT.deleteIgnoreWhere { ParcelsT.id eq id } transaction {
ParcelsT.getId(parcel)?.let { id ->
// Below should cascade automatically ParcelsT.deleteIgnoreWhere { ParcelsT.id eq id }
/*
PrivilegesLocalT.deleteIgnoreWhere { PrivilegesLocalT.parcel_id eq id } // Below should cascade automatically
ParcelOptionsT.deleteIgnoreWhere(limit = 1) { ParcelOptionsT.parcel_id eq id } /*
*/ PrivilegesLocalT.deleteIgnoreWhere { PrivilegesLocalT.parcel_id eq id }
} ParcelOptionsT.deleteIgnoreWhere(limit = 1) { ParcelOptionsT.parcel_id eq id }
*/
} }
return
} }
return
transaction { }
val id = ParcelsT.getOrInitId(parcel)
PrivilegesLocalT.deleteIgnoreWhere { PrivilegesLocalT.attach_id eq id } transaction {
} val id = ParcelsT.getOrInitId(parcel)
PrivilegesLocalT.deleteIgnoreWhere { PrivilegesLocalT.attach_id eq id }
setParcelOwner(parcel, data.owner) }
for ((profile, privilege) in data.privilegeMap) { setParcelOwner(parcel, data.owner)
PrivilegesLocalT.setPrivilege(parcel, profile, privilege)
} for ((profile, privilege) in data.privilegeMap) {
PrivilegesLocalT.setPrivilege(parcel, profile, privilege)
data.privilegeOfStar.takeIf { it != Privilege.DEFAULT }?.let { privilege -> }
PrivilegesLocalT.setPrivilege(parcel, PlayerProfile.Star, privilege)
} data.privilegeOfStar.takeIf { it != Privilege.DEFAULT }?.let { privilege ->
PrivilegesLocalT.setPrivilege(parcel, PlayerProfile.Star, privilege)
setParcelOptionsInteractConfig(parcel, data.interactableConfig) }
}
setParcelOptionsInteractConfig(parcel, data.interactableConfig)
override fun setParcelOwner(parcel: ParcelId, owner: PlayerProfile?) { }
val id = if (owner == null)
ParcelsT.getId(parcel) ?: return override fun setParcelOwner(parcel: ParcelId, owner: PlayerProfile?) {
else val id = if (owner == null)
ParcelsT.getOrInitId(parcel) ParcelsT.getId(parcel) ?: return
else
val owner_id = owner?.let { ProfilesT.getOrInitId(it.toOwnerProfile()) } ParcelsT.getOrInitId(parcel)
val time = owner?.let { DateTime.now() }
val owner_id = owner?.let { ProfilesT.getOrInitId(it.toOwnerProfile()) }
ParcelsT.update({ ParcelsT.id eq id }) { val time = owner?.let { DateTime.now() }
it[ParcelsT.owner_id] = owner_id
it[claim_time] = time ParcelsT.update({ ParcelsT.id eq id }) {
it[sign_oudated] = false it[ParcelsT.owner_id] = owner_id
} it[claim_time] = time
} it[sign_oudated] = false
}
override fun setParcelOwnerSignOutdated(parcel: ParcelId, outdated: Boolean) { }
val id = ParcelsT.getId(parcel) ?: return
ParcelsT.update({ ParcelsT.id eq id }) { override fun setParcelOwnerSignOutdated(parcel: ParcelId, outdated: Boolean) {
it[sign_oudated] = outdated val id = ParcelsT.getId(parcel) ?: return
} ParcelsT.update({ ParcelsT.id eq id }) {
} it[sign_oudated] = outdated
}
override fun setLocalPrivilege(parcel: ParcelId, player: PlayerProfile, privilege: Privilege) { }
PrivilegesLocalT.setPrivilege(parcel, player.toRealProfile(), privilege)
} override fun setLocalPrivilege(parcel: ParcelId, player: PlayerProfile, privilege: Privilege) {
PrivilegesLocalT.setPrivilege(parcel, player.toRealProfile(), privilege)
override fun setParcelOptionsInteractConfig(parcel: ParcelId, config: InteractableConfiguration) { }
val bitmaskArray = (config as? BitmaskInteractableConfiguration ?: return).bitmaskArray
val isAllZero = !bitmaskArray.fold(false) { cur, elem -> cur || elem != 0 } override fun setParcelOptionsInteractConfig(parcel: ParcelId, config: InteractableConfiguration) {
val bitmaskArray = (config as? BitmaskInteractableConfiguration ?: return).bitmaskArray
if (isAllZero) { val isAllZero = !bitmaskArray.fold(false) { cur, elem -> cur || elem != 0 }
val id = ParcelsT.getId(parcel) ?: return
ParcelOptionsT.deleteWhere { ParcelOptionsT.parcel_id eq id } if (isAllZero) {
return val id = ParcelsT.getId(parcel) ?: return
} ParcelOptionsT.deleteWhere { ParcelOptionsT.parcel_id eq id }
return
if (bitmaskArray.size != 1) throw IllegalArgumentException() }
val array = bitmaskArray.toByteArray()
val id = ParcelsT.getOrInitId(parcel) if (bitmaskArray.size != 1) throw IllegalArgumentException()
ParcelOptionsT.upsert(ParcelOptionsT.parcel_id) { val array = bitmaskArray.toByteArray()
it[parcel_id] = id val id = ParcelsT.getOrInitId(parcel)
it[interact_bitmask] = array ParcelOptionsT.upsert(ParcelOptionsT.parcel_id) {
} it[parcel_id] = id
} it[interact_bitmask] = array
}
override fun transmitAllGlobalPrivileges(channel: SendChannel<PrivilegePair<PlayerProfile>>) { }
PrivilegesGlobalT.sendAllPrivilegesH(channel)
channel.close() override fun transmitAllGlobalPrivileges(channel: SendChannel<PrivilegePair<PlayerProfile>>) {
} PrivilegesGlobalT.sendAllPrivilegesH(channel)
channel.close()
override fun readGlobalPrivileges(owner: PlayerProfile): PrivilegesHolder? { }
return PrivilegesGlobalT.readPrivileges(ProfilesT.getId(owner.toOwnerProfile()) ?: return null)
} override fun readGlobalPrivileges(owner: PlayerProfile): PrivilegesHolder? {
return PrivilegesGlobalT.readPrivileges(ProfilesT.getId(owner.toOwnerProfile()) ?: return null)
override fun setGlobalPrivilege(owner: PlayerProfile, player: PlayerProfile, privilege: Privilege) { }
PrivilegesGlobalT.setPrivilege(owner, player.toRealProfile(), privilege)
} override fun setGlobalPrivilege(owner: PlayerProfile, player: PlayerProfile, privilege: Privilege) {
PrivilegesGlobalT.setPrivilege(owner, player.toRealProfile(), privilege)
private fun rowToParcelData(row: ResultRow) = ParcelDataHolder().apply { }
owner = row[ParcelsT.owner_id]?.let { ProfilesT.getItem(it) }
lastClaimTime = row[ParcelsT.claim_time] private fun rowToParcelData(row: ResultRow) = ParcelDataHolder().apply {
isOwnerSignOutdated = row[ParcelsT.sign_oudated] owner = row[ParcelsT.owner_id]?.let { ProfilesT.getItem(it) }
lastClaimTime = row[ParcelsT.claim_time]
val id = row[ParcelsT.id] isOwnerSignOutdated = row[ParcelsT.sign_oudated]
ParcelOptionsT.select { ParcelOptionsT.parcel_id eq id }.firstOrNull()?.let { optrow ->
val source = optrow[ParcelOptionsT.interact_bitmask].toIntArray() val id = row[ParcelsT.id]
val target = (interactableConfig as? BitmaskInteractableConfiguration ?: return@let).bitmaskArray ParcelOptionsT.select { ParcelOptionsT.parcel_id eq id }.firstOrNull()?.let { optrow ->
System.arraycopy(source, 0, target, 0, source.size.clampMax(target.size)) val source = optrow[ParcelOptionsT.interact_bitmask].toIntArray()
} val target = (interactableConfig as? BitmaskInteractableConfiguration ?: return@let).bitmaskArray
System.arraycopy(source, 0, target, 0, source.size.clampMax(target.size))
val privileges = PrivilegesLocalT.readPrivileges(id) }
if (privileges != null) {
copyPrivilegesFrom(privileges) val privileges = PrivilegesLocalT.readPrivileges(id)
} if (privileges != null) {
} copyPrivilegesFrom(privileges)
}
} }
}

View File

@@ -1,14 +1,23 @@
package io.dico.parcels2.util package io.dico.parcels2.util
import io.dico.parcels2.util.ext.isValid import io.dico.parcels2.util.ext.isValid
import org.bukkit.Bukkit import org.bukkit.Bukkit
import org.bukkit.OfflinePlayer import org.bukkit.OfflinePlayer
import java.util.UUID import java.lang.IllegalArgumentException
import java.util.UUID
fun getPlayerName(uuid: UUID): String? = getOfflinePlayer(uuid)?.name
fun getPlayerName(uuid: UUID): String? = getOfflinePlayer(uuid)?.name
fun getOfflinePlayer(uuid: UUID): OfflinePlayer? = Bukkit.getOfflinePlayer(uuid).takeIf { it.isValid }
fun getOfflinePlayer(uuid: UUID): OfflinePlayer? = Bukkit.getOfflinePlayer(uuid).takeIf { it.isValid }
fun getOfflinePlayer(name: String): OfflinePlayer? = Bukkit.getOfflinePlayer(name).takeIf { it.isValid }
fun getOfflinePlayer(name: String): OfflinePlayer? = Bukkit.getOfflinePlayer(name).takeIf { it.isValid }
fun isServerThread(): Boolean = Thread.currentThread().name == "Server thread"
fun isServerThread(): Boolean = Thread.currentThread().name == "Server thread"
fun isPlayerNameValid(name: String): Boolean =
name.length in 3..16
&& name.find { it !in "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_" } == null
fun checkPlayerNameValid(name: String) {
if (!isPlayerNameValid(name)) throw IllegalArgumentException("Invalid player name: $name")
}

View File

@@ -0,0 +1,18 @@
package io.dico.parcels2.util
import org.bukkit.plugin.Plugin
import org.bukkit.scheduler.BukkitTask
interface PluginAware {
val plugin: Plugin
}
inline fun PluginAware.schedule(delay: Int = 0, crossinline task: () -> Unit): BukkitTask {
return plugin.server.scheduler.runTaskLater(plugin, { task() }, delay.toLong())
}
inline fun PluginAware.scheduleRepeating(interval: Int, delay: Int = 0, crossinline task: () -> Unit): BukkitTask {
return plugin.server.scheduler.runTaskTimer(plugin, { task() }, delay.toLong(), interval.toLong())
}

View File

@@ -1,20 +0,0 @@
package io.dico.parcels2.util
import org.bukkit.plugin.Plugin
import org.bukkit.scheduler.BukkitTask
interface PluginScheduler {
val plugin: Plugin
fun schedule(delay: Int, task: () -> Unit): BukkitTask {
return plugin.server.scheduler.runTaskLater(plugin, task, delay.toLong())
}
fun scheduleRepeating(delay: Int, interval: Int, task: () -> Unit): BukkitTask {
return plugin.server.scheduler.runTaskTimer(plugin, task, delay.toLong(), interval.toLong())
}
}
@Suppress("NOTHING_TO_INLINE")
inline fun PluginScheduler.schedule(noinline task: () -> Unit) = schedule(0, task)

View File

@@ -1,53 +1,61 @@
package io.dico.parcels2.util.math package io.dico.parcels2.util.math
import org.bukkit.Location import org.bukkit.Location
import kotlin.math.sqrt import kotlin.math.sqrt
data class Vec3d( data class Vec3d(
val x: Double, val x: Double,
val y: Double, val y: Double,
val z: Double val z: Double
) { ) {
constructor(loc: Location) : this(loc.x, loc.y, loc.z) constructor(loc: Location) : this(loc.x, loc.y, loc.z)
operator fun plus(o: Vec3d) = Vec3d(x + o.x, y + o.y, z + o.z) operator fun plus(o: Vec3d) = Vec3d(x + o.x, y + o.y, z + o.z)
operator fun minus(o: Vec3i) = Vec3d(x - o.x, y - o.y, z - o.z) operator fun plus(o: Vec3i) = Vec3d(x + o.x, y + o.y, z + o.z)
infix fun addX(o: Double) = Vec3d(x + o, y, z) operator fun minus(o: Vec3d) = Vec3d(x - o.x, y - o.y, z - o.z)
infix fun addY(o: Double) = Vec3d(x, y + o, z) operator fun minus(o: Vec3i) = Vec3d(x - o.x, y - o.y, z - o.z)
infix fun addZ(o: Double) = Vec3d(x, y, z + o) infix fun addX(o: Double) = Vec3d(x + o, y, z)
infix fun withX(o: Double) = Vec3d(o, y, z) infix fun addY(o: Double) = Vec3d(x, y + o, z)
infix fun withY(o: Double) = Vec3d(x, o, z) infix fun addZ(o: Double) = Vec3d(x, y, z + o)
infix fun withZ(o: Double) = Vec3d(x, y, o) infix fun withX(o: Double) = Vec3d(o, y, z)
fun add(ox: Double, oy: Double, oz: Double) = Vec3d(x + ox, y + oy, z + oz) infix fun withY(o: Double) = Vec3d(x, o, z)
fun toVec3i() = Vec3i(x.floor(), y.floor(), z.floor()) infix fun withZ(o: Double) = Vec3d(x, y, o)
fun add(ox: Double, oy: Double, oz: Double) = Vec3d(x + ox, y + oy, z + oz)
fun distanceSquared(o: Vec3d): Double { fun toVec3i() = Vec3i(x.floor(), y.floor(), z.floor())
val dx = o.x - x
val dy = o.y - y fun distanceSquared(o: Vec3d): Double {
val dz = o.z - z val dx = o.x - x
return dx * dx + dy * dy + dz * dz val dy = o.y - y
} val dz = o.z - z
return dx * dx + dy * dy + dz * dz
fun distance(o: Vec3d) = sqrt(distanceSquared(o)) }
operator fun get(dimension: Dimension) = fun distance(o: Vec3d) = sqrt(distanceSquared(o))
when (dimension) {
Dimension.X -> x operator fun get(dimension: Dimension) =
Dimension.Y -> y when (dimension) {
Dimension.Z -> z Dimension.X -> x
} Dimension.Y -> y
Dimension.Z -> z
fun with(dimension: Dimension, value: Double) = }
when (dimension) {
Dimension.X -> withX(value) fun with(dimension: Dimension, value: Double) =
Dimension.Y -> withY(value) when (dimension) {
Dimension.Z -> withZ(value) Dimension.X -> withX(value)
} Dimension.Y -> withY(value)
Dimension.Z -> withZ(value)
fun add(dimension: Dimension, value: Double) = }
when (dimension) {
Dimension.X -> addX(value) fun add(dimension: Dimension, value: Double) =
Dimension.Y -> addY(value) when (dimension) {
Dimension.Z -> addZ(value) Dimension.X -> addX(value)
} Dimension.Y -> addY(value)
Dimension.Z -> addZ(value)
}
fun copyInto(loc: Location) {
loc.x = x
loc.y = y
loc.z = z
}
} }

View File

@@ -1,105 +1,107 @@
package io.dico.parcels2.util.math package io.dico.parcels2.util.math
import org.bukkit.Location import org.bukkit.Location
import org.bukkit.World import org.bukkit.World
import org.bukkit.block.Block import org.bukkit.block.Block
import org.bukkit.block.BlockFace import org.bukkit.block.BlockFace
data class Vec3i( data class Vec3i(
val x: Int, val x: Int,
val y: Int, val y: Int,
val z: Int val z: Int
) { ) {
constructor(loc: Location) : this(loc.blockX, loc.blockY, loc.blockZ) constructor(loc: Location) : this(loc.blockX, loc.blockY, loc.blockZ)
constructor(block: Block) : this(block.x, block.y, block.z) constructor(block: Block) : this(block.x, block.y, block.z)
fun toVec2i() = Vec2i(x, z) fun toVec2i() = Vec2i(x, z)
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) operator fun plus(o: Vec3d) = Vec3d(x + o.x, y + o.y, z + o.z)
infix fun addX(o: Int) = Vec3i(x + o, y, z) operator fun minus(o: Vec3i) = Vec3i(x - o.x, y - o.y, z - o.z)
infix fun addY(o: Int) = Vec3i(x, y + o, z) operator fun minus(o: Vec3d) = Vec3d(x - o.x, y - o.y, z - o.z)
infix fun addZ(o: Int) = Vec3i(x, y, z + o) infix fun addX(o: Int) = Vec3i(x + o, y, z)
infix fun withX(o: Int) = Vec3i(o, y, z) infix fun addY(o: Int) = Vec3i(x, y + o, z)
infix fun withY(o: Int) = Vec3i(x, o, z) infix fun addZ(o: Int) = Vec3i(x, y, z + o)
infix fun withZ(o: Int) = Vec3i(x, y, o) infix fun withX(o: Int) = Vec3i(o, y, z)
fun add(ox: Int, oy: Int, oz: Int) = Vec3i(x + ox, y + oy, z + oz) infix fun withY(o: Int) = Vec3i(x, o, z)
fun neg() = Vec3i(-x, -y, -z) infix fun withZ(o: Int) = Vec3i(x, y, o)
fun clampMax(o: Vec3i) = Vec3i(x.clampMax(o.x), y.clampMax(o.y), z.clampMax(o.z)) fun add(ox: Int, oy: Int, oz: Int) = Vec3i(x + ox, y + oy, z + oz)
fun neg() = Vec3i(-x, -y, -z)
operator fun get(dimension: Dimension) = fun clampMax(o: Vec3i) = Vec3i(x.clampMax(o.x), y.clampMax(o.y), z.clampMax(o.z))
when (dimension) {
Dimension.X -> x operator fun get(dimension: Dimension) =
Dimension.Y -> y when (dimension) {
Dimension.Z -> z Dimension.X -> x
} Dimension.Y -> y
Dimension.Z -> z
fun with(dimension: Dimension, value: Int) = }
when (dimension) {
Dimension.X -> withX(value) fun with(dimension: Dimension, value: Int) =
Dimension.Y -> withY(value) when (dimension) {
Dimension.Z -> withZ(value) Dimension.X -> withX(value)
} Dimension.Y -> withY(value)
Dimension.Z -> withZ(value)
fun add(dimension: Dimension, value: Int) = }
when (dimension) {
Dimension.X -> addX(value) fun add(dimension: Dimension, value: Int) =
Dimension.Y -> addY(value) when (dimension) {
Dimension.Z -> addZ(value) Dimension.X -> addX(value)
} Dimension.Y -> addY(value)
Dimension.Z -> addZ(value)
companion object { }
private operator fun invoke(face: BlockFace) = Vec3i(face.modX, face.modY, face.modZ)
val down = Vec3i(BlockFace.DOWN) companion object {
val up = Vec3i(BlockFace.UP) private operator fun invoke(face: BlockFace) = Vec3i(face.modX, face.modY, face.modZ)
val north = Vec3i(BlockFace.NORTH) val down = Vec3i(BlockFace.DOWN)
val east = Vec3i(BlockFace.EAST) val up = Vec3i(BlockFace.UP)
val south = Vec3i(BlockFace.SOUTH) val north = Vec3i(BlockFace.NORTH)
val west = Vec3i(BlockFace.WEST) val east = Vec3i(BlockFace.EAST)
val south = Vec3i(BlockFace.SOUTH)
fun convert(face: BlockFace) = when (face) { val west = Vec3i(BlockFace.WEST)
BlockFace.DOWN -> down
BlockFace.UP -> up fun convert(face: BlockFace) = when (face) {
BlockFace.NORTH -> north BlockFace.DOWN -> down
BlockFace.EAST -> east BlockFace.UP -> up
BlockFace.SOUTH -> south BlockFace.NORTH -> north
BlockFace.WEST -> west BlockFace.EAST -> east
else -> Vec3i(face) BlockFace.SOUTH -> south
} BlockFace.WEST -> west
} else -> Vec3i(face)
} }
}
@Suppress("NOTHING_TO_INLINE") }
inline operator fun World.get(vec: Vec3i): Block = getBlockAt(vec.x, vec.y, vec.z)
@Suppress("NOTHING_TO_INLINE")
/* inline operator fun World.get(vec: Vec3i): Block = getBlockAt(vec.x, vec.y, vec.z)
private /*inline */class IVec3i(private val data: Long) {
/*
private companion object { private /*inline */class IVec3i(private val data: Long) {
const val mask = 0x001F_FFFF
const val max: Int = 0x000F_FFFF // +1048575 private companion object {
const val min: Int = -max - 1 // -1048575 // 0xFFF0_0000 const val mask = 0x001F_FFFF
const val max: Int = 0x000F_FFFF // +1048575
@Suppress("NOTHING_TO_INLINE") const val min: Int = -max - 1 // -1048575 // 0xFFF0_0000
inline fun Int.compressIntoLong(offset: Int): Long {
if (this !in min..max) throw IllegalArgumentException() @Suppress("NOTHING_TO_INLINE")
return and(mask).toLong().shl(offset) inline fun Int.compressIntoLong(offset: Int): Long {
} if (this !in min..max) throw IllegalArgumentException()
return and(mask).toLong().shl(offset)
@Suppress("NOTHING_TO_INLINE") }
inline fun Long.extractInt(offset: Int): Int {
val result = ushr(offset).toInt().and(mask) @Suppress("NOTHING_TO_INLINE")
return if (result > max) result or mask.inv() else result inline fun Long.extractInt(offset: Int): Int {
} val result = ushr(offset).toInt().and(mask)
} return if (result > max) result or mask.inv() else result
}
constructor(x: Int, y: Int, z: Int) : this( }
x.compressIntoLong(42)
or y.compressIntoLong(21) constructor(x: Int, y: Int, z: Int) : this(
or z.compressIntoLong(0)) x.compressIntoLong(42)
or y.compressIntoLong(21)
val x: Int get() = data.extractInt(42) or z.compressIntoLong(0))
val y: Int get() = data.extractInt(21)
val z: Int get() = data.extractInt(0) val x: Int get() = data.extractInt(42)
val y: Int get() = data.extractInt(21)
} val z: Int get() = data.extractInt(0)
*/
}
*/

View File

@@ -0,0 +1,9 @@
package io.dico.parcels2.util
fun doParallel() {
val array = IntArray(1000)
IntRange(0, 1000).chunked()
}

206
todo.md
View File

@@ -1,103 +1,103 @@
# Parcels Todo list # Parcels Todo list
Commands Commands
- -
Basically all admin commands. 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).~~
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~~ 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)
layers are done upwards, and the rest downwards.~~ layers are done upwards, and the rest downwards.~~
Events Events
- -
Prevent block spreading subject to conditions. Prevent block spreading subject to conditions.
Scan through blocks that were added since original Parcels implementation, Scan through blocks that were added since original Parcels implementation,
that might introduce things that need to be checked or listened for. that might introduce things that need to be checked or listened for.
~~WorldEdit Listener.~~ ~~WorldEdit Listener.~~
Limit number of beacons in a parcel and/or avoid potion effects being applied outside the parcel. Limit number of beacons in a parcel and/or avoid potion effects being applied outside the parcel.
Database Database
- -
Find and patch ways to add new useless entries (for regular players at least) Find and patch ways to add new useless entries (for regular players at least)
Prevent invalid player names from being saved to the database. ~~Prevent invalid player names from being saved to the database.
Here, invalid player names mean names that contain invalid characters. Here, invalid player names mean names that contain invalid characters.~~
Use an atomic GET OR INSERT query so that parallel execution doesn't cause problems Use an atomic GET OR INSERT query so that parallel execution doesn't cause problems
(as is currently the case when migrating). (as is currently the case when migrating).
Implement a container that doesn't require loading all parcel data on startup (Complex). Implement a container that doesn't require loading all parcel data on startup (Complex).
~~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~~
After testing on Redstoner After testing on Redstoner
- -
Clear (and swap) entities on /p clear etc ~~Clear (and swap) entities on /p clear etc~~
Fix command lag Fix command lag
Chorus fruit can grow outside plots Chorus fruit can grow outside plots
Vines can grow outside plots Vines can grow outside plots
Ghasts, bats, phantoms and magma cubes can be spawned with eggs Ghasts, bats, phantoms and magma cubes can be spawned with eggs
ParcelTarget doesn't report a world that wasn't found correctly ParcelTarget doesn't report a world that wasn't found correctly
Jumping on turtle eggs is considered as interacting with pressure plates Jumping on turtle eggs is considered as interacting with pressure plates
Setbiome internal error when progress reporting is attached Setbiome internal error when progress reporting is attached
Unclaim doesn't clear the plot. It probably should. Unclaim doesn't clear the plot. It probably should.
Players can shoot boats and minecarts. Players can shoot boats and minecarts.
You can use disabled items by rightclicking air. You can use disabled items by rightclicking air.
Tab complete isn't working correctly. Tab complete isn't working correctly.
~~Bed use in nether and end might not have to be blocked.~~ ~~Bed use in nether and end might not have to be blocked.~~