├── .gitignore ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── redemptive-core ├── src │ └── main │ │ └── java │ │ └── tech │ │ └── rayline │ │ └── core │ │ ├── rx │ │ ├── ConcurrencyMode.java │ │ ├── BaseStreamer.java │ │ ├── OperatorReportExceptionTransparent.java │ │ ├── ObservablePost.java │ │ ├── RxBukkitScheduler.java │ │ ├── PeriodicPlayerStreamer.java │ │ └── EventStreamer.java │ │ ├── util │ │ ├── Vector.java │ │ ├── SoundUtil.java │ │ ├── CooldownUnexpiredException.java │ │ ├── PlayerUtil.java │ │ ├── Region.java │ │ ├── CachedObjectContainer.java │ │ ├── CooldownManager.java │ │ ├── Point.java │ │ ├── RunnableShorthand.java │ │ ├── ItemShorthand.java │ │ ├── Timeline.java │ │ ├── PlayerState.java │ │ └── GeneralUtils.java │ │ ├── gui │ │ ├── InventoryGUIAction.java │ │ ├── ControlledInventoryButton.java │ │ ├── ClickAction.java │ │ ├── InventoryGUIButton.java │ │ ├── SimpleInventoryGUIButton.java │ │ ├── ControlledInventory.java │ │ └── InventoryGUI.java │ │ ├── command │ │ ├── CommandException.java │ │ ├── AsyncCommand.java │ │ ├── EmptyHandlerException.java │ │ ├── CommandPermission.java │ │ ├── UnhandledCommandExceptionException.java │ │ ├── FriendlyException.java │ │ ├── NormalCommandException.java │ │ ├── CommandMeta.java │ │ ├── PermissionException.java │ │ ├── ArgumentRequirementException.java │ │ └── RDCommand.java │ │ ├── inject │ │ ├── DisableHandler.java │ │ ├── Inject.java │ │ ├── InjectionProvider.java │ │ ├── Injectable.java │ │ └── Injector.java │ │ ├── parse │ │ ├── ReadOnlyResource.java │ │ ├── RegisteredResourceFile.java │ │ ├── ResourceFileHook.java │ │ ├── ResourceFile.java │ │ ├── SnakeYAMLResourceFileHook.java │ │ └── ResourceFileGraph.java │ │ ├── library │ │ ├── MavenRepo.java │ │ ├── MavenLibraries.java │ │ ├── IgnoreLibraries.java │ │ ├── MavenLibrary.java │ │ └── LibraryHandler.java │ │ ├── plugin │ │ ├── UsesFormats.java │ │ ├── NoConfig.java │ │ ├── YAMLConfigurationFile.java │ │ ├── Formatter.java │ │ └── RedemptivePlugin.java │ │ └── effect │ │ └── Scoreboarder.java └── build.gradle ├── settings.gradle ├── redemptive-sql ├── src │ └── main │ │ └── java │ │ └── tech │ │ └── rayline │ │ └── core │ │ └── sql │ │ ├── DatabaseConfiguration.java │ │ └── HikariCPBridge.java └── build.gradle ├── redemptive-persist ├── build.gradle └── src │ └── main │ └── java │ └── tech │ └── rayline │ └── core │ └── persist │ └── MorphiaHelper.java ├── redemptive-serialize ├── build.gradle └── src │ └── main │ └── java │ └── tech │ └── rayline │ └── core │ ├── WorldTypeAdapter.java │ ├── OfflinePlayerTypeAdapter.java │ ├── GsonBridge.java │ ├── GsonResourceFileHook.java │ ├── LocationTypeAdapter.java │ └── ItemStackTypeAdapter.java ├── gradlew.bat ├── gradlew └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | .idea 3 | *.iml 4 | target/ 5 | .DS_Store 6 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Twister915/redemptive/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /redemptive-core/src/main/java/tech/rayline/core/rx/ConcurrencyMode.java: -------------------------------------------------------------------------------- 1 | package tech.rayline.core.rx; 2 | 3 | public enum ConcurrencyMode { 4 | SYNC, 5 | ASYNC 6 | } 7 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'redemptive-parent' 2 | include 'redemptive-serialize' 3 | include 'redemptive-persist' 4 | include 'redemptive-core' 5 | include 'redemptive-sql' 6 | -------------------------------------------------------------------------------- /redemptive-core/src/main/java/tech/rayline/core/util/Vector.java: -------------------------------------------------------------------------------- 1 | package tech.rayline.core.util; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public final class Vector { 7 | private final Point origin; 8 | private final double theta, mag; 9 | } 10 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat Jul 08 11:06:30 NZST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.11-bin.zip 7 | -------------------------------------------------------------------------------- /redemptive-core/src/main/java/tech/rayline/core/gui/InventoryGUIAction.java: -------------------------------------------------------------------------------- 1 | package tech.rayline.core.gui; 2 | 3 | import lombok.Data; 4 | import org.bukkit.entity.Player; 5 | 6 | @Data 7 | public class InventoryGUIAction { 8 | private final Player player; 9 | private final ClickAction action; 10 | } 11 | -------------------------------------------------------------------------------- /redemptive-core/src/main/java/tech/rayline/core/command/CommandException.java: -------------------------------------------------------------------------------- 1 | package tech.rayline.core.command; 2 | 3 | import lombok.Data; 4 | import lombok.EqualsAndHashCode; 5 | 6 | @EqualsAndHashCode(callSuper = true) 7 | @Data 8 | public class CommandException extends Exception { 9 | private final String message; 10 | } 11 | -------------------------------------------------------------------------------- /redemptive-core/src/main/java/tech/rayline/core/util/SoundUtil.java: -------------------------------------------------------------------------------- 1 | package tech.rayline.core.util; 2 | 3 | import org.bukkit.Sound; 4 | import org.bukkit.entity.Player; 5 | 6 | public final class SoundUtil { 7 | public static void playTo(Player player, Sound sound) { 8 | player.playSound(player.getLocation(), sound, 20f, 0f); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /redemptive-core/src/main/java/tech/rayline/core/gui/ControlledInventoryButton.java: -------------------------------------------------------------------------------- 1 | package tech.rayline.core.gui; 2 | 3 | import org.bukkit.entity.Player; 4 | import org.bukkit.inventory.ItemStack; 5 | 6 | public abstract class ControlledInventoryButton { 7 | protected void onUse(Player player) {} 8 | protected abstract ItemStack getStack(Player player); 9 | } 10 | -------------------------------------------------------------------------------- /redemptive-core/src/main/java/tech/rayline/core/command/AsyncCommand.java: -------------------------------------------------------------------------------- 1 | package tech.rayline.core.command; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target(ElementType.TYPE) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface AsyncCommand {} 11 | -------------------------------------------------------------------------------- /redemptive-core/src/main/java/tech/rayline/core/inject/DisableHandler.java: -------------------------------------------------------------------------------- 1 | package tech.rayline.core.inject; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target(ElementType.METHOD) 10 | public @interface DisableHandler { 11 | } 12 | -------------------------------------------------------------------------------- /redemptive-core/src/main/java/tech/rayline/core/inject/Inject.java: -------------------------------------------------------------------------------- 1 | package tech.rayline.core.inject; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target({ElementType.TYPE, ElementType.FIELD}) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface Inject {} 11 | -------------------------------------------------------------------------------- /redemptive-core/src/main/java/tech/rayline/core/inject/InjectionProvider.java: -------------------------------------------------------------------------------- 1 | package tech.rayline.core.inject; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target(ElementType.CONSTRUCTOR) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface InjectionProvider {} 11 | -------------------------------------------------------------------------------- /redemptive-core/src/main/java/tech/rayline/core/parse/ReadOnlyResource.java: -------------------------------------------------------------------------------- 1 | package tech.rayline.core.parse; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target(ElementType.FIELD) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface ReadOnlyResource { 11 | } 12 | -------------------------------------------------------------------------------- /redemptive-core/src/main/java/tech/rayline/core/library/MavenRepo.java: -------------------------------------------------------------------------------- 1 | package tech.rayline.core.library; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target(ElementType.LOCAL_VARIABLE) 10 | public @interface MavenRepo { 11 | String url(); 12 | } 13 | -------------------------------------------------------------------------------- /redemptive-core/src/main/java/tech/rayline/core/library/MavenLibraries.java: -------------------------------------------------------------------------------- 1 | package tech.rayline.core.library; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target(ElementType.TYPE) 10 | public @interface MavenLibraries { 11 | MavenLibrary[] value() default {}; 12 | } 13 | -------------------------------------------------------------------------------- /redemptive-core/src/main/java/tech/rayline/core/plugin/UsesFormats.java: -------------------------------------------------------------------------------- 1 | package tech.rayline.core.plugin; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target(ElementType.TYPE) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface UsesFormats { 11 | @Deprecated String file() default "formats.yml"; 12 | } 13 | -------------------------------------------------------------------------------- /redemptive-core/src/main/java/tech/rayline/core/command/EmptyHandlerException.java: -------------------------------------------------------------------------------- 1 | package tech.rayline.core.command; 2 | 3 | public final class EmptyHandlerException extends CommandException implements FriendlyException { 4 | public EmptyHandlerException() { 5 | super("There was no handler found for this command!"); 6 | } 7 | 8 | @Override 9 | public String getFriendlyMessage(RDCommand command) { 10 | return getMessage(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /redemptive-core/src/main/java/tech/rayline/core/parse/RegisteredResourceFile.java: -------------------------------------------------------------------------------- 1 | package tech.rayline.core.parse; 2 | 3 | import lombok.Data; 4 | 5 | import java.io.File; 6 | import java.lang.reflect.Field; 7 | 8 | @Data final class RegisteredResourceFile { 9 | private final ResourceFile annotation; 10 | private final File file; 11 | private final Object instanceBound; 12 | private final Field field; 13 | private final ResourceFileHook hook; 14 | } 15 | -------------------------------------------------------------------------------- /redemptive-core/src/main/java/tech/rayline/core/plugin/NoConfig.java: -------------------------------------------------------------------------------- 1 | package tech.rayline.core.plugin; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Annotate a plugin with this if no config is required 10 | */ 11 | @Target(ElementType.TYPE) 12 | @Retention(RetentionPolicy.RUNTIME) 13 | public @interface NoConfig { 14 | } 15 | -------------------------------------------------------------------------------- /redemptive-core/src/main/java/tech/rayline/core/command/CommandPermission.java: -------------------------------------------------------------------------------- 1 | package tech.rayline.core.command; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target(ElementType.TYPE) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface CommandPermission { 11 | String value(); 12 | boolean isOpExempt() default true; 13 | } 14 | -------------------------------------------------------------------------------- /redemptive-core/src/main/java/tech/rayline/core/command/UnhandledCommandExceptionException.java: -------------------------------------------------------------------------------- 1 | package tech.rayline.core.command; 2 | 3 | import lombok.Getter; 4 | 5 | public class UnhandledCommandExceptionException extends CommandException { 6 | @Getter 7 | private final Exception causingException; 8 | public UnhandledCommandExceptionException(Exception e) { 9 | super("Unhandled exception " + e.getMessage()); 10 | this.causingException = e; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /redemptive-core/src/main/java/tech/rayline/core/command/FriendlyException.java: -------------------------------------------------------------------------------- 1 | package tech.rayline.core.command; 2 | 3 | public interface FriendlyException { 4 | /** 5 | * Grabs a friendly version of the message to be displayed during an exception. 6 | * @param command The command that is attempting to get the friendly message. 7 | * @return A message to be displayed to the user during failure by default. 8 | */ 9 | String getFriendlyMessage(RDCommand command); 10 | } 11 | -------------------------------------------------------------------------------- /redemptive-core/src/main/java/tech/rayline/core/command/NormalCommandException.java: -------------------------------------------------------------------------------- 1 | package tech.rayline.core.command; 2 | 3 | import org.bukkit.ChatColor; 4 | 5 | public final class NormalCommandException extends CommandException implements FriendlyException { 6 | public NormalCommandException(String message) { 7 | super(message); 8 | } 9 | 10 | @Override 11 | public String getFriendlyMessage(RDCommand command) { 12 | return ChatColor.RED + getMessage(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /redemptive-core/src/main/java/tech/rayline/core/command/CommandMeta.java: -------------------------------------------------------------------------------- 1 | package tech.rayline.core.command; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target(ElementType.TYPE) 10 | public @interface CommandMeta { 11 | String description() default ""; 12 | String[] aliases() default {}; 13 | String usage() default ""; 14 | } 15 | -------------------------------------------------------------------------------- /redemptive-core/src/main/java/tech/rayline/core/command/PermissionException.java: -------------------------------------------------------------------------------- 1 | package tech.rayline.core.command; 2 | 3 | import org.bukkit.ChatColor; 4 | 5 | public final class PermissionException extends CommandException implements FriendlyException { 6 | public PermissionException(String message) { 7 | super(message); 8 | } 9 | 10 | @Override 11 | public String getFriendlyMessage(RDCommand command) { 12 | return ChatColor.RED + "You do not have permission for this!"; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /redemptive-core/src/main/java/tech/rayline/core/library/IgnoreLibraries.java: -------------------------------------------------------------------------------- 1 | package tech.rayline.core.library; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target(ElementType.TYPE) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | /** 11 | * When any class in an inheritance tree has this annotation, all libraries will be ignored! 12 | */ 13 | public @interface IgnoreLibraries {} 14 | -------------------------------------------------------------------------------- /redemptive-core/src/main/java/tech/rayline/core/library/MavenLibrary.java: -------------------------------------------------------------------------------- 1 | package tech.rayline.core.library; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target(ElementType.LOCAL_VARIABLE) 10 | public @interface MavenLibrary { 11 | String value(); 12 | MavenRepo repo() default @MavenRepo(url = "http://repo1.maven.org/maven2"); 13 | } 14 | -------------------------------------------------------------------------------- /redemptive-core/src/main/java/tech/rayline/core/command/ArgumentRequirementException.java: -------------------------------------------------------------------------------- 1 | package tech.rayline.core.command; 2 | 3 | import org.bukkit.ChatColor; 4 | 5 | public final class ArgumentRequirementException extends CommandException implements FriendlyException { 6 | public ArgumentRequirementException(String message) { 7 | super(message); 8 | } 9 | 10 | @Override 11 | public String getFriendlyMessage(RDCommand command) { 12 | return ChatColor.RED + this.getMessage(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /redemptive-core/src/main/java/tech/rayline/core/gui/ClickAction.java: -------------------------------------------------------------------------------- 1 | package tech.rayline.core.gui; 2 | 3 | import org.bukkit.event.inventory.ClickType; 4 | 5 | public enum ClickAction { 6 | RIGHT_CLICK, 7 | LEFT_CLICK; 8 | 9 | public static ClickAction from(ClickType action) { 10 | switch (action) { 11 | case RIGHT: 12 | case SHIFT_RIGHT: 13 | return ClickAction.RIGHT_CLICK; 14 | default: 15 | return ClickAction.LEFT_CLICK; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /redemptive-core/src/main/java/tech/rayline/core/parse/ResourceFileHook.java: -------------------------------------------------------------------------------- 1 | package tech.rayline.core.parse; 2 | 3 | import tech.rayline.core.plugin.RedemptivePlugin; 4 | 5 | import java.io.File; 6 | 7 | public interface ResourceFileHook { 8 | T read(RedemptivePlugin plugin, File file, Class type) throws Exception; 9 | RawType readRaw(RedemptivePlugin plugin, File file) throws Exception; 10 | 11 | void write(RedemptivePlugin plugin, Object o, File file) throws Exception; 12 | void writeRaw(RedemptivePlugin plugin, Object type, File file) throws Exception; 13 | } 14 | -------------------------------------------------------------------------------- /redemptive-sql/src/main/java/tech/rayline/core/sql/DatabaseConfiguration.java: -------------------------------------------------------------------------------- 1 | package tech.rayline.core.sql; 2 | 3 | import com.zaxxer.hikari.HikariConfig; 4 | 5 | public final class DatabaseConfiguration { 6 | public String host, database, username, password; 7 | public int port; 8 | 9 | public HikariConfig getHikariConfig() { 10 | HikariConfig hikariConfig = new HikariConfig(); 11 | hikariConfig.setJdbcUrl("jdbc:mysql://" + host + ":" + port + "/" + database); 12 | hikariConfig.setUsername(username); 13 | hikariConfig.setPassword(password); 14 | 15 | return hikariConfig; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /redemptive-core/src/main/java/tech/rayline/core/inject/Injectable.java: -------------------------------------------------------------------------------- 1 | package tech.rayline.core.inject; 2 | 3 | import tech.rayline.core.library.MavenLibrary; 4 | 5 | import java.lang.annotation.ElementType; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | import java.lang.annotation.Target; 9 | 10 | @Target(ElementType.TYPE) 11 | @Retention(RetentionPolicy.RUNTIME) 12 | /** 13 | * Used to mark classes which are to be injected so that we can tell IntelliJ that these classes are actually used indirectly 14 | */ 15 | public @interface Injectable { 16 | MavenLibrary[] libraries() default {}; 17 | } 18 | -------------------------------------------------------------------------------- /redemptive-sql/build.gradle: -------------------------------------------------------------------------------- 1 | group 'tech.rayline.redemptive' 2 | version '1.1-SNAPSHOT' 3 | 4 | apply plugin: 'maven' 5 | apply plugin: 'java' 6 | 7 | sourceCompatibility = 1.7 8 | 9 | repositories { 10 | mavenCentral() 11 | mavenLocal() 12 | 13 | maven { 14 | url "https://hub.spigotmc.org/nexus/content/repositories/snapshots/" 15 | } 16 | 17 | maven { 18 | url 'https://oss.sonatype.org/content/repositories/snapshots' 19 | } 20 | } 21 | 22 | dependencies { 23 | testCompile group: 'junit', name: 'junit', version: '4.11' 24 | compile project(':redemptive-core') 25 | compile 'com.zaxxer:HikariCP:2.4.3' 26 | } 27 | -------------------------------------------------------------------------------- /redemptive-persist/build.gradle: -------------------------------------------------------------------------------- 1 | group 'tech.rayline.redemptive' 2 | version '1.1-SNAPSHOT' 3 | 4 | apply plugin: 'java' 5 | apply plugin: 'maven' 6 | 7 | sourceCompatibility = 1.7 8 | 9 | repositories { 10 | mavenCentral() 11 | mavenLocal() 12 | 13 | maven { 14 | url "https://hub.spigotmc.org/nexus/content/repositories/snapshots/" 15 | } 16 | 17 | maven { 18 | url 'https://oss.sonatype.org/content/repositories/snapshots' 19 | } 20 | } 21 | 22 | dependencies { 23 | testCompile group: 'junit', name: 'junit', version: '4.11' 24 | compile 'org.mongodb.morphia:morphia:1.0.1' 25 | compile project(':redemptive-core') 26 | } -------------------------------------------------------------------------------- /redemptive-serialize/build.gradle: -------------------------------------------------------------------------------- 1 | group 'tech.rayline.redemptive' 2 | version '1.1-SNAPSHOT' 3 | 4 | apply plugin: 'java' 5 | apply plugin: 'maven' 6 | 7 | sourceCompatibility = 1.7 8 | 9 | repositories { 10 | mavenCentral() 11 | mavenLocal() 12 | 13 | maven { 14 | url "https://hub.spigotmc.org/nexus/content/repositories/snapshots/" 15 | } 16 | 17 | maven { 18 | url 'https://oss.sonatype.org/content/repositories/snapshots' 19 | } 20 | } 21 | 22 | dependencies { 23 | compile project(':redemptive-core') 24 | compile 'com.google.code.gson:gson:2.5' 25 | testCompile group: 'junit', name: 'junit', version: '4.11' 26 | } 27 | -------------------------------------------------------------------------------- /redemptive-core/build.gradle: -------------------------------------------------------------------------------- 1 | group 'tech.rayline.redemptive' 2 | version '1.1-SNAPSHOT' 3 | 4 | apply plugin: 'java' 5 | apply plugin: 'maven' 6 | 7 | sourceCompatibility = 1.7 8 | 9 | repositories { 10 | mavenCentral() 11 | mavenLocal() 12 | 13 | maven { 14 | url "https://hub.spigotmc.org/nexus/content/repositories/snapshots/" 15 | } 16 | 17 | maven { 18 | url 'https://oss.sonatype.org/content/repositories/snapshots' 19 | } 20 | } 21 | 22 | dependencies { 23 | compile 'org.spigotmc:spigot-api:1.11.2-R0.1-SNAPSHOT' 24 | compile 'org.projectlombok:lombok:1.16.4' 25 | compile 'io.reactivex:rxjava:1.0.16' 26 | testCompile group: 'junit', name: 'junit', version: '4.11' 27 | } 28 | -------------------------------------------------------------------------------- /redemptive-serialize/src/main/java/tech/rayline/core/WorldTypeAdapter.java: -------------------------------------------------------------------------------- 1 | package tech.rayline.core; 2 | 3 | import com.google.gson.TypeAdapter; 4 | import com.google.gson.stream.JsonReader; 5 | import com.google.gson.stream.JsonWriter; 6 | import org.bukkit.Bukkit; 7 | import org.bukkit.World; 8 | 9 | import java.io.IOException; 10 | import java.util.UUID; 11 | 12 | public final class WorldTypeAdapter extends TypeAdapter { 13 | @Override 14 | public void write(JsonWriter out, World value) throws IOException { 15 | out.value(value.getUID().toString()); 16 | } 17 | 18 | @Override 19 | public World read(JsonReader in) throws IOException { 20 | return Bukkit.getWorld(UUID.fromString(in.nextString())); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /redemptive-serialize/src/main/java/tech/rayline/core/OfflinePlayerTypeAdapter.java: -------------------------------------------------------------------------------- 1 | package tech.rayline.core; 2 | 3 | import com.google.gson.TypeAdapter; 4 | import com.google.gson.stream.JsonReader; 5 | import com.google.gson.stream.JsonWriter; 6 | import org.bukkit.Bukkit; 7 | import org.bukkit.OfflinePlayer; 8 | 9 | import java.io.IOException; 10 | import java.util.UUID; 11 | 12 | public final class OfflinePlayerTypeAdapter extends TypeAdapter { 13 | @Override 14 | public void write(JsonWriter out, OfflinePlayer value) throws IOException { 15 | out.value(value.getUniqueId().toString()); 16 | } 17 | 18 | @Override 19 | public OfflinePlayer read(JsonReader in) throws IOException { 20 | return Bukkit.getOfflinePlayer(UUID.fromString(in.nextString())); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /redemptive-core/src/main/java/tech/rayline/core/util/CooldownUnexpiredException.java: -------------------------------------------------------------------------------- 1 | package tech.rayline.core.util; 2 | 3 | import lombok.Data; 4 | import lombok.EqualsAndHashCode; 5 | import tech.rayline.core.command.CommandException; 6 | 7 | import java.util.concurrent.TimeUnit; 8 | 9 | @EqualsAndHashCode(callSuper = false) 10 | @Data 11 | public final class CooldownUnexpiredException extends CommandException { 12 | private final Long timeRemaining; 13 | private final TimeUnit timeUnit; 14 | 15 | public CooldownUnexpiredException(Long timeRemaining, TimeUnit timeUnit) { 16 | super("Unexpired cooldown"); 17 | this.timeRemaining = timeRemaining; 18 | this.timeUnit = timeUnit; 19 | } 20 | 21 | @Override 22 | public synchronized Throwable fillInStackTrace() { 23 | return this; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /redemptive-core/src/main/java/tech/rayline/core/parse/ResourceFile.java: -------------------------------------------------------------------------------- 1 | package tech.rayline.core.parse; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target(ElementType.FIELD) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface ResourceFile { 11 | /** 12 | * @return The name of the file in the plugin data directory 13 | */ 14 | String filename(); 15 | 16 | /** 17 | * @return The resource hook for serialization 18 | */ 19 | Class hook() default SnakeYAMLResourceFileHook.class; 20 | 21 | /** 22 | * @return If we should serialize this as a bean or use it as a container for the raw type related to this resource file hook. 23 | */ 24 | boolean raw() default false; 25 | } 26 | -------------------------------------------------------------------------------- /redemptive-core/src/main/java/tech/rayline/core/gui/InventoryGUIButton.java: -------------------------------------------------------------------------------- 1 | package tech.rayline.core.gui; 2 | 3 | import lombok.Getter; 4 | import org.bukkit.entity.Player; 5 | import org.bukkit.inventory.ItemStack; 6 | import tech.rayline.core.command.EmptyHandlerException; 7 | 8 | @Getter 9 | public abstract class InventoryGUIButton { 10 | private ItemStack currentRepresentation; 11 | 12 | public InventoryGUIButton() {} 13 | public InventoryGUIButton(ItemStack stack) { 14 | this.currentRepresentation = stack; 15 | } 16 | 17 | public void setStack(ItemStack stack) { 18 | this.currentRepresentation = stack; 19 | } 20 | 21 | public void onPlayerClick(Player player, ClickAction action) throws EmptyHandlerException { throw new EmptyHandlerException(); } 22 | protected void onRemove() {} 23 | protected void onAdd() {} 24 | protected void onUpdate() {} 25 | } 26 | -------------------------------------------------------------------------------- /redemptive-core/src/main/java/tech/rayline/core/gui/SimpleInventoryGUIButton.java: -------------------------------------------------------------------------------- 1 | package tech.rayline.core.gui; 2 | 3 | import org.bukkit.entity.Player; 4 | import org.bukkit.inventory.ItemStack; 5 | import rx.functions.Action1; 6 | import tech.rayline.core.command.EmptyHandlerException; 7 | 8 | public class SimpleInventoryGUIButton extends InventoryGUIButton { 9 | private final Action1 action; 10 | 11 | public SimpleInventoryGUIButton(ItemStack stack, Action1 action) { 12 | super(stack); 13 | this.action = action; 14 | } 15 | 16 | @Override 17 | public void onPlayerClick(Player player, ClickAction action) throws EmptyHandlerException { 18 | try { 19 | this.action.call(new InventoryGUIAction(player, action)); 20 | } catch (Exception e) { 21 | e.printStackTrace(); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /redemptive-core/src/main/java/tech/rayline/core/rx/BaseStreamer.java: -------------------------------------------------------------------------------- 1 | package tech.rayline.core.rx; 2 | 3 | import lombok.Data; 4 | import org.bukkit.plugin.Plugin; 5 | import rx.Observable; 6 | 7 | @Data 8 | public abstract class BaseStreamer { 9 | protected final Plugin plugin; 10 | protected final RxBukkitScheduler syncScheduler, asyncScheduler; 11 | 12 | public Observable.Transformer getSyncTransformer() { 13 | return new Observable.Transformer() { 14 | @Override 15 | public Observable call(Observable tObservable) { 16 | return tObservable.subscribeOn(syncScheduler); 17 | } 18 | }; 19 | } 20 | 21 | public Observable.Transformer getAsyncTransformer() { 22 | return new Observable.Transformer() { 23 | @Override 24 | public Observable call(Observable tObservable) { 25 | return tObservable.subscribeOn(asyncScheduler); 26 | } 27 | }; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /redemptive-core/src/main/java/tech/rayline/core/util/PlayerUtil.java: -------------------------------------------------------------------------------- 1 | package tech.rayline.core.util; 2 | 3 | import org.bukkit.GameMode; 4 | import org.bukkit.entity.Player; 5 | import org.bukkit.inventory.ItemStack; 6 | import org.bukkit.potion.PotionEffect; 7 | 8 | public final class PlayerUtil { 9 | public static void resetPlayer(Player player) { 10 | player.setMaxHealth(20); 11 | player.setHealth(player.getMaxHealth()); 12 | player.setFireTicks(0); 13 | player.setFoodLevel(20); 14 | player.resetPlayerTime(); 15 | player.resetPlayerWeather(); 16 | player.getInventory().clear(); 17 | player.getInventory().setArmorContents(new ItemStack[4]); 18 | player.setExp(0); 19 | player.setLevel(0); 20 | if (player.getAllowFlight()) player.setFlying(false); 21 | player.setAllowFlight(false); 22 | player.setGameMode(GameMode.SURVIVAL); 23 | for (PotionEffect potionEffect : player.getActivePotionEffects()) 24 | player.removePotionEffect(potionEffect.getType()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /redemptive-core/src/main/java/tech/rayline/core/util/Region.java: -------------------------------------------------------------------------------- 1 | package tech.rayline.core.util; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public final class Region { 7 | private final Point min, max; 8 | 9 | private final double width, height, depth; 10 | 11 | public Region(Point a, Point b) { 12 | min = new Point(Math.min(a.getX(), b.getX()), Math.min(a.getY(), b.getY()), Math.min(a.getZ(), b.getZ()), Math.min(a.getPitch(), b.getPitch()), Math.min(a.getYaw(), b.getYaw())); 13 | max = new Point(Math.max(a.getX(), b.getX()), Math.max(a.getY(), b.getY()), Math.max(a.getZ(), b.getZ()), Math.max(a.getPitch(), b.getPitch()), Math.max(a.getYaw(), b.getYaw())); 14 | 15 | width = max.getX() - min.getX(); 16 | height = max.getY() - min.getX(); 17 | depth = max.getZ() - min.getZ(); 18 | } 19 | 20 | public boolean inRegion(Point point) { 21 | return inRegion(point.getX(), point.getY(), point.getZ()); 22 | } 23 | 24 | public boolean inRegion(double x, double y, double z) { 25 | return x >= min.getX() && x <= max.getX() 26 | && y >= min.getY() && y <= max.getY() 27 | && z >= min.getZ() && z <= max.getZ(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /redemptive-serialize/src/main/java/tech/rayline/core/GsonBridge.java: -------------------------------------------------------------------------------- 1 | package tech.rayline.core; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.GsonBuilder; 5 | import lombok.Getter; 6 | import org.bukkit.Location; 7 | import org.bukkit.OfflinePlayer; 8 | import org.bukkit.World; 9 | import org.bukkit.inventory.ItemStack; 10 | import tech.rayline.core.inject.Injectable; 11 | import tech.rayline.core.inject.InjectionProvider; 12 | import tech.rayline.core.library.MavenLibrary; 13 | import tech.rayline.core.plugin.RedemptivePlugin; 14 | 15 | import java.lang.reflect.Modifier; 16 | 17 | @Getter 18 | @Injectable(libraries = {@MavenLibrary("com.google.code.gson:gson:2.5")}) 19 | public final class GsonBridge { 20 | private final Gson gson = new GsonBuilder() 21 | .excludeFieldsWithModifiers(Modifier.TRANSIENT) 22 | .registerTypeHierarchyAdapter(Location.class, new LocationTypeAdapter()) 23 | .registerTypeHierarchyAdapter(OfflinePlayer.class, new OfflinePlayerTypeAdapter()) 24 | .registerTypeHierarchyAdapter(World.class, new WorldTypeAdapter()) 25 | .registerTypeHierarchyAdapter(ItemStack.class, new ItemStackTypeAdapter()) 26 | .create(); 27 | 28 | @InjectionProvider 29 | public GsonBridge(RedemptivePlugin redemptivePlugin) {} 30 | public GsonBridge() {} 31 | } 32 | -------------------------------------------------------------------------------- /redemptive-core/src/main/java/tech/rayline/core/parse/SnakeYAMLResourceFileHook.java: -------------------------------------------------------------------------------- 1 | package tech.rayline.core.parse; 2 | 3 | import org.yaml.snakeyaml.Yaml; 4 | import org.yaml.snakeyaml.constructor.CustomClassLoaderConstructor; 5 | import tech.rayline.core.plugin.RedemptivePlugin; 6 | import tech.rayline.core.plugin.YAMLConfigurationFile; 7 | 8 | import java.io.File; 9 | import java.io.FileReader; 10 | import java.io.FileWriter; 11 | 12 | public final class SnakeYAMLResourceFileHook implements ResourceFileHook { 13 | private final Yaml snakeYaml = new Yaml(new CustomClassLoaderConstructor(getClass().getClassLoader())); 14 | 15 | @Override 16 | public T read(RedemptivePlugin plugin, File file, Class type) throws Exception { 17 | return snakeYaml.loadAs(new FileReader(file), type); 18 | } 19 | 20 | @Override 21 | public YAMLConfigurationFile readRaw(RedemptivePlugin plugin, File file) throws Exception { 22 | return new YAMLConfigurationFile(plugin, file); 23 | } 24 | 25 | @Override 26 | public void write(RedemptivePlugin plugin, Object o, File file) throws Exception { 27 | snakeYaml.dump(o, new FileWriter(file)); 28 | } 29 | 30 | @Override 31 | public void writeRaw(RedemptivePlugin plugin, Object yamlConfigurationFile, File file) throws Exception { 32 | ((YAMLConfigurationFile) yamlConfigurationFile).saveConfig(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /redemptive-persist/src/main/java/tech/rayline/core/persist/MorphiaHelper.java: -------------------------------------------------------------------------------- 1 | package tech.rayline.core.persist; 2 | 3 | import org.mongodb.morphia.mapping.DefaultCreator; 4 | import org.mongodb.morphia.mapping.Mapper; 5 | import org.mongodb.morphia.mapping.MapperOptions; 6 | import tech.rayline.core.inject.Injectable; 7 | import tech.rayline.core.inject.InjectionProvider; 8 | import tech.rayline.core.library.MavenLibraries; 9 | import tech.rayline.core.library.MavenLibrary; 10 | import tech.rayline.core.plugin.RedemptivePlugin; 11 | 12 | @Injectable(libraries = {@MavenLibrary("org.mongodb.morphia:morphia:1.0.1"), @MavenLibrary("org.mongodb:mongo-java-driver:3.0.2")}) 13 | public final class MorphiaHelper { 14 | private final RedemptivePlugin plugin; 15 | 16 | @InjectionProvider 17 | public MorphiaHelper(RedemptivePlugin plugin) { 18 | this.plugin = plugin; 19 | } 20 | 21 | public Mapper createNewMapper() { 22 | //this here is some magic to get Bukkit classes to work in the mapper 23 | MapperOptions mapperOptions = new MapperOptions(); 24 | final ClassLoader ourClassLoader = plugin.getClass().getClassLoader(); 25 | mapperOptions.setObjectFactory(new DefaultCreator() { 26 | @Override 27 | protected ClassLoader getClassLoaderForClass() { 28 | return ourClassLoader; 29 | } 30 | }); 31 | 32 | return new Mapper(mapperOptions); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /redemptive-sql/src/main/java/tech/rayline/core/sql/HikariCPBridge.java: -------------------------------------------------------------------------------- 1 | package tech.rayline.core.sql; 2 | 3 | import com.zaxxer.hikari.pool.HikariPool; 4 | import lombok.Getter; 5 | import tech.rayline.core.inject.DisableHandler; 6 | import tech.rayline.core.inject.Injectable; 7 | import tech.rayline.core.inject.InjectionProvider; 8 | import tech.rayline.core.library.MavenLibrary; 9 | import tech.rayline.core.parse.ReadOnlyResource; 10 | import tech.rayline.core.parse.ResourceFile; 11 | import tech.rayline.core.plugin.RedemptivePlugin; 12 | 13 | import java.sql.Connection; 14 | import java.sql.SQLException; 15 | 16 | @Injectable(libraries = {@MavenLibrary("com.zaxxer:HikariCP:2.4.3"), @MavenLibrary("org.slf4j:slf4j-api:1.7.12")}) 17 | @Getter 18 | public final class HikariCPBridge { 19 | @ResourceFile(filename = "database.yml") @ReadOnlyResource private DatabaseConfiguration databaseConfiguration; 20 | private final HikariPool pool; 21 | 22 | @InjectionProvider 23 | public HikariCPBridge(RedemptivePlugin plugin) { 24 | plugin.getResourceFileGraph().addObject(this); 25 | pool = new HikariPool(databaseConfiguration.getHikariConfig()); 26 | } 27 | 28 | @DisableHandler 29 | private void onDisable() throws InterruptedException { 30 | pool.shutdown(); 31 | } 32 | 33 | public boolean doesTableExist(String tableName) throws SQLException { 34 | try (Connection connection = pool.getConnection()) { 35 | return connection.getMetaData().getTables(null, null, tableName, null).first(); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /redemptive-core/src/main/java/tech/rayline/core/util/CachedObjectContainer.java: -------------------------------------------------------------------------------- 1 | package tech.rayline.core.util; 2 | 3 | import lombok.EqualsAndHashCode; 4 | import lombok.ToString; 5 | import rx.functions.Func0; 6 | import rx.functions.Func1; 7 | 8 | import java.util.Optional; 9 | 10 | 11 | @EqualsAndHashCode 12 | @ToString 13 | public class CachedObjectContainer { 14 | private final Func0 provider; 15 | private final Func1 checker; 16 | 17 | private K currentValue; 18 | 19 | public CachedObjectContainer(Func0 provider) { 20 | this(provider, new Func1() { 21 | @Override 22 | public Boolean call(K k) { 23 | return true; 24 | } 25 | }); 26 | } 27 | 28 | public CachedObjectContainer(Func0 provider, Func1 checker) { 29 | this.provider = provider; 30 | this.checker = checker; 31 | } 32 | 33 | public Optional getValue() { 34 | attemptReconstruct(); 35 | return Optional.ofNullable(currentValue); 36 | } 37 | 38 | /** 39 | * Returns true of the object that we are currently holding is "clean" or has not been broken or is not considered broken. 40 | * @return above 41 | */ 42 | public boolean isCurrentObjectClean() { 43 | return currentValue != null && checker.call(currentValue); 44 | } 45 | 46 | public void breakObject() { 47 | currentValue = null; 48 | attemptReconstruct(); 49 | } 50 | 51 | protected void attemptReconstruct() { 52 | if (!isCurrentObjectClean()) 53 | currentValue = provider.call(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /redemptive-serialize/src/main/java/tech/rayline/core/GsonResourceFileHook.java: -------------------------------------------------------------------------------- 1 | package tech.rayline.core; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.JsonElement; 5 | import com.google.gson.JsonParser; 6 | import tech.rayline.core.parse.ResourceFileHook; 7 | import tech.rayline.core.plugin.RedemptivePlugin; 8 | 9 | import java.io.File; 10 | import java.io.FileReader; 11 | import java.io.FileWriter; 12 | 13 | public final class GsonResourceFileHook implements ResourceFileHook { 14 | private final Gson gson = new GsonBridge().getGson(); 15 | private final JsonParser jsonParser = new JsonParser(); 16 | 17 | @Override 18 | public T read(RedemptivePlugin plugin, File file, Class type) throws Exception { 19 | return gson.fromJson(new FileReader(file), type); 20 | } 21 | 22 | @Override 23 | //warning- this method does not use any of the type adapters from this project 24 | public JsonElement readRaw(RedemptivePlugin plugin, File file) throws Exception { 25 | return jsonParser.parse(new FileReader(file)); 26 | } 27 | 28 | @Override 29 | public void write(RedemptivePlugin plugin, Object o, File file) throws Exception { 30 | try (FileWriter fileWriter = new FileWriter(file)) { 31 | String s = gson.toJson(o); 32 | fileWriter.write(s); 33 | } 34 | } 35 | 36 | @Override 37 | public void writeRaw(RedemptivePlugin plugin, Object jsonElement, File file) throws Exception { 38 | FileWriter fileWriter = new FileWriter(file); 39 | try { 40 | fileWriter.write(jsonElement.toString()); 41 | } finally { 42 | fileWriter.flush(); 43 | fileWriter.close(); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /redemptive-core/src/main/java/tech/rayline/core/rx/OperatorReportExceptionTransparent.java: -------------------------------------------------------------------------------- 1 | package tech.rayline.core.rx; 2 | 3 | import lombok.Data; 4 | import rx.Observable; 5 | import rx.Subscriber; 6 | import rx.exceptions.Exceptions; 7 | import rx.functions.Action1; 8 | import rx.functions.Func1; 9 | 10 | /** 11 | * Allows you to transparently handle an exception for logging purposes 12 | */ 13 | @Data 14 | public final class OperatorReportExceptionTransparent implements Observable.Operator { 15 | /** 16 | * The action to perform with any exceptions, transparently. 17 | */ 18 | private final Action1 handler; 19 | private final Func1 filter; 20 | 21 | public static OperatorReportExceptionTransparent with(Action1 action) { 22 | return new OperatorReportExceptionTransparent<>(action, null); 23 | } 24 | 25 | @Override 26 | public Subscriber call(final Subscriber subscriber) { 27 | return new Subscriber() { 28 | @Override 29 | public void onCompleted() { 30 | subscriber.onCompleted(); 31 | } 32 | 33 | @Override 34 | public void onError(final Throwable e) { 35 | try { 36 | if (filter != null && filter.call(e)) 37 | handler.call(e); 38 | } catch (Exception e1) { 39 | Exceptions.throwOrReport(e1, subscriber); 40 | } finally { 41 | subscriber.onError(e); 42 | } 43 | } 44 | 45 | @Override 46 | public void onNext(final T t) { 47 | subscriber.onNext(t); 48 | } 49 | }; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /redemptive-core/src/main/java/tech/rayline/core/util/CooldownManager.java: -------------------------------------------------------------------------------- 1 | package tech.rayline.core.util; 2 | 3 | import java.util.Date; 4 | import java.util.HashMap; 5 | import java.util.Map; 6 | import java.util.concurrent.TimeUnit; 7 | 8 | public final class CooldownManager { 9 | private final Map cooldownMilliseconds = new HashMap(); 10 | 11 | public void testCooldown(String key, Long time, TimeUnit unit, Boolean reset) throws CooldownUnexpiredException { 12 | //Get the last time this cooldown was stored 13 | Date lastFiredDate = cooldownMilliseconds.get(key); 14 | //And get now 15 | Date currentDate = new Date(); 16 | //If we don't have a previous countdown 17 | if (lastFiredDate == null) { 18 | cooldownMilliseconds.put(key, currentDate); 19 | return; 20 | } 21 | //See how long ago that was in milliseconds 22 | long millisecondsPassed = currentDate.getTime() - lastFiredDate.getTime(); 23 | //And see how long we're supposed to wait 24 | long milliseconds = unit.toMillis(time); 25 | //If we're supposed to wait longer than we have 26 | if (milliseconds >= millisecondsPassed) { 27 | //The cooldown has yet to expire 28 | if (reset) cooldownMilliseconds.put(key, currentDate); 29 | throw new CooldownUnexpiredException(unit.toMillis(milliseconds-millisecondsPassed), unit); 30 | } 31 | cooldownMilliseconds.put(key, currentDate); 32 | } 33 | 34 | public void testCooldown(String key, Long time, TimeUnit unit) throws CooldownUnexpiredException { 35 | testCooldown(key, time, unit, false); 36 | } 37 | 38 | public void testCooldown(String key, Long seconds) throws CooldownUnexpiredException { 39 | testCooldown(key, seconds, TimeUnit.SECONDS); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /redemptive-core/src/main/java/tech/rayline/core/rx/ObservablePost.java: -------------------------------------------------------------------------------- 1 | package tech.rayline.core.rx; 2 | 3 | import lombok.Synchronized; 4 | import rx.Observable; 5 | import rx.Subscriber; 6 | import rx.exceptions.Exceptions; 7 | import rx.functions.Action0; 8 | import rx.subscriptions.Subscriptions; 9 | 10 | import java.util.HashSet; 11 | import java.util.Set; 12 | 13 | /** 14 | * This represents something that can churn out {@link rx.Observable}s and lets you post objects to the subscribers 15 | */ 16 | public final class ObservablePost { 17 | private final Set> subscribers = new HashSet<>(); 18 | 19 | @Synchronized 20 | public void post(final T obj) { 21 | for (Subscriber subscriber : subscribers) { 22 | try { 23 | subscriber.onNext(obj); 24 | } catch (Throwable t) { 25 | Exceptions.throwOrReport(t, subscriber); 26 | } 27 | } 28 | 29 | } 30 | 31 | @Synchronized 32 | public void complete() { 33 | for (Subscriber subscriber : subscribers) 34 | subscriber.onCompleted(); 35 | subscribers.clear(); 36 | } 37 | 38 | @Synchronized 39 | public Observable observe() { 40 | return Observable.create(new Observable.OnSubscribe() { 41 | @Override 42 | public void call(final Subscriber subscriber) { 43 | subscriber.add(Subscriptions.create(new Action0() { 44 | @Override 45 | public void call() { 46 | ObservablePost.this.removeSubscriber(subscriber); 47 | } 48 | })); 49 | subscribers.add(subscriber); 50 | } 51 | }); 52 | } 53 | 54 | @Synchronized 55 | private void removeSubscriber(Subscriber subscriber) { 56 | subscribers.remove(subscriber); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /redemptive-core/src/main/java/tech/rayline/core/util/Point.java: -------------------------------------------------------------------------------- 1 | package tech.rayline.core.util; 2 | 3 | import lombok.Data; 4 | import org.bukkit.Location; 5 | import org.bukkit.World; 6 | import org.bukkit.block.Block; 7 | import org.bukkit.entity.Entity; 8 | 9 | @Data 10 | public final class Point { 11 | private final double x, y, z; 12 | private final float pitch, yaw; 13 | 14 | public Location in(World world) { 15 | return new Location(world, x, y, z, yaw, pitch); 16 | } 17 | 18 | public double distanceSquared(Point point) { 19 | return distanceSquared(point.getX(), point.getY(), point.getZ()); 20 | } 21 | 22 | public double distanceSquared(Location location) { 23 | return distanceSquared(location.getX(), location.getY(), location.getZ()); 24 | } 25 | 26 | public double distanceSquared(double x, double y, double z) { 27 | return distanceSquared(x, z) + Math.pow(this.y - y, 2); 28 | } 29 | 30 | public double distanceSquared(double x, double z) { 31 | return Math.pow(this.x - x, 2) + Math.pow(this.z - z, 2); 32 | } 33 | 34 | public double distance(double x, double y, double z) { 35 | return Math.sqrt(distanceSquared(x, y, z)); 36 | } 37 | 38 | public double distance(double x, double z) { 39 | return Math.sqrt(distanceSquared(x, z)); 40 | } 41 | 42 | public double distance(Point point) { 43 | return Math.sqrt(distanceSquared(point)); 44 | } 45 | 46 | public double distance(Location location) { 47 | return Math.sqrt(distanceSquared(location)); 48 | } 49 | 50 | public static Point of(Location location) { 51 | return new Point(location.getX(), location.getY(), location.getZ(), location.getPitch(), location.getYaw()); 52 | } 53 | 54 | public static Point of(Block block) { 55 | return new Point(block.getX(), block.getY(), block.getZ(), 0f, 0f); 56 | } 57 | 58 | public static Point of(Entity entity) { 59 | return of(entity.getLocation()); 60 | } 61 | 62 | public Point add(double x, double y, double z) { 63 | return new Point(this.x + x, this.y + y, this.z + z, pitch, yaw); 64 | } 65 | 66 | public Point subtract(double x, double y, double z) { 67 | return add(-x, -y, -z); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /redemptive-core/src/main/java/tech/rayline/core/util/RunnableShorthand.java: -------------------------------------------------------------------------------- 1 | package tech.rayline.core.util; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import org.bukkit.plugin.java.JavaPlugin; 5 | import org.bukkit.scheduler.BukkitRunnable; 6 | import org.bukkit.scheduler.BukkitTask; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | import java.util.function.Consumer; 11 | 12 | @RequiredArgsConstructor(staticName = "forPlugin") 13 | public final class RunnableShorthand { 14 | private final JavaPlugin plugin; 15 | private final List runnables = new ArrayList<>(); 16 | private boolean async; 17 | private long delay; 18 | 19 | public BukkitTask repeat(long ticks) { 20 | BukkitRunnable bukkitRunnable = toRunnable(); 21 | if (async) 22 | return bukkitRunnable.runTaskTimerAsynchronously(plugin, delay, ticks); 23 | else 24 | return bukkitRunnable.runTaskTimer(plugin, delay, ticks); 25 | } 26 | 27 | public BukkitTask later(long ticks) { 28 | BukkitRunnable bukkitRunnable = toRunnable(); 29 | if (async) 30 | return bukkitRunnable.runTaskLaterAsynchronously(plugin, ticks + delay); 31 | else 32 | return bukkitRunnable.runTaskLater(plugin, ticks + delay); 33 | } 34 | 35 | public BukkitTask go() { 36 | BukkitRunnable bukkitRunnable = toRunnable(); 37 | if (async) 38 | return bukkitRunnable.runTaskAsynchronously(plugin); 39 | else 40 | return bukkitRunnable.runTask(plugin); 41 | } 42 | 43 | private BukkitRunnable toRunnable() { 44 | return new BukkitRunnable() { 45 | @Override 46 | public void run() { 47 | for (Runnable runnable : runnables) 48 | runnable.run(); 49 | } 50 | }; 51 | } 52 | 53 | public RunnableShorthand async() { 54 | async = !async; 55 | return this; 56 | } 57 | 58 | public RunnableShorthand delay(long ticks) { 59 | delay += ticks; 60 | return this; 61 | } 62 | 63 | public RunnableShorthand resetDelay() { 64 | delay = 0; 65 | return this; 66 | } 67 | 68 | public RunnableShorthand with(Runnable runnable) { 69 | runnables.add(runnable); 70 | return this; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /redemptive-core/src/main/java/tech/rayline/core/plugin/YAMLConfigurationFile.java: -------------------------------------------------------------------------------- 1 | package tech.rayline.core.plugin; 2 | 3 | import lombok.Data; 4 | import lombok.NonNull; 5 | import org.bukkit.configuration.file.FileConfiguration; 6 | import org.bukkit.configuration.file.YamlConfiguration; 7 | import org.bukkit.plugin.java.JavaPlugin; 8 | 9 | import java.io.*; 10 | import java.util.logging.Level; 11 | 12 | @Data 13 | public final class YAMLConfigurationFile { 14 | private final String fileName; 15 | private final JavaPlugin plugin; 16 | 17 | private File configFile; 18 | private FileConfiguration fileConfiguration; 19 | 20 | public YAMLConfigurationFile(@NonNull JavaPlugin plugin, @NonNull String fileName) { 21 | this(plugin, new File(plugin.getDataFolder(), fileName)); 22 | } 23 | 24 | public YAMLConfigurationFile(@NonNull JavaPlugin plugin, File file) { 25 | if (!plugin.isEnabled()) 26 | throw new IllegalArgumentException("plugin must be enabled"); 27 | this.plugin = plugin; 28 | this.fileName = file.getName(); 29 | File dataFolder = plugin.getDataFolder(); 30 | if (dataFolder == null) 31 | throw new IllegalStateException(); 32 | this.configFile = file; 33 | } 34 | 35 | public void reloadConfig() { 36 | fileConfiguration = YamlConfiguration.loadConfiguration(configFile); 37 | 38 | // Look for defaults in the jar 39 | InputStream inputStream = plugin.getResource(fileName); 40 | if (inputStream != null) { 41 | YamlConfiguration defConfig = YamlConfiguration.loadConfiguration(new InputStreamReader(inputStream)); 42 | fileConfiguration.setDefaults(defConfig); 43 | } 44 | } 45 | 46 | public FileConfiguration getConfig() { 47 | if (fileConfiguration == null) { 48 | this.reloadConfig(); 49 | } 50 | return fileConfiguration; 51 | } 52 | 53 | public void saveConfig() { 54 | if (fileConfiguration == null || configFile == null) { 55 | return; 56 | } 57 | try { 58 | getConfig().save(configFile); 59 | } catch (IOException ex) { 60 | plugin.getLogger().log(Level.SEVERE, "Could not save config to " + configFile, ex); 61 | } 62 | } 63 | 64 | public void saveDefaultConfig() { 65 | if (!configFile.exists()) { 66 | this.plugin.saveResource(fileName, false); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /redemptive-core/src/main/java/tech/rayline/core/rx/RxBukkitScheduler.java: -------------------------------------------------------------------------------- 1 | package tech.rayline.core.rx; 2 | 3 | import lombok.Data; 4 | import lombok.Delegate; 5 | import lombok.EqualsAndHashCode; 6 | import org.bukkit.Bukkit; 7 | import org.bukkit.plugin.java.JavaPlugin; 8 | import org.bukkit.scheduler.BukkitTask; 9 | import rx.Scheduler; 10 | import rx.Subscription; 11 | import rx.functions.Action0; 12 | import rx.internal.schedulers.ScheduledAction; 13 | import rx.subscriptions.CompositeSubscription; 14 | import rx.subscriptions.Subscriptions; 15 | import tech.rayline.core.util.RunnableShorthand; 16 | 17 | import java.util.concurrent.TimeUnit; 18 | 19 | @EqualsAndHashCode(callSuper = false) 20 | @Data 21 | public final class RxBukkitScheduler extends Scheduler { 22 | private final JavaPlugin plugin; 23 | private final ConcurrencyMode concurrencyMode; 24 | 25 | private BukkitTask actualSchedule(final Action0 action, int ticksDelay) { 26 | RunnableShorthand with = RunnableShorthand.forPlugin(plugin).with(new Runnable() { 27 | @Override 28 | public void run() { 29 | action.call(); 30 | } 31 | }); 32 | if (concurrencyMode == ConcurrencyMode.ASYNC) 33 | with.async(); 34 | return with.later(ticksDelay); 35 | } 36 | 37 | @Override 38 | public Worker createWorker() { 39 | return new BukkitWorker(); 40 | } 41 | 42 | private final class BukkitWorker extends Worker { 43 | @Delegate(types = Subscription.class) 44 | private final CompositeSubscription allSubscriptions = new CompositeSubscription(); 45 | 46 | 47 | @Override 48 | public Subscription schedule(Action0 action) { 49 | return schedule(action, 0, TimeUnit.MILLISECONDS); 50 | } 51 | 52 | @Override 53 | public Subscription schedule(Action0 action, long delayTime, TimeUnit unit) { 54 | if (unit.toMillis(delayTime) == 0 && concurrencyMode == ConcurrencyMode.SYNC && Bukkit.getServer().isPrimaryThread()) { 55 | action.call(); 56 | return Subscriptions.unsubscribed(); 57 | } 58 | 59 | final BukkitTask bukkitTask = actualSchedule(action, (int) Math.round((double) unit.toMillis(delayTime) / 50D)); 60 | ScheduledAction scheduledAction = new ScheduledAction(action, allSubscriptions); 61 | scheduledAction.add(Subscriptions.create(new Action0() { 62 | @Override 63 | public void call() { 64 | bukkitTask.cancel(); 65 | } 66 | })); 67 | return scheduledAction; 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /redemptive-serialize/src/main/java/tech/rayline/core/LocationTypeAdapter.java: -------------------------------------------------------------------------------- 1 | package tech.rayline.core; 2 | 3 | import com.google.gson.JsonParseException; 4 | import com.google.gson.TypeAdapter; 5 | import com.google.gson.stream.JsonReader; 6 | import com.google.gson.stream.JsonWriter; 7 | import org.bukkit.Bukkit; 8 | import org.bukkit.Location; 9 | import org.bukkit.World; 10 | 11 | import java.io.IOException; 12 | import java.util.UUID; 13 | 14 | public final class LocationTypeAdapter extends TypeAdapter { 15 | private final static String WORLD_KEY = "w", X_KEY = "x", Y_KEY = "y", Z_KEY = "z", PITCH = "p", YAW = "ya"; 16 | 17 | @Override 18 | public void write(JsonWriter out, Location value) throws IOException { 19 | out.beginObject(); 20 | out.name(WORLD_KEY); 21 | out.value(value.getWorld().getUID().toString()); 22 | out.name(X_KEY); 23 | out.value(value.getX()); 24 | out.name(Y_KEY); 25 | out.value(value.getY()); 26 | out.name(Z_KEY); 27 | out.value(value.getZ()); 28 | out.name(PITCH); 29 | out.value(value.getPitch()); 30 | out.name(YAW); 31 | out.value(value.getYaw()); 32 | out.endObject(); 33 | } 34 | 35 | @Override 36 | public Location read(JsonReader in) throws IOException { 37 | in.beginObject(); 38 | Double x = null, y = null, z = null, pitch = null, yaw = null; 39 | World world = null; 40 | while (in.hasNext()) { 41 | String s = in.nextName(); 42 | if (s.equals(WORLD_KEY)) { 43 | UUID uuid = UUID.fromString(in.nextString()); 44 | world = Bukkit.getWorld(uuid); 45 | if (world == null) 46 | throw new JsonParseException("Could not find the world by the UUID: " + uuid.toString()); 47 | continue; 48 | } 49 | double v = in.nextDouble(); 50 | switch (s) { 51 | case X_KEY: 52 | x = v; 53 | break; 54 | case Y_KEY: 55 | y = v; 56 | break; 57 | case Z_KEY: 58 | z = v; 59 | break; 60 | case PITCH: 61 | pitch = v; 62 | break; 63 | case YAW: 64 | yaw = v; 65 | break; 66 | } 67 | } 68 | in.endObject(); 69 | 70 | if (world == null || x == null || y == null || z == null || pitch == null || yaw == null) 71 | throw new JsonParseException("Could not read Location object, missing a critical value (expecting world, x, y, z, p, ya)"); 72 | 73 | return new Location(world, x, y, z, yaw.floatValue(), pitch.floatValue()); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /redemptive-core/src/main/java/tech/rayline/core/rx/PeriodicPlayerStreamer.java: -------------------------------------------------------------------------------- 1 | package tech.rayline.core.rx; 2 | 3 | import org.bukkit.entity.Player; 4 | import org.bukkit.plugin.Plugin; 5 | import rx.Observable; 6 | import rx.Scheduler; 7 | import rx.Subscriber; 8 | import rx.exceptions.Exceptions; 9 | import rx.functions.Action0; 10 | import rx.functions.Func1; 11 | import rx.subscriptions.Subscriptions; 12 | 13 | import java.util.concurrent.TimeUnit; 14 | 15 | public final class PeriodicPlayerStreamer extends BaseStreamer { 16 | public PeriodicPlayerStreamer(Plugin plugin, RxBukkitScheduler syncScheduler, RxBukkitScheduler asyncScheduler) { 17 | super(plugin, syncScheduler, asyncScheduler); 18 | } 19 | 20 | public final Observable observePlayerEvery(final Player player, final long time, final TimeUnit unit) { 21 | //create an observer which will... 22 | return Observable 23 | .create(new Observable.OnSubscribe() { 24 | @Override 25 | public void call(final Subscriber subscriber) { 26 | //start immediately 27 | subscriber.onStart(); 28 | 29 | //create a worker which can run our stuff: 30 | final Scheduler.Worker worker = syncScheduler.createWorker(); 31 | //scheduling a task which... 32 | worker.schedulePeriodically(new Action0() { 33 | @Override 34 | public void call() { 35 | //attempts to emit a player, catch and reporting ALL exceptions 36 | try { 37 | subscriber.onNext(player); 38 | } catch (Throwable e) { 39 | try { 40 | worker.unsubscribe(); 41 | } finally { 42 | Exceptions.throwOrReport(e, subscriber); 43 | } 44 | } 45 | } 46 | }, 0, time, unit); 47 | 48 | //and unsubscribing the worker with the subscription 49 | subscriber.add(Subscriptions.create(new Action0() { 50 | @Override 51 | public void call() { 52 | worker.unsubscribe(); 53 | } 54 | })); 55 | } 56 | }) 57 | //keep this up while the player is online 58 | .takeWhile(new Func1() { 59 | @Override 60 | public Boolean call(Player player1) { 61 | return player1.isOnline(); 62 | } 63 | }) 64 | //toss in some of the transformer sauce 65 | .compose(this.getSyncTransformer()); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /redemptive-core/src/main/java/tech/rayline/core/plugin/Formatter.java: -------------------------------------------------------------------------------- 1 | package tech.rayline.core.plugin; 2 | 3 | import org.bukkit.ChatColor; 4 | import org.bukkit.configuration.file.FileConfiguration; 5 | 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | 9 | public final class Formatter { 10 | private final YAMLConfigurationFile formatsFile; 11 | private String loadedPrefix; 12 | 13 | public Formatter(YAMLConfigurationFile formatsFile) { 14 | this.formatsFile = formatsFile; 15 | loadedPrefix = formatsFile.getConfig().contains("prefix") ? ChatColor.translateAlternateColorCodes('&', formatsFile.getConfig().getString("prefix")) : null; 16 | } 17 | 18 | public FormatBuilder begin(String path) { 19 | // Throw a proper error 20 | FileConfiguration config = formatsFile.getConfig(); 21 | if (config.contains(path)) { 22 | return new FormatBuilder(config.getString(path)); 23 | } else { 24 | // TODO probably a better exception for this 25 | throw new RuntimeException("No such string at path: " + path + " in formats.yml"); 26 | } 27 | } 28 | 29 | public boolean has(String path) { 30 | return formatsFile.getConfig().contains(path); 31 | } 32 | 33 | public FormatBuilder withValue(String value) { 34 | return new FormatBuilder(value); 35 | } 36 | 37 | public final class FormatBuilder { 38 | private final String formatString; 39 | private final Map modifiers = new HashMap(); 40 | private boolean prefix = true, coloredInputs = true; 41 | 42 | private FormatBuilder(String formatString) { 43 | this.formatString = formatString; 44 | } 45 | 46 | public FormatBuilder withModifier(String key, Object value) { 47 | modifiers.put(key, value.toString()); 48 | return this; 49 | } 50 | 51 | public FormatBuilder with(String key, Object value) { 52 | modifiers.put(key, value.toString()); 53 | return this; 54 | } 55 | 56 | public FormatBuilder withPrefix(boolean p) { 57 | prefix = p; 58 | return this; 59 | } 60 | 61 | public FormatBuilder withColoredInputs(boolean c) { 62 | coloredInputs = c; 63 | return this; 64 | } 65 | 66 | public String get() { 67 | if (formatString == null) return "Issue finding format from the formats.yml file!"; 68 | String s = ChatColor.translateAlternateColorCodes('&', formatString); 69 | for (Map.Entry stringStringEntry : modifiers.entrySet()) { 70 | String value = stringStringEntry.getValue(); 71 | if (coloredInputs) value = ChatColor.translateAlternateColorCodes('&', value); 72 | s = s.replaceAll(String.format("\\{\\{%s\\}\\}", stringStringEntry.getKey()), value); 73 | } 74 | if (prefix && loadedPrefix != null) return loadedPrefix + s; 75 | return s; 76 | } 77 | 78 | @Override 79 | public String toString() { 80 | return get(); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /redemptive-core/src/main/java/tech/rayline/core/util/ItemShorthand.java: -------------------------------------------------------------------------------- 1 | package tech.rayline.core.util; 2 | 3 | import lombok.Getter; 4 | import org.bukkit.ChatColor; 5 | import org.bukkit.Material; 6 | import org.bukkit.enchantments.Enchantment; 7 | import org.bukkit.inventory.ItemStack; 8 | import org.bukkit.inventory.meta.ItemMeta; 9 | 10 | import java.util.ArrayList; 11 | import java.util.HashMap; 12 | import java.util.List; 13 | import java.util.Map; 14 | 15 | @Getter public final class ItemShorthand { 16 | private final Material material; 17 | private String name; 18 | private List lore; 19 | private Map enchantments; 20 | private short dataValue; 21 | private int quantity; 22 | 23 | public ItemShorthand(Material m) { 24 | this.material = m; 25 | } 26 | 27 | public static ItemShorthand setMaterial(Material material) { 28 | return new ItemShorthand(material); 29 | } 30 | 31 | public static ItemShorthand withMaterial(Material material) { 32 | return setMaterial(material); 33 | } 34 | 35 | public ItemShorthand setQuantity(int quantity) { 36 | this.quantity = quantity; 37 | return this; 38 | } 39 | 40 | public ItemShorthand withQuantity(int quantity) { 41 | return setQuantity(quantity); 42 | } 43 | 44 | public ItemShorthand setName(String name) { 45 | this.name = name; 46 | return this; 47 | } 48 | 49 | public ItemShorthand withName(String name) { 50 | return setName(name); 51 | } 52 | 53 | public ItemShorthand setLore(String l) { 54 | checkLore(); 55 | lore.add(ChatColor.translateAlternateColorCodes('&', l)); 56 | return this; 57 | } 58 | 59 | public ItemShorthand withLore(String lore) { 60 | return setLore(lore); 61 | } 62 | 63 | public ItemShorthand setDataValue(short dataValue) { 64 | this.dataValue = dataValue; 65 | return this; 66 | } 67 | 68 | public ItemShorthand withDataValue(short dataValue) { 69 | return setDataValue(dataValue); 70 | } 71 | 72 | public ItemShorthand setEnchantment(Enchantment enchantment, int level) { 73 | checkEnchantments(); 74 | enchantments.put(enchantment, level); 75 | return this; 76 | } 77 | 78 | public ItemShorthand withEnchantment(Enchantment enchantment, int level) { 79 | return setEnchantment(enchantment, level); 80 | } 81 | 82 | public ItemShorthand withLore(List lore) { 83 | checkLore(); 84 | this.lore.clear(); 85 | this.lore.addAll(lore); 86 | return this; 87 | } 88 | 89 | private void checkLore() { 90 | if (lore == null) lore = new ArrayList<>(); 91 | } 92 | 93 | private void checkEnchantments() { 94 | if (enchantments == null) enchantments = new HashMap<>(); 95 | } 96 | 97 | public ItemStack get() { 98 | ItemStack item = new ItemStack(material); 99 | ItemMeta meta = item.getItemMeta(); 100 | if (name != null) meta.setDisplayName(ChatColor.translateAlternateColorCodes('&', name)); 101 | if (lore != null) meta.setLore(lore); 102 | item.setItemMeta(meta); 103 | if (quantity > 1) item.setAmount(quantity); 104 | if (dataValue > 0) item.setDurability(dataValue); 105 | if (enchantments != null) item.addUnsafeEnchantments(enchantments); 106 | return item; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /redemptive-core/src/main/java/tech/rayline/core/util/Timeline.java: -------------------------------------------------------------------------------- 1 | package tech.rayline.core.util; 2 | 3 | import lombok.Data; 4 | import rx.Scheduler; 5 | import rx.functions.Action0; 6 | 7 | import java.util.ArrayList; 8 | import java.util.Collections; 9 | import java.util.Comparator; 10 | import java.util.List; 11 | import java.util.concurrent.TimeUnit; 12 | 13 | public final class Timeline { 14 | private final List nodes = new ArrayList<>(); 15 | private Scheduler.Worker worker; 16 | 17 | public static TimelineBuilder create() { 18 | return new TimelineBuilder(); 19 | } 20 | 21 | public void addAt(Runnable runnable, long time, TimeUnit unit) { 22 | ensureNotPlaying(); 23 | nodes.add(new TimelineNode(runnable, unit.toMillis(time))); 24 | } 25 | 26 | public void playLoop(final Scheduler scheduler) { 27 | play(scheduler, new Action0() { 28 | @Override 29 | public void call() { 30 | clearWorker(); 31 | playLoop(scheduler); 32 | } 33 | }); 34 | } 35 | 36 | public void playOnce(Scheduler scheduler) { 37 | play(scheduler, new Action0() { 38 | @Override 39 | public void call() { 40 | clearWorker(); 41 | } 42 | }); 43 | } 44 | 45 | public void play(Scheduler scheduler, Action0 endHandler) { 46 | ensureNotPlaying(); 47 | worker = scheduler.createWorker(); 48 | Collections.sort(nodes, new Comparator() { 49 | @Override 50 | public int compare(TimelineNode o1, TimelineNode o2) { 51 | return (int) (o2.msAfter - o1.msAfter); 52 | } 53 | }); 54 | for (TimelineNode node : nodes) 55 | worker.schedule(node, node.msAfter, TimeUnit.MILLISECONDS); 56 | long endTime = nodes.get(nodes.size() - 1).msAfter; 57 | worker.schedule(endHandler, endTime, TimeUnit.MILLISECONDS); 58 | } 59 | 60 | public boolean isPlaying() { 61 | return worker != null; 62 | } 63 | 64 | public void stop() { 65 | if (!isPlaying()) 66 | throw new IllegalStateException("We're not currently playing this timeline!"); 67 | 68 | worker.unsubscribe(); 69 | clearWorker(); 70 | } 71 | 72 | private void clearWorker() { 73 | worker = null; 74 | } 75 | 76 | private void ensureNotPlaying() { 77 | if (isPlaying()) 78 | throw new IllegalStateException("We're currently playing the timeline!"); 79 | } 80 | 81 | @Data public static final class TimelineNode implements Action0 { 82 | private final Runnable runnable; 83 | private final long msAfter; 84 | 85 | @Override 86 | public void call() { 87 | runnable.run(); 88 | } 89 | } 90 | 91 | public static final class TimelineBuilder { 92 | private final Timeline building = new Timeline(); 93 | private long msIn = 0; 94 | 95 | public TimelineBuilder then(Runnable runnable, long time, TimeUnit later) { 96 | return at(runnable, (msIn += later.toMillis(time)), TimeUnit.MILLISECONDS); 97 | } 98 | 99 | public TimelineBuilder at(Runnable runnable, long absoluteTime, TimeUnit later) { 100 | building.addAt(runnable, absoluteTime, later); 101 | return this; 102 | } 103 | 104 | public Timeline get() { 105 | return building; 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /redemptive-core/src/main/java/tech/rayline/core/util/PlayerState.java: -------------------------------------------------------------------------------- 1 | package tech.rayline.core.util; 2 | 3 | import lombok.EqualsAndHashCode; 4 | import lombok.Getter; 5 | import lombok.ToString; 6 | import org.bukkit.Bukkit; 7 | import org.bukkit.GameMode; 8 | import org.bukkit.Location; 9 | import org.bukkit.OfflinePlayer; 10 | import org.bukkit.entity.Player; 11 | import org.bukkit.inventory.ItemStack; 12 | import org.bukkit.inventory.PlayerInventory; 13 | import org.bukkit.potion.PotionEffect; 14 | 15 | import java.util.Arrays; 16 | import java.util.Collection; 17 | import java.util.UUID; 18 | 19 | @Getter 20 | @ToString(of = {"from", "origin", "restored"}) 21 | @EqualsAndHashCode(of = {"origin", "from"}) 22 | public final class PlayerState { 23 | private final UUID origin; 24 | private final ItemStack[] inventory, armour; 25 | private final GameMode mode; 26 | private final PotionEffect[] effects; 27 | private final double health, food, walkSpeed, flySpeed; 28 | private final boolean allowFlight; 29 | private final Location from; 30 | 31 | private transient boolean restored; 32 | 33 | public PlayerState(Player player) { 34 | origin = player.getUniqueId(); 35 | player.closeInventory(); 36 | PlayerInventory inventory = player.getInventory(); 37 | ItemStack[] contents = inventory.getContents(), armourContents = inventory.getArmorContents(); 38 | this.inventory = Arrays.copyOf(contents, contents.length); 39 | armour = Arrays.copyOf(armourContents, armourContents.length); 40 | mode = player.getGameMode(); 41 | Collection activePotionEffects = player.getActivePotionEffects(); 42 | effects = activePotionEffects.toArray(new PotionEffect[activePotionEffects.size()]); 43 | health = player.getHealth(); 44 | food = player.getFoodLevel(); 45 | walkSpeed = player.getWalkSpeed(); 46 | flySpeed = player.getFlySpeed(); 47 | allowFlight = player.getAllowFlight(); 48 | from = player.getLocation(); 49 | 50 | //clear 51 | reset(true); 52 | } 53 | 54 | public void reset(boolean flying) { 55 | OfflinePlayer offlinePlayer = getPlayer(); 56 | if (!offlinePlayer.isOnline()) 57 | throw new IllegalStateException("The player is not currently online!"); 58 | Player player = offlinePlayer.getPlayer(); 59 | 60 | PlayerInventory inventory = player.getInventory(); 61 | 62 | inventory.setArmorContents(new ItemStack[4]); 63 | inventory.clear(); 64 | for (PotionEffect potionEffect : player.getActivePotionEffects()) 65 | player.removePotionEffect(potionEffect.getType()); 66 | player.setHealth(20); 67 | player.setFoodLevel(20); 68 | player.setWalkSpeed(1f); 69 | player.setFlySpeed(1f); 70 | player.setAllowFlight(flying); 71 | if (flying) 72 | player.setFlying(true); 73 | player.setFallDistance(0f); 74 | player.setGameMode(GameMode.SPECTATOR); 75 | restored = false; 76 | } 77 | 78 | public OfflinePlayer getPlayer() { 79 | return Bukkit.getOfflinePlayer(origin); 80 | } 81 | 82 | public void restore() { 83 | restore(false); 84 | } 85 | 86 | public void restore(boolean force) { 87 | if (restored && !force) 88 | throw new IllegalStateException("This player state has already been restored!"); 89 | 90 | reset(false); 91 | 92 | OfflinePlayer offlinePlayer = getPlayer(); 93 | if (!offlinePlayer.isOnline()) 94 | throw new IllegalStateException("The player is not currently online!"); 95 | Player player = offlinePlayer.getPlayer(); 96 | 97 | PlayerInventory inventory = player.getInventory(); 98 | inventory.setArmorContents(armour); 99 | inventory.setContents(this.inventory); 100 | player.updateInventory(); 101 | player.setGameMode(mode); 102 | player.setFlySpeed((float) flySpeed); 103 | player.setWalkSpeed((float) walkSpeed); 104 | player.setAllowFlight(allowFlight); 105 | if (allowFlight) player.setFlying(true); 106 | player.teleport(from); 107 | for (PotionEffect effect : effects) player.addPotionEffect(effect); 108 | player.setHealth(health); 109 | player.setFoodLevel((int) food); 110 | 111 | restored = true; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /redemptive-core/src/main/java/tech/rayline/core/inject/Injector.java: -------------------------------------------------------------------------------- 1 | package tech.rayline.core.inject; 2 | 3 | import org.bukkit.plugin.java.JavaPlugin; 4 | import tech.rayline.core.plugin.RedemptivePlugin; 5 | 6 | import java.lang.reflect.Constructor; 7 | import java.lang.reflect.Field; 8 | import java.lang.reflect.Method; 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | public final class Injector { 13 | public static Object[] injectTo(RedemptivePlugin plugin) throws Exception { 14 | return injectTo(plugin, plugin.getClass()); 15 | } 16 | 17 | public static Object[] injectTo(RedemptivePlugin plugin, Class clazz) throws Exception { 18 | List itemsCreated = new ArrayList<>(); 19 | 20 | Field[] declaredFields = clazz.getDeclaredFields(); 21 | //iterates through all the fields that we might want to inject a dependency into 22 | FIELD_LOOP: for (Field declaredField : declaredFields) { 23 | //if we don't have the annotation, we don't care 24 | if (!declaredField.isAnnotationPresent(Inject.class)) 25 | continue; 26 | //set it accessible 27 | declaredField.setAccessible(true); 28 | //get the type so we can check that it can be injected by... 29 | Class type = declaredField.getType(); 30 | //now we should try-catch on this field so that we can inject other fields if this fails 31 | try { 32 | //checking the annotation and... 33 | if (!type.isAnnotationPresent(Injectable.class)) 34 | throw new IllegalStateException("The class you are attempting to inject is not injectable"); 35 | //searching for the constructor 36 | for (Constructor constructor : type.getDeclaredConstructors()) { 37 | //which has the InjectionProvider annotation 38 | if (!constructor.isAnnotationPresent(InjectionProvider.class)) 39 | continue; 40 | //and once we find it, we set it accessible and create a new instance 41 | constructor.setAccessible(true); 42 | Object o = constructor.newInstance(plugin); 43 | //if successful (tons of exceptions being thrown up the stack above), we want to keep track of instance we just created 44 | itemsCreated.add(o); 45 | //and also set the field 46 | declaredField.set(plugin, o); 47 | continue FIELD_LOOP; //continue the outer loop to avoid the exception trap below 48 | } 49 | //if we never continued, we'll find ourselves here, meaning that we never found the constructor for this field's dependency 50 | //throw an exception- we've failed 51 | throw new IllegalStateException("Could not find a valid constructor in the class you're attempting to inject!"); 52 | } catch (Exception e) { 53 | plugin.getLogger().severe("Could not inject " + type.getSimpleName() + " - " + declaredField.getName() + "!"); 54 | e.printStackTrace(); 55 | throw new RuntimeException("Injection failed!"); 56 | } 57 | } 58 | 59 | //grab the objects that we've gotten this time around 60 | Object[] objects = itemsCreated.toArray(); 61 | 62 | //do some recursion so we get all private fields 63 | Class superclass = clazz.getSuperclass(); 64 | if (shouldRecurse(superclass)) { //stop at JavaPlugin (or Object if we overshoot... somehow) 65 | Object[] recursiveObjects = injectTo(plugin, superclass); 66 | Object[] finalObjects = new Object[objects.length + recursiveObjects.length]; 67 | //combine the arrays quickly 68 | System.arraycopy(objects, 0, finalObjects, 0, objects.length); 69 | System.arraycopy(recursiveObjects, 0, finalObjects, objects.length, recursiveObjects.length); 70 | return finalObjects; //return out the result 71 | } 72 | 73 | //if we didn't need to recurse we can just return stuff 74 | return objects; 75 | } 76 | 77 | public static boolean shouldRecurse(Class superclass) { 78 | return superclass != JavaPlugin.class && superclass != Object.class; 79 | } 80 | 81 | public static void handleDisable(Object[] injected) throws Exception { 82 | for (Object o : injected) 83 | handleDisable(o, o.getClass()); 84 | } 85 | 86 | public static void handleDisable(Object object, Class clazz) throws Exception { 87 | for (Method method : clazz.getDeclaredMethods()) { 88 | if (!method.isAnnotationPresent(DisableHandler.class)) 89 | continue; 90 | method.setAccessible(true); 91 | method.invoke(object); 92 | } 93 | 94 | Class superclass = clazz.getSuperclass(); 95 | if (shouldRecurse(superclass)) 96 | handleDisable(object, superclass); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /redemptive-core/src/main/java/tech/rayline/core/util/GeneralUtils.java: -------------------------------------------------------------------------------- 1 | package tech.rayline.core.util; 2 | 3 | import org.bukkit.*; 4 | import org.bukkit.entity.EntityType; 5 | import org.bukkit.entity.Firework; 6 | import org.bukkit.inventory.meta.FireworkMeta; 7 | import tech.rayline.core.plugin.RedemptivePlugin; 8 | 9 | import java.io.File; 10 | import java.io.IOException; 11 | import java.lang.reflect.Array; 12 | import java.nio.file.Files; 13 | import java.util.Random; 14 | 15 | public final class GeneralUtils { 16 | public static boolean delete(File file) { 17 | if (file.isDirectory()) { 18 | File[] files = file.listFiles(); 19 | if (files == null) return false; 20 | for (File file1 : files) { 21 | if (!delete(file1)) return false; 22 | } 23 | } 24 | return file.delete(); 25 | } 26 | 27 | public static void copy(File source, File dest) throws IOException { 28 | Files.copy(source.toPath(), dest.toPath()); 29 | if (source.isDirectory()) { 30 | for (String s : source.list()) { 31 | copy(new File(source, s), new File(dest, s)); 32 | } 33 | } 34 | } 35 | 36 | public static boolean arrayContains(T[] ts, T t) { 37 | for (T t1 : ts) { 38 | if ((t1 == null || t == null) && t != t1) continue; 39 | if (t1 == t || t1.equals(t)) return true; 40 | } 41 | return false; 42 | } 43 | 44 | public static T[] grow(Class type, T[] array) { 45 | T[] ts = (T[]) Array.newInstance(type, array.length << 1); 46 | System.arraycopy(array, 0, ts, 0, array.length); 47 | return ts; 48 | } 49 | 50 | public static T[] trim(Class type, T[] array) { 51 | int len = 0; 52 | while (array[len] != null && len < array.length) len++; 53 | T[] ts = (T[]) Array.newInstance(type, len); 54 | System.arraycopy(array, 0, ts, 0, len); 55 | return ts; 56 | } 57 | 58 | public static void randomlySpawnFireworks(RedemptivePlugin plugin, final World world, final Point center, final Point offset, int magnitude) { 59 | final Random random = new Random(); 60 | for (int i = 0; i < random.nextInt(magnitude) + 2; i++) 61 | Bukkit.getScheduler().runTaskLater(plugin, new Runnable() { 62 | @Override 63 | public void run() { 64 | Location fireworkOrig = new Point( 65 | center.getX() + (random.nextGaussian() * offset.getX()), 66 | center.getY() + (random.nextGaussian() * offset.getY()), 67 | center.getZ() + (random.nextGaussian() * offset.getZ()), 68 | 0F, 0F).in(world); 69 | Firework firework = (Firework) world.spawnEntity(fireworkOrig, EntityType.FIREWORK); 70 | FireworkMeta fireworkMeta = firework.getFireworkMeta(); 71 | fireworkMeta.setPower(1); 72 | FireworkEffect.Builder builder = FireworkEffect.builder(); 73 | builder.with(getRandom(FireworkEffect.Type.values())); 74 | for (int m = 0; m < random.nextInt(3) + 2; m++) 75 | builder.withColor(getRandom(DyeColor.values()).getColor()); 76 | if (Math.random() < 0.5) builder.withFlicker(); 77 | if (Math.random() < 0.5) builder.withTrail(); 78 | fireworkMeta.addEffect(builder.build()); 79 | firework.setFireworkMeta(fireworkMeta); 80 | } 81 | }, i * 3); 82 | } 83 | 84 | private static T getRandom(T[] ts) { 85 | return ts[((int) (Math.random() * ts.length))]; 86 | } 87 | 88 | public static String formatSeconds(Integer seconds) { 89 | StringBuilder builder = new StringBuilder(); 90 | int ofNext = seconds; 91 | for (TimeUnit unit : TimeUnit.values()) { 92 | int ofUnit; 93 | if (unit.perNext != -1) { 94 | ofUnit = ofNext % unit.perNext; 95 | ofNext = Math.floorDiv(ofNext, unit.perNext); 96 | } 97 | else { 98 | ofUnit = ofNext; 99 | ofNext = 0; 100 | } 101 | builder.insert(0, unit.shortName).insert(0, String.format("%02d", ofUnit)); 102 | if (ofNext == 0) break; 103 | } 104 | return builder.toString(); 105 | } 106 | 107 | private enum TimeUnit { 108 | SECONDS(60, 's'), 109 | MINUTES(60, 'm'), 110 | HOURS(24, 'h'), 111 | DAYS('d'); 112 | 113 | private final int perNext; 114 | private final char shortName; 115 | 116 | TimeUnit(int i, char h) { 117 | perNext = i; 118 | shortName = h; 119 | } 120 | 121 | 122 | TimeUnit(char d) { 123 | perNext = -1; 124 | shortName = d; 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /redemptive-serialize/src/main/java/tech/rayline/core/ItemStackTypeAdapter.java: -------------------------------------------------------------------------------- 1 | package tech.rayline.core; 2 | 3 | import com.google.gson.JsonNull; 4 | import com.google.gson.JsonParseException; 5 | import com.google.gson.TypeAdapter; 6 | import com.google.gson.stream.JsonReader; 7 | import com.google.gson.stream.JsonToken; 8 | import com.google.gson.stream.JsonWriter; 9 | import org.bukkit.Material; 10 | import org.bukkit.enchantments.Enchantment; 11 | import org.bukkit.inventory.ItemStack; 12 | import org.bukkit.inventory.meta.ItemMeta; 13 | 14 | import java.io.IOException; 15 | import java.util.ArrayList; 16 | import java.util.HashMap; 17 | import java.util.List; 18 | import java.util.Map; 19 | 20 | public final class ItemStackTypeAdapter extends TypeAdapter { 21 | private final static String 22 | MATERIAL = "m", 23 | DATA_VALUE = "d", 24 | AMOUNT = "a", 25 | LORE = "l", 26 | NAME = "n", 27 | ENCHANTS = "e"; 28 | 29 | @Override 30 | public void write(JsonWriter out, ItemStack value) throws IOException { 31 | if (value == null) { 32 | out.nullValue(); 33 | return; 34 | } 35 | out.beginObject(); 36 | out.name(MATERIAL); 37 | out.value(value.getType().name()); 38 | short durability = value.getDurability(); 39 | if (durability != 0) { 40 | out.name(DATA_VALUE); 41 | out.value(durability); 42 | } 43 | int amount = value.getAmount(); 44 | if (amount != 1) { 45 | out.name(AMOUNT); 46 | out.value(amount); 47 | } 48 | ItemMeta itemMeta = value.getItemMeta(); 49 | if (itemMeta != null) { 50 | List lore = itemMeta.getLore(); 51 | if (lore != null) { 52 | out.name(LORE); 53 | out.beginArray(); 54 | for (String s : lore) 55 | out.value(s); 56 | out.endArray(); 57 | } 58 | String name = itemMeta.getDisplayName(); 59 | if (name != null) { 60 | out.name(NAME); 61 | out.value(name); 62 | } 63 | } 64 | Map enchantments = value.getEnchantments(); 65 | if (enchantments != null && !enchantments.isEmpty()) { 66 | out.name(ENCHANTS); 67 | out.beginObject(); 68 | for (Map.Entry enchantmentIntegerEntry : enchantments.entrySet()) { 69 | out.name(enchantmentIntegerEntry.getKey().getName()); 70 | out.value(enchantmentIntegerEntry.getValue()); 71 | } 72 | out.endObject(); 73 | } 74 | out.endObject(); 75 | } 76 | 77 | @Override 78 | public ItemStack read(JsonReader in) throws IOException { 79 | Material material = null; 80 | Integer amount = null; 81 | Short dataValue = null; 82 | String name = null; 83 | List lore = new ArrayList<>(); 84 | Map enchantments = new HashMap<>(); 85 | 86 | if (in.peek() == JsonToken.NULL) { 87 | in.skipValue(); 88 | return null; 89 | } 90 | 91 | in.beginObject(); 92 | 93 | while (in.hasNext()) 94 | switch (in.nextName()) { 95 | case MATERIAL: 96 | material = Material.getMaterial(in.nextString()); 97 | break; 98 | case DATA_VALUE: 99 | dataValue = (short) in.nextInt(); 100 | break; 101 | case AMOUNT: 102 | amount = in.nextInt(); 103 | break; 104 | case LORE: 105 | in.beginArray(); 106 | while (in.hasNext()) { 107 | lore.add(in.nextString()); 108 | } 109 | in.endArray(); 110 | break; 111 | case NAME: 112 | name = in.nextString(); 113 | break; 114 | case ENCHANTS: 115 | in.beginObject(); 116 | while (in.hasNext()) 117 | enchantments.put(Enchantment.getByName(in.nextName()), in.nextInt()); 118 | in.endObject(); 119 | break; 120 | } 121 | 122 | if (material == null) 123 | throw new JsonParseException("An ItemStack must have a material field!"); 124 | 125 | ItemStack stack = new ItemStack(material); 126 | if (amount != null) stack.setAmount(amount); 127 | if (dataValue != null) stack.setDurability(dataValue); 128 | if (name != null || !lore.isEmpty()) { 129 | ItemMeta itemMeta = stack.getItemMeta(); 130 | if (name != null) itemMeta.setDisplayName(name); 131 | if (!lore.isEmpty()) itemMeta.setLore(lore); 132 | stack.setItemMeta(itemMeta); 133 | } 134 | if (!enchantments.isEmpty()) 135 | for (Map.Entry enchantmentIntegerEntry : enchantments.entrySet()) { 136 | stack.addUnsafeEnchantment(enchantmentIntegerEntry.getKey(), enchantmentIntegerEntry.getValue()); 137 | } 138 | in.endObject(); 139 | 140 | return stack; 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /redemptive-core/src/main/java/tech/rayline/core/rx/EventStreamer.java: -------------------------------------------------------------------------------- 1 | package tech.rayline.core.rx; 2 | 3 | import org.bukkit.Bukkit; 4 | import org.bukkit.event.*; 5 | import org.bukkit.event.server.PluginDisableEvent; 6 | import org.bukkit.plugin.EventExecutor; 7 | import org.bukkit.plugin.Plugin; 8 | import org.bukkit.plugin.PluginManager; 9 | import rx.Observable; 10 | import rx.Subscriber; 11 | import rx.exceptions.Exceptions; 12 | import rx.functions.Action0; 13 | import rx.subscriptions.Subscriptions; 14 | 15 | public final class EventStreamer extends BaseStreamer { 16 | public EventStreamer(Plugin plugin, RxBukkitScheduler syncScheduler, RxBukkitScheduler asyncScheduler) { 17 | super(plugin, syncScheduler, asyncScheduler); 18 | } 19 | 20 | @SafeVarargs 21 | public final Observable observeEvent(Class... events) { 22 | return observeEventRaw(events).compose(this.getSyncTransformer()); 23 | } 24 | 25 | @SafeVarargs 26 | public final Observable observeEvent(EventPriority priority, Class... events) { 27 | return observeEventRaw(priority, events).compose(this.getSyncTransformer()); 28 | } 29 | 30 | @SafeVarargs 31 | public final Observable observeEvent(EventPriority priority, boolean ignoreCancelled, Class... events) { 32 | return observeEventRaw(priority, ignoreCancelled, events).compose(this.getSyncTransformer()); 33 | } 34 | 35 | @SafeVarargs 36 | public final Observable observeEventRaw(Class... events) { 37 | return observeEventRaw(EventPriority.NORMAL, events); 38 | } 39 | 40 | @SafeVarargs 41 | public final Observable observeEventRaw(EventPriority priority, Class... events) { 42 | return observeEventRaw(priority, false, events); 43 | } 44 | 45 | @SafeVarargs 46 | public final Observable observeEventRaw(final EventPriority priority, final boolean ignoreCancelled, final Class... events) { 47 | //creates an observer which... 48 | return Observable.create(new Observable.OnSubscribe() { 49 | @Override 50 | public void call(final Subscriber subscriber) { 51 | //creates an empty listener 52 | final Listener listener = new Listener() { 53 | }; 54 | //creates an event executor 55 | @SuppressWarnings("unchecked") 56 | EventExecutor executor = new EventExecutor() { 57 | @Override 58 | public void execute(Listener listener1, Event event) throws EventException { 59 | //check to make sure the event bukkit sent us can apply to what the observer is expecting (type T) 60 | //strangely, they'll send things like the EntityDamageEvent when we want to cast to EntityDamageByEntity 61 | //so it's best if we manually check here to make sure it can apply to any class 62 | boolean canAssign = false; //track a "canAssign" value 63 | Class eventClass = event.getClass(); //get the class of the event 64 | for (Class aClass : events) { //go through all classes we're concerned with emitting (T is their mutual supertype) 65 | if (aClass.isAssignableFrom(eventClass)) { //if one of the classes (from the events classes arg) is assignable to the event class (is a superclass or equal to) 66 | canAssign = true; //then we're good, and can break 67 | break; 68 | } 69 | } 70 | 71 | //if we never discovered a class which is assignable, this is one of those weird and rare cases where Bukkit is stupid 72 | if (!canAssign) 73 | return; //so we return 74 | 75 | try { 76 | //noinspection unchecked 77 | subscriber.onNext((T) event); 78 | } catch (Throwable t) { 79 | Exceptions.throwOrReport(t, subscriber); 80 | } 81 | } 82 | }; 83 | 84 | //registers all the event types to that listener 85 | PluginManager pluginManager = Bukkit.getPluginManager(); 86 | for (Class event : events) 87 | pluginManager.registerEvent(event, listener, priority, executor, plugin, ignoreCancelled); 88 | 89 | //and registers a HandlerList.unregisterAll call as the unsubscribe action 90 | subscriber.add(Subscriptions.create(new Action0() { 91 | @Override 92 | public void call() { 93 | HandlerList.unregisterAll(listener); 94 | } 95 | })); 96 | 97 | //also needs to unsubscribe when the plugin disables 98 | pluginManager.registerEvent(PluginDisableEvent.class, listener, EventPriority.MONITOR, new EventExecutor() { 99 | @Override 100 | public void execute(Listener l, Event event) throws EventException { 101 | PluginDisableEvent disableEvent = (PluginDisableEvent) event; 102 | if (disableEvent.getPlugin().equals(plugin)) 103 | subscriber.onCompleted(); 104 | 105 | } 106 | }, plugin, false); 107 | } 108 | }); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /redemptive-core/src/main/java/tech/rayline/core/gui/ControlledInventory.java: -------------------------------------------------------------------------------- 1 | package tech.rayline.core.gui; 2 | 3 | import lombok.Data; 4 | import org.bukkit.GameMode; 5 | import org.bukkit.entity.Player; 6 | import org.bukkit.event.EventHandler; 7 | import org.bukkit.event.EventPriority; 8 | import org.bukkit.event.Listener; 9 | import org.bukkit.event.block.Action; 10 | import org.bukkit.event.inventory.InventoryClickEvent; 11 | import org.bukkit.event.player.PlayerDropItemEvent; 12 | import org.bukkit.event.player.PlayerInteractEvent; 13 | import org.bukkit.event.player.PlayerQuitEvent; 14 | 15 | import java.util.HashMap; 16 | import java.util.HashSet; 17 | import java.util.Map; 18 | import java.util.Set; 19 | 20 | @SuppressWarnings("deprecation") 21 | @Data 22 | public abstract class ControlledInventory implements Listener { 23 | private final Map buttons = new HashMap(); 24 | private final Set players = new HashSet(); 25 | 26 | public ControlledInventory() { 27 | this(true); 28 | } 29 | 30 | public ControlledInventory(boolean reload) { 31 | if (reload) reload(); 32 | } 33 | 34 | protected abstract ControlledInventoryButton getNewButtonAt(Integer slot); 35 | 36 | // @Override 37 | // public final void onPlayerLogin(CPlayer player, InetAddress address) throws CPlayerJoinException {} 38 | // 39 | // @Override 40 | // public final void onPlayerDisconnect(CPlayer player) { 41 | // players.remove(player); 42 | // } 43 | 44 | public final void reload() { 45 | buttons.clear(); 46 | for (int i = 0; i < 36; i++) { 47 | ControlledInventoryButton newButtonAt = getNewButtonAt(i); 48 | if (newButtonAt == null) continue; 49 | buttons.put(i, newButtonAt); 50 | } 51 | for (Player player : players) { 52 | updateForPlayer(player); 53 | } 54 | } 55 | 56 | public final void updateItems() { 57 | for (Player player : players) { 58 | updateForPlayer(player); 59 | } 60 | } 61 | 62 | public final void setActive(Player player) { 63 | players.add(player); 64 | updateForPlayer(player); 65 | } 66 | 67 | public final void remove(Player player) { 68 | if (!players.contains(player)) return; 69 | clearForPlayer(player); 70 | players.remove(player); 71 | } 72 | 73 | protected void updateForPlayer(Player player) { 74 | for (Map.Entry entry : buttons.entrySet()) { 75 | player.getInventory().setItem(entry.getKey(), entry.getValue().getStack(player)); 76 | } 77 | player.updateInventory(); 78 | } 79 | 80 | private void clearForPlayer(Player player) { 81 | for (Integer integer : buttons.keySet()) { 82 | player.getInventory().setItem(integer, null); 83 | } 84 | player.updateInventory(); 85 | } 86 | 87 | @EventHandler 88 | public final void onPlayerQuit(PlayerQuitEvent event) { 89 | players.remove(event.getPlayer()); 90 | } 91 | 92 | @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) 93 | public final void onPlayerInventoryMove(InventoryClickEvent event) { 94 | if (!(event.getWhoClicked() instanceof Player)) return; 95 | Player onlinePlayer = (Player) event.getWhoClicked(); 96 | if (!players.contains(onlinePlayer)) return; 97 | if (buttons.keySet().contains(event.getSlot()) 98 | && players.contains(onlinePlayer) 99 | && event.getClickedInventory().equals(onlinePlayer.getInventory())) { 100 | event.setCancelled(true); 101 | } 102 | } 103 | 104 | @EventHandler(priority = EventPriority.HIGH) 105 | public final void onInteract(PlayerInteractEvent event) { 106 | if (event.getPlayer().getGameMode() == GameMode.CREATIVE) return; 107 | if (event.getAction() == Action.PHYSICAL) return; 108 | ControlledInventoryButton controlledInventoryButton = buttons.get(event.getPlayer().getInventory().getHeldItemSlot()); 109 | if (controlledInventoryButton == null) return; 110 | Player onlinePlayer = event.getPlayer(); 111 | if (!players.contains(onlinePlayer)) return; 112 | // try { 113 | // onlinePlayer.getCooldownManager().testCooldown((controlledInventoryButton.hashCode() + "_inv"), 1L, TimeUnit.SECONDS); 114 | // } catch (CooldownUnexpiredException e) { 115 | // if (e.getTimeRemaining() > 800) 116 | // return; 117 | // else //TODO make a sound and send a message. 118 | // return; 119 | // } 120 | controlledInventoryButton.onUse(onlinePlayer); 121 | updateForPlayer(onlinePlayer); 122 | event.setCancelled(true); 123 | } 124 | 125 | 126 | @EventHandler(priority = EventPriority.LOW) 127 | public final void onPlayerDrop(PlayerDropItemEvent event) { 128 | Player onlinePlayer = event.getPlayer(); 129 | if (!players.contains(onlinePlayer)) return; 130 | if (buttons.get(onlinePlayer.getInventory().getHeldItemSlot()) != null) { 131 | event.setCancelled(false); 132 | event.getItemDrop().remove(); 133 | updateForPlayer(onlinePlayer); 134 | // try { 135 | // onlinePlayer.getCooldownManager().testCooldown(hashCode() + "_inv_drop", 500L, TimeUnit.MILLISECONDS, false); 136 | // } catch (CooldownUnexpiredException e) { 137 | // onlinePlayer.kickPlayer(ChatColor.RED + "Spamming inventory drops (more than 1 per second)"); 138 | // } 139 | } 140 | } 141 | 142 | 143 | protected final Set> getButtons() { 144 | return buttons.entrySet(); 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /redemptive-core/src/main/java/tech/rayline/core/library/LibraryHandler.java: -------------------------------------------------------------------------------- 1 | package tech.rayline.core.library; 2 | 3 | import lombok.Data; 4 | import org.bukkit.plugin.java.JavaPlugin; 5 | import tech.rayline.core.inject.Inject; 6 | import tech.rayline.core.inject.Injectable; 7 | import tech.rayline.core.plugin.RedemptivePlugin; 8 | 9 | import java.io.File; 10 | import java.io.FileInputStream; 11 | import java.io.IOException; 12 | import java.io.InputStream; 13 | import java.lang.reflect.Field; 14 | import java.lang.reflect.Method; 15 | import java.net.MalformedURLException; 16 | import java.net.URL; 17 | import java.net.URLClassLoader; 18 | import java.nio.file.Files; 19 | import java.nio.file.StandardCopyOption; 20 | import java.util.Collections; 21 | import java.util.HashSet; 22 | import java.util.Set; 23 | import java.util.zip.ZipInputStream; 24 | 25 | public final class LibraryHandler { 26 | public static void loadLibraries(RedemptivePlugin plugin) { 27 | Class aClass = plugin.getClass(); 28 | Set libraries = getLibraries(aClass); 29 | Set jars = new HashSet<>(); 30 | for (MavenLibrary library : libraries) { 31 | try { 32 | ParsedLibrary parsedLibrary = parseLibrary(library); 33 | File location = createAndGetWriteLocation(parsedLibrary); 34 | if (!location.exists()) { 35 | plugin.getLogger().info("Downloading " + getFileName(parsedLibrary) + " from " + library.repo().url()); 36 | try (InputStream inputStream = getUrl(library.repo(), parsedLibrary).openStream()) { 37 | Files.copy(inputStream, location.toPath(), StandardCopyOption.REPLACE_EXISTING); 38 | } 39 | } else { 40 | plugin.getLogger().info("Library " + library.value() + " is already downloaded!"); 41 | } 42 | jars.add(location); 43 | } catch (Exception e) { 44 | plugin.getLogger().warning("Could not load library " + library.value()); 45 | e.printStackTrace(); 46 | } 47 | } 48 | 49 | for (File jar : jars) { 50 | try { 51 | addFile(jar); 52 | } catch (IOException e) { 53 | plugin.getLogger().warning("Could not load jar file " + jar.getName()); 54 | continue; 55 | } 56 | 57 | plugin.getLogger().info("Loaded library " + jar.getName()); 58 | } 59 | } 60 | 61 | private static ParsedLibrary parseLibrary(MavenLibrary library) { 62 | String[] split = library.value().split(":", 3); 63 | if (split.length != 3) 64 | throw new IllegalArgumentException("The library specified " + library.value() + " is bad!"); 65 | return new ParsedLibrary(split[1], split[0], split[2]); 66 | } 67 | 68 | private static URL getUrl(MavenRepo repo, ParsedLibrary library) throws MalformedURLException { 69 | return new URL(repo.url() + "/" + getPath(library) + getFileName(library)); 70 | } 71 | 72 | private static String getPath(ParsedLibrary library) { 73 | return library.group.replaceAll("\\.", "/") + "/" + library.artifact + "/" + library.version + "/"; 74 | } 75 | 76 | private static String getFileName(ParsedLibrary library) { 77 | return library.artifact + "-" + library.version + ".jar"; 78 | } 79 | 80 | private static File createAndGetWriteLocation(ParsedLibrary library) throws IOException { 81 | File rootDir = new File(".libs"); 82 | if ((!rootDir.exists() || !rootDir.isDirectory()) && !rootDir.mkdir()) 83 | throw new IOException("Could not create root directory .libs"); 84 | 85 | File path = new File(rootDir, getPath(library)); 86 | path.mkdirs(); 87 | 88 | return new File(path, getFileName(library)); 89 | } 90 | 91 | public static Set getLibraries(Class plugin) { 92 | return getLibrariesRecurse(plugin, new HashSet()); 93 | } 94 | 95 | private static Set getLibrariesRecurse(Class targetClass, Set libraries) { 96 | if (targetClass.isAnnotationPresent(IgnoreLibraries.class)) 97 | return Collections.emptySet(); 98 | 99 | MavenLibraries annotation = targetClass.getAnnotation(MavenLibraries.class); 100 | //actual annotations 101 | if (annotation != null) 102 | Collections.addAll(libraries, annotation.value()); 103 | 104 | try { 105 | for (Field field : targetClass.getDeclaredFields()) { 106 | if (!field.isAnnotationPresent(Inject.class)) continue; 107 | Injectable injectable = field.getType().getAnnotation(Injectable.class); 108 | if (injectable == null) continue; 109 | Collections.addAll(libraries, injectable.libraries()); 110 | } 111 | } catch (Throwable t) { 112 | System.err.println("WARNING: Could not read fields for " + targetClass.getSimpleName()); 113 | } 114 | 115 | targetClass = targetClass.getSuperclass(); 116 | if (targetClass == Object.class || targetClass == JavaPlugin.class) 117 | return libraries; 118 | return getLibrariesRecurse(targetClass, libraries); 119 | } 120 | 121 | private static void addFile(File f) throws IOException { 122 | addURL(f.toURI().toURL()); 123 | } 124 | 125 | private static void addURL(URL u) throws IOException { 126 | //check if this is already loaded 127 | URLClassLoader sysloader = (URLClassLoader)ClassLoader.getSystemClassLoader(); 128 | Class sysclass = URLClassLoader.class; 129 | try { 130 | Method method = sysclass.getDeclaredMethod("addURL", URL.class); 131 | method.setAccessible(true); 132 | method.invoke(sysloader, u); 133 | } 134 | catch (Throwable t) { 135 | t.printStackTrace(); 136 | throw new IOException("Error, could not add URL to system classloader"); 137 | } 138 | } 139 | 140 | @Data private static final class ParsedLibrary { 141 | private final String artifact, group, version; 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /redemptive-core/src/main/java/tech/rayline/core/effect/Scoreboarder.java: -------------------------------------------------------------------------------- 1 | package tech.rayline.core.effect; 2 | 3 | import com.google.common.base.Optional; 4 | import com.google.common.collect.BiMap; 5 | import com.google.common.collect.HashBiMap; 6 | import lombok.AccessLevel; 7 | import lombok.Data; 8 | import lombok.EqualsAndHashCode; 9 | import lombok.Setter; 10 | import org.bukkit.Bukkit; 11 | import org.bukkit.ChatColor; 12 | import org.bukkit.entity.Player; 13 | import org.bukkit.event.player.PlayerQuitEvent; 14 | import org.bukkit.scheduler.BukkitTask; 15 | import org.bukkit.scoreboard.DisplaySlot; 16 | import org.bukkit.scoreboard.Objective; 17 | import org.bukkit.scoreboard.Scoreboard; 18 | import rx.Subscription; 19 | import rx.functions.Action1; 20 | import rx.functions.Func1; 21 | import tech.rayline.core.plugin.RedemptivePlugin; 22 | import tech.rayline.core.util.RunnableShorthand; 23 | 24 | import java.util.HashSet; 25 | import java.util.Set; 26 | import java.util.concurrent.ThreadLocalRandom; 27 | 28 | /** 29 | * This represents a "Scoreboard Type" which can be connected with a score boarding function which populates a scoreboard. 30 | * 31 | * This class is not to be extended, and simply instantiated with the function needed 32 | */ 33 | @Data 34 | public final class Scoreboarder implements Runnable { 35 | private final static String OBJECTIVE = "obj" + ThreadLocalRandom.current().nextInt(10000); 36 | private final static int MAX_STRING_LENGTH = 64, MAX_ROWS = 15; 37 | 38 | private final RedemptivePlugin plugin; 39 | private final Action1 scoreboardingFunction; 40 | private final Set scoreboardStates = new HashSet<>(); 41 | private BukkitTask task; 42 | 43 | public void disable() { 44 | task.cancel(); 45 | task = null; 46 | } 47 | 48 | public Scoreboarder enable() { 49 | if (task == null) 50 | task = RunnableShorthand.forPlugin(plugin).with(this).repeat(1); 51 | return this; 52 | } 53 | 54 | public void addPlayer(final Player player) { 55 | if (getStateFor(player).isPresent()) 56 | return; 57 | 58 | Subscription subscribe = plugin 59 | .observeEvent(PlayerQuitEvent.class) 60 | .filter(new Func1() { 61 | @Override 62 | public Boolean call(PlayerQuitEvent event) { 63 | return event.getPlayer().equals(player); 64 | } 65 | }) 66 | .take(1) 67 | .subscribe(new Action1() { 68 | @Override 69 | public void call(PlayerQuitEvent event) { 70 | Scoreboarder.this.removePlayer(player); 71 | } 72 | }); 73 | scoreboardStates.add(new PlayerScoreboardState(player, subscribe)); 74 | } 75 | 76 | public Optional getStateFor(final Player player) { 77 | for (PlayerScoreboardState scoreboardState : scoreboardStates) 78 | if (scoreboardState.getPlayer().equals(player)) 79 | return Optional.of(scoreboardState); 80 | return Optional.absent(); 81 | } 82 | 83 | public void removePlayer(Player player) { 84 | Optional stateFor = getStateFor(player); 85 | if (stateFor.isPresent()) { 86 | PlayerScoreboardState state = stateFor.get(); 87 | scoreboardStates.remove(state); 88 | state.clear(); 89 | } 90 | } 91 | 92 | @Override 93 | public void run() { 94 | for (PlayerScoreboardState scoreboardState : scoreboardStates) 95 | scoreboardState.update(); 96 | } 97 | 98 | @Data 99 | @Setter(AccessLevel.NONE) 100 | @EqualsAndHashCode(of = {"player", "scoreboard"}) 101 | public final class PlayerScoreboardState { 102 | private final Player player; 103 | private final Subscription playerQuitSubscription; 104 | private final BiMap lines = HashBiMap.create(); 105 | private final Scoreboard scoreboard; 106 | private final Objective scoreboardObjective; 107 | 108 | private String title; 109 | private int nullIndex = 0; 110 | private transient int internalCounter; 111 | 112 | public PlayerScoreboardState(Player player, Subscription playerQuitSubscription) { 113 | this.player = player; 114 | this.playerQuitSubscription = playerQuitSubscription; 115 | 116 | scoreboard = Bukkit.getScoreboardManager().getNewScoreboard(); 117 | player.setScoreboard(scoreboard); 118 | scoreboardObjective = scoreboard.registerNewObjective(OBJECTIVE, "dummy"); 119 | scoreboardObjective.setDisplaySlot(DisplaySlot.SIDEBAR); 120 | } 121 | 122 | private void update() { 123 | internalCounter = MAX_ROWS + 1; 124 | scoreboardingFunction.call(this); 125 | for (int i = --internalCounter; i >= 0; i--) 126 | removeLine(i); 127 | } 128 | 129 | public void removeLine(int x) { 130 | if (lines.containsKey(x)) 131 | scoreboard.resetScores(lines.get(x)); 132 | lines.remove(x); 133 | } 134 | 135 | public void set(int id, String text) { 136 | text = text.substring(0, Math.min(text.length(), MAX_STRING_LENGTH)); 137 | while (text.endsWith("§")) text = text.substring(0, text.length()-1); 138 | if (lines.containsKey(id)) { 139 | if (lines.get(id).equals(text) || (ChatColor.stripColor(lines.get(id)).trim().equals("") && ChatColor.stripColor(text).trim().equals(""))) return; 140 | else removeLine(id); 141 | } 142 | if (lines.containsValue(text)) lines.inverse().remove(text); 143 | lines.put(id, text); 144 | scoreboardObjective.getScore(text).setScore(id); 145 | } 146 | 147 | public PlayerScoreboardState then(String message) { 148 | set(--internalCounter, message); 149 | return this; 150 | } 151 | 152 | public PlayerScoreboardState skipLine() { 153 | set(--internalCounter, nextNull()); 154 | return this; 155 | } 156 | 157 | public PlayerScoreboardState setTitle(String title) { 158 | if (this.title != null && this.title.equals(title)) return this; 159 | this.title = title; 160 | scoreboardObjective.setDisplayName(title); 161 | player.setScoreboard(scoreboard); 162 | return this; 163 | } 164 | 165 | public String nextNull() { 166 | String s; 167 | do { 168 | nullIndex = (nullIndex + 1) % ChatColor.values().length; 169 | s = ChatColor.values()[nullIndex].toString(); 170 | } while (lines.containsValue(s)); 171 | return s; 172 | } 173 | 174 | public void clear() { 175 | player.setScoreboard(Bukkit.getScoreboardManager().getMainScoreboard()); 176 | } 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /redemptive-core/src/main/java/tech/rayline/core/parse/ResourceFileGraph.java: -------------------------------------------------------------------------------- 1 | package tech.rayline.core.parse; 2 | 3 | import org.bukkit.plugin.java.JavaPlugin; 4 | import tech.rayline.core.plugin.RedemptivePlugin; 5 | 6 | import java.io.*; 7 | import java.lang.reflect.Constructor; 8 | import java.lang.reflect.Field; 9 | import java.util.HashMap; 10 | import java.util.HashSet; 11 | import java.util.Map; 12 | import java.util.Set; 13 | 14 | public final class ResourceFileGraph { 15 | private final RedemptivePlugin plugin; 16 | private final Set registeredResources = new HashSet<>(); 17 | private final Map, ResourceFileHook> fileHooks = new HashMap<>(); 18 | 19 | public ResourceFileGraph(RedemptivePlugin plugin) { 20 | this.plugin = plugin; 21 | } 22 | 23 | public T getResourceHook(Class clazz) throws Exception { 24 | ResourceFileHook resourceFileHook = fileHooks.get(clazz); 25 | if (resourceFileHook == null || !(resourceFileHook.getClass().equals(clazz))) { 26 | resourceFileHook = clazz.newInstance(); 27 | fileHooks.put(clazz, resourceFileHook); 28 | } 29 | //noinspection unchecked 30 | return (T) resourceFileHook; 31 | } 32 | 33 | public void addObject(Object object) { 34 | hookToObject(object); 35 | writeDefaultsFor(object); 36 | loadFor(object); 37 | } 38 | 39 | public void hookToObject(Object object) { 40 | hookToObject(object, object.getClass()); 41 | } 42 | 43 | public void hookToObject(Object object, Class type) { 44 | for (Field field : type.getDeclaredFields()) { 45 | ResourceFile annotation = field.getAnnotation(ResourceFile.class); 46 | if (annotation == null) continue; 47 | try { 48 | ResourceFileHook resourceHook = getResourceHook(annotation.hook()); 49 | File file = new File(plugin.getDataFolder(), annotation.filename()); 50 | registeredResources.add(new RegisteredResourceFile(annotation, file, object, field, resourceHook)); 51 | } catch (Exception e) { 52 | e.printStackTrace(); 53 | plugin.getLogger().severe("Could not register resource from " + type.getSimpleName() + "'s " + field.getName() + " field of type " + field.getType().getName()); 54 | } 55 | } 56 | Class superclass = type.getSuperclass(); 57 | if (superclass == Object.class || superclass == JavaPlugin.class) 58 | return; 59 | 60 | hookToObject(object, superclass); 61 | } 62 | 63 | public void writeDefaults() { 64 | for (RegisteredResourceFile registeredResource : registeredResources) 65 | writeDefault(registeredResource); 66 | } 67 | 68 | private void writeDefault(RegisteredResourceFile resourceFile) { 69 | try { 70 | if (!plugin.getDataFolder().exists() && !plugin.getDataFolder().mkdirs()) throw new IOException("Could not write default resource to data directory!"); 71 | try (InputStream input = plugin.getResource(resourceFile.getAnnotation().filename())) { 72 | if (input == null) return; 73 | File file = resourceFile.getFile(); 74 | if (file.exists()) 75 | return; 76 | 77 | if (!file.createNewFile()) 78 | throw new IOException("Could not create new file!"); 79 | 80 | try (OutputStream output = new FileOutputStream(file)) { 81 | byte[] buffer = new byte[4096]; 82 | int len; 83 | while ((len = input.read(buffer)) != -1) 84 | output.write(buffer, 0, len); 85 | } 86 | } 87 | } catch (Exception e) { 88 | e.printStackTrace(); 89 | plugin.getLogger().warning("Could not write default for " + resourceFile.getAnnotation().filename()); 90 | } 91 | } 92 | 93 | public void loadAll() { 94 | for (RegisteredResourceFile registeredResource : registeredResources) 95 | load(registeredResource); 96 | } 97 | 98 | private void load(RegisteredResourceFile registeredResource) { 99 | ResourceFile annotation = registeredResource.getAnnotation(); 100 | try { 101 | Object object = registeredResource.getInstanceBound(); 102 | Field field = registeredResource.getField(); 103 | File file = registeredResource.getFile(); 104 | if (!file.exists()) 105 | file.createNewFile(); 106 | 107 | ResourceFileHook resourceHook = registeredResource.getHook(); 108 | Object read; 109 | if (annotation.raw()) { 110 | read = resourceHook.readRaw(plugin, file); 111 | } else { 112 | read = resourceHook.read(plugin, file, field.getType()); 113 | } 114 | field.setAccessible(true); 115 | if (read == null && field.get(object) != null) { 116 | try { 117 | read = field.getType().getConstructor().newInstance(); 118 | } catch (Exception e) { 119 | return; 120 | } 121 | } 122 | field.set(object, read); 123 | } catch (Exception e) { 124 | e.printStackTrace(); 125 | plugin.getLogger().severe("Could not read/load resource " + annotation.filename()); 126 | } 127 | } 128 | 129 | public void saveAll() { 130 | for (RegisteredResourceFile registeredResource : registeredResources) 131 | save(registeredResource); 132 | } 133 | 134 | public void save(Object instance, String fieldName) { 135 | for (RegisteredResourceFile registeredResource : registeredResources) 136 | if (registeredResource.getInstanceBound() == instance && registeredResource.getField().getName().equals(fieldName)) { 137 | save(registeredResource); 138 | return; 139 | } 140 | } 141 | 142 | public void saveAll(Object o) { 143 | for (RegisteredResourceFile registeredResource : registeredResources) 144 | if (registeredResource.getInstanceBound() == o) 145 | save(registeredResource); 146 | } 147 | 148 | private void save(RegisteredResourceFile registeredResource) { 149 | try { 150 | Field field = registeredResource.getField(); 151 | field.setAccessible(true); 152 | if (field.isAnnotationPresent(ReadOnlyResource.class)) 153 | return; 154 | 155 | Object value = field.get(registeredResource.getInstanceBound()); 156 | ResourceFileHook hook = registeredResource.getHook(); 157 | 158 | File file = registeredResource.getFile(); 159 | if (registeredResource.getAnnotation().raw()) 160 | hook.writeRaw(plugin, value, file); 161 | else 162 | hook.write(plugin, value, file); 163 | } 164 | catch (Exception e) { 165 | e.printStackTrace(); 166 | Field field = registeredResource.getField(); 167 | plugin.getLogger().severe("Could not write resource from " + field.getType().getSimpleName() + " field named " + field.getName()); 168 | } 169 | plugin.getLogger().info("Wrote resource " + registeredResource.getFile().getName() + " for " + registeredResource.getField().getName() +" on " + registeredResource.getInstanceBound().getClass().getSimpleName() + "!"); 170 | } 171 | 172 | public void loadFor(Object object) { 173 | for (RegisteredResourceFile registeredResource : registeredResources) 174 | if (registeredResource.getInstanceBound() == object) 175 | load(registeredResource); 176 | } 177 | 178 | public void writeDefaultsFor(Object object) { 179 | for (RegisteredResourceFile registeredResource : registeredResources) 180 | if (registeredResource.getInstanceBound() == object) 181 | writeDefault(registeredResource); 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /redemptive-core/src/main/java/tech/rayline/core/plugin/RedemptivePlugin.java: -------------------------------------------------------------------------------- 1 | package tech.rayline.core.plugin; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.Getter; 5 | import org.bukkit.Bukkit; 6 | import org.bukkit.command.CommandMap; 7 | import org.bukkit.command.PluginCommand; 8 | import org.bukkit.event.Event; 9 | import org.bukkit.event.EventPriority; 10 | import org.bukkit.event.Listener; 11 | import org.bukkit.plugin.Plugin; 12 | import org.bukkit.plugin.PluginManager; 13 | import org.bukkit.plugin.java.JavaPlugin; 14 | import rx.Observable; 15 | import tech.rayline.core.command.CommandMeta; 16 | import tech.rayline.core.command.RDCommand; 17 | import tech.rayline.core.inject.Injector; 18 | import tech.rayline.core.library.LibraryHandler; 19 | import tech.rayline.core.library.MavenLibraries; 20 | import tech.rayline.core.library.MavenLibrary; 21 | import tech.rayline.core.parse.ReadOnlyResource; 22 | import tech.rayline.core.parse.ResourceFile; 23 | import tech.rayline.core.parse.ResourceFileGraph; 24 | import tech.rayline.core.rx.ConcurrencyMode; 25 | import tech.rayline.core.rx.EventStreamer; 26 | import tech.rayline.core.rx.PeriodicPlayerStreamer; 27 | import tech.rayline.core.rx.RxBukkitScheduler; 28 | 29 | import java.lang.reflect.Constructor; 30 | import java.lang.reflect.Field; 31 | import java.util.Arrays; 32 | 33 | /** 34 | * Represents a {@link org.bukkit.plugin.Plugin} extension using all that redemptive has to offer 35 | */ 36 | @Getter 37 | @MavenLibraries({@MavenLibrary("io.reactivex:rxjava:1.0.16")}) 38 | public abstract class RedemptivePlugin extends JavaPlugin { 39 | private Formatter formatter; 40 | private RxBukkitScheduler syncScheduler, asyncScheduler; 41 | private EventStreamer eventStreamer; 42 | private PeriodicPlayerStreamer playerStreamer; 43 | private ResourceFileGraph resourceFileGraph; 44 | 45 | @ResourceFile(raw = true, filename = "formats.yml") @ReadOnlyResource private YAMLConfigurationFile formatsFile; 46 | 47 | @Getter(AccessLevel.NONE) private Object[] injected; 48 | 49 | //"abstract" methods 50 | protected void onModuleEnable() throws Exception {} 51 | protected void onModuleDisable() throws Exception {} 52 | 53 | @Override 54 | public void onLoad() { 55 | //get libraries, first and foremost 56 | LibraryHandler.loadLibraries(this); 57 | } 58 | 59 | //plugin stuff 60 | @Override 61 | public final void onEnable() { 62 | try { 63 | //init rx 64 | syncScheduler = new RxBukkitScheduler(this, ConcurrencyMode.SYNC); 65 | asyncScheduler = new RxBukkitScheduler(this, ConcurrencyMode.ASYNC); 66 | eventStreamer = new EventStreamer(this, syncScheduler, asyncScheduler); 67 | 68 | //resource file graph 69 | resourceFileGraph = new ResourceFileGraph(this); 70 | resourceFileGraph.hookToObject(this); 71 | resourceFileGraph.writeDefaults(); 72 | resourceFileGraph.loadAll(); 73 | 74 | //inject 75 | injected = Injector.injectTo(this); 76 | 77 | //init files 78 | if (!getClass().isAnnotationPresent(NoConfig.class)) 79 | if (getResource("config.yml") != null) 80 | saveDefaultConfig(); 81 | 82 | if (getClass().isAnnotationPresent(UsesFormats.class)) 83 | formatter = new Formatter(formatsFile); 84 | 85 | else formatter = null; 86 | 87 | onModuleEnable(); 88 | } catch (Throwable t) { 89 | getLogger().severe("Unable to properly enable this plugin!"); 90 | t.printStackTrace(); 91 | Bukkit.getPluginManager().disablePlugin(this); 92 | } 93 | } 94 | 95 | @Override 96 | public final void onDisable() { 97 | try { 98 | onModuleDisable(); 99 | 100 | Injector.handleDisable(injected); 101 | injected = null; 102 | 103 | resourceFileGraph.saveAll(); 104 | } catch (Throwable t) { 105 | getLogger().severe("Unable to properly disable this plugin!"); 106 | t.printStackTrace(); 107 | } 108 | } 109 | 110 | public final Formatter.FormatBuilder formatAt(String key) { 111 | return getFormatter().begin(key); 112 | } 113 | 114 | public void saveAll() { 115 | resourceFileGraph.saveAll(); 116 | } 117 | 118 | public void saveResourcesFor(Object object) { 119 | resourceFileGraph.saveAll(object); 120 | } 121 | 122 | public void loadResourcesFor(Object object) { 123 | resourceFileGraph.hookToObject(object); 124 | resourceFileGraph.writeDefaultsFor(object); 125 | resourceFileGraph.loadFor(object); 126 | } 127 | 128 | //register commands 129 | public final T registerCommand(T command) { 130 | PluginCommand pluginCommand = getCommand(command.getName()); 131 | if (pluginCommand == null) { 132 | try { 133 | Constructor commandConstructor = PluginCommand.class.getDeclaredConstructor(String.class, Plugin.class); 134 | commandConstructor.setAccessible(true); 135 | pluginCommand = (PluginCommand) commandConstructor.newInstance(command.getName(), this); 136 | } catch (Exception ex) { 137 | throw new IllegalStateException("Could not register command " + command.getName()); 138 | } 139 | CommandMap commandMap; 140 | try { 141 | PluginManager pluginManager = Bukkit.getPluginManager(); 142 | Field commandMapField = pluginManager.getClass().getDeclaredField("commandMap"); 143 | commandMapField.setAccessible(true); 144 | commandMap = (CommandMap) commandMapField.get(pluginManager); 145 | } catch (Exception ex) { 146 | throw new IllegalStateException("Could not register command " + command.getName()); 147 | } 148 | CommandMeta annotation = command.getClass().getAnnotation(CommandMeta.class); //Get the commandMeta 149 | if (annotation != null) { 150 | pluginCommand.setAliases(Arrays.asList(annotation.aliases())); 151 | pluginCommand.setDescription(annotation.description()); 152 | pluginCommand.setUsage(annotation.usage()); 153 | } 154 | commandMap.register(this.getDescription().getName(), pluginCommand); //Register it with Bukkit 155 | } 156 | pluginCommand.setExecutor(command); //Set the exectuor 157 | pluginCommand.setTabCompleter(command); //Tab completer 158 | 159 | if (command.getPlugin() == null) 160 | command.setPlugin(this); 161 | else 162 | command.setPlugin(null); 163 | getLogger().info("Registered command /" + command.getName()); 164 | 165 | return command; 166 | } 167 | 168 | @Deprecated public final void regsiterCommand(RDCommand... commands) { 169 | for (RDCommand command : commands) registerCommand(command); 170 | } 171 | 172 | public final void registerCommand(RDCommand... commands) { 173 | for (RDCommand command : commands) registerCommand(command); 174 | } 175 | 176 | @SafeVarargs 177 | public final Observable observeEvent(Class... events) { 178 | return eventStreamer.observeEvent(events); 179 | } 180 | 181 | @SafeVarargs 182 | public final Observable observeEventRaw(EventPriority priority, Class... events) { 183 | return eventStreamer.observeEventRaw(priority, events); 184 | } 185 | 186 | @SafeVarargs 187 | public final Observable observeEvent(EventPriority priority, Class... events) { 188 | return eventStreamer.observeEvent(priority, events); 189 | } 190 | 191 | @SafeVarargs 192 | public final Observable observeEventRaw(Class... events) { 193 | return eventStreamer.observeEventRaw(events); 194 | } 195 | 196 | @SafeVarargs 197 | public final Observable observeEventRaw(EventPriority priority, boolean ignoreCancelled, Class... events) { 198 | return eventStreamer.observeEventRaw(priority, ignoreCancelled, events); 199 | } 200 | 201 | @SafeVarargs 202 | public final Observable observeEvent(EventPriority priority, boolean ignoreCancelled, Class... events) { 203 | return eventStreamer.observeEvent(priority, ignoreCancelled, events); 204 | } 205 | 206 | public T registerListener(T listener) { 207 | getServer().getPluginManager().registerEvents(listener, this); 208 | return listener; 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Redemptive Core 2 | 3 | Make your plugins simpler. 4 | 5 | ## Features 6 | 7 | The following features are provided by the Redemptive library: 8 | 9 | * Full featured command library 10 | * Scoreboard system (WIP) 11 | * Inventory GUI 12 | * Controlled inventory (hotbar buttons) 13 | * Bean -> File serialization through annotated fields 14 | * Formats system (formats.yml) 15 | * ReactiveX event handling 16 | * Shorthands for common tasks (ItemShorthand, RunnableShorthand) 17 | * Extremely basic geometric data structures (Region, Point) 18 | * Timeline (for sequencing runnables) 19 | * Dependency downloading from maven repos and injection of dependencies at runtime 20 | * Injectable component libraries (redemptive-serialize providing GSON, redemptive-sql providing HikariCP, redemptive-persist providing Morphia) 21 | * Serializable player state encapsulation 22 | 23 | An overview of a few of the key systems in Redemptive 24 | 25 | ## Important differences 26 | 27 | * We use onModuleEnable, not onEnable 28 | * You may throw any exception from onModuleEnable, it will simply disable the plugin 29 | * You do not need to register commands in plugin.yml so long as they are redemptive commands registered by the plugin during runtime (using registerCommand) 30 | * Listeners are exceptionally rare, being replaced by the ReactiveX event handling system. 31 | 32 | ## Commands 33 | 34 | Below is an example command- 35 | 36 | ``` 37 | public final class CancelCommand extends RDCommand { 38 | public CancelCommand() { 39 | super("cancel"); 40 | } 41 | 42 | @Override 43 | protected void handleCommand(Player player, String[] args, BlockRecorder recorder) throws CommandException { 44 | player.sendMessage(Animatic.getInstance().formatAt("recording.cancelled").get()); 45 | Animatic.getInstance().clearRecorder(player); 46 | } 47 | } 48 | ``` 49 | 50 | Registration: 51 | 52 | ``` 53 | registerCommand(new CancelCommand()); 54 | ``` 55 | 56 | You can also use a RedemptiveCommand as a subcommand to another RedemptiveCommand 57 | ``` 58 | @CommandPermission("animatic.any") 59 | public final class AnimaticCommand extends RDCommand { 60 | public AnimaticCommand() { 61 | super("animatic", new RecordCommand(), new PlayerCommand("player"), new ReloadCommand()); 62 | } 63 | } 64 | ``` 65 | 66 | Note, that this principal applies recursively. You can make a subcommand itself have a subcommand. 67 | 68 | All subcommands are automatically tab-completable for those that have permission (checks for this are done through @CommandPermission, you have no obligation to use this, but for "vanilla redemptive" permission checks, like inbuilt tab complete, you must use this annotation) 69 | 70 | All errors should be handled through the use of exceptions. You may handle exceptions thrown from a command in a general sense by overriding this method 71 | 72 | ``` 73 | protected void handleCommandException(CommandException ex, String[] args, CommandSender sender) 74 | ``` 75 | 76 | If there is an unhandled exception that does not subclass CommandException, then you will receive the hilariously named **UnhandledCommandExceptionException** 77 | 78 | Here is a more complete example of a command which shows how to use exceptions and other features of redemptive commands 79 | 80 | ``` 81 | @Override 82 | protected void handleCommand(Player player, String[] args) throws CommandException { 83 | if (args.length < 3) throw new ArgumentRequirementException("[mob type] [tutorial] [name...]"); 84 | EntityType type; 85 | try { 86 | type = EntityType.valueOf(args[0]); 87 | } catch (Exception e) { 88 | throw new ArgumentRequirementException("The mob type is not valid!"); 89 | } 90 | String[] nameParts = new String[args.length - 2]; 91 | System.arraycopy(args, 2, nameParts, 0, nameParts.length); 92 | String name = Joiner.on(' ').join(nameParts), tutorial = args[1]; 93 | TutorialManager tutorialManager = TutorialPlugin.getInstance().getTutorialManager(); 94 | Optional tut = tutorialManager.getTutorial(tutorial); 95 | if (!tut.isPresent()) 96 | throw new ArgumentRequirementException("Could not find that tutorial!"); 97 | tutorialManager.getMobsFile().addMob(new InteractableMob(player.getLocation().clone(), tutorial, name, type)); 98 | player.sendMessage(formatAt("added-mob").get()); 99 | } 100 | ``` 101 | 102 | Also note some interesting feature methods, including promptSender 103 | 104 | ``` 105 | @Override 106 | protected void handleCommand(Player player, String[] args) throws CommandException { 107 | SetupContext contextFor = getContextFor(player); 108 | contextFor.setLock(true); 109 | SegmentContext segmentContext = contextFor.getSegmentContext(); 110 | promptSender(player, "type a line to add into the chat").subscribe(line -> { 111 | segmentContext.addLine(line); 112 | messageSet(player, "lines #" + segmentContext.getLines().size(), line); 113 | contextFor.setLock(false); 114 | }); 115 | } 116 | ``` 117 | 118 | This method will return to you, through rx event handling, a string which the player typed into the chat. 119 | 120 | The implementation of this will be displayed in the ReactiveX event handling section. 121 | 122 | ## ReactiveX event handling 123 | 124 | ReactiveX is documented here https://www.spigotmc.org/threads/rxbukkit-a-new-event-philosphy.115344/ 125 | 126 | You may observe events using the methods on your plugin, all of which are named observeEvent. 127 | 128 | Here's an example of a feature which is in our command system using this methodology 129 | ``` 130 | protected void messagePrompt(CommandSender sender, String action) { 131 | sender.sendMessage(formatAt("setup.prompt").withModifier("action", action).get()); 132 | } 133 | 134 | protected final Single promptPlayer(Player player, String s) { 135 | return promptSender(player, s); 136 | } 137 | 138 | protected final Single promptSender(final CommandSender sender, String s) { 139 | messagePrompt(sender, s); 140 | Observable observable; 141 | if (sender instanceof Player) { 142 | observable = getPlugin() 143 | .observeEvent(AsyncPlayerChatEvent.class) 144 | .filter(new Func1() { 145 | @Override 146 | public Boolean call(AsyncPlayerChatEvent event) { 147 | return event.getPlayer().equals(sender); 148 | } 149 | }) 150 | .map(new Func1() { 151 | @Override 152 | public String call(AsyncPlayerChatEvent event) { 153 | event.setCancelled(true); 154 | return event.getMessage(); 155 | } 156 | }); 157 | } else if (sender instanceof ConsoleCommandSender) { 158 | observable = getPlugin().observeEvent(ServerCommandEvent.class) 159 | .map(new Func1() { 160 | @Override 161 | public String call(ServerCommandEvent event) { 162 | event.setCancelled(true); 163 | return event.getCommand(); 164 | } 165 | }); 166 | } else throw new IllegalArgumentException("You cannot perform this command!"); 167 | 168 | return observable.take(1).toSingle(); 169 | } 170 | ``` 171 | 172 | 173 | ## Formats System 174 | 175 | You may specify a set of "formats" for a plugin as to not include any message strings in your source. 176 | 177 | The following is an example of "getting" a format with variables 178 | 179 | ``` 180 | player.sendMessage(instance.formatAt("players.list-diffworld-line").withModifier("name", staticPlayer.getName()).get()); 181 | ``` 182 | 183 | You must add 184 | ``` 185 | @UsesFormats 186 | ``` 187 | to your plugin type to enable this feature. 188 | 189 | Note, there is a special key: ```prefix``` which can be added to your formats.yml and will always be prepended to formats. You can omit the prefix setting in a specific format (say, for an action bar message) by using ```withPrefix(false)``` while building your format. 190 | 191 | ## Dependency Downloading and Loading 192 | You may annotate your plugin with the following annotation: 193 | ``` 194 | @MavenLibraries({@MavenLibrary("io.reactivex:rxjava:1.0.16")}) 195 | ``` 196 | 197 | *Note* This library (reactivex) is already downloaded for every plugin. 198 | 199 | The only library you are required to shade into your final plugin jar is redemptive-core itself. 200 | 201 | You may also specify a repository to download the dependency from through the property _repo_ on the _MavenLibrary_ annotation 202 | 203 | If you wish to shade dependencies manually, add the ```@IgnoreLibraries``` annotation and all superclasses will be ignored. 204 | 205 | ## Component Injection 206 | 207 | This feature is designed as a fix for carrying around code you don't need in your jar. If you don't need gson, don't use it. Inject the component library if you do need it. 208 | 209 | Your first step is to have the dependency for your component be present at runtime. This may either be accomplished via the dependency downloading system, or by simply shading the component in (far more common for the redemptive-* components). 210 | 211 | Then, simply add a field to your plugin class that looks like this 212 | ``` 213 | @Inject private GsonBridge bridge 214 | ``` 215 | 216 | Where, in this case, we are injecting the GsonBridge from redemptive-serialize. The field will be non-null (assuming no exceptions) before your onModuleEnable is called. -------------------------------------------------------------------------------- /redemptive-core/src/main/java/tech/rayline/core/gui/InventoryGUI.java: -------------------------------------------------------------------------------- 1 | package tech.rayline.core.gui; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | 5 | import org.bukkit.Bukkit; 6 | import org.bukkit.Sound; 7 | import org.bukkit.entity.Player; 8 | import org.bukkit.event.inventory.InventoryClickEvent; 9 | import org.bukkit.event.inventory.InventoryCloseEvent; 10 | import org.bukkit.event.inventory.InventoryType; 11 | import org.bukkit.event.player.PlayerQuitEvent; 12 | import org.bukkit.inventory.Inventory; 13 | import org.bukkit.inventory.ItemStack; 14 | 15 | import java.util.HashMap; 16 | import java.util.HashSet; 17 | import java.util.Map; 18 | import java.util.NoSuchElementException; 19 | import java.util.Set; 20 | import java.util.UUID; 21 | 22 | import rx.Observable; 23 | import rx.Subscriber; 24 | import rx.Subscription; 25 | import rx.functions.Action0; 26 | import rx.functions.Action1; 27 | import rx.functions.Func1; 28 | import rx.subscriptions.CompositeSubscription; 29 | import rx.subscriptions.Subscriptions; 30 | import tech.rayline.core.command.EmptyHandlerException; 31 | import tech.rayline.core.plugin.RedemptivePlugin; 32 | import tech.rayline.core.util.SoundUtil; 33 | 34 | /** 35 | * This class represents an InventoryGUI which can be opened for players 36 | * 37 | * @version 2.0 38 | */ 39 | public class InventoryGUI { 40 | /** 41 | * A collection of all the players who currently have the inventory GUI open 42 | */ 43 | private final Set observers = new HashSet<>(); 44 | /** 45 | * All the current button mappings for different slots 46 | */ 47 | private final Map buttons = new HashMap<>(); 48 | /** 49 | * The slots which have been updated by our mutator methods, but have yet to be put in the linked bukkit inventory 50 | */ 51 | private final Set touchedSlots = new HashSet<>(); 52 | /** 53 | * The plugin which created this inventory GUI 54 | */ 55 | private final RedemptivePlugin plugin; 56 | /** 57 | * The linked bukkit inventory 58 | */ 59 | private final Inventory bukkitInventory; 60 | /** 61 | * The event handling subscription- this can be unsubscribed from to cause this class to fall completely out of scope 62 | * @see #invalidate() 63 | */ 64 | private final Subscription mainSubscription; 65 | 66 | /** 67 | * Allows you to create an inventory GUI of a certain type 68 | * @param plugin The plugin which this GUI is a member of 69 | * @param type The type of inventory to use 70 | * @param title The title of the GUI 71 | */ 72 | public InventoryGUI(RedemptivePlugin plugin, InventoryType type, String title) { 73 | this.plugin = plugin; 74 | if (type == InventoryType.CHEST) 75 | throw new IllegalArgumentException("You must use the constructor accepting an integer, not InventoryType, if you wish to create a standard chest inventory!"); 76 | 77 | bukkitInventory = Bukkit.createInventory(null, type, title); 78 | mainSubscription = beginObserving(); 79 | } 80 | 81 | /** 82 | * Allows you to create a chest inventory GUI of a certain size 83 | * @param plugin The plugin which this GUI is a member of 84 | * @param size The size of the inventory 85 | * @param title The title of the GUI 86 | */ 87 | public InventoryGUI(RedemptivePlugin plugin, int size, String title) { 88 | this.plugin = plugin; 89 | bukkitInventory = Bukkit.createInventory(null, size, title); 90 | mainSubscription = beginObserving(); 91 | } 92 | 93 | //creates and returns the subscription for the main events 94 | private Subscription beginObserving() { 95 | //using a composite subscription because we have two entirely separate subscriptions to two different events. We want to still represent this as a single subscription, however. 96 | final CompositeSubscription subscription = new CompositeSubscription(); 97 | //noinspection SuspiciousMethodCalls 98 | subscription.add( 99 | //this is the main subscription that we use to actually catch clicks 100 | plugin.observeEvent(InventoryClickEvent.class) 101 | .filter(new Func1() { 102 | @Override 103 | public Boolean call(InventoryClickEvent event) { 104 | //noinspection SuspiciousMethodCalls 105 | return event.getInventory().getTitle().equals(bukkitInventory.getTitle()) && observers.contains(event.getWhoClicked().getUniqueId()); 106 | } 107 | }) 108 | .subscribe(new Action1() { 109 | @Override 110 | public void call(InventoryClickEvent event) { 111 | event.setCancelled(true); 112 | 113 | try { 114 | InventoryGUIButton buttonAt = getButtonAt(event.getRawSlot()); 115 | if (buttonAt == null) 116 | return; 117 | 118 | Player whoClicked = (Player) event.getWhoClicked(); 119 | try { 120 | buttonAt.onPlayerClick(whoClicked, ClickAction.from(event.getClick())); 121 | whoClicked.updateInventory(); 122 | } catch (EmptyHandlerException e) { 123 | SoundUtil.playTo(whoClicked, Sound.BLOCK_NOTE_PLING); 124 | } 125 | } catch (Exception e) { 126 | e.printStackTrace(); 127 | } 128 | } 129 | })); 130 | 131 | return subscription; 132 | } 133 | 134 | public Observable observeSlot(final ItemStack buttonStack, final Integer slot) { 135 | return Observable.create(new Observable.OnSubscribe() { 136 | @Override 137 | public void call(final Subscriber subscriber) { 138 | InventoryGUI.this.setButton(new SimpleInventoryGUIButton(buttonStack, new Action1() { 139 | @Override 140 | public void call(InventoryGUIAction action) { 141 | if (!subscriber.isUnsubscribed()) 142 | subscriber.onNext(action); 143 | } 144 | }) { 145 | @Override 146 | protected void onRemove() { 147 | if (!subscriber.isUnsubscribed()) 148 | subscriber.onCompleted(); 149 | } 150 | 151 | @Override 152 | protected void onAdd() { 153 | if (!subscriber.isUnsubscribed()) 154 | subscriber.onStart(); 155 | } 156 | }, slot); 157 | 158 | subscriber.add(Subscriptions.create(new Action0() { 159 | @Override 160 | public void call() { 161 | InventoryGUI.this.clearButton(slot); 162 | } 163 | })); 164 | } 165 | }); 166 | } 167 | 168 | /** 169 | * This will force the inventory to fall out of scope by clearing and un-subscribing various things 170 | */ 171 | 172 | 173 | public void invalidate() { 174 | mainSubscription.unsubscribe(); 175 | for (UUID observer : observers) 176 | Bukkit.getPlayer(observer).closeInventory(); 177 | observers.clear(); 178 | touchedSlots.clear(); 179 | for (Integer integer : buttons.keySet()) 180 | clearButton(integer); 181 | } 182 | 183 | public void addButton(InventoryGUIButton button) { 184 | setButton(button, getNextSlot()); 185 | } 186 | 187 | public ImmutableList getButtons() { 188 | return ImmutableList.copyOf(buttons.values()); 189 | } 190 | 191 | /** 192 | * Changes the button at a location 193 | * @param button The button you wish to now be at that location 194 | * @param slot The slot location 195 | */ 196 | public void setButton(InventoryGUIButton button, Integer slot) { 197 | InventoryGUIButton buttonAt = getButtonAt(slot); 198 | if (buttonAt == null || !buttonAt.equals(button)) 199 | button.onAdd(); 200 | clearButton(slot); 201 | 202 | buttons.put(slot, button); 203 | markForUpdate(slot); 204 | } 205 | 206 | /** 207 | * Removes a button from the inventory GUI 208 | * @param slot The slot of the button 209 | */ 210 | public void clearButton(Integer slot) { 211 | InventoryGUIButton remove = buttons.remove(slot); 212 | if (remove != null) 213 | remove.onRemove(); 214 | markForUpdate(slot); 215 | } 216 | 217 | /** 218 | * Check if a button is in a slot 219 | * @param slot The slot to check 220 | * @return the presence of a button in that slot 221 | */ 222 | public boolean hasButton(Integer slot) { 223 | return buttons.containsKey(slot); 224 | } 225 | 226 | /** 227 | * Gets you the current inventory button at a location 228 | * @param slot The location to get the button from 229 | * @return The button at that location, or null if there is none 230 | */ 231 | public InventoryGUIButton getButtonAt(Integer slot) { 232 | return buttons.get(slot); 233 | } 234 | 235 | /** 236 | * Forces all currently staged modifications to the inventory to appear for all clients. This must be called if you call any of the following methods: 237 | * 238 | *
    239 | *
  1. {@link #setButton(InventoryGUIButton, Integer)}
  2. 240 | *
  3. {@link #clearButton(Integer)}
  4. 241 | *
242 | */ 243 | public void updateInventory() { 244 | for (Integer touchedSlot : touchedSlots) { 245 | InventoryGUIButton buttonAt = getButtonAt(touchedSlot); 246 | boolean b = buttonAt == null; 247 | if (!b && buttonAt.getCurrentRepresentation() == null) 248 | throw new IllegalStateException("Your inventory button " + buttonAt.toString() + " has failed to provide an item during the update cycle!"); 249 | ItemStack itemStack = b ? null : buttonAt.getCurrentRepresentation(); 250 | bukkitInventory.setItem(touchedSlot, itemStack); 251 | } 252 | 253 | for (UUID observer : observers) 254 | Bukkit.getPlayer(observer).updateInventory(); 255 | touchedSlots.clear(); 256 | } 257 | 258 | /** 259 | * Opens the inventory for the player 260 | * @param player The player who you want to show this inventory to 261 | */ 262 | public void openFor(final Player player) { 263 | if (mainSubscription.isUnsubscribed()) 264 | throw new IllegalStateException("You cannot use this inventory anymore! You have invalidated it!"); 265 | 266 | observers.add(player.getUniqueId()); 267 | //listens to the player quit event and the inventory close event 268 | Observable.merge( 269 | plugin.observeEvent(PlayerQuitEvent.class).map(new Func1() { 270 | @Override 271 | public Player call(PlayerQuitEvent playerQuitEvent) { 272 | return playerQuitEvent.getPlayer(); 273 | } 274 | }), 275 | plugin.observeEvent(InventoryCloseEvent.class).map(new Func1() { 276 | @Override 277 | public Player call(InventoryCloseEvent inventoryCloseEvent) { 278 | return (Player) inventoryCloseEvent.getPlayer(); 279 | } 280 | }) 281 | //with both- filter out where the player is not the one we're looking for 282 | .filter(new Func1() { 283 | @Override 284 | public Boolean call(Player pl) { 285 | return pl.equals(player); 286 | } 287 | }) 288 | //only take one, and only while the player still has the inventory open 289 | .takeWhile(new Func1() { 290 | @Override 291 | public Boolean call(Player pl) { 292 | return InventoryGUI.this.isOpenFor(player); 293 | } 294 | })) 295 | .take(1) 296 | //notify us that the inventory has been closed! 297 | .subscribe(new Action1() { 298 | @Override 299 | public void call(Player player) { 300 | inventoryClosed(player); 301 | } 302 | }); 303 | 304 | //open the inventory 305 | player.openInventory(bukkitInventory); 306 | } 307 | 308 | public boolean isOpenFor(Player player) { 309 | return observers.contains(player.getUniqueId()); 310 | } 311 | 312 | /** 313 | * Closes the inventory for the player- this is immediate. 314 | * 315 | * This call will simlpy do nothing if the player does not currently have our inventory open (at least, in our eyes) 316 | * @param player The player who you want to close the inventory for 317 | */ 318 | public void closeFor(Player player) { 319 | if (!observers.contains(player.getUniqueId())) return; 320 | player.closeInventory(); 321 | inventoryClosed(player); 322 | } 323 | 324 | //removes players from the observable set now that they've closed the inventory 325 | protected void inventoryClosed(Player player) { 326 | observers.remove(player.getUniqueId()); 327 | } 328 | 329 | //used to mark a slot for update 330 | protected void markForUpdate(Integer slot) { 331 | touchedSlots.add(slot); 332 | } 333 | 334 | public int getSlotFor(InventoryGUIButton button) { 335 | for (Map.Entry entry : buttons.entrySet()) { 336 | if (entry.getValue().equals(button)) 337 | return entry.getKey(); 338 | } 339 | 340 | throw new NoSuchElementException("Could not find that button!"); 341 | } 342 | 343 | protected void markForUpdate(InventoryGUIButton button) { 344 | markForUpdate(getSlotFor(button)); 345 | } 346 | 347 | public void removeButton(InventoryGUIButton button) { 348 | removeButton(getSlotFor(button)); 349 | } 350 | 351 | public void removeButton(int slot) { 352 | clearButton(slot); 353 | } 354 | 355 | public Integer getNextSlot() { 356 | for (int i = 0; i < bukkitInventory.getSize(); i++) { 357 | if (getButtonAt(i) == null) 358 | return i; 359 | } 360 | throw new ArrayIndexOutOfBoundsException("There are no more slots in the inventory!"); 361 | } 362 | } 363 | -------------------------------------------------------------------------------- /redemptive-core/src/main/java/tech/rayline/core/command/RDCommand.java: -------------------------------------------------------------------------------- 1 | package tech.rayline.core.command; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | import lombok.AccessLevel; 5 | import lombok.Data; 6 | import lombok.Getter; 7 | import lombok.Setter; 8 | import org.bukkit.Bukkit; 9 | import org.bukkit.ChatColor; 10 | import org.bukkit.command.*; 11 | import org.bukkit.entity.Player; 12 | import org.bukkit.event.player.AsyncPlayerChatEvent; 13 | import org.bukkit.event.server.ServerCommandEvent; 14 | import rx.*; 15 | import rx.Observable; 16 | import rx.functions.Func1; 17 | import tech.rayline.core.plugin.*; 18 | import tech.rayline.core.plugin.Formatter; 19 | import tech.rayline.core.util.RunnableShorthand; 20 | 21 | import java.util.*; 22 | 23 | @Data 24 | public abstract class RDCommand implements CommandExecutor, TabCompleter { 25 | /** 26 | * Holds a list of the sub-commands bound to their names used for quick access. 27 | */ 28 | private final Map subCommands = new HashMap(); 29 | /** 30 | * Holds the name of this command. 31 | */ 32 | @Getter private final String name; 33 | @Setter(AccessLevel.PROTECTED) @Getter private RDCommand superCommand = null; 34 | @Getter private CommandMeta meta = getClass().isAnnotationPresent(CommandMeta.class) ? getClass().getAnnotation(CommandMeta.class) : null; 35 | @Setter private RedemptivePlugin plugin; 36 | 37 | /** 38 | * Main constructor without sub-commands. 39 | * @param name The name of the command. 40 | */ 41 | protected RDCommand(String name) { 42 | this.name = name; 43 | } 44 | 45 | /** 46 | * Main constructor with sub-commands. 47 | * @param name The name of the command. 48 | * @param subCommands The sub-commands you wish to register. 49 | */ 50 | protected RDCommand(final String name, RDCommand... subCommands) { 51 | this.name = name; 52 | registerSubCommand(subCommands); 53 | } 54 | 55 | /** 56 | * Registers sub commands with the command and re-creates the help command. 57 | * @param subCommands The sub-commands you wish to register. 58 | */ 59 | public final void registerSubCommand(RDCommand... subCommands) { 60 | //Toss all the sub commands in the map 61 | for (RDCommand subCommand : subCommands) { 62 | if (subCommand.getSuperCommand() != null) throw new IllegalArgumentException("The command you attempted to register already has a supercommand."); 63 | this.subCommands.put(subCommand.getName(), subCommand); 64 | CommandMeta meta = subCommand.getMeta(); 65 | if (meta != null && meta.aliases() != null) 66 | for (String a : meta.aliases()) 67 | this.subCommands.put(a, subCommand); 68 | subCommand.setSuperCommand(this); 69 | } 70 | //Add a provided help command 71 | regenerateHelpCommand(); 72 | } 73 | 74 | public final void unregisterSubCommand(RDCommand... subCommands) { 75 | for (RDCommand subCommand : subCommands) { 76 | //if (!subCommand.getSuperCommand().equals(this)) continue; 77 | this.subCommands.remove(subCommand.getName()); 78 | subCommand.setSuperCommand(null); 79 | } 80 | regenerateHelpCommand(); 81 | } 82 | 83 | public final ImmutableList getSubCommands() { 84 | return ImmutableList.copyOf(this.subCommands.values()); 85 | } 86 | 87 | private void regenerateHelpCommand() { 88 | if (!shouldGenerateHelpCommand()) return; 89 | final Map subCommandsLV = this.subCommands; 90 | final RDCommand superHelpCommand = this; 91 | this.subCommands.put("help", new RDCommand("help") { 92 | @Override 93 | public void handleCommandUnspecific(CommandSender sender, String[] args) { 94 | StringBuilder builder = new StringBuilder(); 95 | for (Map.Entry stringCLCommandEntry : subCommandsLV.entrySet()) { 96 | builder.append(stringCLCommandEntry.getKey()).append("|"); 97 | } 98 | String s = builder.toString(); 99 | //Looks like this /name - [subcommand1|subcommand2|] 100 | sender.sendMessage(ChatColor.AQUA + "/" + ChatColor.DARK_AQUA + superHelpCommand.getFormattedName() + ChatColor.YELLOW + " - [" + s.substring(0, s.length()-1) + "]"); 101 | } 102 | }); 103 | } 104 | 105 | public final boolean onCommand(final CommandSender sender, Command command, String s, final String[] args) { 106 | //Handling commands can be done by the logic below, and all errors should be thrown using an exception. 107 | //If you wish to override the behavior of displaying that error to the player, it is discouraged to do that in 108 | //your command logic, and you are encouraged to use the provided method handleCommandException. 109 | try { 110 | //STEP ONE: Handle sub-commands 111 | RDCommand subCommand = null; 112 | 113 | //Get the permission and test for it 114 | if (getClass().isAnnotationPresent(CommandPermission.class)) { 115 | CommandPermission annotation = getClass().getAnnotation(CommandPermission.class); 116 | if (!sender.hasPermission(annotation.value()) && !(sender.isOp() && annotation.isOpExempt())) throw new PermissionException("You do not have permission for this command!"); 117 | } 118 | 119 | //Check if we HAVE to use sub-commands (a behavior this class provides) 120 | if (isUsingSubCommandsOnly()) { 121 | //Check if there are not enough args for there to be a sub command 122 | if (args.length < 1) 123 | throw new ArgumentRequirementException("You must specify a sub-command for this command!"); 124 | //Also check if the sub command is valid by assigning and checking the value of the resolved sub command from the first argument. 125 | if ((subCommand = getSubCommandFor(args[0])) == null) 126 | throw new ArgumentRequirementException("The sub-command you have specified is invalid!"); 127 | } 128 | if (subCommand == null && args.length > 0) subCommand = getSubCommandFor(args[0]); //If we're not requiring sub-commands but we can have them, let's try that 129 | //By now we have validated that the sub command can be executed if it MUST, now lets see if we can execute it 130 | //In this case, if we must execute the sub command, this check will always past. In cases where it's an option, this check will also pass. 131 | //That way, we can use this feature of sub commands without actually requiring it. 132 | if (subCommand != null) { 133 | String[] choppedArgs = args.length < 2 ? new String[0] : Arrays.copyOfRange(args, 1, args.length); 134 | preSubCommandDispatch(sender, choppedArgs, subCommand); //Notify the subclass that we are using a sub-command in case any staging needs to take place. 135 | subCommand.onCommand(sender, command, s, choppedArgs); 136 | try { 137 | handlePostSubCommand(sender, args); 138 | } catch (EmptyHandlerException ignored) {} 139 | return true; 140 | } 141 | 142 | //Now that we've made it past the sub commands and permissions, STEP TWO: actually handle the command and it's args. 143 | if (getClass().isAnnotationPresent(AsyncCommand.class)) 144 | RunnableShorthand.forPlugin(plugin).async().with(new Runnable() { 145 | @Override 146 | public void run() { 147 | try { 148 | actualDispatch(sender, args); 149 | } catch (CommandException e) { 150 | handleCommandException(e, args, sender); 151 | } catch (Exception e) { 152 | handleCommandException(new UnhandledCommandExceptionException(e), args, sender); 153 | } 154 | } 155 | }).go(); 156 | else 157 | actualDispatch(sender, args); 158 | } //STEP THREE: Check for any command exceptions (intended) and any exceptions thrown in general and dispatch a call for an unhandled error to the handler. 159 | catch (CommandException ex) { 160 | handleCommandException(ex, args, sender); 161 | } catch (Exception e) { 162 | handleCommandException(new UnhandledCommandExceptionException(e), args, sender); 163 | } 164 | //STEP FOUR: Tell Bukkit we're done! 165 | return true; 166 | } 167 | 168 | private void actualDispatch(CommandSender sender, String[] args) throws CommandException { 169 | try { 170 | if (sender instanceof Player) handleCommand(((Player) sender), args); 171 | else if (sender instanceof ConsoleCommandSender) handleCommand((ConsoleCommandSender)sender, args); 172 | else if (sender instanceof BlockCommandSender) handleCommand((BlockCommandSender)sender, args); 173 | } catch (EmptyHandlerException e) { 174 | handleCommandUnspecific(sender, args); 175 | } 176 | } 177 | 178 | public final List onTabComplete(CommandSender sender, Command command, String alias, String[] args) { 179 | //Security for tab complete 180 | if (getClass().isAnnotationPresent(CommandPermission.class)) { 181 | CommandPermission annotation = getClass().getAnnotation(CommandPermission.class); 182 | if (!sender.hasPermission(annotation.value()) && !(sender.isOp() && annotation.isOpExempt())) return Collections.emptyList(); 183 | } 184 | //Step one, check if we have to go a level deeper in the sub command system: 185 | if (args.length > 1) { 186 | //If so, check if there's an actual match for the sub-command to delegate to. 187 | RDCommand possibleHigherLevelSubCommand; 188 | if ((possibleHigherLevelSubCommand = getSubCommandFor(args[0])) != null) 189 | return possibleHigherLevelSubCommand.onTabComplete(sender, command, alias, Arrays.copyOfRange(args, 1, args.length)); 190 | //NOW THINK. If there's not one, you'll reach this line, and exit this block of the if statement. The next statement is an else if, so it will skip that 191 | //And go to the very bottom "handleTabComplete." 192 | } else if (args.length == 1) { //So if we have exactly one argument, let's try and complete the sub-command for that argument 193 | //Grab some sub commands from the method we defined for this purpose 194 | List subCommandsForPartial = getSubCommandsForPartial(args[0]); 195 | //And if we found some 196 | if (subCommandsForPartial.size() != 0) { 197 | //Get the command names 198 | subCommandsForPartial.addAll(handleTabComplete(sender, command, alias, args)); 199 | //And return them 200 | return subCommandsForPartial; 201 | } 202 | //Otherwise, head to the delegated call at the bottom. 203 | } 204 | return handleTabComplete(sender, command, alias, args); 205 | } 206 | 207 | /** 208 | * This method should be overridden by any sub-classes as the functionality it provides is limited. 209 | * 210 | * The goal of this method should always be conveying an error message to a user in a friendly manner. The {@link tech.rayline.core.command.CommandException} can be extended by your {@link tech.rayline.core.plugin.RedemptivePlugin} to provide extended functionality. 211 | * 212 | * The {@code args} are the same args that would be passed to your handlers. Meaning, if this is a sub-command they will be cut to fit that sub-command, and if this is a root level command they will be all of the arguments. 213 | * 214 | * @param ex The exception used to hold the error message and any other details about the failure. If there was an exception during the handling of the command this will be an {@link tech.rayline.core.command.UnhandledCommandExceptionException}. 215 | * @param args The arguments passed to the command. 216 | * @param sender The sender of the command, cannot be directly cast to {@link org.bukkit.entity.Player}. 217 | */ 218 | @SuppressWarnings("UnusedParameters") 219 | protected void handleCommandException(CommandException ex, String[] args, CommandSender sender) { 220 | //Get the friendly message if supported 221 | if (ex instanceof FriendlyException) sender.sendMessage(((FriendlyException) ex).getFriendlyMessage(this)); 222 | else sender.sendMessage(ChatColor.RED + ex.getClass().getSimpleName() + ": " + ex.getMessage() + "!"); 223 | if (ex instanceof UnhandledCommandExceptionException) ((UnhandledCommandExceptionException) ex).getCausingException().printStackTrace(); 224 | } 225 | 226 | @SuppressWarnings("UnusedParameters") 227 | protected void preSubCommandDispatch(CommandSender sender, String[] args, RDCommand subCommand) {} 228 | 229 | public final RDCommand getSubCommandFor(String s) { 230 | //If we have an exact match, case and all, don't waste the CPU cycles on the lower for loop. 231 | if (subCommands.containsKey(s)) return subCommands.get(s); 232 | //Otherwise, loop through the sub-commands and do a case insensitive check. 233 | for (String s1 : subCommands.keySet()) { 234 | if (s1.equalsIgnoreCase(s)) return subCommands.get(s1); 235 | } 236 | //And we didn't find anything, so let's return nothing. 237 | return null; 238 | } 239 | 240 | public List getSubCommandsForPartial(String s) { 241 | List commands = new ArrayList<>(); //Create a place to hold our possible commands 242 | RDCommand subCommand; 243 | if ((subCommand = getSubCommandFor(s)) != null) { //Check if we can get an exact sub-command 244 | commands.add(subCommand.getName()); 245 | return commands; //exact sub-command is all we need. 246 | } 247 | String s2 = s.toUpperCase(); //Get the case-insensitive comparator. 248 | for (String s1 : subCommands.keySet()) { 249 | if (s1.toUpperCase().startsWith(s2)) 250 | commands.add(s1); //We found one that starts with the argument. 251 | } 252 | return commands; 253 | } 254 | 255 | public Formatter.FormatBuilder formatAt(String key) { 256 | if (getPlugin() == null) 257 | throw new IllegalStateException("This command has been registered by multiple plugins, or (likely) none at all!"); 258 | return getPlugin().formatAt(key); 259 | } 260 | 261 | public RedemptivePlugin getPlugin() { 262 | if (getSuperCommand() != null) 263 | return getSuperCommand().getPlugin(); 264 | return plugin; 265 | } 266 | 267 | protected void messagePrompt(CommandSender sender, String action) { 268 | sender.sendMessage(formatAt("setup.prompt").withModifier("action", action).get()); 269 | } 270 | 271 | protected final Single promptPlayer(Player player, String s) { 272 | return promptSender(player, s); 273 | } 274 | 275 | protected final Single promptSender(final CommandSender sender, String s) { 276 | messagePrompt(sender, s); 277 | Observable observable; 278 | if (sender instanceof Player) { 279 | observable = getPlugin() 280 | .observeEvent(AsyncPlayerChatEvent.class) 281 | .filter(new Func1() { 282 | @Override 283 | public Boolean call(AsyncPlayerChatEvent event) { 284 | return event.getPlayer().equals(sender); 285 | } 286 | }) 287 | .map(new Func1() { 288 | @Override 289 | public String call(AsyncPlayerChatEvent event) { 290 | event.setCancelled(true); 291 | return event.getMessage(); 292 | } 293 | }); 294 | } else if (sender instanceof ConsoleCommandSender) { 295 | observable = getPlugin().observeEvent(ServerCommandEvent.class) 296 | .map(new Func1() { 297 | @Override 298 | public String call(ServerCommandEvent event) { 299 | event.setCancelled(true); 300 | return event.getCommand(); 301 | } 302 | }); 303 | } else throw new IllegalArgumentException("You cannot perform this command!"); 304 | 305 | return observable.take(1).toSingle(); 306 | } 307 | 308 | //Default behavior is to do nothing, these methods can be overridden by the sub-class. 309 | protected void handleCommand(Player player, String[] args) throws CommandException {throw new EmptyHandlerException();} 310 | protected void handleCommand(ConsoleCommandSender commandSender, String[] args) throws CommandException {throw new EmptyHandlerException();} 311 | protected void handleCommand(BlockCommandSender commandSender, String[] args) throws CommandException {throw new EmptyHandlerException();} 312 | 313 | //Handles for all types in the event that no specific handler is overridden above. 314 | protected void handleCommandUnspecific(CommandSender sender, String[] args) throws CommandException {throw new EmptyHandlerException();} 315 | protected void handlePostSubCommand(CommandSender sender, String[] args) throws CommandException {throw new EmptyHandlerException();} 316 | 317 | protected boolean shouldGenerateHelpCommand() {return true;} 318 | 319 | //Default behavior if we delegate the call to the sub-class 320 | protected List handleTabComplete(CommandSender sender, Command command, String alias, String[] args) { 321 | if (isUsingSubCommandsOnly() || subCommands.size() > 0) return Collections.emptyList(); 322 | List ss = new ArrayList(); //Create a list to put possible names 323 | String arg = args.length > 0 ? args[args.length - 1].toLowerCase() : ""; //Get the last argument 324 | for (Player player : Bukkit.getOnlinePlayers()) { //Loop through all the players 325 | String name1 = player.getName(); //Get this players name (since we reference it twice) 326 | if (name1.toLowerCase().startsWith(arg)) ss.add(name1); //And if it starts with the argument we add it to this list 327 | } 328 | return ss; //Return what we found. 329 | } 330 | 331 | protected boolean isUsingSubCommandsOnly() {return false;} 332 | 333 | protected String getFormattedName() {return superCommand == null ? name : superCommand.getFormattedName() + " " + name;} 334 | 335 | @Override 336 | public String toString() { 337 | return "Command -> " + getFormattedName(); 338 | } 339 | } 340 | --------------------------------------------------------------------------------