Merge pull request #89 from PolyhedralDev/dev/commands

Platform-agnostic command API
This commit is contained in:
dfsek
2021-03-10 16:16:40 -07:00
committed by GitHub
127 changed files with 2419 additions and 1857 deletions

View File

@@ -49,4 +49,19 @@ public interface TerraPlugin extends LoaderRegistrar {
DebugLogger getDebugLogger();
EventManager getEventManager();
default String getVersion() {
return "@VERSION@";
}
/**
* Runs a task that may or may not be thread safe, depending on platform.
* <p>
* Allows platforms to define what code is safe to be run asynchronously.
*
* @param task Task to be run.
*/
default void runPossiblyUnsafeTask(Runnable task) {
task.run();
}
}

View File

@@ -0,0 +1,17 @@
package com.dfsek.terra.api.command;
import com.dfsek.terra.api.command.exception.CommandException;
import com.dfsek.terra.api.command.exception.MalformedCommandException;
import com.dfsek.terra.api.platform.CommandSender;
import java.util.List;
public interface CommandManager {
void execute(String command, CommandSender sender, List<String> args) throws CommandException;
void register(String name, Class<? extends CommandTemplate> clazz) throws MalformedCommandException;
List<String> tabComplete(String command, CommandSender sender, List<String> args) throws CommandException;
int getMaxArgumentDepth();
}

View File

@@ -0,0 +1,7 @@
package com.dfsek.terra.api.command;
import com.dfsek.terra.api.platform.CommandSender;
public interface CommandTemplate {
void execute(CommandSender sender);
}

View File

@@ -0,0 +1,38 @@
package com.dfsek.terra.api.command;
import com.dfsek.terra.api.platform.CommandSender;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
public final class ExecutionState {
private final Set<String> switches = new HashSet<>();
private final Map<String, String> args = new HashMap<>();
private final CommandSender sender;
protected ExecutionState(CommandSender sender) {
this.sender = sender;
}
protected void addSwitch(String flag) {
switches.add(flag);
}
protected void addArgument(String arg, String value) {
args.put(arg, value);
}
public String getArgument(String argument) {
return args.get(argument);
}
public boolean hasSwitch(String flag) {
return switches.contains(flag);
}
public CommandSender getSender() {
return sender;
}
}

View File

@@ -0,0 +1,284 @@
package com.dfsek.terra.api.command;
import com.dfsek.terra.api.TerraPlugin;
import com.dfsek.terra.api.command.annotation.Argument;
import com.dfsek.terra.api.command.annotation.Command;
import com.dfsek.terra.api.command.annotation.Subcommand;
import com.dfsek.terra.api.command.annotation.Switch;
import com.dfsek.terra.api.command.annotation.inject.ArgumentTarget;
import com.dfsek.terra.api.command.annotation.inject.SwitchTarget;
import com.dfsek.terra.api.command.annotation.type.DebugCommand;
import com.dfsek.terra.api.command.annotation.type.PlayerCommand;
import com.dfsek.terra.api.command.annotation.type.WorldCommand;
import com.dfsek.terra.api.command.arg.ArgumentParser;
import com.dfsek.terra.api.command.exception.CommandException;
import com.dfsek.terra.api.command.exception.ExecutionException;
import com.dfsek.terra.api.command.exception.InvalidArgumentsException;
import com.dfsek.terra.api.command.exception.MalformedCommandException;
import com.dfsek.terra.api.command.exception.SwitchFormatException;
import com.dfsek.terra.api.command.tab.TabCompleter;
import com.dfsek.terra.api.injection.Injector;
import com.dfsek.terra.api.injection.exception.InjectionException;
import com.dfsek.terra.api.platform.CommandSender;
import com.dfsek.terra.api.platform.entity.Player;
import com.dfsek.terra.api.util.ReflectionUtil;
import com.dfsek.terra.world.TerraWorld;
import net.jafama.FastMath;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class TerraCommandManager implements CommandManager {
private final Map<String, CommandHolder> commands = new HashMap<>();
private final Injector<TerraPlugin> pluginInjector;
private final TerraPlugin main;
public TerraCommandManager(TerraPlugin main) {
this.main = main;
this.pluginInjector = new Injector<>(main);
pluginInjector.addExplicitTarget(TerraPlugin.class);
}
@Override
public void execute(String commandName, CommandSender sender, List<String> argsIn) throws CommandException {
if(!commands.containsKey(commandName)) throw new InvalidArgumentsException("No such command \"" + commandName + "\"");
execute(commands.get(commandName), sender, new ArrayList<>(argsIn));
}
private void execute(CommandHolder commandHolder, CommandSender sender, List<String> args) throws CommandException {
Class<? extends CommandTemplate> commandClass = commandHolder.clazz;
if(commandClass.isAnnotationPresent(DebugCommand.class) && !main.isDebug()) {
sender.sendMessage("Command must be executed with debug mode enabled.");
return;
}
if(commandClass.isAnnotationPresent(PlayerCommand.class) && !(sender instanceof Player)) {
sender.sendMessage("Command must be executed by player.");
return;
}
if(commandClass.isAnnotationPresent(WorldCommand.class) && (!(sender instanceof Player) || !TerraWorld.isTerraWorld(((Player) sender).getWorld()))) {
sender.sendMessage("Command must be executed in a Terra world.");
return;
}
List<String> ogArgs = new ArrayList<>(args);
ExecutionState state = new ExecutionState(sender);
if(!commandClass.isAnnotationPresent(Command.class)) {
invoke(commandClass, state, commandHolder);
return;
}
Command command = commandClass.getAnnotation(Command.class);
if(command.arguments().length == 0 && command.subcommands().length == 0) {
if(args.isEmpty()) {
invoke(commandClass, state, commandHolder);
return;
} else throw new InvalidArgumentsException("Expected 0 arguments, found " + args.size());
}
if(!args.isEmpty() && commandHolder.subcommands.containsKey(args.get(0))) {
String c = args.get(0);
args.remove(0);
execute(commandHolder.subcommands.get(c), sender, args);
return;
}
boolean req = true;
for(Argument argument : command.arguments()) {
if(!req && argument.required()) {
throw new MalformedCommandException("Required arguments must come first! Arguments: " + Arrays.toString(command.arguments()));
}
req = argument.required();
if(args.isEmpty()) {
if(req) throw new InvalidArgumentsException("Invalid arguments: " + ogArgs + ", usage: " + command.usage());
break;
}
String arg = args.get(0);
if(arg.startsWith("-")) { // switches have started.
if(req) throw new InvalidArgumentsException("Switches must come after arguments.");
break;
}
state.addArgument(argument.value(), args.remove(0));
}
while(!args.isEmpty()) {
String aSwitch = args.remove(0);
if(!aSwitch.startsWith("-")) throw new SwitchFormatException("Invalid switch \"" + aSwitch + "\"");
String val = aSwitch.substring(1); // remove dash
if(!commandHolder.switches.containsKey(val)) throw new SwitchFormatException("No such switch \"" + aSwitch + "\"");
state.addSwitch(commandHolder.switches.get(val));
}
invoke(commandClass, state, commandHolder);
}
private void invoke(Class<? extends CommandTemplate> clazz, ExecutionState state, CommandHolder holder) throws CommandException {
try {
CommandTemplate template = clazz.getConstructor().newInstance();
pluginInjector.inject(template);
for(Field field : ReflectionUtil.getFields(clazz)) {
if(field.isAnnotationPresent(ArgumentTarget.class)) {
ArgumentTarget argumentTarget = field.getAnnotation(ArgumentTarget.class);
if(!holder.argumentMap.containsKey(argumentTarget.value())) {
throw new MalformedCommandException("Argument Target specifies nonexistent argument \"" + argumentTarget.value() + "\"");
}
String argument = argumentTarget.value();
ArgumentParser<?> argumentParser = holder.argumentMap.get(argumentTarget.value()).argumentParser().getConstructor().newInstance();
pluginInjector.inject(argumentParser);
field.setAccessible(true);
String value = state.getArgument(argument);
if(value == null) value = holder.argumentMap.get(argumentTarget.value()).defaultValue();
field.set(template, argumentParser.parse(state.getSender(), value));
}
if(field.isAnnotationPresent(SwitchTarget.class)) {
SwitchTarget switchTarget = field.getAnnotation(SwitchTarget.class);
if(!holder.switches.containsValue(switchTarget.value())) {
System.out.println(holder.switches);
throw new MalformedCommandException("Switch Target specifies nonexistent switch \"" + switchTarget.value() + "\"");
}
if(!(field.getType() == boolean.class)) {
throw new MalformedCommandException("Switch Target must be of type boolean.");
}
field.setAccessible(true);
field.setBoolean(template, state.hasSwitch(switchTarget.value()));
}
}
try {
template.execute(state.getSender());
} catch(Throwable e) {
throw new ExecutionException("Failed to execute command: " + e.getMessage(), e);
}
} catch(InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException | InjectionException e) {
throw new MalformedCommandException("Unable to reflectively instantiate command: ", e);
}
}
@Override
public void register(String name, Class<? extends CommandTemplate> clazz) throws MalformedCommandException {
commands.put(name, new CommandHolder(clazz));
}
@Override
public List<String> tabComplete(String command, CommandSender sender, List<String> args) throws CommandException {
if(args.isEmpty()) return new ArrayList<>(commands.keySet()).stream().sorted(String::compareTo).collect(Collectors.toList());
if(!commands.containsKey(command)) return Collections.emptyList();
return tabComplete(commands.get(command), sender, new ArrayList<>(args)).stream().filter(s -> s.toLowerCase().startsWith(args.get(args.size() - 1).toLowerCase())).sorted(String::compareTo).collect(Collectors.toList());
}
@Override
public int getMaxArgumentDepth() {
int max = 0;
for(CommandHolder value : commands.values()) {
max = FastMath.max(getMaxArgumentDepth(value), max);
}
return max;
}
private int getMaxArgumentDepth(CommandHolder holder) {
int max = 0;
max = FastMath.max(holder.arguments.size() + holder.switchList.size(), max);
for(CommandHolder value : holder.subcommands.values()) {
max = FastMath.max(max, getMaxArgumentDepth(value) + 1);
}
return max;
}
private List<String> tabComplete(CommandHolder holder, CommandSender sender, List<String> args) throws CommandException {
if(args.isEmpty()) return Collections.emptyList();
List<String> completions = new ArrayList<>();
if(args.size() == 1) {
completions.addAll(holder.subcommands.keySet());
}
if(holder.subcommands.containsKey(args.get(0))) {
List<String> newArgs = new ArrayList<>(args);
newArgs.remove(0);
completions.addAll(tabComplete(holder.subcommands.get(args.get(0)), sender, newArgs));
}
try {
if(args.size() <= holder.arguments.size()) {
TabCompleter completer = holder.arguments.get(args.size() - 1).tabCompleter().getConstructor().newInstance();
pluginInjector.inject(completer);
completions.addAll(completer.complete(sender));
}
} catch(InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException | InjectionException e) {
throw new MalformedCommandException("Unable to reflectively instantiate tab-completer: ", e);
}
return completions;
}
/**
* Pre-processes command metadata.
*/
private static final class CommandHolder {
private final Class<? extends CommandTemplate> clazz;
private final Map<String, CommandHolder> subcommands = new HashMap<>();
private final Map<String, String> switches = new HashMap<>();
private final List<Argument> arguments;
private final List<Switch> switchList;
private final Map<String, Argument> argumentMap = new HashMap<>();
private CommandHolder(Class<? extends CommandTemplate> clazz) throws MalformedCommandException {
this.clazz = clazz;
if(clazz.isAnnotationPresent(Command.class)) {
Command command = clazz.getAnnotation(Command.class);
for(Subcommand subcommand : command.subcommands()) {
if(subcommands.containsKey(subcommand.value()))
throw new MalformedCommandException("Duplicate subcommand: " + subcommand);
CommandHolder holder = new CommandHolder(subcommand.clazz());
subcommands.put(subcommand.value(), holder);
for(String alias : subcommand.aliases()) {
subcommands.put(alias, holder);
}
}
for(Switch aSwitch : command.switches()) {
if(switches.containsKey(aSwitch.value())) throw new MalformedCommandException("Duplicate switch: " + aSwitch);
switches.put(aSwitch.value(), aSwitch.value());
for(String alias : aSwitch.aliases()) {
switches.put(alias, aSwitch.value());
}
}
for(Argument argument : command.arguments()) {
if(argumentMap.containsKey(argument.value())) throw new MalformedCommandException("Duplicate argument: " + argument);
argumentMap.put(argument.value(), argument);
}
arguments = Arrays.asList(command.arguments());
switchList = Arrays.asList(command.switches());
} else {
arguments = Collections.emptyList();
switchList = Collections.emptyList();
}
}
}
}

