Merge pull request #80 from PolyhedralDev/ver/5.0.0

Addon loader, fleshed out addon API, and Fabric finalization
This commit is contained in:
dfsek 2021-03-16 10:45:42 -07:00 committed by GitHub
commit 7f11373f75
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
458 changed files with 7970 additions and 4174 deletions

1
.gitignore vendored
View File

@ -52,6 +52,7 @@ gradle-app.setting
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid* hs_err_pid*
*.hprof
### JetBrains template ### JetBrains template
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider

View File

@ -1,20 +1,25 @@
# Terra # Terra
Terra is a data-driven world generator based on [Gaea](https://github.com/PolyhedralDev/Gaea). Find out more on our
[Spigot page](https://www.spigotmc.org/resources/85151/)! Terra is an incredibly powerful free & open-source data-driven, platform-agnostic world generator. It allows you to create a world exactly
to your specifications, with no knowledge of Java required.
## Downloads:
* Paper+ servers (Paper, Tuinity, Purpur, etc): [SpigotMC](https://www.spigotmc.org/resources/85151/)
* Fabric: [Modrinth](https://modrinth.com/mod/terra) / [CurseForge](https://www.curseforge.com/minecraft/mc-mods/terra-world-generator)
## Building and running Terra ## Building and running Terra
To build, simply run `./gradlew build` on Linux/MacOS, or `gradlew.bat build` on Windows.
This will produce a jar in `build/libs` called `Terra-[CURRENT VERSION].jar`. To build, simply run `./gradlew build` (`gradlew.bat build` on Windows). This will produce a jar in `build/libs`
You can put this right into your plugins dir, along with the correct Gaea version. called `Terra-[CURRENT VERSION].jar`. You can put this right into your plugins dir, along with the correct Gaea version.
If you would like to test it with a default server config, just run `./gradlew setupServer` or If you would like to test it with a default server config, just run `./gradlew setupServer` or
`./gradlew.bat setupServer` to set up the server, then `./gradlew testWithPaper` or `gradlew.bat testWithPaper` to run `./gradlew.bat setupServer` to set up the server, then `./gradlew testWithPaper` or `gradlew.bat testWithPaper` to run the server. If you
the server. If you want a clean installation of the server, re-run the `setupServer` task. want a clean installation of the server, re-run the `setupServer` task. This will download a default server config
This will download a default server config from [here](https://github.com/PolyhedralDev/WorldGenTestServer) from [here](https://github.com/PolyhedralDev/WorldGenTestServer)
and install the server in the `target/server` directory, along with all the needed plugins. and install the server in the `target/server` directory, along with all the needed plugins.
**Note: You will need to adjust the `NAME` variable `bukkit.yml` of the test server if you are not using the default **Note: You will need to adjust the `NAME` variable `bukkit.yml` of the test server if you are not using the default Terra config.**
Terra config.**
## Contributing ## Contributing
Contributions are welcome! If you want to see a feature in Terra, please, open an issue, or implement it yourself and Contributions are welcome! If you want to see a feature in Terra, please, open an issue, or implement it yourself and

View File

@ -1,6 +1,6 @@
import com.dfsek.terra.getGitHash import com.dfsek.terra.getGitHash
val versionObj = Version("4", "3", "0", true) val versionObj = Version("5", "0", "0", true)
allprojects { allprojects {
version = versionObj version = versionObj

View File

@ -6,7 +6,9 @@ import org.gradle.api.plugins.JavaPluginConvention
import org.gradle.api.tasks.compile.JavaCompile import org.gradle.api.tasks.compile.JavaCompile
import org.gradle.api.tasks.javadoc.Javadoc import org.gradle.api.tasks.javadoc.Javadoc
import org.gradle.kotlin.dsl.configure import org.gradle.kotlin.dsl.configure
import org.gradle.kotlin.dsl.filter
import org.gradle.kotlin.dsl.withType import org.gradle.kotlin.dsl.withType
import org.gradle.language.jvm.tasks.ProcessResources
fun Project.configureCompilation() { fun Project.configureCompilation() {
configure<JavaPluginConvention> { configure<JavaPluginConvention> {
@ -21,6 +23,15 @@ fun Project.configureCompilation() {
} }
} }
tasks.withType<ProcessResources> {
include("**/*.*")
filter<org.apache.tools.ant.filters.ReplaceTokens>(
"tokens" to mapOf(
"VERSION" to project.version.toString()
)
)
}
tasks.withType<Javadoc> { tasks.withType<Javadoc> {
options.encoding = "UTF-8" options.encoding = "UTF-8"
} }

View File

@ -18,10 +18,13 @@ dependencies {
"shadedApi"("net.jafama:jafama:2.3.2") "shadedApi"("net.jafama:jafama:2.3.2")
"shadedApi"("org.yaml:snakeyaml:1.27") "shadedApi"("org.yaml:snakeyaml:1.27")
"shadedApi"("org.ow2.asm:asm:9.0") "shadedApi"("org.ow2.asm:asm:9.0")
"shadedApi"("commons-io:commons-io:2.6")
"compileOnly"("com.googlecode.json-simple:json-simple:1.1") "compileOnly"("com.googlecode.json-simple:json-simple:1.1")
"shadedApi"("com.google.guava:guava:30.0-jre") "compileOnly"("com.google.guava:guava:30.0-jre")
"testImplementation"("com.google.guava:guava:30.0-jre")
} }
publishing { publishing {

View File

@ -0,0 +1,61 @@
package com.dfsek.terra.addon;
import com.dfsek.terra.api.addons.TerraAddon;
import com.dfsek.terra.api.addons.annotations.Addon;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
public class AddonClassLoader extends URLClassLoader {
static {
ClassLoader.registerAsParallelCapable();
}
public AddonClassLoader(URL[] urls, ClassLoader parent) {
super(urls, parent);
}
public AddonClassLoader(URL[] urls) {
super(urls);
}
@SuppressWarnings("unchecked")
public static Set<Class<? extends TerraAddon>> fetchAddonClasses(File file) throws IOException {
JarFile jarFile = new JarFile(file);
Enumeration<JarEntry> entries = jarFile.entries();
AddonClassLoader loader = new AddonClassLoader(new URL[] {file.toURI().toURL()}, AddonClassLoader.class.getClassLoader());
Set<Class<? extends TerraAddon>> set = new HashSet<>();
while(entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
if(entry.isDirectory() || !entry.getName().endsWith(".class")) continue;
String className = entry.getName().substring(0, entry.getName().length() - 6).replace('/', '.');
try {
Class<?> clazz = loader.loadClass(className);
Addon addon = clazz.getAnnotation(Addon.class);
if(addon == null) continue;
if(!TerraAddon.class.isAssignableFrom(clazz))
throw new IllegalArgumentException("Addon class \"" + clazz + "\" must extend TerraAddon.");
set.add((Class<? extends TerraAddon>) clazz);
} catch(ClassNotFoundException e) {
throw new IllegalStateException(e); // this should literally never happen, if it does something is very wrong
}
}
return set;
}
}

View File

@ -0,0 +1,42 @@
package com.dfsek.terra.addon;
import com.dfsek.terra.addon.exception.AddonLoadException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
public class AddonPool {
private final Map<String, PreLoadAddon> pool = new HashMap<>();
public void add(PreLoadAddon addon) throws AddonLoadException {
if(pool.containsKey(addon.getId())) {
String message = "Duplicate addon ID: " +
addon.getId() + "; original ID from file: " +
pool.get(addon.getId()).getFile().getAbsolutePath() +
", class: " +
pool.get(addon.getId()).getAddonClass().getCanonicalName() +
"Duplicate ID from file: " +
addon.getFile().getAbsolutePath() +
", class: " +
addon.getAddonClass().getCanonicalName();
throw new AddonLoadException(message);
}
pool.put(addon.getId(), addon);
}
public PreLoadAddon get(String id) {
return pool.get(id);
}
public void buildAll() throws AddonLoadException {
for(PreLoadAddon value : pool.values()) {
value.rebuildDependencies(this, value, true);
}
}
public Set<PreLoadAddon> getAddons() {
return new HashSet<>(pool.values());
}
}

View File

@ -0,0 +1,58 @@
package com.dfsek.terra.addon;
import com.dfsek.terra.addon.exception.AddonLoadException;
import com.dfsek.terra.addon.exception.CircularDependencyException;
import com.dfsek.terra.addon.exception.DependencyMissingException;
import com.dfsek.terra.api.addons.TerraAddon;
import com.dfsek.terra.api.addons.annotations.Addon;
import com.dfsek.terra.api.addons.annotations.Depends;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class PreLoadAddon {
private final List<PreLoadAddon> depends = new ArrayList<>();
private final Class<? extends TerraAddon> addonClass;
private final String id;
private final String[] dependencies;
private final File file;
public PreLoadAddon(Class<? extends TerraAddon> addonClass, File file) {
this.addonClass = addonClass;
this.id = addonClass.getAnnotation(Addon.class).value();
this.file = file;
Depends depends = addonClass.getAnnotation(Depends.class);
this.dependencies = depends == null ? new String[] {} : depends.value();
}
public List<PreLoadAddon> getDepends() {
return depends;
}
public void rebuildDependencies(AddonPool pool, PreLoadAddon origin, boolean levelG1) throws AddonLoadException {
if(this.equals(origin) && !levelG1)
throw new CircularDependencyException("Detected circular dependency in addon \"" + id + "\", dependencies: " + Arrays.toString(dependencies));
for(String dependency : dependencies) {
PreLoadAddon preLoadAddon = pool.get(dependency);
if(preLoadAddon == null)
throw new DependencyMissingException("Dependency " + dependency + " was not found. Please install " + dependency + " to use " + id + ".");
depends.add(preLoadAddon);
preLoadAddon.rebuildDependencies(pool, origin, false);
}
}
public String getId() {
return id;
}
public Class<? extends TerraAddon> getAddonClass() {
return addonClass;
}
public File getFile() {
return file;
}
}

View File

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

View File

@ -0,0 +1,13 @@
package com.dfsek.terra.addon.exception;
public class CircularDependencyException extends AddonLoadException {
private static final long serialVersionUID = 7398510879124125121L;
public CircularDependencyException(String message) {
super(message);
}
public CircularDependencyException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -0,0 +1,13 @@
package com.dfsek.terra.addon.exception;
public class DependencyMissingException extends AddonLoadException {
private static final long serialVersionUID = -8419489102208521583L;
public DependencyMissingException(String message) {
super(message);
}
public DependencyMissingException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -0,0 +1,67 @@
package com.dfsek.terra.api;
import com.dfsek.terra.api.addons.TerraAddon;
import com.dfsek.terra.api.event.EventManager;
import com.dfsek.terra.api.platform.handle.ItemHandle;
import com.dfsek.terra.api.platform.handle.WorldHandle;
import com.dfsek.terra.api.platform.world.World;
import com.dfsek.terra.api.registry.CheckedRegistry;
import com.dfsek.terra.api.registry.LockedRegistry;
import com.dfsek.terra.api.util.logging.DebugLogger;
import com.dfsek.terra.api.util.logging.Logger;
import com.dfsek.terra.config.PluginConfig;
import com.dfsek.terra.config.lang.Language;
import com.dfsek.terra.config.pack.ConfigPack;
import com.dfsek.terra.world.TerraWorld;
import java.io.File;
/**
* Represents a Terra mod/plugin instance.
*/
public interface TerraPlugin extends LoaderRegistrar {
WorldHandle getWorldHandle();
TerraWorld getWorld(World world);
Logger logger();
PluginConfig getTerraConfig();
File getDataFolder();
boolean isDebug();
Language getLanguage();
CheckedRegistry<ConfigPack> getConfigRegistry();
LockedRegistry<TerraAddon> getAddons();
boolean reload();
ItemHandle getItemHandle();
void saveDefaultConfig();
String platformName();
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,49 @@
package com.dfsek.terra.api.addons;
import com.dfsek.terra.api.addons.annotations.Addon;
import com.dfsek.terra.api.addons.annotations.Author;
import com.dfsek.terra.api.addons.annotations.Version;
import org.jetbrains.annotations.NotNull;
/**
* Represents an entry point for an addon. Implementations must be annotated with {@link Addon}.
*/
public abstract class TerraAddon {
/**
* Gets the version of this addon.
*
* @return Addon version.
*/
public final @NotNull String getVersion() {
Version version = getClass().getAnnotation(Version.class);
return version == null ? "0.1.0" : version.value();
}
/**
* Gets the author of this addon.
*
* @return Addon author.
*/
public final @NotNull String getAuthor() {
Author author = getClass().getAnnotation(Author.class);
return author == null ? "Anon Y. Mous" : author.value();
}
/**
* Gets the name (ID) of this addon.
*
* @return Addon ID.
*/
public final @NotNull String getName() {
Addon addon = getClass().getAnnotation(Addon.class);
if(addon == null)
throw new IllegalStateException("Addon annotation not present"); // This should never happen; the presence of this annotation is checked by the addon loader.
return addon.value();
}
/**
* Invoked immediately after an addon is loaded.
*/
public abstract void initialize();
}

View File

@ -0,0 +1,20 @@
package com.dfsek.terra.api.addons.annotations;
import org.jetbrains.annotations.NotNull;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Specifies that the annotated class is an entry point for a Terra addon.
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Addon {
/**
* @return The ID of the addon.
*/
@NotNull String value();
}

View File

@ -0,0 +1,20 @@
package com.dfsek.terra.api.addons.annotations;
import org.jetbrains.annotations.NotNull;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Optional annotation that specifies the author of an addon.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Author {
/**
* @return Name of the addon author.
*/
@NotNull String value();
}

View File

@ -0,0 +1,20 @@
package com.dfsek.terra.api.addons.annotations;
import org.jetbrains.annotations.NotNull;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Optional annotation that specifies dependencies of an addon.
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Depends {
/**
* @return All addons this addon is dependent upon.
*/
@NotNull String[] value();
}

View File

@ -0,0 +1,20 @@
package com.dfsek.terra.api.addons.annotations;
import org.jetbrains.annotations.NotNull;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Optional annotation that specifies the version of an addon.
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Version {
/**
* @return Version of the addon.
*/
@NotNull String value();
}

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

@ -1,48 +0,0 @@
package com.dfsek.terra.api.core;
import com.dfsek.terra.api.LoaderRegistrar;
import com.dfsek.terra.api.core.event.EventManager;
import com.dfsek.terra.api.platform.handle.ItemHandle;
import com.dfsek.terra.api.platform.handle.WorldHandle;
import com.dfsek.terra.api.platform.world.World;
import com.dfsek.terra.config.PluginConfig;
import com.dfsek.terra.config.lang.Language;
import com.dfsek.terra.debug.DebugLogger;
import com.dfsek.terra.registry.ConfigRegistry;
import com.dfsek.terra.world.TerraWorld;
import java.io.File;
import java.util.logging.Logger;
public interface TerraPlugin extends LoaderRegistrar {
WorldHandle getWorldHandle();
boolean isEnabled();
TerraWorld getWorld(World world);
Logger getLogger();
PluginConfig getTerraConfig();
File getDataFolder();
boolean isDebug();
Language getLanguage();
ConfigRegistry getRegistry();
void reload();
ItemHandle getItemHandle();
void saveDefaultConfig();
String platformName();
DebugLogger getDebugLogger();
EventManager getEventManager();
}

View File

@ -1,4 +0,0 @@
package com.dfsek.terra.api.core.event;
public interface EventListener {
}

View File

@ -1,14 +0,0 @@
package com.dfsek.terra.api.core.event;
import com.dfsek.terra.api.core.event.events.Event;
public interface EventManager {
/**
* Call an event, and return the execution status.
* @param event Event to pass to all registered EventListeners.
* @return False if the event is cancellable and has been cancelled, otherwise true.
*/
boolean callEvent(Event event);
void registerListener(EventListener listener);
}

View File

@ -1,11 +0,0 @@
package com.dfsek.terra.api.core.event.events;
/**
* Events that implement this interface may be cancelled.
*
* Cancelling an event is assumed to stop the execution of whatever action triggered the event.
*/
public interface Cancellable extends Event {
boolean isCancelled();
void setCancelled();
}

View File

@ -1,4 +0,0 @@
package com.dfsek.terra.api.core.event.events;
public interface Event {
}

View File

@ -1,16 +0,0 @@
package com.dfsek.terra.api.core.event.events.config;
import com.dfsek.terra.api.core.event.events.Event;
import com.dfsek.terra.config.pack.ConfigPack;
public abstract class ConfigPackLoadEvent implements Event {
private final ConfigPack pack;
public ConfigPackLoadEvent(ConfigPack pack) {
this.pack = pack;
}
public ConfigPack getPack() {
return pack;
}
}

View File

@ -0,0 +1,12 @@
package com.dfsek.terra.api.event;
import com.dfsek.terra.api.event.events.Event;
/**
* Marker interface for a class that contains event listener methods.
*
* @see Event
* @see EventManager
*/
public interface EventListener {
}

View File

@ -0,0 +1,25 @@
package com.dfsek.terra.api.event;
import com.dfsek.terra.api.addons.TerraAddon;
import com.dfsek.terra.api.event.events.Event;
/**
* Manages event registration and triggering.
*/
public interface EventManager {
/**
* Call an event, and return the execution status.
*
* @param event Event to pass to all registered EventListeners.
* @return False if the event is cancellable and has been cancelled, otherwise true.
*/
boolean callEvent(Event event);
/**
* Register an {@link EventListener} under an {@link TerraAddon}.
*
* @param addon Addon to register listener for.
* @param listener Listener to register.
*/
void registerListener(TerraAddon addon, EventListener listener);
}

View File

@ -1,9 +1,12 @@
package com.dfsek.terra.api.core.event; package com.dfsek.terra.api.event;
import com.dfsek.terra.api.core.TerraPlugin; import com.dfsek.terra.api.TerraPlugin;
import com.dfsek.terra.api.core.event.annotations.Priority; import com.dfsek.terra.api.addons.TerraAddon;
import com.dfsek.terra.api.core.event.events.Cancellable; import com.dfsek.terra.api.event.annotations.Global;
import com.dfsek.terra.api.core.event.events.Event; import com.dfsek.terra.api.event.annotations.Priority;
import com.dfsek.terra.api.event.events.Cancellable;
import com.dfsek.terra.api.event.events.Event;
import com.dfsek.terra.api.event.events.PackEvent;
import com.dfsek.terra.api.util.ReflectionUtil; import com.dfsek.terra.api.util.ReflectionUtil;
import java.io.PrintWriter; import java.io.PrintWriter;
@ -29,19 +32,30 @@ public class TerraEventManager implements EventManager {
public boolean callEvent(Event event) { public boolean callEvent(Event event) {
listeners.getOrDefault(event.getClass(), Collections.emptyList()).forEach(listenerHolder -> { listeners.getOrDefault(event.getClass(), Collections.emptyList()).forEach(listenerHolder -> {
try { try {
if(event instanceof PackEvent && !listenerHolder.global) {
PackEvent packEvent = (PackEvent) event;
if(packEvent
.getPack()
.getTemplate()
.getAddons()
.contains(listenerHolder.addon)) {
listenerHolder.method.invoke(listenerHolder.listener, event); listenerHolder.method.invoke(listenerHolder.listener, event);
}
} else {
listenerHolder.method.invoke(listenerHolder.listener, event);
}
} catch(InvocationTargetException e) { } catch(InvocationTargetException e) {
StringWriter writer = new StringWriter(); StringWriter writer = new StringWriter();
e.getTargetException().printStackTrace(new PrintWriter(writer)); e.getTargetException().printStackTrace(new PrintWriter(writer));
main.getLogger().warning("Exception occurred during event handling:"); main.logger().warning("Exception occurred during event handling:");
main.getLogger().warning(writer.toString()); main.logger().warning(writer.toString());
main.getLogger().warning("Report this to the maintainers of " + listenerHolder.method.getName()); main.logger().warning("Report this to the maintainers of " + listenerHolder.method.getName());
} catch(Exception e) { } catch(Exception e) {
StringWriter writer = new StringWriter(); StringWriter writer = new StringWriter();
e.printStackTrace(new PrintWriter(writer)); e.printStackTrace(new PrintWriter(writer));
main.getLogger().warning("Exception occurred during event handling:"); main.logger().warning("Exception occurred during event handling:");
main.getLogger().warning(writer.toString()); main.logger().warning(writer.toString());
main.getLogger().warning("Report this to the maintainers of " + listenerHolder.method.getName()); main.logger().warning("Report this to the maintainers of " + listenerHolder.method.getName());
} }
} }
); );
@ -51,7 +65,7 @@ public class TerraEventManager implements EventManager {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Override @Override
public void registerListener(EventListener listener) { public void registerListener(TerraAddon addon, EventListener listener) {
Class<? extends EventListener> listenerClass = listener.getClass(); Class<? extends EventListener> listenerClass = listener.getClass();
Method[] methods = ReflectionUtil.getMethods(listenerClass); Method[] methods = ReflectionUtil.getMethods(listenerClass);
@ -68,7 +82,7 @@ public class TerraEventManager implements EventManager {
List<ListenerHolder> holders = listeners.computeIfAbsent((Class<? extends Event>) eventParam, e -> new ArrayList<>()); List<ListenerHolder> holders = listeners.computeIfAbsent((Class<? extends Event>) eventParam, e -> new ArrayList<>());
holders.add(new ListenerHolder(method, listener, priority)); holders.add(new ListenerHolder(method, listener, priority, addon, method.getAnnotation(Global.class) != null));
holders.sort(Comparator.comparingInt(ListenerHolder::getPriority)); // Sort priorities. holders.sort(Comparator.comparingInt(ListenerHolder::getPriority)); // Sort priorities.
} }
@ -78,11 +92,15 @@ public class TerraEventManager implements EventManager {
private final Method method; private final Method method;
private final EventListener listener; private final EventListener listener;
private final int priority; private final int priority;
private final TerraAddon addon;
private final boolean global;
private ListenerHolder(Method method, EventListener listener, int priority) { private ListenerHolder(Method method, EventListener listener, int priority, TerraAddon addon, boolean global) {
this.method = method; this.method = method;
this.listener = listener; this.listener = listener;
this.priority = priority; this.priority = priority;
this.addon = addon;
this.global = global;
} }
public int getPriority() { public int getPriority() {

View File

@ -0,0 +1,17 @@
package com.dfsek.terra.api.event.annotations;
import com.dfsek.terra.api.event.events.PackEvent;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Specifies that an event handler is to handle all {@link PackEvent}s, regardless of whether the pack
* depends on the addon's listener.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Global {
}

View File

@ -1,4 +1,4 @@
package com.dfsek.terra.api.core.event.annotations; package com.dfsek.terra.api.event.annotations;
import java.lang.annotation.ElementType; import java.lang.annotation.ElementType;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
@ -12,11 +12,11 @@ import java.lang.annotation.Target;
@Target(ElementType.METHOD) @Target(ElementType.METHOD)
public @interface Priority { public @interface Priority {
/** /**
* Highest possible priority. Listeners with this priority will always be invoked first. * Highest possible priority. Listeners with this priority will always be invoked last.
*/ */
int HIGHEST = Integer.MAX_VALUE; int HIGHEST = Integer.MAX_VALUE;
/** /**
* Lowest possible priority. Listeners with this priority will always be invoked last. * Lowest possible priority. Listeners with this priority will always be invoked first.
*/ */
int LOWEST = Integer.MIN_VALUE; int LOWEST = Integer.MIN_VALUE;
/** /**

View File

@ -0,0 +1,22 @@
package com.dfsek.terra.api.event.events;
/**
* Events that implement this interface may be cancelled.
* <p>
* Cancelling an event is assumed to stop the execution of whatever action triggered the event.
*/
public interface Cancellable extends Event {
/**
* Get the cancellation status of the event.
*
* @return Whether event is cancelled.
*/
boolean isCancelled();
/**
* Set the cancellation status of the event.
*
* @param cancelled Whether event is cancelled.
*/
void setCancelled(boolean cancelled);
}

View File

@ -0,0 +1,7 @@
package com.dfsek.terra.api.event.events;
/**
* An event that addons may listen to.
*/
public interface Event {
}

View File

@ -0,0 +1,20 @@
package com.dfsek.terra.api.event.events;
import com.dfsek.terra.api.event.annotations.Global;
import com.dfsek.terra.config.pack.ConfigPack;
/**
* An event with functionality directly linked to a {@link ConfigPack}.
* <p>
* PackEvents are only invoked when the pack specifies the addon in its
* {@code addon} key (or when the listener is annotated {@link Global}).
*/
@SuppressWarnings("InterfaceMayBeAnnotatedFunctional")
public interface PackEvent extends Event {
/**
* Get the {@link ConfigPack} associated with this event.
*
* @return ConfigPack associated with the event.
*/
ConfigPack getPack();
}

View File

@ -0,0 +1,20 @@
package com.dfsek.terra.api.event.events.config;
import com.dfsek.terra.api.event.events.PackEvent;
import com.dfsek.terra.config.pack.ConfigPack;
/**
* An event related to the loading process of config packs.
*/
public abstract class ConfigPackLoadEvent implements PackEvent {
private final ConfigPack pack;
public ConfigPackLoadEvent(ConfigPack pack) {
this.pack = pack;
}
@Override
public ConfigPack getPack() {
return pack;
}
}

View File

@ -1,4 +1,4 @@
package com.dfsek.terra.api.core.event.events.config; package com.dfsek.terra.api.event.events.config;
import com.dfsek.terra.config.pack.ConfigPack; import com.dfsek.terra.config.pack.ConfigPack;

View File

@ -1,4 +1,4 @@
package com.dfsek.terra.api.core.event.events.config; package com.dfsek.terra.api.event.events.config;
import com.dfsek.terra.config.pack.ConfigPack; import com.dfsek.terra.config.pack.ConfigPack;

View File

@ -1,6 +1,9 @@
package com.dfsek.terra.api.core.event.events.world; package com.dfsek.terra.api.event.events.world;
import com.dfsek.terra.api.core.event.events.Event; import com.dfsek.terra.api.event.events.Event;
import com.dfsek.terra.api.event.events.PackEvent;
import com.dfsek.terra.config.pack.ConfigPack;
import com.dfsek.terra.config.pack.WorldConfig;
import com.dfsek.terra.world.TerraWorld; import com.dfsek.terra.world.TerraWorld;
/** /**
@ -16,4 +19,8 @@ public class TerraWorldLoadEvent implements Event {
public TerraWorld getWorld() { public TerraWorld getWorld() {
return world; return world;
} }
public WorldConfig getPack() {
return world.getConfig();
}
} }

View File

@ -0,0 +1,78 @@
package com.dfsek.terra.api.injection;
import com.dfsek.terra.api.injection.annotations.Inject;
import com.dfsek.terra.api.injection.exception.InjectionException;
import com.dfsek.terra.api.util.ReflectionUtil;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.HashSet;
import java.util.Set;
/**
* Dynamic dependency injector.
* <p>
* Stores an object to inject, and injects it into objects passed to {@link #inject(Object)}.
*
* @param <T> Type of object to inject.
*/
public class Injector<T> {
private final T value;
private final Set<Class<? extends T>> targets = new HashSet<>();
/**
* Instantiate an Injector with a value to inject
*
* @param value Value to inject
*/
public Injector(T value) {
this.value = value;
}
/**
* Add an explicit class as a target. Useful for applications where subclasses may cause issues with DI.
*
* @param target Target class type.
*/
public void addExplicitTarget(Class<? extends T> target) {
targets.add(target);
}
/**
* Inject the stored object into an object.
* <p>
* Injects the stored object into any non-static, non-final fields
* annotated with {@link Inject},
* with type matching the stored object or any explicit targets
* ({@link #addExplicitTarget(Class)}.
*
* @param object Object to inject into
* @throws InjectionException If:
* <ul>
* <li>Matching field annotated with {@link Inject} is final</li>
* <li>Matching field annotated with {@link Inject} is static</li>
* <li>A reflective access exception occurs</li>
* </ul>
*/
public void inject(Object object) throws InjectionException {
for(Field field : ReflectionUtil.getFields(object.getClass())) {
Inject inject = field.getAnnotation(Inject.class);
if(inject == null) continue;
if(value.getClass().equals(field.getType()) || targets.contains(field.getType())) {
int mod = field.getModifiers();
if(Modifier.isFinal(mod)) {
throw new InjectionException("Attempted to inject final field: " + field);
}
if(Modifier.isStatic(mod)) {
throw new InjectionException("Attempted to inject static field: " + field);
}
field.setAccessible(true);
try {
field.set(object, value);
} catch(IllegalAccessException e) {
throw new InjectionException("Failed to inject field: " + field, e);
}
}
}
}
}

View File

@ -0,0 +1,14 @@
package com.dfsek.terra.api.injection.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Specifies that a field is a target for dependency injection.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Inject {
}

View File

@ -0,0 +1,18 @@
package com.dfsek.terra.api.injection.exception;
import com.dfsek.terra.api.injection.Injector;
/**
* Thrown when dynamic dependency injection cannot be completed by an {@link Injector}.
*/
public class InjectionException extends Exception {
private static final long serialVersionUID = -6929631447064215387L;
public InjectionException(String message) {
super(message);
}
public InjectionException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -1,7 +1,7 @@
package com.dfsek.terra.api.math; package com.dfsek.terra.api.math;
import com.dfsek.terra.api.util.FastRandom; import com.dfsek.terra.api.util.FastRandom;
import com.dfsek.terra.world.generation.math.Sampler; import com.dfsek.terra.world.generation.math.samplers.Sampler;
import net.jafama.FastMath; import net.jafama.FastMath;
import java.util.List; import java.util.List;

View File

@ -1,96 +0,0 @@
package com.dfsek.terra.api.math;
import com.dfsek.terra.api.math.noise.NoiseSampler;
import java.util.Collections;
import java.util.HashSet;
import java.util.Random;
import java.util.Set;
@SuppressWarnings("unchecked")
public class ProbabilityCollection<E> {
private final Set<E> cont = new HashSet<>();
private Object[] array = new Object[0];
private int size;
public com.dfsek.terra.api.math.ProbabilityCollection<E> add(E item, int probability) {
if(!cont.contains(item)) size++;
cont.add(item);
int oldLength = array.length;
Object[] newArray = new Object[array.length + probability];
System.arraycopy(array, 0, newArray, 0, array.length); // Expand array.
array = newArray;
for(int i = oldLength; i < array.length; i++) array[i] = item;
return this;
}
public E get(Random r) {
if(array.length == 0) return null;
return (E) array[r.nextInt(array.length)];
}
public E get(NoiseSampler n, double x, double y, double z) {
if(array.length == 0) return null;
return (E) array[MathUtil.normalizeIndex(n.getNoise(x, y, z), array.length)];
}
public E get(NoiseSampler n, double x, double z) {
if(array.length == 0) return null;
return (E) array[MathUtil.normalizeIndex(n.getNoise(x, z), array.length)];
}
public int getTotalProbability() {
return array.length;
}
public int size() {
return size;
}
public Set<E> getContents() {
return new HashSet<>(cont);
}
public static final class Singleton<T> extends ProbabilityCollection<T> {
private final T single;
public Singleton(T single) {
this.single = single;
}
@Override
public ProbabilityCollection<T> add(T item, int probability) {
throw new UnsupportedOperationException();
}
@Override
public T get(Random r) {
return single;
}
@Override
public T get(NoiseSampler n, double x, double y, double z) {
return single;
}
@Override
public T get(NoiseSampler n, double x, double z) {
return single;
}
@Override
public int getTotalProbability() {
return 1;
}
@Override
public int size() {
return 1;
}
@Override
public Set<T> getContents() {
return Collections.singleton(single);
}
}
}

View File

@ -24,7 +24,7 @@ public class Range implements Iterable<Integer> {
return max; return max;
} }
public com.dfsek.terra.api.math.Range setMax(int max) { public Range setMax(int max) {
this.max = max; this.max = max;
return this; return this;
} }
@ -33,7 +33,7 @@ public class Range implements Iterable<Integer> {
return min; return min;
} }
public com.dfsek.terra.api.math.Range setMin(int min) { public Range setMin(int min) {
this.min = min; this.min = min;
return this; return this;
} }
@ -42,35 +42,35 @@ public class Range implements Iterable<Integer> {
return max - min; return max - min;
} }
public com.dfsek.terra.api.math.Range multiply(int mult) { public Range multiply(int mult) {
min *= mult; min *= mult;
max *= mult; max *= mult;
return this; return this;
} }
public com.dfsek.terra.api.math.Range reflect(int pt) { public Range reflect(int pt) {
return new com.dfsek.terra.api.math.Range(2 * pt - this.getMax(), 2 * pt - this.getMin()); return new Range(2 * pt - this.getMax(), 2 * pt - this.getMin());
} }
public int get(Random r) { public int get(Random r) {
return r.nextInt((max - min) + 1) + min; return r.nextInt((max - min) + 1) + min;
} }
public com.dfsek.terra.api.math.Range intersects(com.dfsek.terra.api.math.Range other) { public Range intersects(com.dfsek.terra.api.math.Range other) {
try { try {
return new com.dfsek.terra.api.math.Range(FastMath.max(this.getMin(), other.getMin()), FastMath.min(this.getMax(), other.getMax())); return new Range(FastMath.max(this.getMin(), other.getMin()), FastMath.min(this.getMax(), other.getMax()));
} catch(IllegalArgumentException e) { } catch(IllegalArgumentException e) {
return null; return null;
} }
} }
public com.dfsek.terra.api.math.Range add(int add) { public Range add(int add) {
this.min += add; this.min += add;
this.max += add; this.max += add;
return this; return this;
} }
public com.dfsek.terra.api.math.Range sub(int sub) { public Range sub(int sub) {
this.min -= sub; this.min -= sub;
this.max -= sub; this.max -= sub;
return this; return this;
@ -89,7 +89,7 @@ public class Range implements Iterable<Integer> {
@Override @Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
if(!(obj instanceof com.dfsek.terra.api.math.Range)) return false; if(!(obj instanceof com.dfsek.terra.api.math.Range)) return false;
com.dfsek.terra.api.math.Range other = (com.dfsek.terra.api.math.Range) obj; Range other = (com.dfsek.terra.api.math.Range) obj;
return other.getMin() == this.getMin() && other.getMax() == this.getMax(); return other.getMin() == this.getMin() && other.getMax() == this.getMax();
} }
@ -100,10 +100,10 @@ public class Range implements Iterable<Integer> {
} }
private static class RangeIterator implements Iterator<Integer> { private static class RangeIterator implements Iterator<Integer> {
private final com.dfsek.terra.api.math.Range m; private final Range m;
private Integer current; private Integer current;
public RangeIterator(com.dfsek.terra.api.math.Range m) { public RangeIterator(Range m) {
this.m = m; this.m = m;
current = m.getMin(); current = m.getMin();
} }

View File

@ -14,7 +14,7 @@ import com.dfsek.terra.config.loaders.config.function.FunctionTemplate;
import java.util.Map; import java.util.Map;
/** /**
* Sampler implementation using Paralithic expression * Sampler3D implementation using Paralithic expression
*/ */
public class ExpressionSampler implements NoiseSampler { public class ExpressionSampler implements NoiseSampler {
private final Expression expression; private final Expression expression;

View File

@ -1,7 +1,7 @@
package com.dfsek.terra.api.math.noise.samplers.noise; package com.dfsek.terra.api.math.noise.samplers.noise;
/** /**
* Sampler implementation that returns a constant. * Sampler3D implementation that returns a constant.
*/ */
public class ConstantSampler extends NoiseFunction { public class ConstantSampler extends NoiseFunction {
private final double constant; private final double constant;

View File

@ -0,0 +1,105 @@
package com.dfsek.terra.api.math.noise.samplers.noise;
import com.dfsek.terra.api.math.noise.samplers.noise.random.WhiteNoiseSampler;
import net.jafama.FastMath;
public class GaborNoiseSampler extends NoiseFunction {
private final WhiteNoiseSampler rand;
private double k = 1.0;
private double a = 0.1;
private double f0 = 0.625;
private double kernelRadius = (FastMath.sqrt(-FastMath.log(0.05) / Math.PI) / a);
private double omega0 = Math.PI * 0.25;
private boolean isotropic = true;
private double impulsesPerKernel = 64d;
private double impulseDensity = (impulsesPerKernel / (Math.PI * kernelRadius * kernelRadius));
private double impulsesPerCell = impulseDensity * kernelRadius * kernelRadius;
private double g = FastMath.exp(-impulsesPerCell);
public GaborNoiseSampler(int seed) {
super(seed);
rand = new WhiteNoiseSampler(seed);
}
public void setIsotropic(boolean isotropic) {
this.isotropic = isotropic;
}
public void setImpulsesPerKernel(double impulsesPerKernel) {
this.impulsesPerKernel = impulsesPerKernel;
recalculateRadiusAndDensity();
}
public void setA(double a) {
this.a = a;
recalculateRadiusAndDensity();
}
public void setFrequency0(double f0) {
this.f0 = f0;
}
public void setRotation(double omega0) {
this.omega0 = Math.PI * omega0;
}
public void setDeviation(double k) {
this.k = k;
}
private void recalculateRadiusAndDensity() {
kernelRadius = (FastMath.sqrt(-FastMath.log(0.05) / Math.PI) / this.a);
impulseDensity = (impulsesPerKernel / (Math.PI * kernelRadius * kernelRadius));
impulsesPerCell = impulseDensity * kernelRadius * kernelRadius;
g = FastMath.exp(-impulsesPerCell);
}
@Override
public double getNoiseRaw(int seed, double x, double z) {
return gaborNoise(seed, x, z);
}
@Override
public double getNoiseRaw(int seed, double x, double y, double z) {
return gaborNoise(seed, x, z);
}
private double gaborNoise(int seed, double x, double y) {
x /= kernelRadius;
y /= kernelRadius;
int xi = fastFloor(x);
int yi = fastFloor(y);
double xf = x - xi;
double yf = y - yi;
double noise = 0;
for(int dx = -1; dx <= 1; dx++) {
for(int dz = -1; dz <= 1; dz++) {
noise += calculateCell(seed, xi + dx, yi + dz, xf - dx, yf - dz);
}
}
return noise;
}
private double calculateCell(int seed, int xi, int yi, double x, double y) {
long mashedSeed = murmur64(31L * xi + yi) + seed;
double gaussianSource = (rand.getNoiseRaw(mashedSeed++) + 1) / 2;
int impulses = 0;
while(gaussianSource > g) {
impulses++;
gaussianSource *= (rand.getNoiseRaw(mashedSeed++) + 1) / 2;
}
double noise = 0;
for(int i = 0; i < impulses; i++) {
noise += rand.getNoiseRaw(mashedSeed++) * gabor(isotropic ? (rand.getNoiseRaw(mashedSeed++) + 1) * Math.PI : omega0, x * kernelRadius, y * kernelRadius);
}
return noise;
}
private double gabor(double omega_0, double x, double y) {
return k * (FastMath.exp(-Math.PI * (a * a) * (x * x + y * y)) * fastCos(2 * Math.PI * f0 * (x * fastCos(omega_0) + y * fastSin(omega_0))));
}
}

View File

@ -22,6 +22,8 @@ public abstract class NoiseFunction implements NoiseSampler {
return f >= 0 ? (int) f : (int) f - 1; return f >= 0 ? (int) f : (int) f - 1;
} }
static final int precision = 100;
protected static int hash(int seed, int xPrimed, int yPrimed, int zPrimed) { protected static int hash(int seed, int xPrimed, int yPrimed, int zPrimed) {
int hash = seed ^ xPrimed ^ yPrimed ^ zPrimed; int hash = seed ^ xPrimed ^ yPrimed ^ zPrimed;
@ -36,6 +38,8 @@ public abstract class NoiseFunction implements NoiseSampler {
return hash; return hash;
} }
static final int modulus = 360 * precision;
protected static int fastRound(double f) { protected static int fastRound(double f) {
return f >= 0 ? (int) (f + 0.5f) : (int) (f - 0.5); return f >= 0 ? (int) (f + 0.5f) : (int) (f - 0.5);
} }
@ -73,6 +77,48 @@ public abstract class NoiseFunction implements NoiseSampler {
return FastMath.sqrt(f); return FastMath.sqrt(f);
} }
static final double[] sin = new double[360 * 100]; // lookup table
static {
for(int i = 0; i < sin.length; i++) {
sin[i] = (float) Math.sin((double) (i) / (precision));
}
}
protected static int fastCeil(double f) {
int i = (int) f;
if(i < f) i++;
return i;
}
/**
* Murmur64 hashing function
*
* @param h Input value
* @return Hashed value
*/
protected static long murmur64(long h) {
h ^= h >>> 33;
h *= 0xff51afd7ed558ccdL;
h ^= h >>> 33;
h *= 0xc4ceb9fe1a85ec53L;
h ^= h >>> 33;
return h;
}
private static double sinLookup(int a) {
return a >= 0 ? sin[a % (modulus)] : -sin[-a % (modulus)];
}
protected static double fastSin(double a) {
return sinLookup((int) (a * precision + 0.5f));
}
protected static double fastCos(double a) {
return sinLookup((int) ((a + Math.PI / 2) * precision + 0.5f));
}
public void setSeed(int seed) { public void setSeed(int seed) {
this.seed = seed; this.seed = seed;
} }

View File

@ -12,38 +12,41 @@ public class WhiteNoiseSampler extends NoiseFunction {
super(seed); super(seed);
} }
public double getNoiseRaw(long seed) {
return (Double.longBitsToDouble((murmur64(seed) & 0x000fffffffffffffL) | POSITIVE_POW1) - 1.5) * 2;
}
@Override @Override
public double getNoiseRaw(int seed, double x, double y) { public double getNoiseRaw(int seed, double x, double y) {
long hashX = Double.doubleToRawLongBits(x) ^ seed; return (getNoiseUnmapped(seed, x, y) - 1.5) * 2;
long hashZ = Double.doubleToRawLongBits(y) ^ seed;
long hash = ((hashX ^ (hashX >>> 32)) + ((hashZ ^ (hashZ >>> 32)) << 32)) ^ seed;
long base = (murmur64(hash) & 0x000fffffffffffffL)
| POSITIVE_POW1; // Sign and exponent
return (Double.longBitsToDouble(base) - 1.5) * 2;
} }
@Override @Override
public double getNoiseRaw(int seed, double x, double y, double z) { public double getNoiseRaw(int seed, double x, double y, double z) {
return (getNoiseUnmapped(seed, x, y, z) - 1.5) * 2;
}
public double getNoiseUnmapped(int seed, double x, double y, double z) {
long base = ((randomBits(seed, x, y, z)) & 0x000fffffffffffffL) | POSITIVE_POW1; // Sign and exponent
return Double.longBitsToDouble(base);
}
public double getNoiseUnmapped(int seed, double x, double y) {
long base = (randomBits(seed, x, y) & 0x000fffffffffffffL) | POSITIVE_POW1; // Sign and exponent
return Double.longBitsToDouble(base);
}
public long randomBits(int seed, double x, double y, double z) {
long hashX = Double.doubleToRawLongBits(x) ^ seed; long hashX = Double.doubleToRawLongBits(x) ^ seed;
long hashZ = Double.doubleToRawLongBits(y) ^ seed; long hashZ = Double.doubleToRawLongBits(y) ^ seed;
long hash = (((hashX ^ (hashX >>> 32)) + ((hashZ ^ (hashZ >>> 32)) << 32)) ^ seed) + Double.doubleToRawLongBits(z); long hash = (((hashX ^ (hashX >>> 32)) + ((hashZ ^ (hashZ >>> 32)) << 32)) ^ seed) + Double.doubleToRawLongBits(z);
long base = ((murmur64(hash)) & 0x000fffffffffffffL) return murmur64(hash);
| POSITIVE_POW1; // Sign and exponent
return (Double.longBitsToDouble(base) - 1.5) * 2;
} }
/** public long randomBits(int seed, double x, double y) {
* Murmur64 hashing function long hashX = Double.doubleToRawLongBits(x) ^ seed;
* long hashZ = Double.doubleToRawLongBits(y) ^ seed;
* @param h Input value long hash = ((hashX ^ (hashX >>> 32)) + ((hashZ ^ (hashZ >>> 32)) << 32)) ^ seed;
* @return Hashed value return murmur64(hash);
*/
private static long murmur64(long h) {
h ^= h >>> 33;
h *= 0xff51afd7ed558ccdL;
h ^= h >>> 33;
h *= 0xc4ceb9fe1a85ec53L;
h ^= h >>> 33;
return h;
} }
} }

View File

@ -0,0 +1,243 @@
package com.dfsek.terra.api.math.noise.samplers.noise.simplex;
public class SimplexSampler extends SimplexStyleSampler {
private static final Double2[] GRAD_2D = {
new Double2(-1, -1), new Double2(1, -1), new Double2(-1, 1), new Double2(1, 1),
new Double2(0, -1), new Double2(-1, 0), new Double2(0, 1), new Double2(1, 0),
};
private static final Double3[] GRAD_3D = {
new Double3(1, 1, 0), new Double3(-1, 1, 0), new Double3(1, -1, 0), new Double3(-1, -1, 0),
new Double3(1, 0, 1), new Double3(-1, 0, 1), new Double3(1, 0, -1), new Double3(-1, 0, -1),
new Double3(0, 1, 1), new Double3(0, -1, 1), new Double3(0, 1, -1), new Double3(0, -1, -1),
new Double3(1, 1, 0), new Double3(0, -1, 1), new Double3(-1, 1, 0), new Double3(0, -1, -1),
};
private static final double F2 = 1.0 / 2.0;
private static final double F3 = (1.0 / 3.0);
private static final double G2 = 1.0 / 4.0;
private static final double G3 = (1.0 / 6.0);
private static final double G33 = G3 * 3 - 1;
private static final int X_PRIME = 1619;
private static final int Y_PRIME = 31337;
private static final int Z_PRIME = 6971;
public SimplexSampler(int seed) {
super(seed);
}
private static double gradCoord3D(int seed, int x, int y, int z, double xd, double yd, double zd) {
int hash = seed;
hash ^= X_PRIME * x;
hash ^= Y_PRIME * y;
hash ^= Z_PRIME * z;
hash = hash * hash * hash * 60493;
hash = (hash >> 13) ^ hash;
Double3 g = GRAD_3D[hash & 15];
return xd * g.x + yd * g.y + zd * g.z;
}
private static double gradCoord2D(int seed, int x, int y, double xd, double yd) {
int hash = seed;
hash ^= X_PRIME * x;
hash ^= Y_PRIME * y;
hash = hash * hash * hash * 60493;
hash = (hash >> 13) ^ hash;
Double2 g = GRAD_2D[hash & 7];
return xd * g.x + yd * g.y;
}
@Override
public double getNoiseRaw(int seed, double x, double y) {
double t = (x + y) * F2;
int i = fastFloor(x + t);
int j = fastFloor(y + t);
t = (i + j) * G2;
double X0 = i - t;
double Y0 = j - t;
double x0 = x - X0;
double y0 = y - Y0;
int i1, j1;
if(x0 > y0) {
i1 = 1;
j1 = 0;
} else {
i1 = 0;
j1 = 1;
}
double x1 = x0 - i1 + G2;
double y1 = y0 - j1 + G2;
double x2 = x0 - 1 + F2;
double y2 = y0 - 1 + F2;
double n0, n1, n2;
t = 0.5 - x0 * x0 - y0 * y0;
if(t < 0) {
n0 = 0;
} else {
t *= t;
n0 = t * t * gradCoord2D(seed, i, j, x0, y0);
}
t = 0.5 - x1 * x1 - y1 * y1;
if(t < 0) {
n1 = 0;
} else {
t *= t;
n1 = t * t * gradCoord2D(seed, i + i1, j + j1, x1, y1);
}
t = 0.5 - x2 * x2 - y2 * y2;
if(t < 0) {
n2 = 0;
} else {
t *= t;
n2 = t * t * gradCoord2D(seed, i + 1, j + 1, x2, y2);
}
return 50 * (n0 + n1 + n2);
}
@Override
public double getNoiseRaw(int seed, double x, double y, double z) {
double t = (x + y + z) * F3;
int i = fastFloor(x + t);
int j = fastFloor(y + t);
int k = fastFloor(z + t);
t = (i + j + k) * G3;
double x0 = x - (i - t);
double y0 = y - (j - t);
double z0 = z - (k - t);
int i1, j1, k1;
int i2, j2, k2;
if(x0 >= y0) {
if(y0 >= z0) {
i1 = 1;
j1 = 0;
k1 = 0;
i2 = 1;
j2 = 1;
k2 = 0;
} else if(x0 >= z0) {
i1 = 1;
j1 = 0;
k1 = 0;
i2 = 1;
j2 = 0;
k2 = 1;
} else // x0 < z0
{
i1 = 0;
j1 = 0;
k1 = 1;
i2 = 1;
j2 = 0;
k2 = 1;
}
} else // x0 < y0
{
if(y0 < z0) {
i1 = 0;
j1 = 0;
k1 = 1;
i2 = 0;
j2 = 1;
k2 = 1;
} else if(x0 < z0) {
i1 = 0;
j1 = 1;
k1 = 0;
i2 = 0;
j2 = 1;
k2 = 1;
} else // x0 >= z0
{
i1 = 0;
j1 = 1;
k1 = 0;
i2 = 1;
j2 = 1;
k2 = 0;
}
}
double x1 = x0 - i1 + G3;
double y1 = y0 - j1 + G3;
double z1 = z0 - k1 + G3;
double x2 = x0 - i2 + F3;
double y2 = y0 - j2 + F3;
double z2 = z0 - k2 + F3;
double x3 = x0 + G33;
double y3 = y0 + G33;
double z3 = z0 + G33;
double n0, n1, n2, n3;
t = 0.6 - x0 * x0 - y0 * y0 - z0 * z0;
if(t < 0) n0 = 0;
else {
t *= t;
n0 = t * t * gradCoord3D(seed, i, j, k, x0, y0, z0);
}
t = 0.6 - x1 * x1 - y1 * y1 - z1 * z1;
if(t < 0) {
n1 = 0;
} else {
t *= t;
n1 = t * t * gradCoord3D(seed, i + i1, j + j1, k + k1, x1, y1, z1);
}
t = 0.6 - x2 * x2 - y2 * y2 - z2 * z2;
if(t < 0) {
n2 = 0;
} else {
t *= t;
n2 = t * t * gradCoord3D(seed, i + i2, j + j2, k + k2, x2, y2, z2);
}
t = 0.6 - x3 * x3 - y3 * y3 - z3 * z3;
if(t < 0) {
n3 = 0;
} else {
t *= t;
n3 = t * t * gradCoord3D(seed, i + 1, j + 1, k + 1, x3, y3, z3);
}
return 32 * (n0 + n1 + n2 + n3);
}
private static class Double2 {
public final double x, y;
public Double2(double x, double y) {
this.x = x;
this.y = y;
}
}
private static class Double3 {
public final double x, y, z;
public Double3(double x, double y, double z) {
this.x = x;
this.y = y;
this.z = z;
}
}
}

View File

@ -1,7 +1,7 @@
package com.dfsek.terra.api.math.paralithic.noise; package com.dfsek.terra.api.math.paralithic.noise;
import com.dfsek.terra.api.math.noise.NoiseSampler; import com.dfsek.terra.api.math.noise.NoiseSampler;
import com.dfsek.terra.util.hash.HashMapDoubleDouble; import com.dfsek.terra.api.util.hash.HashMapDoubleDouble;

View File

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

View File

@ -1,6 +0,0 @@
package com.dfsek.terra.api.platform;
import com.dfsek.terra.api.platform.entity.Entity;
public interface Player extends Entity {
}

View File

@ -11,7 +11,9 @@ public interface Block extends Handle {
BlockState getState(); BlockState getState();
Block getRelative(BlockFace face); default Block getRelative(BlockFace face) {
return getRelative(face, 1);
}
Block getRelative(BlockFace face, int len); Block getRelative(BlockFace face, int len);
@ -19,7 +21,9 @@ public interface Block extends Handle {
Location getLocation(); Location getLocation();
MaterialData getType(); default BlockType getType() {
return getBlockData().getBlockType();
}
int getX(); int getX();

View File

@ -3,11 +3,16 @@ package com.dfsek.terra.api.platform.block;
import com.dfsek.terra.api.platform.Handle; import com.dfsek.terra.api.platform.Handle;
public interface BlockData extends Cloneable, Handle { public interface BlockData extends Cloneable, Handle {
MaterialData getMaterial();
boolean matches(MaterialData materialData); BlockType getBlockType();
boolean matches(BlockData other);
BlockData clone(); BlockData clone();
String getAsString(); String getAsString();
boolean isAir();
boolean isStructureVoid();
} }

View File

@ -0,0 +1,9 @@
package com.dfsek.terra.api.platform.block;
import com.dfsek.terra.api.platform.Handle;
public interface BlockType extends Handle {
BlockData getDefaultData();
boolean isSolid();
}

View File

@ -1,17 +0,0 @@
package com.dfsek.terra.api.platform.block;
import com.dfsek.terra.api.platform.Handle;
public interface MaterialData extends Handle {
boolean matches(MaterialData other);
boolean matches(BlockData other);
boolean isSolid();
boolean isAir();
double getMaxDurability();
BlockData createBlockData();
}

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.math.vector.Location;
import com.dfsek.terra.api.platform.CommandSender; import com.dfsek.terra.api.platform.CommandSender;
import com.dfsek.terra.api.platform.Handle; import com.dfsek.terra.api.platform.Handle;
import com.dfsek.terra.api.platform.world.World;
public interface Entity extends Handle, CommandSender { public interface Entity extends Handle, CommandSender {
Location getLocation(); Location getLocation();
void setLocation(Location location);
World getWorld();
} }

View File

@ -0,0 +1,4 @@
package com.dfsek.terra.api.platform.entity;
public interface Player extends Entity {
}

View File

@ -1,13 +1,13 @@
package com.dfsek.terra.api.platform.handle; package com.dfsek.terra.api.platform.handle;
import com.dfsek.terra.api.platform.block.MaterialData; import com.dfsek.terra.api.platform.inventory.Item;
import com.dfsek.terra.api.platform.inventory.ItemStack;
import com.dfsek.terra.api.platform.inventory.item.Enchantment; import com.dfsek.terra.api.platform.inventory.item.Enchantment;
import java.util.Set; import java.util.Set;
public interface ItemHandle { public interface ItemHandle {
ItemStack newItemStack(MaterialData material, int amount);
Item createItem(String data);
Enchantment getEnchantment(String id); Enchantment getEnchantment(String id);

View File

@ -1,23 +1,26 @@
package com.dfsek.terra.api.platform.handle; 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.block.BlockData;
import com.dfsek.terra.api.platform.block.MaterialData;
import com.dfsek.terra.api.platform.entity.EntityType; 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. * Interface to be implemented for world manipulation.
*/ */
public interface WorldHandle { public interface WorldHandle {
void setBlockData(Block block, BlockData data, boolean physics);
BlockData getBlockData(Block block);
MaterialData getType(Block block);
BlockData createBlockData(String data); BlockData createBlockData(String data);
MaterialData createMaterialData(String data);
EntityType getEntity(String id); 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

@ -0,0 +1,12 @@
package com.dfsek.terra.api.platform.inventory;
import com.dfsek.terra.api.platform.Handle;
/**
* An inventory item.
*/
public interface Item extends Handle {
ItemStack newItemStack(int amount);
double getMaxDurability();
}

View File

@ -1,17 +1,14 @@
package com.dfsek.terra.api.platform.inventory; package com.dfsek.terra.api.platform.inventory;
import com.dfsek.terra.api.platform.Handle; import com.dfsek.terra.api.platform.Handle;
import com.dfsek.terra.api.platform.block.MaterialData;
import com.dfsek.terra.api.platform.inventory.item.ItemMeta; import com.dfsek.terra.api.platform.inventory.item.ItemMeta;
public interface ItemStack extends Handle, Cloneable { public interface ItemStack extends Handle {
int getAmount(); int getAmount();
void setAmount(int i); void setAmount(int i);
MaterialData getType(); Item getType();
ItemStack clone();
ItemMeta getItemMeta(); ItemMeta getItemMeta();

View File

@ -1,4 +1,6 @@
/** /**
* API for platform implementations. Mostly interfaces to be implemented by platform delegates. * API for platform implementations. Mostly interfaces to be implemented by platform delegates.
* <p>
* Interfaces in this package generally should <b>not</b> be implemented by addons.
*/ */
package com.dfsek.terra.api.platform; package com.dfsek.terra.api.platform;

View File

@ -25,11 +25,19 @@ public interface World extends Handle {
Chunk getChunkAt(int x, int z); Chunk getChunkAt(int x, int z);
default Chunk getChunkAt(Location location) {
return getChunkAt(location.getBlockX() >> 4, location.getBlockZ() >> 4);
}
File getWorldFolder(); File getWorldFolder();
Block getBlockAt(int x, int y, int z); 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); Entity spawnEntity(Location location, EntityType entityType);
int getMinHeight();
} }

View File

@ -0,0 +1,14 @@
package com.dfsek.terra.api.platform.world.generator;
import com.dfsek.terra.api.platform.world.ChunkAccess;
public interface ChunkData extends ChunkAccess {
/**
* Get the maximum height for the chunk.
* <p>
* Setting blocks at or above this height will do nothing.
*
* @return the maximum height
*/
int getMaxHeight();
}

View File

@ -1,43 +1,7 @@
package com.dfsek.terra.api.platform.world.generator; package com.dfsek.terra.api.platform.world.generator;
import com.dfsek.terra.api.platform.Handle; import com.dfsek.terra.api.platform.Handle;
import com.dfsek.terra.api.platform.world.BiomeGrid;
import com.dfsek.terra.api.platform.world.ChunkAccess;
import com.dfsek.terra.api.platform.world.World;
import com.dfsek.terra.api.world.generation.TerraChunkGenerator;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Random;
public interface ChunkGenerator extends Handle { public interface ChunkGenerator extends Handle {
boolean isParallelCapable();
boolean shouldGenerateCaves();
boolean shouldGenerateDecorations();
boolean shouldGenerateMobs();
boolean shouldGenerateStructures();
ChunkData generateChunkData(@NotNull World world, @NotNull Random random, int x, int z, @NotNull BiomeGrid biome);
List<BlockPopulator> getDefaultPopulators(World world);
@Nullable
TerraChunkGenerator getTerraGenerator();
interface ChunkData extends ChunkAccess {
/**
* Get the maximum height for the chunk.
* <p>
* Setting blocks at or above this height will do nothing.
*
* @return the maximum height
*/
int getMaxHeight();
}
} }

View File

@ -1,6 +1,9 @@
package com.dfsek.terra.api.platform.world.generator; package com.dfsek.terra.api.platform.world.generator;
import com.dfsek.terra.api.platform.Handle; import com.dfsek.terra.api.platform.Handle;
import com.dfsek.terra.api.world.generation.TerraChunkGenerator;
public interface GeneratorWrapper extends Handle { public interface GeneratorWrapper extends Handle {
@Override
TerraChunkGenerator getHandle();
} }

View File

@ -0,0 +1,84 @@
package com.dfsek.terra.api.registry;
import com.dfsek.tectonic.exception.LoadException;
import com.dfsek.tectonic.loading.ConfigLoader;
import com.dfsek.terra.registry.OpenRegistry;
import com.dfsek.terra.registry.exception.DuplicateEntryException;
import java.lang.reflect.Type;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
/**
* Wrapper for a registry that ensures checked additions.
*
* @param <T> Type in registry
*/
public class CheckedRegistry<T> implements Registry<T> {
private final OpenRegistry<T> registry;
public CheckedRegistry(OpenRegistry<T> registry) {
this.registry = registry;
}
/**
* Add a value to this registry, checking whether it is present first.
*
* @param identifier Identifier to assign value.
* @param value Value to add.
* @throws DuplicateEntryException If an entry with the same identifier is already present.
*/
public void add(String identifier, T value) throws DuplicateEntryException {
registry.addChecked(identifier, value);
}
/**
* Add a value to the registry, without checking presence beforehand.
* <p>
* Use of this method is generally discouraged, as it is bad practice to overwrite registry values.
*
* @param identifier Identifier to assign value.
* @param value Value to add.
* @deprecated Use of {@link #add(String, Object)} is encouraged.
*/
@Deprecated
public void addUnchecked(String identifier, T value) {
registry.add(identifier, value);
}
@Override
public T get(String identifier) {
return registry.get(identifier);
}
@Override
public boolean contains(String identifier) {
return registry.contains(identifier);
}
@Override
public void forEach(Consumer<T> consumer) {
registry.forEach(consumer);
}
@Override
public void forEach(BiConsumer<String, T> consumer) {
registry.forEach(consumer);
}
@Override
public Set<T> entries() {
return registry.entries();
}
@Override
public Set<String> keys() {
return registry.keys();
}
@Override
public T load(Type t, Object c, ConfigLoader loader) throws LoadException {
return registry.load(t, c, loader);
}
}

View File

@ -0,0 +1,57 @@
package com.dfsek.terra.api.registry;
import com.dfsek.tectonic.exception.LoadException;
import com.dfsek.tectonic.loading.ConfigLoader;
import java.lang.reflect.Type;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
/**
* Wrapper for a registry that forbids all write access.
*
* @param <T> Type in registry
*/
public class LockedRegistry<T> implements Registry<T> {
private final Registry<T> registry;
public LockedRegistry(Registry<T> registry) {
this.registry = registry;
}
@Override
public T get(String identifier) {
return registry.get(identifier);
}
@Override
public boolean contains(String identifier) {
return registry.contains(identifier);
}
@Override
public void forEach(Consumer<T> consumer) {
registry.forEach(consumer);
}
@Override
public void forEach(BiConsumer<String, T> consumer) {
registry.forEach(consumer);
}
@Override
public Set<T> entries() {
return registry.entries();
}
@Override
public Set<String> keys() {
return registry.keys();
}
@Override
public T load(Type t, Object c, ConfigLoader loader) throws LoadException {
return registry.load(t, c, loader);
}
}

View File

@ -0,0 +1,53 @@
package com.dfsek.terra.api.registry;
import com.dfsek.tectonic.loading.TypeLoader;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
public interface Registry<T> extends TypeLoader<T> {
/**
* Get a value from the registry.
*
* @param identifier Identifier of value.
* @return Value matching the identifier, {@code null} if no value is present.
*/
T get(String identifier);
/**
* Check if the registry contains a value.
*
* @param identifier Identifier of value.
* @return Whether the registry contains the value.
*/
boolean contains(String identifier);
/**
* Perform the given action for every value in the registry.
*
* @param consumer Action to perform on value.
*/
void forEach(Consumer<T> consumer);
/**
* Perform an action for every key-value pair in the registry.
*
* @param consumer Action to perform on pair.
*/
void forEach(BiConsumer<String, T> consumer);
/**
* Get the entries of this registry as a {@link Set}.
*
* @return Set containing all entries.
*/
Set<T> entries();
/**
* Get all the keys in this registry.
*
* @return Keys in registry
*/
Set<String> keys();
}

View File

@ -1,7 +1,7 @@
package com.dfsek.terra.api.structures.loot; package com.dfsek.terra.api.structures.loot;
import com.dfsek.terra.api.core.TerraPlugin; import com.dfsek.terra.api.TerraPlugin;
import com.dfsek.terra.api.platform.block.MaterialData; import com.dfsek.terra.api.platform.inventory.Item;
import com.dfsek.terra.api.platform.inventory.ItemStack; import com.dfsek.terra.api.platform.inventory.ItemStack;
import com.dfsek.terra.api.structures.loot.functions.AmountFunction; import com.dfsek.terra.api.structures.loot.functions.AmountFunction;
import com.dfsek.terra.api.structures.loot.functions.DamageFunction; import com.dfsek.terra.api.structures.loot.functions.DamageFunction;
@ -19,10 +19,9 @@ import java.util.Random;
* Representation of a single item entry within a Loot Table pool. * Representation of a single item entry within a Loot Table pool.
*/ */
public class Entry { public class Entry {
private final MaterialData item; private final Item item;
private final long weight; private final long weight;
private final List<LootFunction> functions = new GlueList<>(); private final List<LootFunction> functions = new GlueList<>();
private final TerraPlugin main;
/** /**
* Instantiates an Entry from a JSON representation. * Instantiates an Entry from a JSON representation.
@ -30,9 +29,8 @@ public class Entry {
* @param entry The JSON Object to instantiate from. * @param entry The JSON Object to instantiate from.
*/ */
public Entry(JSONObject entry, TerraPlugin main) { public Entry(JSONObject entry, TerraPlugin main) {
this.main = main;
String id = entry.get("name").toString(); String id = entry.get("name").toString();
this.item = main.getWorldHandle().createMaterialData(id); this.item = main.getItemHandle().createItem(id);
long weight1; long weight1;
try { try {
@ -85,7 +83,7 @@ public class Entry {
* @return ItemStack - The ItemStack with all functions applied. * @return ItemStack - The ItemStack with all functions applied.
*/ */
public ItemStack getItem(Random r) { public ItemStack getItem(Random r) {
ItemStack item = main.getItemHandle().newItemStack(this.item, 1); ItemStack item = this.item.newItemStack(1);
for(LootFunction f : functions) { for(LootFunction f : functions) {
item = f.apply(item, r); item = f.apply(item, r);
} }

View File

@ -1,6 +1,6 @@
package com.dfsek.terra.api.structures.loot; package com.dfsek.terra.api.structures.loot;
import com.dfsek.terra.api.core.TerraPlugin; import com.dfsek.terra.api.TerraPlugin;
import com.dfsek.terra.api.platform.inventory.Inventory; import com.dfsek.terra.api.platform.inventory.Inventory;
import com.dfsek.terra.api.platform.inventory.ItemStack; import com.dfsek.terra.api.platform.inventory.ItemStack;
import com.dfsek.terra.api.util.GlueList; import com.dfsek.terra.api.util.GlueList;
@ -58,7 +58,8 @@ public class LootTable {
for(ItemStack stack : loot) { for(ItemStack stack : loot) {
int attempts = 0; int attempts = 0;
while(stack.getAmount() != 0 && attempts < 10) { while(stack.getAmount() != 0 && attempts < 10) {
ItemStack newStack = stack.clone(); ItemStack newStack = stack.getType().newItemStack(stack.getAmount());
newStack.setItemMeta(stack.getItemMeta());
newStack.setAmount(1); newStack.setAmount(1);
int slot = r.nextInt(i.getSize()); int slot = r.nextInt(i.getSize());
ItemStack slotItem = i.getItem(slot); ItemStack slotItem = i.getItem(slot);
@ -66,7 +67,8 @@ public class LootTable {
i.setItem(slot, newStack); i.setItem(slot, newStack);
stack.setAmount(stack.getAmount() - 1); stack.setAmount(stack.getAmount() - 1);
} else if(slotItem.getType().equals(newStack.getType())) { } else if(slotItem.getType().equals(newStack.getType())) {
ItemStack dep = newStack.clone(); ItemStack dep = newStack.getType().newItemStack(newStack.getAmount());
dep.setItemMeta(newStack.getItemMeta());
dep.setAmount(newStack.getAmount() + slotItem.getAmount()); dep.setAmount(newStack.getAmount() + slotItem.getAmount());
i.setItem(slot, dep); i.setItem(slot, dep);
stack.setAmount(stack.getAmount() - 1); stack.setAmount(stack.getAmount() - 1);

View File

@ -1,9 +1,9 @@
package com.dfsek.terra.api.structures.loot; package com.dfsek.terra.api.structures.loot;
import com.dfsek.terra.api.core.TerraPlugin; import com.dfsek.terra.api.TerraPlugin;
import com.dfsek.terra.api.math.ProbabilityCollection;
import com.dfsek.terra.api.platform.inventory.ItemStack; import com.dfsek.terra.api.platform.inventory.ItemStack;
import com.dfsek.terra.api.util.GlueList; import com.dfsek.terra.api.util.GlueList;
import com.dfsek.terra.api.util.collections.ProbabilityCollection;
import net.jafama.FastMath; import net.jafama.FastMath;
import org.json.simple.JSONArray; import org.json.simple.JSONArray;
import org.json.simple.JSONObject; import org.json.simple.JSONObject;

View File

@ -33,8 +33,11 @@ public class DamageFunction implements LootFunction {
*/ */
@Override @Override
public ItemStack apply(ItemStack original, Random r) { public ItemStack apply(ItemStack original, Random r) {
if(original == null) return null;
ItemMeta meta = original.getItemMeta();
if(!(meta instanceof Damageable)) return original;
double itemDurability = (r.nextDouble() * (max - min)) + min; double itemDurability = (r.nextDouble() * (max - min)) + min;
Damageable damage = (Damageable) original.getItemMeta(); Damageable damage = (Damageable) meta;
damage.setDamage((int) (original.getType().getMaxDurability() - (itemDurability / 100) * original.getType().getMaxDurability())); damage.setDamage((int) (original.getType().getMaxDurability() - (itemDurability / 100) * original.getType().getMaxDurability()));
original.setItemMeta((ItemMeta) damage); original.setItemMeta((ItemMeta) damage);
return original; return original;

View File

@ -1,6 +1,6 @@
package com.dfsek.terra.api.structures.loot.functions; package com.dfsek.terra.api.structures.loot.functions;
import com.dfsek.terra.api.core.TerraPlugin; import com.dfsek.terra.api.TerraPlugin;
import com.dfsek.terra.api.platform.inventory.ItemStack; import com.dfsek.terra.api.platform.inventory.ItemStack;
import com.dfsek.terra.api.platform.inventory.item.Enchantment; import com.dfsek.terra.api.platform.inventory.item.Enchantment;
import com.dfsek.terra.api.platform.inventory.item.ItemMeta; import com.dfsek.terra.api.platform.inventory.item.ItemMeta;
@ -35,6 +35,8 @@ public class EnchantFunction implements LootFunction {
*/ */
@Override @Override
public ItemStack apply(ItemStack original, Random r) { public ItemStack apply(ItemStack original, Random r) {
if(original.getItemMeta() == null) return original;
double enchant = (r.nextDouble() * (max - min)) + min; double enchant = (r.nextDouble() * (max - min)) + min;
List<Enchantment> possible = new GlueList<>(); List<Enchantment> possible = new GlueList<>();
for(Enchantment ench : main.getItemHandle().getEnchantments()) { for(Enchantment ench : main.getItemHandle().getEnchantments()) {
@ -55,7 +57,7 @@ public class EnchantFunction implements LootFunction {
try { try {
meta.addEnchantment(chosen, FastMath.max(lvl, 1)); meta.addEnchantment(chosen, FastMath.max(lvl, 1));
} catch(IllegalArgumentException e) { } catch(IllegalArgumentException e) {
main.getLogger().warning("Attempted to enchant " + original.getType() + " with " + chosen + " at level " + FastMath.max(lvl, 1) + ", but an unexpected exception occurred! Usually this is caused by a misbehaving enchantment plugin."); main.logger().warning("Attempted to enchant " + original.getType() + " with " + chosen + " at level " + FastMath.max(lvl, 1) + ", but an unexpected exception occurred! Usually this is caused by a misbehaving enchantment plugin.");
} }
} }
original.setItemMeta(meta); original.setItemMeta(meta);

View File

@ -1,6 +1,6 @@
package com.dfsek.terra.api.structures.script; package com.dfsek.terra.api.structures.script;
import com.dfsek.terra.api.core.TerraPlugin; import com.dfsek.terra.api.TerraPlugin;
import com.dfsek.terra.api.math.vector.Location; import com.dfsek.terra.api.math.vector.Location;
import com.dfsek.terra.api.platform.world.Chunk; import com.dfsek.terra.api.platform.world.Chunk;
import com.dfsek.terra.api.structures.parser.Parser; import com.dfsek.terra.api.structures.parser.Parser;
@ -28,10 +28,9 @@ import com.dfsek.terra.api.structures.structure.Rotation;
import com.dfsek.terra.api.structures.structure.buffer.Buffer; import com.dfsek.terra.api.structures.structure.buffer.Buffer;
import com.dfsek.terra.api.structures.structure.buffer.DirectBuffer; import com.dfsek.terra.api.structures.structure.buffer.DirectBuffer;
import com.dfsek.terra.api.structures.structure.buffer.StructureBuffer; import com.dfsek.terra.api.structures.structure.buffer.StructureBuffer;
import com.dfsek.terra.registry.FunctionRegistry; import com.dfsek.terra.registry.config.FunctionRegistry;
import com.dfsek.terra.registry.config.LootRegistry; import com.dfsek.terra.registry.config.LootRegistry;
import com.dfsek.terra.registry.config.ScriptRegistry; import com.dfsek.terra.registry.config.ScriptRegistry;
import com.dfsek.terra.world.generation.math.SamplerCache;
import com.google.common.cache.Cache; import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheBuilder;
import net.jafama.FastMath; import net.jafama.FastMath;
@ -47,9 +46,9 @@ public class StructureScript {
private final String id; private final String id;
private final Cache<Location, StructureBuffer> cache; private final Cache<Location, StructureBuffer> cache;
private final TerraPlugin main; private final TerraPlugin main;
String tempID; private String tempID;
public StructureScript(InputStream inputStream, TerraPlugin main, ScriptRegistry registry, LootRegistry lootRegistry, SamplerCache cache, FunctionRegistry functionRegistry) throws ParseException { public StructureScript(InputStream inputStream, TerraPlugin main, ScriptRegistry registry, LootRegistry lootRegistry, FunctionRegistry functionRegistry) throws ParseException {
Parser parser; Parser parser;
try { try {
parser = new Parser(IOUtils.toString(inputStream)); parser = new Parser(IOUtils.toString(inputStream));
@ -57,8 +56,10 @@ public class StructureScript {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
functionRegistry.forEach(parser::registerFunction); // Register registry functions.
parser.registerFunction("block", new BlockFunctionBuilder(main)) parser.registerFunction("block", new BlockFunctionBuilder(main))
.registerFunction("check", new CheckFunctionBuilder(main, cache)) .registerFunction("check", new CheckFunctionBuilder(main))
.registerFunction("structure", new StructureFunctionBuilder(registry, main)) .registerFunction("structure", new StructureFunctionBuilder(registry, main))
.registerFunction("randomInt", new RandomFunctionBuilder()) .registerFunction("randomInt", new RandomFunctionBuilder())
.registerFunction("recursions", new RecursionsFunctionBuilder()) .registerFunction("recursions", new RecursionsFunctionBuilder())
@ -86,8 +87,6 @@ public class StructureScript {
.registerFunction("max", new BinaryNumberFunctionBuilder((number, number2) -> FastMath.max(number.doubleValue(), number2.doubleValue()))) .registerFunction("max", new BinaryNumberFunctionBuilder((number, number2) -> FastMath.max(number.doubleValue(), number2.doubleValue())))
.registerFunction("min", new BinaryNumberFunctionBuilder((number, number2) -> FastMath.min(number.doubleValue(), number2.doubleValue()))); .registerFunction("min", new BinaryNumberFunctionBuilder((number, number2) -> FastMath.min(number.doubleValue(), number2.doubleValue())));
functionRegistry.forEach(parser::registerFunction); // Register registry functions.
block = parser.parse(); block = parser.parse();
this.id = parser.getID(); this.id = parser.getID();
tempID = id; tempID = id;
@ -149,7 +148,7 @@ public class StructureScript {
try { try {
return !block.apply(arguments).getLevel().equals(Block.ReturnLevel.FAIL); return !block.apply(arguments).getLevel().equals(Block.ReturnLevel.FAIL);
} catch(RuntimeException e) { } catch(RuntimeException e) {
main.getLogger().severe("Failed to generate structure at " + arguments.getBuffer().getOrigin() + ": " + e.getMessage()); main.logger().severe("Failed to generate structure at " + arguments.getBuffer().getOrigin() + ": " + e.getMessage());
main.getDebugLogger().stack(e); main.getDebugLogger().stack(e);
return false; return false;
} }

View File

@ -1,6 +1,6 @@
package com.dfsek.terra.api.structures.script.builders; package com.dfsek.terra.api.structures.script.builders;
import com.dfsek.terra.api.core.TerraPlugin; import com.dfsek.terra.api.TerraPlugin;
import com.dfsek.terra.api.structures.parser.lang.Returnable; import com.dfsek.terra.api.structures.parser.lang.Returnable;
import com.dfsek.terra.api.structures.parser.lang.functions.FunctionBuilder; import com.dfsek.terra.api.structures.parser.lang.functions.FunctionBuilder;
import com.dfsek.terra.api.structures.script.functions.BiomeFunction; import com.dfsek.terra.api.structures.script.functions.BiomeFunction;

View File

@ -1,6 +1,6 @@
package com.dfsek.terra.api.structures.script.builders; package com.dfsek.terra.api.structures.script.builders;
import com.dfsek.terra.api.core.TerraPlugin; import com.dfsek.terra.api.TerraPlugin;
import com.dfsek.terra.api.structures.parser.exceptions.ParseException; import com.dfsek.terra.api.structures.parser.exceptions.ParseException;
import com.dfsek.terra.api.structures.parser.lang.Returnable; import com.dfsek.terra.api.structures.parser.lang.Returnable;
import com.dfsek.terra.api.structures.parser.lang.constants.BooleanConstant; import com.dfsek.terra.api.structures.parser.lang.constants.BooleanConstant;

Some files were not shown because too many files have changed in this diff Show More