View File

@@ -0,0 +1,25 @@
package com.dfsek.terra.api.command.annotation;
import com.dfsek.terra.api.command.arg.ArgumentParser;
import com.dfsek.terra.api.command.arg.StringArgumentParser;
import com.dfsek.terra.api.command.tab.NothingCompleter;
import com.dfsek.terra.api.command.tab.TabCompleter;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Argument {
String value();
boolean required() default true;
Class<? extends TabCompleter> tabCompleter() default NothingCompleter.class;
Class<? extends ArgumentParser<?>> argumentParser() default StringArgumentParser.class;
String defaultValue() default "";
}

View File

@@ -0,0 +1,18 @@
package com.dfsek.terra.api.command.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Command {
Argument[] arguments() default {};
Switch[] switches() default {};
Subcommand[] subcommands() default {};
String usage() default "";
}

View File

@@ -0,0 +1,18 @@
package com.dfsek.terra.api.command.annotation;
import com.dfsek.terra.api.command.CommandTemplate;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Subcommand {
String value();
String[] aliases() default {};
Class<? extends CommandTemplate> clazz();
}

View File

@@ -0,0 +1,14 @@
package com.dfsek.terra.api.command.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Switch {
String value();
String[] aliases() default {};
}

View File

@@ -0,0 +1,12 @@
package com.dfsek.terra.api.command.annotation.inject;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ArgumentTarget {
String value();
}

View File

@@ -0,0 +1,12 @@
package com.dfsek.terra.api.command.annotation.inject;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface SwitchTarget {
String value();
}

View File

@@ -0,0 +1,14 @@
package com.dfsek.terra.api.command.annotation.type;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Command may only be executed with debug mode enabled.
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DebugCommand {
}

View File

@@ -0,0 +1,14 @@
package com.dfsek.terra.api.command.annotation.type;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Marks command as player-only
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface PlayerCommand {
}

View File

@@ -0,0 +1,14 @@
package com.dfsek.terra.api.command.annotation.type;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Command may only be executed in a Terra world.
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface WorldCommand {
}

View File

@@ -0,0 +1,7 @@
package com.dfsek.terra.api.command.arg;
import com.dfsek.terra.api.platform.CommandSender;
public interface ArgumentParser<T> {
T parse(CommandSender sender, String arg);
}

View File

@@ -0,0 +1,10 @@
package com.dfsek.terra.api.command.arg;
import com.dfsek.terra.api.platform.CommandSender;
public class DoubleArgumentParser implements ArgumentParser<Double> {
@Override
public Double parse(CommandSender sender, String arg) {
return arg == null ? null : Double.parseDouble(arg);
}
}

View File

@@ -0,0 +1,10 @@
package com.dfsek.terra.api.command.arg;
import com.dfsek.terra.api.platform.CommandSender;
public class IntegerArgumentParser implements ArgumentParser<Integer> {
@Override
public Integer parse(CommandSender sender, String arg) {
return arg == null ? null : Integer.parseInt(arg);
}
}

View File

@@ -0,0 +1,10 @@
package com.dfsek.terra.api.command.arg;
import com.dfsek.terra.api.platform.CommandSender;
public class StringArgumentParser implements ArgumentParser<String> {
@Override
public String parse(CommandSender sender, String arg) {
return arg;
}
}

View File

@@ -0,0 +1,13 @@
package com.dfsek.terra.api.command.exception;
public abstract class CommandException extends Exception {
private static final long serialVersionUID = -2955328495045879822L;
public CommandException(String message) {
super(message);
}
public CommandException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@@ -0,0 +1,13 @@
package com.dfsek.terra.api.command.exception;
public class ExecutionException extends CommandException {
private static final long serialVersionUID = -6345523475880607959L;
public ExecutionException(String message) {
super(message);
}
public ExecutionException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@@ -0,0 +1,13 @@
package com.dfsek.terra.api.command.exception;
public class InvalidArgumentsException extends CommandException {
private static final long serialVersionUID = 7563619667472569824L;
public InvalidArgumentsException(String message) {
super(message);
}
public InvalidArgumentsException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@@ -0,0 +1,16 @@
package com.dfsek.terra.api.command.exception;
/**
* Thrown when command is incorrectly defined.
*/
public class MalformedCommandException extends CommandException {
private static final long serialVersionUID = -5417760860407895496L;
public MalformedCommandException(String message) {
super(message);
}
public MalformedCommandException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@@ -0,0 +1,13 @@
package com.dfsek.terra.api.command.exception;
public class SwitchFormatException extends CommandException {
private static final long serialVersionUID = -965858989317844628L;
public SwitchFormatException(String message) {
super(message);
}
public SwitchFormatException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@@ -0,0 +1,13 @@
package com.dfsek.terra.api.command.tab;
import com.dfsek.terra.api.platform.CommandSender;
import java.util.Collections;
import java.util.List;
public class NothingCompleter implements TabCompleter {
@Override
public List<String> complete(CommandSender sender) {
return Collections.emptyList();
}
}

View File

@@ -0,0 +1,9 @@
package com.dfsek.terra.api.command.tab;
import com.dfsek.terra.api.platform.CommandSender;
import java.util.List;
public interface TabCompleter {
List<String> complete(CommandSender sender);
}

View File

@@ -170,4 +170,9 @@ public class Location implements Cloneable {
public String toString() {
return "[" + world + ": (" + getX() + ", " + getY() + ", " + getZ() + ")]";
}
public Location multiply(double v) {
vector.multiply(v);
return this;
}
}

View File

@@ -13,4 +13,6 @@ public interface BlockData extends Cloneable, Handle {
String getAsString();
boolean isAir();
boolean isStructureVoid();
}

View File

@@ -3,7 +3,12 @@ package com.dfsek.terra.api.platform.entity;
import com.dfsek.terra.api.math.vector.Location;
import com.dfsek.terra.api.platform.CommandSender;
import com.dfsek.terra.api.platform.Handle;
import com.dfsek.terra.api.platform.world.World;
public interface Entity extends Handle, CommandSender {
Location getLocation();
void setLocation(Location location);
World getWorld();
}

View File

@@ -1,22 +1,26 @@
package com.dfsek.terra.api.platform.handle;
import com.dfsek.terra.api.platform.block.Block;
import com.dfsek.terra.api.math.vector.Location;
import com.dfsek.terra.api.platform.block.BlockData;
import com.dfsek.terra.api.platform.entity.EntityType;
import com.dfsek.terra.api.platform.entity.Player;
import com.dfsek.terra.api.util.generic.pair.Pair;
/**
* Interface to be implemented for world manipulation.
*/
public interface WorldHandle {
default void setBlockData(Block block, BlockData data, boolean physics) {
block.setBlockData(data, physics);
}
default BlockData getBlockData(Block block) {
return block.getBlockData();
}
BlockData createBlockData(String data);
EntityType getEntity(String id);
/**
* Get the locations selected by a player. (Usually via WorldEdit)
*
* @param player Player to get locations for
* @return Pair of locations.
*/
default Pair<Location, Location> getSelectedLocation(Player player) {
throw new UnsupportedOperationException("Cannot get selection on this platform.");
}
}

View File

@@ -25,11 +25,17 @@ public interface World extends Handle {
Chunk getChunkAt(int x, int z);
default Chunk getChunkAt(Location location) {
return getChunkAt(location.getBlockX() >> 4, location.getBlockZ() >> 4);
}
File getWorldFolder();
Block getBlockAt(int x, int y, int z);
Block getBlockAt(Location l);
default Block getBlockAt(Location l) {
return getBlockAt(l.getBlockX(), l.getBlockY(), l.getBlockZ());
}
Entity spawnEntity(Location location, EntityType entityType);

View File

@@ -2,9 +2,12 @@ package com.dfsek.terra.api.util;
import org.jetbrains.annotations.NotNull;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.function.Consumer;
import java.util.stream.Stream;
public class ReflectionUtil {
@@ -25,4 +28,9 @@ public class ReflectionUtil {
}
return result;
}
public static <T extends Annotation> void ifAnnotationPresent(AnnotatedElement element, Class<? extends T> annotation, Consumer<T> operation) {
T a = element.getAnnotation(annotation);
if(a != null) operation.accept(a);
}
}

View File

@@ -0,0 +1,52 @@
package com.dfsek.terra.api.util.generic.either;
import java.util.Optional;
import java.util.function.Consumer;
public final class Either<L, R> {
private final L left;
private final R right;
private final boolean leftPresent;
private Either(L left, R right, boolean leftPresent) {
this.left = left;
this.right = right;
this.leftPresent = leftPresent;
}
public static <L1, R1> Either<L1, R1> left(L1 left) {
return new Either<>(left, null, true);
}
public static <L1, R1> Either<L1, R1> right(R1 right) {
return new Either<>(null, right, false);
}
public Optional<L> getLeft() {
if(leftPresent) return Optional.of(left);
return Optional.empty();
}
public Optional<R> getRight() {
if(!leftPresent) return Optional.of(right);
return Optional.empty();
}
public Either<L, R> ifLeft(Consumer<L> action) {
if(leftPresent) action.accept(left);
return this;
}
public Either<L, R> ifRight(Consumer<R> action) {
if(!leftPresent) action.accept(right);
return this;
}
public boolean hasLeft() {
return leftPresent;
}
public boolean hasRight() {
return !leftPresent;
}
}

View File

@@ -0,0 +1,27 @@
package com.dfsek.terra.api.util.generic.pair;
public class ImmutablePair<L, R> {
private final L left;
private final R right;
public ImmutablePair(L left, R right) {
this.left = left;
this.right = right;
}
public static <L1, R1> ImmutablePair<L1, R1> of(L1 left, R1 right) {
return new ImmutablePair<>(left, right);
}
public R getRight() {
return right;
}
public L getLeft() {
return left;
}
public Pair<L, R> mutable() {
return new Pair<>(left, right);
}
}

View File

@@ -0,0 +1,35 @@
package com.dfsek.terra.api.util.generic.pair;
public class Pair<L, R> {
private L left;
private R right;
public Pair(L left, R right) {
this.left = left;
this.right = right;
}
public static <L1, R1> Pair<L1, R1> of(L1 left, R1 right) {
return new Pair<>(left, right);
}
public L getLeft() {
return left;
}
public void setLeft(L left) {
this.left = left;
}
public R getRight() {
return right;
}
public void setRight(R right) {
this.right = right;
}
public ImmutablePair<L, R> immutable() {
return new ImmutablePair<>(left, right);
}
}

View File

@@ -0,0 +1,21 @@
package com.dfsek.terra.commands;
import com.dfsek.terra.api.TerraPlugin;
import com.dfsek.terra.api.command.CommandTemplate;
import com.dfsek.terra.api.command.annotation.Command;
import com.dfsek.terra.api.injection.annotations.Inject;
import com.dfsek.terra.api.platform.CommandSender;
@Command(
usage = "/terra addons"
)
public class AddonsCommand implements CommandTemplate {
@Inject
private TerraPlugin main;
@Override
public void execute(CommandSender sender) {
sender.sendMessage("Installed Addons:");
main.getAddons().forEach(addon -> sender.sendMessage(" - " + addon.getName() + " v" + addon.getVersion() + " by " + addon.getAuthor()));
}
}

View File

@@ -0,0 +1,22 @@
package com.dfsek.terra.commands;
import com.dfsek.terra.api.command.CommandManager;
import com.dfsek.terra.api.command.exception.MalformedCommandException;
import com.dfsek.terra.commands.biome.BiomeCommand;
import com.dfsek.terra.commands.geometry.GeometryCommand;
import com.dfsek.terra.commands.profiler.ProfileCommand;
import com.dfsek.terra.commands.structure.StructureCommand;
public final class CommandUtil {
public static void registerAll(CommandManager manager) throws MalformedCommandException {
manager.register("structure", StructureCommand.class);
manager.register("profile", ProfileCommand.class);
manager.register("reload", ReloadCommand.class);
manager.register("addons", AddonsCommand.class);
manager.register("version", VersionCommand.class);
manager.register("getblock", GetBlockCommand.class);
manager.register("packs", PacksCommand.class);
manager.register("biome", BiomeCommand.class);
manager.register("geometry", GeometryCommand.class);
}
}

View File

@@ -0,0 +1,28 @@
package com.dfsek.terra.commands;
import com.dfsek.terra.api.TerraPlugin;
import com.dfsek.terra.api.command.CommandTemplate;
import com.dfsek.terra.api.command.annotation.Command;
import com.dfsek.terra.api.command.annotation.type.DebugCommand;
import com.dfsek.terra.api.command.annotation.type.PlayerCommand;
import com.dfsek.terra.api.command.annotation.type.WorldCommand;
import com.dfsek.terra.api.injection.annotations.Inject;
import com.dfsek.terra.api.platform.CommandSender;
import com.dfsek.terra.api.platform.entity.Player;
@WorldCommand
@DebugCommand
@PlayerCommand
@Command(
usage = "/terra getblock"
)
public class GetBlockCommand implements CommandTemplate {
@Inject
private TerraPlugin main;
@Override
public void execute(CommandSender sender) {
Player player = (Player) sender;
sender.sendMessage("Block: " + main.getWorld(player.getWorld()).getUngeneratedBlock(player.getLocation()).getAsString());
}
}

View File

@@ -0,0 +1,35 @@
package com.dfsek.terra.commands;
import com.dfsek.terra.api.TerraPlugin;
import com.dfsek.terra.api.command.CommandTemplate;
import com.dfsek.terra.api.command.annotation.Command;
import com.dfsek.terra.api.injection.annotations.Inject;
import com.dfsek.terra.api.platform.CommandSender;
import com.dfsek.terra.api.registry.CheckedRegistry;
import com.dfsek.terra.config.lang.LangUtil;
import com.dfsek.terra.config.pack.ConfigPack;
import com.dfsek.terra.config.pack.ConfigPackTemplate;
@Command(
usage = "/terra packs"
)
public class PacksCommand implements CommandTemplate {
@Inject
private TerraPlugin main;
@Override
public void execute(CommandSender sender) {
CheckedRegistry<ConfigPack> registry = main.getConfigRegistry();
if(registry.entries().size() == 0) {
LangUtil.send("command.packs.none", sender);
return;
}
LangUtil.send("command.packs.main", sender);
registry.entries().forEach(entry -> {
ConfigPackTemplate template = entry.getTemplate();
LangUtil.send("command.packs.pack", sender, template.getID(), template.getAuthor(), template.getVersion());
});
}
}

View File

@@ -0,0 +1,25 @@
package com.dfsek.terra.commands;
import com.dfsek.terra.api.TerraPlugin;
import com.dfsek.terra.api.command.CommandTemplate;
import com.dfsek.terra.api.command.annotation.Command;
import com.dfsek.terra.api.injection.annotations.Inject;
import com.dfsek.terra.api.platform.CommandSender;
import com.dfsek.terra.config.lang.LangUtil;
@Command(
usage = "/terra reload"
)
public class ReloadCommand implements CommandTemplate {
@Inject
private TerraPlugin main;
@Override
public void execute(CommandSender sender) {
if(!main.reload()) {
LangUtil.send("command.reload-error", sender);
} else {
LangUtil.send("command.reload", sender);
}
}
}

View File

@@ -0,0 +1,22 @@
package com.dfsek.terra.commands;
import com.dfsek.terra.api.TerraPlugin;
import com.dfsek.terra.api.command.CommandTemplate;
import com.dfsek.terra.api.command.annotation.Command;
import com.dfsek.terra.api.injection.annotations.Inject;
import com.dfsek.terra.api.platform.CommandSender;
import com.dfsek.terra.config.lang.LangUtil;
@Command(
usage = "/terra version"
)
public class VersionCommand implements CommandTemplate {
@Inject
private TerraPlugin main;
@Override
public void execute(CommandSender sender) {
String terraVersion = main.getVersion();
LangUtil.send("command.version", sender, terraVersion, main.platformName());
}
}

View File

@@ -0,0 +1,45 @@
package com.dfsek.terra.commands.biome;
import com.dfsek.terra.api.TerraPlugin;
import com.dfsek.terra.api.command.CommandTemplate;
import com.dfsek.terra.api.command.annotation.Command;
import com.dfsek.terra.api.command.annotation.Subcommand;
import com.dfsek.terra.api.command.annotation.type.PlayerCommand;
import com.dfsek.terra.api.command.annotation.type.WorldCommand;
import com.dfsek.terra.api.injection.annotations.Inject;
import com.dfsek.terra.api.platform.CommandSender;
import com.dfsek.terra.api.platform.entity.Player;
import com.dfsek.terra.api.world.biome.UserDefinedBiome;
import com.dfsek.terra.api.world.biome.provider.BiomeProvider;
import com.dfsek.terra.config.lang.LangUtil;
@Command(
subcommands = {
@Subcommand(
value = "info",
aliases = {"i"},
clazz = BiomeInfoCommand.class
),
@Subcommand(
value = "locate",
aliases = {"l"},
clazz = BiomeLocateCommand.class
)
},
usage = "/terra biome"
)
@WorldCommand
@PlayerCommand
public class BiomeCommand implements CommandTemplate {
@Inject
private TerraPlugin main;
@Override
public void execute(CommandSender sender) {
Player player = (Player) sender;
BiomeProvider provider = main.getWorld(player.getWorld()).getBiomeProvider();
UserDefinedBiome biome = (UserDefinedBiome) provider.getBiome(player.getLocation());
LangUtil.send("command.biome.in", sender, biome.getID());
}
}

View File

@@ -0,0 +1,52 @@
package com.dfsek.terra.commands.biome;
import com.dfsek.terra.api.command.CommandTemplate;
import com.dfsek.terra.api.command.annotation.Argument;
import com.dfsek.terra.api.command.annotation.Command;
import com.dfsek.terra.api.command.annotation.inject.ArgumentTarget;
import com.dfsek.terra.api.platform.CommandSender;
import com.dfsek.terra.api.world.biome.TerraBiome;
import com.dfsek.terra.api.world.biome.UserDefinedBiome;
import com.dfsek.terra.commands.biome.arg.BiomeArgumentParser;
import com.dfsek.terra.commands.biome.tab.BiomeTabCompleter;
import com.dfsek.terra.config.templates.BiomeTemplate;
import com.dfsek.terra.world.population.items.TerraStructure;
import java.util.List;
@Command(
arguments = {
@Argument(
value = "biome",
tabCompleter = BiomeTabCompleter.class,
argumentParser = BiomeArgumentParser.class
)
}
)
public class BiomeInfoCommand implements CommandTemplate {
@ArgumentTarget("biome")
private TerraBiome biome;
@Override
public void execute(CommandSender sender) {
sender.sendMessage("Biome info for \"" + biome.getID() + "\".");
sender.sendMessage("Vanilla biome: " + biome.getVanillaBiomes());
if(biome instanceof UserDefinedBiome) {
BiomeTemplate bio = ((UserDefinedBiome) biome).getConfig();
if(bio.getExtend() != null) sender.sendMessage("Extends: " + bio.getExtend());
List<TerraStructure> structureConfigs = bio.getStructures();
if(structureConfigs.size() == 0) sender.sendMessage("No Structures");
else {
sender.sendMessage("-------Structures-------");
for(TerraStructure c : structureConfigs) {
sender.sendMessage(" - " + c.getTemplate().getID());
}
}
}
}
}

View File

@@ -0,0 +1,78 @@
package com.dfsek.terra.commands.biome;
import com.dfsek.terra.api.TerraPlugin;
import com.dfsek.terra.api.command.CommandTemplate;
import com.dfsek.terra.api.command.annotation.Argument;
import com.dfsek.terra.api.command.annotation.Command;
import com.dfsek.terra.api.command.annotation.Switch;
import com.dfsek.terra.api.command.annotation.inject.ArgumentTarget;
import com.dfsek.terra.api.command.annotation.inject.SwitchTarget;
import com.dfsek.terra.api.command.annotation.type.PlayerCommand;
import com.dfsek.terra.api.command.annotation.type.WorldCommand;
import com.dfsek.terra.api.command.arg.IntegerArgumentParser;
import com.dfsek.terra.api.injection.annotations.Inject;
import com.dfsek.terra.api.math.vector.Location;
import com.dfsek.terra.api.math.vector.Vector3;
import com.dfsek.terra.api.platform.CommandSender;
import com.dfsek.terra.api.platform.entity.Player;
import com.dfsek.terra.api.world.biome.TerraBiome;
import com.dfsek.terra.api.world.locate.AsyncBiomeFinder;
import com.dfsek.terra.commands.biome.arg.BiomeArgumentParser;
import com.dfsek.terra.commands.biome.tab.BiomeTabCompleter;
import com.dfsek.terra.config.lang.LangUtil;
import java.util.Locale;
@PlayerCommand
@WorldCommand
@Command(
arguments = {
@Argument(
value = "biome",
tabCompleter = BiomeTabCompleter.class,
argumentParser = BiomeArgumentParser.class
),
@Argument(
value = "radius",
required = false,
defaultValue = "1000",
argumentParser = IntegerArgumentParser.class
)
},
switches = {
@Switch(
value = "teleport",
aliases = {"t", "tp"}
)
}
)
public class BiomeLocateCommand implements CommandTemplate {
@ArgumentTarget("radius")
private Integer radius;
@ArgumentTarget("biome")
private TerraBiome biome;
@SwitchTarget("teleport")
private boolean teleport;
@Inject
private TerraPlugin main;
@Override
public void execute(CommandSender sender) {
Player player = (Player) sender;
new Thread(new AsyncBiomeFinder(main.getWorld(player.getWorld()).getBiomeProvider(), biome, player.getLocation().clone().multiply((1D / main.getTerraConfig().getBiomeSearchResolution())), 0, radius, location -> {
if(location != null) {
sender.sendMessage(String.format("The nearest %s is at [%d, ~, %d] (%.1f blocks away)", biome.getID().toLowerCase(Locale.ROOT), location.getBlockX(), location.getBlockZ(), location.add(new Vector3(0, player.getLocation().getY(), 0)).distance(player.getLocation().toVector())));
if(teleport) {
main.runPossiblyUnsafeTask(() -> player.setLocation(new Location(player.getWorld(), location.getX(), player.getLocation().getY(), location.getZ())));
}
} else LangUtil.send("command.biome.unable-to-locate", sender);
}, main), "Biome Location Thread").start();
}
}

View File

@@ -0,0 +1,19 @@
package com.dfsek.terra.commands.biome.arg;
import com.dfsek.terra.api.TerraPlugin;
import com.dfsek.terra.api.command.arg.ArgumentParser;
import com.dfsek.terra.api.injection.annotations.Inject;
import com.dfsek.terra.api.platform.CommandSender;
import com.dfsek.terra.api.platform.entity.Player;
import com.dfsek.terra.api.world.biome.TerraBiome;
public class BiomeArgumentParser implements ArgumentParser<TerraBiome> {
@Inject
private TerraPlugin main;
@Override
public TerraBiome parse(CommandSender sender, String arg) {
Player player = (Player) sender;
return main.getWorld(player.getWorld()).getConfig().getBiomeRegistry().get(arg);
}
}

View File

@@ -0,0 +1,22 @@
package com.dfsek.terra.commands.biome.tab;
import com.dfsek.terra.api.TerraPlugin;
import com.dfsek.terra.api.command.tab.TabCompleter;
import com.dfsek.terra.api.injection.annotations.Inject;
import com.dfsek.terra.api.platform.CommandSender;
import com.dfsek.terra.api.platform.entity.Player;
import com.dfsek.terra.api.world.biome.TerraBiome;
import java.util.List;
import java.util.stream.Collectors;
public class BiomeTabCompleter implements TabCompleter {
@Inject
private TerraPlugin main;
@Override
public List<String> complete(CommandSender sender) {
Player player = (Player) sender;
return main.getWorld(player.getWorld()).getConfig().getBiomeRegistry().entries().stream().map(TerraBiome::getID).collect(Collectors.toList());
}
}

View File

@@ -0,0 +1,65 @@
package com.dfsek.terra.commands.geometry;
import com.dfsek.terra.api.TerraPlugin;
import com.dfsek.terra.api.command.CommandTemplate;
import com.dfsek.terra.api.command.annotation.Argument;
import com.dfsek.terra.api.command.annotation.Command;
import com.dfsek.terra.api.command.annotation.inject.ArgumentTarget;
import com.dfsek.terra.api.command.annotation.type.DebugCommand;
import com.dfsek.terra.api.command.annotation.type.PlayerCommand;
import com.dfsek.terra.api.command.arg.DoubleArgumentParser;
import com.dfsek.terra.api.command.arg.IntegerArgumentParser;
import com.dfsek.terra.api.injection.annotations.Inject;
import com.dfsek.terra.api.math.noise.samplers.noise.simplex.OpenSimplex2Sampler;
import com.dfsek.terra.api.math.vector.Vector3;
import com.dfsek.terra.api.math.voxel.DeformedSphere;
import com.dfsek.terra.api.platform.CommandSender;
import com.dfsek.terra.api.platform.entity.Player;
import java.util.concurrent.ThreadLocalRandom;
@DebugCommand
@PlayerCommand
@Command(
arguments = {
@Argument(
value = "radius",
argumentParser = IntegerArgumentParser.class
),
@Argument(
value = "deform",
argumentParser = DoubleArgumentParser.class
),
@Argument(
value = "frequency",
argumentParser = DoubleArgumentParser.class
)
},
usage = "/terra geometry deformedsphere <RADIUS> <DEFORM> <FREQUENCY>"
)
public class DeformedSphereCommand implements CommandTemplate {
@ArgumentTarget("radius")
private Integer radius;
@ArgumentTarget("deform")
private Double deform;
@ArgumentTarget("frequency")
private Double frequency;
@Inject
private TerraPlugin main;
@Override
public void execute(CommandSender sender) {
Player player = (Player) sender;
OpenSimplex2Sampler noise = new OpenSimplex2Sampler(ThreadLocalRandom.current().nextInt());
noise.setFrequency(frequency);
DeformedSphere sphere = new DeformedSphere(player.getLocation().toVector(), radius, deform, noise);
for(Vector3 v : sphere.getGeometry()) {
v.toLocation(player.getWorld()).getBlock().setBlockData(main.getWorldHandle().createBlockData("minecraft:stone"), false);
}
}
}

View File

@@ -0,0 +1,39 @@
package com.dfsek.terra.commands.geometry;
import com.dfsek.terra.api.command.CommandTemplate;
import com.dfsek.terra.api.command.annotation.Command;
import com.dfsek.terra.api.command.annotation.Subcommand;
import com.dfsek.terra.api.command.annotation.type.DebugCommand;
import com.dfsek.terra.api.command.annotation.type.PlayerCommand;
import com.dfsek.terra.api.platform.CommandSender;
import com.dfsek.terra.config.lang.LangUtil;
@DebugCommand
@PlayerCommand
@Command(
subcommands = {
@Subcommand(
value = "sphere",
clazz = SphereCommand.class,
aliases = {"s"}
),
@Subcommand(
value = "deformedsphere",
clazz = DeformedSphereCommand.class,
aliases = {"df"}
),
@Subcommand(
value = "tube",
clazz = TubeCommand.class,
aliases = {"t"}
)
},
usage = "/terra geometry"
)
public class GeometryCommand implements CommandTemplate {
@Override
public void execute(CommandSender sender) {
LangUtil.send("command.geometry.main-menu", sender);
}
}

View File

@@ -0,0 +1,44 @@
package com.dfsek.terra.commands.geometry;
import com.dfsek.terra.api.TerraPlugin;
import com.dfsek.terra.api.command.CommandTemplate;
import com.dfsek.terra.api.command.annotation.Argument;
import com.dfsek.terra.api.command.annotation.Command;
import com.dfsek.terra.api.command.annotation.inject.ArgumentTarget;
import com.dfsek.terra.api.command.annotation.type.DebugCommand;
import com.dfsek.terra.api.command.annotation.type.PlayerCommand;
import com.dfsek.terra.api.command.arg.IntegerArgumentParser;
import com.dfsek.terra.api.injection.annotations.Inject;
import com.dfsek.terra.api.math.vector.Vector3;
import com.dfsek.terra.api.math.voxel.Sphere;
import com.dfsek.terra.api.platform.CommandSender;
import com.dfsek.terra.api.platform.entity.Player;
@DebugCommand
@PlayerCommand
@Command(
arguments = {
@Argument(
value = "radius",
argumentParser = IntegerArgumentParser.class
)
},
usage = "/terra geometry sphere <RADIUS>"
)
public class SphereCommand implements CommandTemplate {
@ArgumentTarget("radius")
private Integer radius;
@Inject
private TerraPlugin main;
@Override
public void execute(CommandSender sender) {
Player player = (Player) sender;
Sphere sphere = new Sphere(player.getLocation().toVector(), radius);
for(Vector3 v : sphere.getGeometry()) {
v.toLocation(player.getWorld()).getBlock().setBlockData(main.getWorldHandle().createBlockData("minecraft:stone"), false);
}
}
}

View File

@@ -0,0 +1,49 @@
package com.dfsek.terra.commands.geometry;
import com.dfsek.terra.api.TerraPlugin;
import com.dfsek.terra.api.command.CommandTemplate;
import com.dfsek.terra.api.command.annotation.Argument;
import com.dfsek.terra.api.command.annotation.Command;
import com.dfsek.terra.api.command.annotation.inject.ArgumentTarget;
import com.dfsek.terra.api.command.annotation.type.DebugCommand;
import com.dfsek.terra.api.command.annotation.type.PlayerCommand;
import com.dfsek.terra.api.command.arg.IntegerArgumentParser;
import com.dfsek.terra.api.injection.annotations.Inject;
import com.dfsek.terra.api.math.vector.Location;
import com.dfsek.terra.api.math.vector.Vector3;
import com.dfsek.terra.api.math.voxel.Tube;
import com.dfsek.terra.api.platform.CommandSender;
import com.dfsek.terra.api.platform.entity.Player;
import com.dfsek.terra.api.util.generic.pair.Pair;
@DebugCommand
@PlayerCommand
@Command(
arguments = {
@Argument(
value = "radius",
argumentParser = IntegerArgumentParser.class
)
},
usage = "/terra geometry tube <RADIUS>"
)
public class TubeCommand implements CommandTemplate {
@ArgumentTarget("radius")
private Integer radius;
@Inject
private TerraPlugin main;
@Override
public void execute(CommandSender sender) {
Player player = (Player) sender;
Pair<Location, Location> locations = main.getWorldHandle().getSelectedLocation(player);
Tube tube = new Tube(locations.getLeft().toVector(), locations.getRight().toVector(), radius);
for(Vector3 v : tube.getGeometry()) {
v.toLocation(player.getWorld()).getBlock().setBlockData(main.getWorldHandle().createBlockData("minecraft:stone"), false);
}
}
}

View File

@@ -0,0 +1,29 @@
package com.dfsek.terra.commands.profiler;
import com.dfsek.terra.api.command.CommandTemplate;
import com.dfsek.terra.api.command.annotation.Command;
import com.dfsek.terra.api.command.annotation.Subcommand;
import com.dfsek.terra.api.command.annotation.type.DebugCommand;
import com.dfsek.terra.api.command.annotation.type.PlayerCommand;
import com.dfsek.terra.api.command.annotation.type.WorldCommand;
import com.dfsek.terra.api.platform.CommandSender;
import com.dfsek.terra.config.lang.LangUtil;
@Command(
subcommands = {
@Subcommand(value = "query", aliases = {"q"}, clazz = ProfileQueryCommand.class),
@Subcommand(value = "start", aliases = {"s"}, clazz = ProfileStartCommand.class),
@Subcommand(value = "stop", aliases = {"st"}, clazz = ProfileStopCommand.class),
@Subcommand(value = "reset", aliases = {"r"}, clazz = ProfileResetCommand.class)
},
usage = "Commands to enable/disable/query/reset the profiler."
)
@WorldCommand
@PlayerCommand
@DebugCommand
public class ProfileCommand implements CommandTemplate {
@Override
public void execute(CommandSender sender) {
LangUtil.send("command.profile.main-menu", sender);
}
}

View File

@@ -0,0 +1,28 @@
package com.dfsek.terra.commands.profiler;
import com.dfsek.terra.api.TerraPlugin;
import com.dfsek.terra.api.command.CommandTemplate;
import com.dfsek.terra.api.command.annotation.Command;
import com.dfsek.terra.api.command.annotation.type.DebugCommand;
import com.dfsek.terra.api.command.annotation.type.PlayerCommand;
import com.dfsek.terra.api.command.annotation.type.WorldCommand;
import com.dfsek.terra.api.injection.annotations.Inject;
import com.dfsek.terra.api.platform.CommandSender;
import com.dfsek.terra.api.platform.entity.Player;
import com.dfsek.terra.world.TerraWorld;
@Command
@WorldCommand
@PlayerCommand
@DebugCommand
public class ProfileQueryCommand implements CommandTemplate {
@Inject
private TerraPlugin main;
@Override
public void execute(CommandSender sender) {
Player player = (Player) sender;
TerraWorld world = main.getWorld(player.getWorld());
player.sendMessage(world.getProfiler().getResultsFormatted());
}
}

View File

@@ -0,0 +1,29 @@
package com.dfsek.terra.commands.profiler;
import com.dfsek.terra.api.TerraPlugin;
import com.dfsek.terra.api.command.CommandTemplate;
import com.dfsek.terra.api.command.annotation.Command;
import com.dfsek.terra.api.command.annotation.type.DebugCommand;
import com.dfsek.terra.api.command.annotation.type.PlayerCommand;
import com.dfsek.terra.api.command.annotation.type.WorldCommand;
import com.dfsek.terra.api.injection.annotations.Inject;
import com.dfsek.terra.api.platform.CommandSender;
import com.dfsek.terra.api.platform.entity.Player;
import com.dfsek.terra.world.TerraWorld;
@Command
@WorldCommand
@PlayerCommand
@DebugCommand
public class ProfileResetCommand implements CommandTemplate {
@Inject
private TerraPlugin main;
@Override
public void execute(CommandSender sender) {
Player player = (Player) sender;
TerraWorld world = main.getWorld(player.getWorld());
world.getProfiler().reset();
player.sendMessage("Profiler reset.");
}
}

View File

@@ -0,0 +1,29 @@
package com.dfsek.terra.commands.profiler;
import com.dfsek.terra.api.TerraPlugin;
import com.dfsek.terra.api.command.CommandTemplate;
import com.dfsek.terra.api.command.annotation.Command;
import com.dfsek.terra.api.command.annotation.type.DebugCommand;
import com.dfsek.terra.api.command.annotation.type.PlayerCommand;
import com.dfsek.terra.api.command.annotation.type.WorldCommand;
import com.dfsek.terra.api.injection.annotations.Inject;
import com.dfsek.terra.api.platform.CommandSender;
import com.dfsek.terra.api.platform.entity.Player;
import com.dfsek.terra.world.TerraWorld;
@Command
@WorldCommand
@PlayerCommand
@DebugCommand
public class ProfileStartCommand implements CommandTemplate {
@Inject
private TerraPlugin main;
@Override
public void execute(CommandSender sender) {
Player player = (Player) sender;
TerraWorld world = main.getWorld(player.getWorld());
world.getProfiler().setProfiling(true);
player.sendMessage("Profiling enabled.");
}
}

View File

@@ -0,0 +1,29 @@
package com.dfsek.terra.commands.profiler;
import com.dfsek.terra.api.TerraPlugin;
import com.dfsek.terra.api.command.CommandTemplate;
import com.dfsek.terra.api.command.annotation.Command;
import com.dfsek.terra.api.command.annotation.type.DebugCommand;
import com.dfsek.terra.api.command.annotation.type.PlayerCommand;
import com.dfsek.terra.api.command.annotation.type.WorldCommand;
import com.dfsek.terra.api.injection.annotations.Inject;
import com.dfsek.terra.api.platform.CommandSender;
import com.dfsek.terra.api.platform.entity.Player;
import com.dfsek.terra.world.TerraWorld;
@Command
@WorldCommand
@PlayerCommand
@DebugCommand
public class ProfileStopCommand implements CommandTemplate {
@Inject
private TerraPlugin main;
@Override
public void execute(CommandSender sender) {
Player player = (Player) sender;
TerraWorld world = main.getWorld(player.getWorld());
world.getProfiler().setProfiling(false);
player.sendMessage("Profiling disabled.");
}
}

View File

@@ -0,0 +1,48 @@
package com.dfsek.terra.commands.structure;
import com.dfsek.terra.api.TerraPlugin;
import com.dfsek.terra.api.command.CommandTemplate;
import com.dfsek.terra.api.command.annotation.Command;
import com.dfsek.terra.api.command.annotation.type.DebugCommand;
import com.dfsek.terra.api.command.annotation.type.PlayerCommand;
import com.dfsek.terra.api.command.annotation.type.WorldCommand;
import com.dfsek.terra.api.injection.annotations.Inject;
import com.dfsek.terra.api.math.vector.Location;
import com.dfsek.terra.api.platform.CommandSender;
import com.dfsek.terra.api.platform.entity.Player;
import com.dfsek.terra.api.structures.parser.lang.constants.NumericConstant;
import com.dfsek.terra.api.structures.script.TerraImplementationArguments;
import com.dfsek.terra.api.structures.script.functions.CheckFunction;
import com.dfsek.terra.api.structures.structure.Rotation;
import com.dfsek.terra.api.structures.structure.buffer.StructureBuffer;
import com.dfsek.terra.api.structures.tokenizer.Position;
import com.dfsek.terra.api.util.FastRandom;
import java.util.HashMap;
@DebugCommand
@PlayerCommand
@WorldCommand
@Command(
usage = "/terra spawn"
)
public class SpawnCommand implements CommandTemplate {
@Inject
private TerraPlugin main;
@Override
public void execute(CommandSender sender) {
Player player = (Player) sender;
Location p = player.getLocation();
int x = p.getBlockX();
int y = p.getBlockY();
int z = p.getBlockZ();
Position dummy = new Position(0, 0);
String check = new CheckFunction(main, new NumericConstant(0, dummy), new NumericConstant(0, dummy), new NumericConstant(0, dummy), dummy).apply(new TerraImplementationArguments(new StructureBuffer(
new com.dfsek.terra.api.math.vector.Location(player.getWorld(), x, y, z)
), Rotation.NONE, new FastRandom(), 0), new HashMap<>());
sender.sendMessage("Found: " + check);
}
}

View File

@@ -0,0 +1,34 @@
package com.dfsek.terra.commands.structure;
import com.dfsek.terra.api.command.CommandTemplate;
import com.dfsek.terra.api.command.annotation.Command;
import com.dfsek.terra.api.command.annotation.Subcommand;
import com.dfsek.terra.api.platform.CommandSender;
import com.dfsek.terra.config.lang.LangUtil;
@Command(
subcommands = {
@Subcommand(
clazz = StructureExportCommand.class,
value = "export",
aliases = "ex"
),
@Subcommand(
clazz = StructureLoadCommand.class,
value = "load",
aliases = "ld"
),
@Subcommand(
clazz = SpawnCommand.class,
value = "spawn",
aliases = "s"
)
},
usage = "/te structure"
)
public class StructureCommand implements CommandTemplate {
@Override
public void execute(CommandSender sender) {
LangUtil.send("command.structure.main-menu", sender);
}
}

View File

@@ -0,0 +1,114 @@
package com.dfsek.terra.commands.structure;
import com.dfsek.terra.api.TerraPlugin;
import com.dfsek.terra.api.command.CommandTemplate;
import com.dfsek.terra.api.command.annotation.Argument;
import com.dfsek.terra.api.command.annotation.Command;
import com.dfsek.terra.api.command.annotation.inject.ArgumentTarget;
import com.dfsek.terra.api.command.annotation.type.DebugCommand;
import com.dfsek.terra.api.command.annotation.type.PlayerCommand;
import com.dfsek.terra.api.command.annotation.type.WorldCommand;
import com.dfsek.terra.api.injection.annotations.Inject;
import com.dfsek.terra.api.math.vector.Location;
import com.dfsek.terra.api.platform.CommandSender;
import com.dfsek.terra.api.platform.block.Block;
import com.dfsek.terra.api.platform.block.BlockData;
import com.dfsek.terra.api.platform.block.state.BlockState;
import com.dfsek.terra.api.platform.block.state.Sign;
import com.dfsek.terra.api.platform.entity.Player;
import com.dfsek.terra.api.util.generic.pair.Pair;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
@PlayerCommand
@WorldCommand
@DebugCommand
@Command(
arguments = {
@Argument(
value = "id"
)
},
usage = "/terra structure export <ID>"
)
public class StructureExportCommand implements CommandTemplate {
@Inject
private TerraPlugin main;
@ArgumentTarget("id")
private String id;
@Override
public void execute(CommandSender sender) {
Player player = (Player) sender;
Pair<Location, Location> l = main.getWorldHandle().getSelectedLocation(player);
Location l1 = l.getLeft();
Location l2 = l.getRight();
StringBuilder scriptBuilder = new StringBuilder("id \"" + id + "\";\nnum y = 0;\n");
int centerX = 0;
int centerY = 0;
int centerZ = 0;
for(int x = l1.getBlockX(); x <= l2.getBlockX(); x++) {
for(int y = l1.getBlockY(); y <= l2.getBlockY(); y++) {
for(int z = l1.getBlockZ(); z <= l2.getBlockZ(); z++) {
Block block = new Location(l1.getWorld(), x, y, z).getBlock();
BlockState state = block.getState();
if(state instanceof Sign) {
Sign sign = (Sign) state;
if(sign.getLine(0).equals("[TERRA]") && sign.getLine(1).equals("[CENTER]")) {
centerX = x - l1.getBlockX();
centerY = y - l1.getBlockY();
centerZ = z - l1.getBlockZ();
}
}
}
}
}
for(int x = l1.getBlockX(); x <= l2.getBlockX(); x++) {
for(int y = l1.getBlockY(); y <= l2.getBlockY(); y++) {
for(int z = l1.getBlockZ(); z <= l2.getBlockZ(); z++) {
Block block = new Location(l1.getWorld(), x, y, z).getBlock();
BlockData data = block.getBlockData();
if(block.getBlockData().isStructureVoid()) continue;
BlockState state = block.getState();
if(state instanceof Sign) {
Sign sign = (Sign) state;
if(sign.getLine(0).equals("[TERRA]")) {
data = main.getWorldHandle().createBlockData(sign.getLine(2) + sign.getLine(3));
}
}
if(!data.isStructureVoid()) {
scriptBuilder.append("block(").append(x - l1.getBlockX() - centerX).append(", y + ").append(y - l1.getBlockY() - centerY).append(", ").append(z - l1.getBlockZ() - centerZ).append(", ")
.append("\"");
scriptBuilder.append(data.getAsString()).append("\");\n");
}
}
}
}
File file = new File(main.getDataFolder() + File.separator + "export" + File.separator + "structures", id + ".tesf");
try {
file.getParentFile().mkdirs();
file.createNewFile();
} catch(IOException e) {
e.printStackTrace();
}
try(BufferedWriter writer = new BufferedWriter(new FileWriter(file))) {
writer.write(scriptBuilder.toString());
} catch(IOException e) {
e.printStackTrace();
}
sender.sendMessage("Exported structure to " + file.getAbsolutePath());
}
}

View File

@@ -0,0 +1,92 @@
package com.dfsek.terra.commands.structure;
import com.dfsek.terra.api.TerraPlugin;
import com.dfsek.terra.api.command.CommandTemplate;
import com.dfsek.terra.api.command.annotation.Argument;
import com.dfsek.terra.api.command.annotation.Command;
import com.dfsek.terra.api.command.annotation.Switch;
import com.dfsek.terra.api.command.annotation.inject.ArgumentTarget;
import com.dfsek.terra.api.command.annotation.inject.SwitchTarget;
import com.dfsek.terra.api.command.annotation.type.DebugCommand;
import com.dfsek.terra.api.command.annotation.type.PlayerCommand;
import com.dfsek.terra.api.command.annotation.type.WorldCommand;
import com.dfsek.terra.api.command.arg.IntegerArgumentParser;
import com.dfsek.terra.api.injection.annotations.Inject;
import com.dfsek.terra.api.platform.CommandSender;
import com.dfsek.terra.api.platform.entity.Player;
import com.dfsek.terra.api.structures.script.StructureScript;
import com.dfsek.terra.api.structures.structure.Rotation;
import com.dfsek.terra.api.util.FastRandom;
import com.dfsek.terra.commands.structure.argument.ScriptArgumentParser;
import com.dfsek.terra.commands.structure.completer.RotationCompleter;
import com.dfsek.terra.commands.structure.completer.ScriptCompleter;
import java.util.concurrent.ThreadLocalRandom;
@PlayerCommand
@DebugCommand
@WorldCommand
@Command(
arguments = {
@Argument(
value = "structure",
tabCompleter = ScriptCompleter.class,
argumentParser = ScriptArgumentParser.class
),
@Argument(
value = "rotation",
required = false,
tabCompleter = RotationCompleter.class,
argumentParser = IntegerArgumentParser.class,
defaultValue = "0"
)
},
switches = {
@Switch(value = "chunk",
aliases = "c"
)
},
usage = "/terra structure load [ROTATION] [-c]"
)
public class StructureLoadCommand implements CommandTemplate {
@ArgumentTarget("rotation")
private Integer rotation = 0;
@SwitchTarget("chunk")
private boolean chunk;
@ArgumentTarget("structure")
private StructureScript script;
@Inject
private TerraPlugin main;
@Override
public void execute(CommandSender sender) {
System.out.println(rotation);
Player player = (Player) sender;
long t = System.nanoTime();
FastRandom random = new FastRandom(ThreadLocalRandom.current().nextLong());
Rotation r;
try {
r = Rotation.fromDegrees(rotation);
} catch(Exception e) {
sender.sendMessage("Invalid rotation: " + rotation);
return;
}
if(script == null) {
sender.sendMessage("Invalid structure.");
return;
}
if(this.chunk) {
script.execute(player.getLocation(), player.getWorld().getChunkAt(player.getLocation()), random, r);
} else {
script.execute(player.getLocation(), random, r);
}
long l = System.nanoTime() - t;
sender.sendMessage("Took " + ((double) l) / 1000000 + "ms");
}
}

View File

@@ -0,0 +1,18 @@
package com.dfsek.terra.commands.structure.argument;
import com.dfsek.terra.api.TerraPlugin;
import com.dfsek.terra.api.command.arg.ArgumentParser;
import com.dfsek.terra.api.injection.annotations.Inject;
import com.dfsek.terra.api.platform.CommandSender;
import com.dfsek.terra.api.platform.entity.Player;
import com.dfsek.terra.api.structures.script.StructureScript;
public class ScriptArgumentParser implements ArgumentParser<StructureScript> {
@Inject
private TerraPlugin main;
@Override
public StructureScript parse(CommandSender sender, String arg) {
return main.getWorld(((Player) sender).getWorld()).getConfig().getScriptRegistry().get(arg);
}
}

View File

@@ -0,0 +1,14 @@
package com.dfsek.terra.commands.structure.completer;
import com.dfsek.terra.api.command.tab.TabCompleter;
import com.dfsek.terra.api.platform.CommandSender;
import java.util.Arrays;
import java.util.List;
public class RotationCompleter implements TabCompleter {
@Override
public List<String> complete(CommandSender sender) {
return Arrays.asList("0", "90", "180", "270");
}
}

View File

@@ -0,0 +1,21 @@
package com.dfsek.terra.commands.structure.completer;
import com.dfsek.terra.api.TerraPlugin;
import com.dfsek.terra.api.command.tab.TabCompleter;
import com.dfsek.terra.api.injection.annotations.Inject;
import com.dfsek.terra.api.platform.CommandSender;
import com.dfsek.terra.api.platform.entity.Player;
import com.dfsek.terra.api.structures.script.StructureScript;
import java.util.List;
import java.util.stream.Collectors;
public class ScriptCompleter implements TabCompleter {
@Inject
private TerraPlugin main;
@Override
public List<String> complete(CommandSender sender) {
return main.getWorld(((Player) sender).getWorld()).getConfig().getScriptRegistry().entries().stream().map(StructureScript::getId).collect(Collectors.toList());
}
}

View File

@@ -63,11 +63,6 @@ public class DummyWorld implements World {
throw new UnsupportedOperationException("Cannot get block in DummyWorld");
}
@Override
public Block getBlockAt(Location l) {
throw new UnsupportedOperationException("Cannot get block in DummyWorld");
}
@Override
public Entity spawnEntity(Location location, EntityType entityType) {
throw new UnsupportedOperationException("Cannot spawn entity in DummyWorld");

View File

@@ -1,19 +1,19 @@
package com.dfsek.terra.config.loaders.config.biome.templates.provider;
import com.dfsek.tectonic.annotations.Value;
import com.dfsek.terra.api.world.biome.TerraBiome;
import com.dfsek.terra.api.world.biome.provider.BiomeProvider;
import com.dfsek.terra.api.world.biome.provider.SingleBiomeProvider;
import com.dfsek.terra.config.builder.BiomeBuilder;
public class SingleBiomeProviderTemplate extends BiomeProviderTemplate {
@Value("biome")
private TerraBiome biome;
private BiomeBuilder biome;
public SingleBiomeProviderTemplate() {
}
@Override
public BiomeProvider build(long seed) {
return new SingleBiomeProvider(biome);
return new SingleBiomeProvider(biome.apply(seed));
}
}

View File

@@ -274,6 +274,7 @@ public class ConfigPack implements LoaderRegistrar {
.registerLoader(NoiseSeeded.class, new NoiseSamplerBuilderLoader(noiseRegistry))
.registerLoader(SingleBiomeProviderTemplate.class, SingleBiomeProviderTemplate::new)
.registerLoader(BiomePipelineTemplate.class, () -> new BiomePipelineTemplate(main))
.registerLoader(ImageProviderTemplate.class, () -> new ImageProviderTemplate(biomeRegistry))
.registerLoader(ImageSamplerTemplate.class, () -> new ImageProviderTemplate(biomeRegistry));
}

View File

@@ -11,7 +11,6 @@ import com.dfsek.terra.api.platform.world.World;
import com.dfsek.terra.api.util.world.PopulationUtil;
import com.dfsek.terra.api.world.generation.TerraBlockPopulator;
import com.dfsek.terra.carving.UserDefinedCarver;
import com.dfsek.terra.config.pack.ConfigPack;
import com.dfsek.terra.config.pack.WorldConfig;
import com.dfsek.terra.config.templates.CarverTemplate;
import com.dfsek.terra.profiler.ProfileFuture;
@@ -49,7 +48,7 @@ public class CavePopulator implements TerraBlockPopulator {
Set<Block> updateNeeded = new HashSet<>();
c.carve(chunk.getX(), chunk.getZ(), world, (v, type) -> {
Block b = chunk.getBlock(v.getBlockX(), v.getBlockY(), v.getBlockZ());
BlockData m = handle.getBlockData(b);
BlockData m = b.getBlockData();
BlockType re = m.getBlockType();
switch(type) {
case CENTER:
@@ -85,18 +84,18 @@ public class CavePopulator implements TerraBlockPopulator {
for(Map.Entry<Location, BlockData> entry : shiftCandidate.entrySet()) {
Location l = entry.getKey();
Location mut = l.clone();
BlockData orig = handle.getBlockData(l.getBlock());
BlockData orig = l.getBlock().getBlockData();
do mut.subtract(0, 1, 0);
while(mut.getY() > 0 && handle.getBlockData(mut.getBlock()).matches(orig));
while(mut.getY() > 0 && mut.getBlock().getBlockData().matches(orig));
try {
if(template.getShift().get(entry.getValue().getBlockType()).contains(mut.getBlock().getBlockData().getBlockType())) {
handle.setBlockData(mut.getBlock(), shiftStorage.computeIfAbsent(entry.getValue().getBlockType(), BlockType::getDefaultData), false);
mut.getBlock().setBlockData(shiftStorage.computeIfAbsent(entry.getValue().getBlockType(), BlockType::getDefaultData), false);
}
} catch(NullPointerException ignore) {
}
}
for(Block b : updateNeeded) {
BlockData orig = handle.getBlockData(b);
BlockData orig = b.getBlockData();
b.setBlockData(AIR, false);
b.setBlockData(orig, true);
}

View File

@@ -118,7 +118,7 @@ public class TerraFlora implements Flora {
((Rotatable) data).setRotation(oneFace);
}
}
handle.setBlockData(location.clone().add(0, i + c, 0).getBlock(), data, physics);
location.clone().add(0, i + c, 0).getBlock().setBlockData(data, physics);
}
return true;
}

View File

@@ -40,7 +40,7 @@ public class DeformedSphereOre extends Ore {
if(oreLoc.distance(origin) < (rad + 0.5) * ((ore.getNoise(x, y, z) + 1) * deform)) {
Block b = c.getBlock(oreLoc.getBlockX(), oreLoc.getBlockY(), oreLoc.getBlockZ());
if(getReplaceable().contains(b.getType()) && b.getLocation().getY() >= 0)
handle.setBlockData(b, getMaterial(), isApplyGravity());
b.setBlockData(getMaterial(), isApplyGravity());
}
}
}

View File

@@ -68,7 +68,7 @@ public class VanillaOre extends Ore {
if(x > 15 || z > 15 || y > 255 || x < 0 || z < 0 || y < 0) continue;
Block block = chunk.getBlock(x, y, z);
if((d13 * d13 + d14 * d14 + d15 * d15 < 1.0D) && getReplaceable().contains(block.getType())) {
handle.setBlockData(block, getMaterial(), isApplyGravity());
block.setBlockData(getMaterial(), isApplyGravity());
}
}
}

View File

@@ -112,7 +112,7 @@ warning:
error:
severe-config: "A severe configuration error has prevented Terra from properly generating terrain at coordinates: %1$s, %2$s. Please check your configuration for errors. Any config errors will have been reported above."
debug:
data-save: "Saved population data for world \"%s\""
data-save : "Saved population data."
use-paper:
- "You appear to be using Spigot/CraftBukkit."
- "While Terra &odoes&r work on Spigot, some functionality will be lost. (Terra is untested on CraftBukkit; no support will be given for CraftBukkit)."

View File

@@ -0,0 +1,236 @@
package command;
import com.dfsek.terra.api.command.CommandManager;
import com.dfsek.terra.api.command.CommandTemplate;
import com.dfsek.terra.api.command.TerraCommandManager;
import com.dfsek.terra.api.command.annotation.Argument;
import com.dfsek.terra.api.command.annotation.Command;
import com.dfsek.terra.api.command.annotation.Subcommand;
import com.dfsek.terra.api.command.annotation.Switch;
import com.dfsek.terra.api.command.annotation.inject.ArgumentTarget;
import com.dfsek.terra.api.command.annotation.inject.SwitchTarget;
import com.dfsek.terra.api.command.arg.DoubleArgumentParser;
import com.dfsek.terra.api.command.arg.IntegerArgumentParser;
import com.dfsek.terra.api.command.exception.CommandException;
import com.dfsek.terra.api.command.exception.InvalidArgumentsException;
import com.dfsek.terra.api.command.exception.MalformedCommandException;
import com.dfsek.terra.api.platform.CommandSender;
import org.junit.jupiter.api.Test;
import java.util.Arrays;
import static org.junit.jupiter.api.Assertions.fail;
public class CommandTest {
@Test
public void subcommand() throws CommandException {
CommandManager manager = new TerraCommandManager(null);
manager.register("test", DemoParentCommand.class);
manager.execute("test", null, Arrays.asList("subcommand1", "first", "2"));
manager.execute("test", null, Arrays.asList("subcommand2", "first", "2"));
manager.execute("test", null, Arrays.asList("s1", "first", "2", "3.4"));
manager.execute("test", null, Arrays.asList("s2", "first", "2"));
manager.execute("test", null, Arrays.asList("sub1", "first", "2", "3.4"));
manager.execute("test", null, Arrays.asList("sub2", "first", "2"));
manager.execute("test", null, Arrays.asList("first", "2")); // Parent command args
System.out.println("ARGS: " + manager.getMaxArgumentDepth());
}
@Test
public void args() throws CommandException {
CommandManager manager = new TerraCommandManager(null);
manager.register("test", DemoCommand.class);
manager.execute("test", null, Arrays.asList("first", "2"));
manager.execute("test", null, Arrays.asList("first", "2", "3.4"));
}
@Test
public void argsBeforeFlags() throws CommandException {
CommandManager manager = new TerraCommandManager(null);
manager.register("test", DemoCommand.class);
try {
manager.execute("test", null, Arrays.asList("first", "-flag", "2"));
fail();
} catch(InvalidArgumentsException ignore) {
}
}
@Test
public void requiredArgsFirst() throws CommandException {
CommandManager manager = new TerraCommandManager(null);
manager.register("test", DemoInvalidCommand.class);
try {
manager.execute("test", null, Arrays.asList("first", "2"));
fail();
} catch(MalformedCommandException ignore) {
}
}
@Test
public void switches() throws CommandException {
CommandManager manager = new TerraCommandManager(null);
manager.register("test", DemoSwitchCommand.class);
manager.execute("test", null, Arrays.asList("first", "2"));
manager.execute("test", null, Arrays.asList("first", "2", "3.4"));
manager.execute("test", null, Arrays.asList("first", "2", "-a"));
manager.execute("test", null, Arrays.asList("first", "2", "3.4", "-b"));
manager.execute("test", null, Arrays.asList("first", "2", "-aSwitch"));
manager.execute("test", null, Arrays.asList("first", "2", "3.4", "-bSwitch"));
manager.execute("test", null, Arrays.asList("first", "2", "-aSwitch", "-b"));
manager.execute("test", null, Arrays.asList("first", "2", "3.4", "-bSwitch", "-a"));
}
@Command(
arguments = {
@Argument(value = "arg0"),
@Argument(value = "arg1", argumentParser = IntegerArgumentParser.class),
@Argument(value = "arg2", required = false, argumentParser = DoubleArgumentParser.class)
}
)
public static final class DemoCommand implements CommandTemplate {
@ArgumentTarget("arg0")
private String arg0;
@ArgumentTarget("arg1")
private Integer arg1;
@ArgumentTarget("arg2")
private Double arg2;
@Override
public void execute(CommandSender sender) {
System.out.println(arg0);
System.out.println(arg1);
System.out.println(arg2);
}
}
@Command(
arguments = {
@Argument(value = "arg0"),
@Argument(value = "arg1"),
@Argument(value = "arg2", required = false)
},
switches = {
@Switch(value = "a", aliases = {"aSwitch"}),
@Switch(value = "b", aliases = {"bSwitch"})
}
)
public static final class DemoSwitchCommand implements CommandTemplate {
@ArgumentTarget("arg0")
private String arg0;
@ArgumentTarget("arg1")
private String arg1;
@ArgumentTarget("arg2")
private String arg2;
@SwitchTarget("a")
private boolean a;
@SwitchTarget("b")
private boolean b;
@Override
public void execute(CommandSender sender) {
System.out.println(arg0);
System.out.println(arg1);
System.out.println(arg2);
System.out.println("A: " + a);
System.out.println("B: " + b);
}
}
@Command(
arguments = {
@Argument(value = "arg0"),
@Argument(value = "arg2", required = false), // optional arguments must be last. this command is invalid.
@Argument(value = "arg1")
}
)
public static final class DemoInvalidCommand implements CommandTemplate {
@Override
public void execute(CommandSender sender) {
throw new Error("this should never be reached");
}
}
@Command(
arguments = {
@Argument(value = "arg0"),
@Argument(value = "arg1"),
@Argument(value = "arg2", required = false),
},
subcommands = {
@Subcommand(
value = "subcommand1",
aliases = {"s1", "sub1"},
clazz = DemoChildCommand.class
),
@Subcommand(
value = "subcommand2",
aliases = {"s2", "sub2"},
clazz = DemoChildCommand.class // Duplicate command intentional.
)
}
)
public static final class DemoParentCommand implements CommandTemplate {
@ArgumentTarget("arg0")
private String arg0;
@ArgumentTarget("arg1")
private String arg1;
@ArgumentTarget("arg2")
private String arg2;
@Override
public void execute(CommandSender sender) {
System.out.println(arg0);
System.out.println(arg1);
System.out.println(arg2);
}
}
@Command(
arguments = {
@Argument(value = "arg0"),
@Argument(value = "arg1"),
@Argument(value = "arg2", required = false),
}
)
public static final class DemoChildCommand implements CommandTemplate {
@ArgumentTarget("arg0")
private String arg0;
@ArgumentTarget("arg1")
private String arg1;
@ArgumentTarget("arg2")
private String arg2;
@Override
public void execute(CommandSender sender) {
System.out.println(arg0);
System.out.println(arg1);
System.out.println(arg2);
}
}
}