├── .githooks └── pre-commit ├── .gitignore ├── LICENSE ├── README.md ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── jitpack.yml ├── res ├── command.rdcml ├── plugin.yml └── redlib │ └── messages.txt ├── settings.gradle └── src └── redempt └── redlib ├── RedLib.java ├── RedLibConfig.java ├── blockdata ├── BlockDataListener.java ├── BlockDataManager.java ├── BlockPosition.java ├── ChunkPosition.java ├── DataBlock.java ├── backend │ ├── BlockDataBackend.java │ ├── PDCBackend.java │ └── SQLiteBackend.java ├── custom │ ├── CustomBlock.java │ ├── CustomBlockRegistry.java │ └── CustomBlockType.java └── events │ ├── CustomBlockPlaceEvent.java │ ├── DataBlockDestroyEvent.java │ └── DataBlockMoveEvent.java ├── config ├── ConfigField.java ├── ConfigManager.java ├── ConfigType.java ├── ConversionManager.java ├── annotations │ ├── Comment.java │ ├── Comments.java │ ├── ConfigConstructor.java │ ├── ConfigMappable.java │ ├── ConfigName.java │ ├── ConfigPath.java │ ├── ConfigPostInit.java │ └── ConfigSubclassable.java ├── conversion │ ├── CollectionConverter.java │ ├── EnumConverter.java │ ├── MapConverter.java │ ├── NativeConverter.java │ ├── ObjectConverter.java │ ├── PrimitiveConverter.java │ ├── StaticRootConverter.java │ ├── StringConverter.java │ ├── SubclassConverter.java │ └── TypeConverter.java ├── data │ ├── ConfigurationSectionDataHolder.java │ ├── DataHolder.java │ ├── ListDataHolder.java │ └── MapDataHolder.java └── instantiation │ ├── ConstructorInstantiator.java │ ├── EmptyInstantiator.java │ ├── FieldSummary.java │ └── Instantiator.java ├── dev ├── ChainCommand.java ├── StructureTool.java └── profiler │ ├── BurstProfiler.java │ ├── PassiveProfiler.java │ ├── Profiler.java │ ├── ProfilerCommands.java │ ├── Sample.java │ ├── SampleSummary.java │ └── TickMonitorProfiler.java ├── enchants ├── CustomEnchant.java ├── EnchantInfo.java ├── EnchantListener.java ├── EnchantRegistry.java ├── EventItems.java ├── events │ ├── PlayerChangedArmorEvent.java │ └── PlayerChangedHeldItemEvent.java └── trigger │ ├── AttackEntityTrigger.java │ ├── EnchantTrigger.java │ ├── EquipArmorTrigger.java │ ├── HoldItemTrigger.java │ ├── KillEntityTrigger.java │ ├── MineBlockTrigger.java │ ├── ShootArrowTrigger.java │ └── TakeDamageTrigger.java ├── inventorygui ├── InventoryGUI.java ├── ItemButton.java └── PaginationPanel.java ├── itemutils ├── CustomItem.java ├── ItemBuilder.java ├── ItemSerializer.java ├── ItemTrait.java ├── ItemUtils.java ├── LoreStats.java └── MockInventory.java ├── json ├── JSONList.java ├── JSONMap.java └── JSONParser.java ├── misc ├── ChatPrompt.java ├── EntityPersistor.java ├── EventListener.java ├── Hologram.java ├── LocationUtils.java ├── Path.java ├── PlayerWrapper.java ├── Task.java ├── UserCache.java └── WeightedRandom.java ├── multiblock ├── MultiBlockStructure.java ├── Rotator.java ├── Structure.java ├── StructureData.java └── StructureFinder.java ├── nms ├── NMSArray.java ├── NMSClass.java ├── NMSHelper.java └── NMSObject.java ├── protection ├── BypassPolicy.java ├── ProtectedRegion.java ├── ProtectionListener.java ├── ProtectionPolicy.java └── ProtectionRegistrations.java ├── region ├── CuboidRegion.java ├── MultiRegion.java ├── MultiRegionMeta.java ├── Overlappable.java ├── Region.java ├── RegionEnterExitListener.java ├── RegionMap.java ├── RegionUtils.java ├── SelectionTool.java ├── SpheroidRegion.java └── events │ ├── RegionEnterEvent.java │ └── RegionExitEvent.java ├── sql ├── SQLCache.java ├── SQLCacheEntry.java └── SQLHelper.java └── worldgen ├── NoiseGenerator.java └── NoiseOctave.java /.githooks/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | sed -E "s/^version: .+/version: $(date -u '+%Y-%m-%d %H:%M')/" -i res/plugin.yml 3 | git add res/plugin.yml 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Eclipse ### 2 | .metadata 3 | bin/ 4 | tmp/ 5 | *.tmp 6 | *.bak 7 | *.swp 8 | *~.nib 9 | local.properties 10 | .settings/ 11 | .loadpath 12 | .recommenders 13 | 14 | # Idea # 15 | .idea 16 | 17 | # External tool builders 18 | .externalToolBuilders/ 19 | 20 | # Locally stored "Eclipse launch configurations" 21 | *.launch 22 | 23 | # PyDev specific (Python IDE for Eclipse) 24 | *.pydevproject 25 | 26 | # CDT-specific (C/C++ Development Tooling) 27 | .cproject 28 | 29 | # CDT- autotools 30 | .autotools 31 | 32 | # Java annotation processor (APT) 33 | .factorypath 34 | 35 | # PDT-specific (PHP Development Tools) 36 | .buildpath 37 | 38 | # sbteclipse plugin 39 | .target 40 | 41 | # Tern plugin 42 | .tern-project 43 | 44 | # TeXlipse plugin 45 | .texlipse 46 | 47 | # STS (Spring Tool Suite) 48 | .springBeans 49 | 50 | # Code Recommenders 51 | .recommenders/ 52 | 53 | # Annotation Processing 54 | .apt_generated/ 55 | 56 | # Scala IDE specific (Scala & Java development for Eclipse) 57 | .cache-main 58 | .scala_dependencies 59 | .worksheet 60 | 61 | ### Eclipse Patch ### 62 | # Eclipse Core 63 | .project 64 | 65 | # JDT-specific (Eclipse Java Development Tools) 66 | .classpath 67 | 68 | # Annotation Processing 69 | .apt_generated 70 | 71 | .sts4-cache/ 72 | 73 | ### Java ### 74 | # Compiled class file 75 | *.class 76 | 77 | # Log file 78 | *.log 79 | 80 | # BlueJ files 81 | *.ctxt 82 | 83 | # Mobile Tools for Java (J2ME) 84 | .mtj.tmp/ 85 | 86 | # Package Files # 87 | *.jar 88 | *.war 89 | *.nar 90 | *.ear 91 | *.zip 92 | *.tar.gz 93 | *.rar 94 | 95 | ### Gradle ### 96 | .gradle 97 | build/ 98 | 99 | # Ignore Gradle GUI config 100 | gradle-app.setting 101 | 102 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 103 | !gradle-wrapper.jar 104 | 105 | # Cache of project 106 | .gradletasknamecache 107 | 108 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 109 | # gradle/wrapper/gradle-wrapper.properties 110 | 111 | ### Gradle Patch ### 112 | **/build/ 113 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Julien Marcuse 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RedLib 2 | RedLib is a Spigot plugin development library, designed to make your life easier and soothe the pain points of plugin development. Below, find instructions for the various components of RedLib. 3 | 4 | Support Discord: https://discord.gg/agu5xGy2YZ 5 | 6 | Docs: https://redempt.dev/javadoc/com/github/Redempt/RedLib/index.html 7 | 8 | # Installation for Development 9 | 10 | RedLib is a standalone plugin, but can also be used as a shaded dependency if you do not want to distribute RedLib directly. To use it as a plugin dependency, you must add it as a dependency in your plugin.yml: 11 | 12 | ```yaml 13 | depend: [RedLib] 14 | ``` 15 | 16 | To get the jar, either download it from the releases tab either here on [GitHub](https://github.com/Redempt/RedLib/releases) or on [Spigot](https://www.spigotmc.org/resources/redlib.78713/), or [build it locally](https://github.com/Redempt/RedLib#build-locally). 17 | 18 | ## Gradle 19 | 20 | ```groovy 21 | repositories { 22 | maven { url = 'https://redempt.dev' } 23 | } 24 | 25 | ``` 26 | 27 | ```groovy 28 | dependencies { 29 | compileOnly 'com.github.Redempt:RedLib:Tag' 30 | } 31 | ``` 32 | 33 | Replace `Tag` with a release tag for RedLib. You can see the latest version [here](https://github.com/Redempt/RedLib/releases/latest). 34 | 35 | To shade RedLib, change the dependency from `compileOnly` to `implementation`, and install the [gradle shadow plugin](https://github.com/johnrengelman/shadow). 36 | 37 | If you are having a problem while building, such as plugin.yml is duplicate, try setting duplicatesStrategy to DuplicatesStrategy.EXCLUDE. 38 | ```groovy 39 | tasks { 40 | processResources { 41 | duplicatesStrategy = DuplicatesStrategy.EXCLUDE 42 | } 43 | } 44 | ``` 45 | 46 | ## Maven: 47 | 48 | ```xml 49 | 50 | redempt.dev 51 | https://redempt.dev 52 | 53 | ``` 54 | 55 | ```xml 56 | 57 | com.github.Redempt 58 | RedLib 59 | Tag 60 | provided 61 | 62 | ``` 63 | Replace `Tag` with a release tag for RedLib. You can see the latest version [here](https://github.com/Redempt/RedLib/releases/latest). 64 | 65 | To shade RedLib, change the scope from `provided` to `compile`. 66 | 67 | ## Build locally: 68 | 69 | For Windows, use Git Bash. For Linux or OSX, just ensure you have Git installed.Navigate to the directory where you want to clone the repository, and run: 70 | 71 | ``` 72 | git clone https://github.com/Redempt/RedLib 73 | cd RedLib 74 | ./gradlew jar 75 | ``` 76 | 77 | After running these commands, the jar will be at `build/libs/RedLib.jar`. 78 | You may also need to add the jar to your classpath. After that, you should be good to go! 79 | 80 | # Usage 81 | 82 | For info on how to use RedLib, please see the [wiki](https://github.com/Redempt/RedLib/wiki). 83 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java-library' 3 | id 'com.github.johnrengelman.shadow' version '7.0.0' 4 | id 'maven-publish' 5 | } 6 | targetCompatibility = 1.8 7 | sourceCompatibility = 1.8 8 | repositories { 9 | mavenCentral() 10 | maven { url = 'https://oss.sonatype.org/content/repositories/snapshots' } 11 | maven { url = 'https://hub.spigotmc.org/nexus/content/repositories/snapshots/' } 12 | maven { url = 'https://redempt.dev' } 13 | mavenLocal() 14 | } 15 | dependencies { 16 | compileOnly 'org.spigotmc:spigot-api:1.18.1-R0.1-SNAPSHOT' 17 | api 'com.github.Redempt:RedCommands:1.5.7' 18 | } 19 | sourceSets { 20 | main { 21 | java { 22 | srcDir 'src' 23 | } 24 | resources { 25 | srcDir 'res' 26 | } 27 | } 28 | } 29 | 30 | jar.configure { 31 | actions.clear() 32 | dependsOn shadowJar 33 | } 34 | 35 | task javadocJar(type: Jar) { 36 | from javadoc 37 | archiveClassifier.set('javadoc') 38 | } 39 | 40 | task sourcesJar(type: Jar) { 41 | from sourceSets.main.allJava 42 | archiveClassifier.set('sources') 43 | } 44 | 45 | shadowJar.configure { 46 | archiveClassifier.set('') 47 | } 48 | 49 | publishing { 50 | publications { 51 | maven(MavenPublication) { 52 | groupId = 'com.github.Redempt' 53 | artifactId = rootProject.name 54 | version = System.env.BUILD_VERSION ?: "1.0" 55 | artifact javadocJar 56 | artifact sourcesJar 57 | from components.java 58 | } 59 | } 60 | } 61 | 62 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boxbeam/RedLib/5242c84787bb534885aab79a692a6ec8707f4652/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /jitpack.yml: -------------------------------------------------------------------------------- 1 | before_install: 2 | - sdk install java 16.0.1.j9-adpt 3 | - sdk use java 16.0.1.j9-adpt 4 | install: 5 | - ./gradlew publishToMavenLocal -------------------------------------------------------------------------------- /res/command.rdcml: -------------------------------------------------------------------------------- 1 | structure,struct { 2 | help The structure base command 3 | permission redlib.dev 4 | tool,wand { 5 | user player 6 | help Get a structure dev tool 7 | hook wand 8 | } 9 | create string:name* { 10 | help Register a structure from the selected points for testing 11 | hook create 12 | user player 13 | } 14 | build int<0,3>:rotation?(0) boolean:mirrored?(false) { 15 | help Build the created structure at your location 16 | hook build 17 | user player 18 | } 19 | export string:filename*?(structure) { 20 | help Exports the structure data for the selected points to a file 21 | hook export 22 | user player 23 | } 24 | import string:filename*? { 25 | help Imports the structure data from a file 26 | hook import 27 | user player 28 | } 29 | skip material:type? { 30 | help Sets a block type to skip when scanning, in case you want to use it for the corners 31 | hook skip 32 | user player 33 | } 34 | } 35 | profiler,rprofiler { 36 | permission redlib.dev 37 | monitor { 38 | start int<0,>:--minimum { 39 | help Starts the tick monitor profiler 40 | hook startmonitor 41 | } 42 | setminimum int<0,>:minimum { 43 | help Sets the minimum milliseconds for a tick to take to be reported 44 | hook setminimum 45 | } 46 | clear { 47 | help Clears the tick monitor profiler reports 48 | hook clear 49 | } 50 | reports { 51 | help Shows reports for the longest ticks since the tick monitor profiler started 52 | hook reports 53 | user player 54 | } 55 | select int:num { 56 | notab 57 | hook selectreport 58 | } 59 | } 60 | start { 61 | help Starts or resets the RedLib passive profiler 62 | hook start 63 | } 64 | stop { 65 | help Stops the RedLib passive profiler 66 | hook stop 67 | } 68 | summary { 69 | help Creates and selects a summary from the manual RedLib profiler 70 | hook summary 71 | user player 72 | } 73 | verbose { 74 | help Toggle verbose mode 75 | hook verbose 76 | user player 77 | } 78 | timeformat { 79 | help Toggle time between percentages and milliseconds 80 | hook timeformat 81 | user player 82 | } 83 | root { 84 | help Shows the root method from the selected summary 85 | hook root 86 | user player 87 | } 88 | select string:selector { 89 | hook select 90 | user player 91 | notab 92 | } 93 | up int<1,>:count?(1) { 94 | help Selects the parent method of the currently selected method 95 | hook up 96 | user player 97 | } 98 | collapse { 99 | help Collapses all methods 100 | hook collapse 101 | user player 102 | } 103 | toggleexpand string:selector { 104 | hook toggleexpand 105 | user player 106 | notab 107 | } 108 | search int<0,>:--depth(0) double<0,>:--over-percent(0) int<0,>:--over-milliseconds(0) string:term { 109 | help Searches the profiler with the given conditions 110 | hook search 111 | user player 112 | } 113 | limit int<1,>:amount { 114 | help Limits the number of children that can be shown under a single method 115 | hook limit 116 | user player 117 | } 118 | } 119 | commandchain,cchain commandchain...:commandchain { 120 | help Runs several commands in a row separated by ; 121 | hook commandchain 122 | permission redlib.commandchain 123 | } -------------------------------------------------------------------------------- /res/plugin.yml: -------------------------------------------------------------------------------- 1 | name: RedLib 2 | main: redempt.redlib.RedLib 3 | version: 2024-02-29 21:48 4 | author: Redempt 5 | api-version: 1.13 6 | load: STARTUP 7 | -------------------------------------------------------------------------------- /res/redlib/messages.txt: -------------------------------------------------------------------------------- 1 | firstLocationSet: &aFirst location set! 2 | secondLocationSet: &aSecond location set! 3 | cancelPromptMessage: &cType '%canceltext%' to cancel. 4 | cancelText: cancel 5 | teleportDelay: &aPlease stand still for %seconds% seconds to teleport... 6 | teleportCancelled: &cTeleport cancelled! -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * This file was generated by the Gradle 'init' task. 3 | * 4 | * The settings file is used to specify which projects to include in your build. 5 | * 6 | * Detailed information about configuring a multi-project build in Gradle can be found 7 | * in the user manual at https://docs.gradle.org/5.4/userguide/multi_project_builds.html 8 | */ 9 | 10 | rootProject.name = 'RedLib' 11 | -------------------------------------------------------------------------------- /src/redempt/redlib/RedLibConfig.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib; 2 | 3 | public class RedLibConfig { 4 | 5 | public static boolean devMode = false; 6 | 7 | } 8 | -------------------------------------------------------------------------------- /src/redempt/redlib/blockdata/BlockDataListener.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.blockdata; 2 | 3 | import org.bukkit.Bukkit; 4 | import org.bukkit.block.Block; 5 | import org.bukkit.event.Cancellable; 6 | import org.bukkit.event.Event; 7 | import org.bukkit.event.EventHandler; 8 | import org.bukkit.event.EventPriority; 9 | import org.bukkit.event.Listener; 10 | import org.bukkit.event.block.BlockBreakEvent; 11 | import org.bukkit.event.block.BlockBurnEvent; 12 | import org.bukkit.event.block.BlockExplodeEvent; 13 | import org.bukkit.event.block.BlockPistonEvent; 14 | import org.bukkit.event.block.BlockPistonExtendEvent; 15 | import org.bukkit.event.block.BlockPistonRetractEvent; 16 | import org.bukkit.event.entity.EntityChangeBlockEvent; 17 | import org.bukkit.event.entity.EntityExplodeEvent; 18 | import org.bukkit.plugin.Plugin; 19 | import redempt.redlib.blockdata.events.DataBlockDestroyEvent; 20 | import redempt.redlib.blockdata.events.DataBlockDestroyEvent.DestroyCause; 21 | import redempt.redlib.blockdata.events.DataBlockMoveEvent; 22 | import redempt.redlib.json.JSONMap; 23 | 24 | import java.util.ArrayList; 25 | import java.util.HashMap; 26 | import java.util.List; 27 | import java.util.Map; 28 | 29 | class BlockDataListener implements Listener { 30 | 31 | private BlockDataManager manager; 32 | 33 | public BlockDataListener(BlockDataManager manager, Plugin plugin) { 34 | this.manager = manager; 35 | Bukkit.getPluginManager().registerEvents(this, plugin); 36 | } 37 | 38 | private void fireDestroy(DataBlock db, Event parent, DestroyCause cause) { 39 | if (db == null) { 40 | return; 41 | } 42 | DataBlockDestroyEvent ev = new DataBlockDestroyEvent(db, parent, cause); 43 | Bukkit.getPluginManager().callEvent(ev); 44 | if (!ev.isCancelled()) { 45 | manager.remove(db); 46 | } 47 | } 48 | 49 | @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) 50 | public void onBreak(BlockBreakEvent e) { 51 | DataBlock db = manager.getDataBlock(e.getBlock(), false); 52 | fireDestroy(db, e, DestroyCause.PLAYER_BREAK); 53 | } 54 | 55 | @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) 56 | public void onExplode(BlockExplodeEvent e) { 57 | handleExplosion(e.blockList(), e); 58 | } 59 | 60 | @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) 61 | public void onExplode(EntityExplodeEvent e) { 62 | handleExplosion(e.blockList(), e); 63 | } 64 | 65 | private void handleExplosion(List blocks, Cancellable e) { 66 | List toRemove = new ArrayList<>(); 67 | blocks.forEach(b -> { 68 | DataBlock db = manager.getDataBlock(b); 69 | if (db == null) { 70 | return; 71 | } 72 | DataBlockDestroyEvent ev = new DataBlockDestroyEvent(db, (Event) e, DestroyCause.EXPLOSION); 73 | Bukkit.getPluginManager().callEvent(ev); 74 | if (!ev.isCancelled()) { 75 | toRemove.add(db); 76 | } 77 | }); 78 | if (e.isCancelled()) { 79 | return; 80 | } 81 | toRemove.forEach(manager::remove); 82 | } 83 | 84 | @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) 85 | public void onCombust(BlockBurnEvent e) { 86 | DataBlock db = manager.getDataBlock(e.getBlock()); 87 | fireDestroy(db, e, DestroyCause.COMBUST); 88 | } 89 | 90 | @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) 91 | public void onPistonExtend(BlockPistonExtendEvent e) { 92 | handlePiston(e.getBlocks(), e); 93 | } 94 | 95 | @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) 96 | public void onPistonRetract(BlockPistonRetractEvent e) { 97 | handlePiston(e.getBlocks(), e); 98 | } 99 | 100 | @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) 101 | public void onEntityChangeBlock(EntityChangeBlockEvent e) { 102 | DataBlock db = manager.getDataBlock(e.getBlock()); 103 | fireDestroy(db, e, DestroyCause.ENTITY); 104 | } 105 | 106 | private void handlePiston(List blocks, BlockPistonEvent e) { 107 | List toMove = new ArrayList<>(); 108 | blocks.forEach(b -> { 109 | DataBlock db = manager.getDataBlock(b); 110 | if (db == null) { 111 | return; 112 | } 113 | Block destination = db.getBlock().getRelative(e.getDirection()); 114 | DataBlockMoveEvent ev = new DataBlockMoveEvent(db, destination, e); 115 | Bukkit.getPluginManager().callEvent(ev); 116 | if (!ev.isCancelled()) { 117 | toMove.add(db); 118 | } 119 | }); 120 | if (e.isCancelled()) { 121 | return; 122 | } 123 | Map moved = new HashMap<>(); 124 | toMove.forEach(db -> { 125 | Block destination = db.getBlock().getRelative(e.getDirection()); 126 | moved.put(destination, db.data); 127 | }); 128 | toMove.forEach(manager::remove); 129 | moved.forEach((block, data) -> { 130 | manager.getDataBlock(block).data = data; 131 | }); 132 | } 133 | 134 | } 135 | -------------------------------------------------------------------------------- /src/redempt/redlib/blockdata/BlockPosition.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.blockdata; 2 | 3 | import org.bukkit.Location; 4 | import org.bukkit.World; 5 | import org.bukkit.block.Block; 6 | 7 | import java.util.Objects; 8 | 9 | class BlockPosition { 10 | 11 | private final int x; 12 | private final int y; 13 | private final int z; 14 | 15 | public BlockPosition(int x, int y, int z) { 16 | this.x = x; 17 | this.y = y; 18 | this.z = z; 19 | } 20 | 21 | public BlockPosition(Block block) { 22 | this(block.getX(), block.getY(), block.getZ()); 23 | } 24 | 25 | public int getX() { 26 | return x; 27 | } 28 | 29 | public int getY() { 30 | return y; 31 | } 32 | 33 | public int getZ() { 34 | return z; 35 | } 36 | 37 | public Block getBlock(World world) { 38 | return world.getBlockAt(x, y, z); 39 | } 40 | 41 | public Location getLocation(World world) { 42 | return new Location(world, x, y, z); 43 | } 44 | 45 | @Override 46 | public int hashCode() { 47 | return Objects.hash(x, y, z); 48 | } 49 | 50 | @Override 51 | public boolean equals(Object o) { 52 | if (!(o instanceof BlockPosition)) { 53 | return false; 54 | } 55 | BlockPosition pos = (BlockPosition) o; 56 | return pos.x == x && pos.y == y && pos.z == z; 57 | } 58 | 59 | @Override 60 | public String toString() { 61 | return x + " " + y + " " + z; 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /src/redempt/redlib/blockdata/ChunkPosition.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.blockdata; 2 | 3 | import org.bukkit.Bukkit; 4 | import org.bukkit.Chunk; 5 | import org.bukkit.World; 6 | import org.bukkit.block.Block; 7 | 8 | import java.util.Objects; 9 | 10 | /** 11 | * Represents a world and chunk X and Z 12 | * 13 | * @author Redempt 14 | */ 15 | public class ChunkPosition { 16 | 17 | private final int x; 18 | private final int z; 19 | private final String world; 20 | 21 | /** 22 | * Creates a ChunkPosition from a chunk 23 | * 24 | * @param chunk The chunk to create a position for 25 | */ 26 | public ChunkPosition(Chunk chunk) { 27 | this(chunk.getX(), chunk.getZ(), chunk.getWorld().getName()); 28 | } 29 | 30 | /** 31 | * Creates a ChunkPosition from a Block 32 | * 33 | * @param block The Block to create a position for 34 | */ 35 | public ChunkPosition(Block block) { 36 | this(new BlockPosition(block), block.getWorld().getName()); 37 | } 38 | 39 | /** 40 | * Creates a ChunkPosition from chunk coordinates and a world name 41 | * 42 | * @param x The chunk X 43 | * @param z The chunk Z 44 | * @param world The world name 45 | */ 46 | public ChunkPosition(int x, int z, String world) { 47 | this.x = x; 48 | this.z = z; 49 | this.world = world; 50 | } 51 | 52 | public ChunkPosition(BlockPosition bPos, String world) { 53 | this(bPos.getX() >> 4, bPos.getZ() >> 4, world); 54 | } 55 | 56 | /** 57 | * @return The chunk X 58 | */ 59 | public int getX() { 60 | return x; 61 | } 62 | 63 | /** 64 | * @return The chunk Z 65 | */ 66 | public int getZ() { 67 | return z; 68 | } 69 | 70 | /** 71 | * @return The world this ChunkPosition is in 72 | */ 73 | public World getWorld() { 74 | return Bukkit.getWorld(world); 75 | } 76 | 77 | /** 78 | * @return The name of the world this ChunkPosition is in 79 | */ 80 | public String getWorldName() { 81 | return world; 82 | } 83 | 84 | /** 85 | * Gets the chunk 86 | * 87 | * @return The chunk at this position 88 | * @throws IllegalStateException if the world is not loaded 89 | */ 90 | public Chunk getChunk() { 91 | World world = this.getWorld(); 92 | if (world == null) { 93 | throw new IllegalStateException("World " + this.world + " is not loaded"); 94 | } 95 | return world.getChunkAt(x, z); 96 | } 97 | 98 | @Override 99 | public int hashCode() { 100 | return Objects.hash(x, z, world); 101 | } 102 | 103 | @Override 104 | public String toString() { 105 | return world + " " + x + " " + z; 106 | } 107 | 108 | @Override 109 | public boolean equals(Object o) { 110 | if (!(o instanceof ChunkPosition)) { 111 | return false; 112 | } 113 | ChunkPosition pos = (ChunkPosition) o; 114 | return pos.x == x && pos.z == z && world.equals(pos.world); 115 | } 116 | 117 | } 118 | -------------------------------------------------------------------------------- /src/redempt/redlib/blockdata/DataBlock.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.blockdata; 2 | 3 | import org.bukkit.Bukkit; 4 | import org.bukkit.block.Block; 5 | import redempt.redlib.json.JSONList; 6 | import redempt.redlib.json.JSONMap; 7 | 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | import java.util.Set; 11 | 12 | /** 13 | * Represents a Block with data attached to it 14 | * 15 | * @author Redempt 16 | */ 17 | public class DataBlock { 18 | 19 | protected JSONMap data; 20 | private BlockDataManager manager; 21 | private BlockPosition block; 22 | private String world; 23 | private Map transientProperties; 24 | 25 | DataBlock(JSONMap data, BlockPosition block, String world, BlockDataManager manager) { 26 | this.data = data; 27 | this.block = block; 28 | this.manager = manager; 29 | this.world = world; 30 | } 31 | 32 | /** 33 | * @return The BlockDataManager this DataBlock belongs to 34 | */ 35 | public BlockDataManager getManager() { 36 | return manager; 37 | } 38 | 39 | /** 40 | * @return A map which can be used to store properties that do not persist 41 | */ 42 | public Map getTransientProperties() { 43 | if (transientProperties == null) { 44 | transientProperties = new HashMap<>(); 45 | } 46 | return transientProperties; 47 | } 48 | 49 | /** 50 | * @return The Block the data is attached to 51 | */ 52 | public Block getBlock() { 53 | return Bukkit.getWorld(world).getBlockAt(block.getX(), block.getY(), block.getZ()); 54 | } 55 | 56 | protected ChunkPosition getChunkPosition() { 57 | return new ChunkPosition(block, world); 58 | } 59 | 60 | protected BlockPosition getBlockPosition() { 61 | return block; 62 | } 63 | 64 | /** 65 | * Gets an object by key 66 | * 67 | * @param key The key the data is mapped to 68 | * @return The data as an Object 69 | */ 70 | public Object getObject(String key) { 71 | return data.get(key); 72 | } 73 | 74 | /** 75 | * Gets a string by key 76 | * 77 | * @param key The key the data is mapped to 78 | * @return The data as a String 79 | */ 80 | public String getString(String key) { 81 | return data.getString(key); 82 | } 83 | 84 | /** 85 | * Gets an int by key 86 | * 87 | * @param key The key the data is mapped to 88 | * @return The data as an Integer 89 | */ 90 | public Integer getInt(String key) { 91 | return data.getInt(key); 92 | } 93 | 94 | /** 95 | * Gets a long by key 96 | * 97 | * @param key The key the data is mapped to 98 | * @return The data as a Long 99 | */ 100 | public Long getLong(String key) { 101 | return data.getLong(key); 102 | } 103 | 104 | /** 105 | * Gets a Double by key 106 | * 107 | * @param key The key the data is mapped to 108 | * @return The data as a Double 109 | */ 110 | public Double getDouble(String key) { 111 | return data.getDouble(key); 112 | } 113 | 114 | /** 115 | * Gets a Boolean by key 116 | * 117 | * @param key The key the data is mapped to 118 | * @return The data as a Boolean 119 | */ 120 | public Boolean getBoolean(String key) { 121 | return data.getBoolean(key); 122 | } 123 | 124 | /** 125 | * Gets a JSONList by key 126 | * 127 | * @param key The key the data is mapped to 128 | * @return The data as a JSONList 129 | */ 130 | public JSONList getList(String key) { 131 | return data.getList(key); 132 | } 133 | 134 | /** 135 | * Gets a JSONMap by key 136 | * 137 | * @param key The key the data is mapped to 138 | * @return The data as a JSONMap 139 | */ 140 | public JSONMap getMap(String key) { 141 | return data.getMap(key); 142 | } 143 | 144 | /** 145 | * Checks if a certain key is used in this DataBlock 146 | * 147 | * @param key The key 148 | * @return Whether the key is used 149 | */ 150 | public boolean contains(String key) { 151 | return data.containsKey(key); 152 | } 153 | 154 | /** 155 | * Clears all data from this DataBlock 156 | */ 157 | public void clear() { 158 | data.clear(); 159 | } 160 | 161 | /** 162 | * Sets data in this DataBlock 163 | * 164 | * @param key The key to set the data with 165 | * @param value The data 166 | */ 167 | public void set(String key, Object value) { 168 | manager.setModified(new ChunkPosition(block, world)); 169 | if (value == null) { 170 | data.remove(key); 171 | return; 172 | } 173 | data.put(key, value); 174 | } 175 | 176 | /** 177 | * Removes a key from this DataBlock 178 | * 179 | * @param key The key to remove 180 | */ 181 | public void remove(String key) { 182 | set(key, null); 183 | } 184 | 185 | /** 186 | * @return All data stored in this DataBlock 187 | */ 188 | public Map getData() { 189 | return data; 190 | } 191 | 192 | /** 193 | * @return All keys used in this DataBlock 194 | */ 195 | public Set getKeys() { 196 | return data.keySet(); 197 | } 198 | 199 | } 200 | -------------------------------------------------------------------------------- /src/redempt/redlib/blockdata/backend/BlockDataBackend.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.blockdata.backend; 2 | 3 | import org.bukkit.plugin.Plugin; 4 | import redempt.redlib.blockdata.BlockDataManager; 5 | import redempt.redlib.blockdata.ChunkPosition; 6 | 7 | import java.nio.file.Path; 8 | import java.util.Map; 9 | import java.util.concurrent.CompletableFuture; 10 | 11 | /** 12 | * Represents a data backend for a BlockDataManager 13 | * 14 | * @author Redempt 15 | */ 16 | public interface BlockDataBackend { 17 | 18 | /** 19 | * Creates a new BlockDataBackend backed by PersistentDataContainer 20 | * 21 | * @param plugin The plugin that owns the BlockDataManager 22 | * @return The BlockDataBackend 23 | */ 24 | public static BlockDataBackend pdc(Plugin plugin) { 25 | return new PDCBackend(plugin); 26 | } 27 | 28 | /** 29 | * Creates a new BlockDataBackend backed by SQLite 30 | * 31 | * @param path The path to the SQLite database 32 | * @return The BlockDataBackend 33 | */ 34 | public static BlockDataBackend sqlite(Path path) { 35 | return new SQLiteBackend(path); 36 | } 37 | 38 | /** 39 | * Loads the String data for a given chunk 40 | * 41 | * @param pos The location of the chunk 42 | * @return A CompletableFuture with the String data 43 | */ 44 | public CompletableFuture load(ChunkPosition pos); 45 | 46 | /** 47 | * Saves String data for a given chunk 48 | * 49 | * @param pos The location of the chunk 50 | * @param data The data to save 51 | * @return A CompletableFuture for the saving task 52 | */ 53 | public CompletableFuture save(ChunkPosition pos, String data); 54 | 55 | /** 56 | * Removes the data attached to a given chunk 57 | * 58 | * @param pos The location of the chunk 59 | * @return A CompletableFuture for the removal task 60 | */ 61 | public CompletableFuture remove(ChunkPosition pos); 62 | 63 | /** 64 | * Saves all data that has been modified with this BlockDataBackend 65 | * 66 | * @return A CompletableFuture for the saving task 67 | */ 68 | public CompletableFuture saveAll(); 69 | 70 | /** 71 | * Closes and cleans up any connections if needed 72 | * 73 | * @return A CompletableFuture for the closing task 74 | */ 75 | public CompletableFuture close(); 76 | 77 | /** 78 | * Attempts to load all data stored in the backend, not supported by PDC 79 | * 80 | * @return A CompletableFuture with all the data 81 | */ 82 | public CompletableFuture> loadAll(); 83 | 84 | /** 85 | * Attempts to migrate SQLite from an older schema used by the previous BlockDataManager library 86 | * 87 | * @param manager The BlockDataManager 88 | * @return Whether a migration was performed successfully 89 | */ 90 | public boolean attemptMigration(BlockDataManager manager); 91 | 92 | } 93 | -------------------------------------------------------------------------------- /src/redempt/redlib/blockdata/backend/PDCBackend.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.blockdata.backend; 2 | 3 | import org.bukkit.NamespacedKey; 4 | import org.bukkit.persistence.PersistentDataContainer; 5 | import org.bukkit.persistence.PersistentDataType; 6 | import org.bukkit.plugin.Plugin; 7 | import redempt.redlib.blockdata.BlockDataManager; 8 | import redempt.redlib.blockdata.ChunkPosition; 9 | 10 | import java.util.Map; 11 | import java.util.concurrent.CompletableFuture; 12 | 13 | class PDCBackend implements BlockDataBackend { 14 | 15 | private NamespacedKey key; 16 | 17 | public PDCBackend(Plugin plugin) { 18 | key = new NamespacedKey(plugin, "blockData"); 19 | } 20 | 21 | @Override 22 | public CompletableFuture load(ChunkPosition pos) { 23 | PersistentDataContainer pdc = pos.getWorld().getChunkAt(pos.getX(), pos.getZ()).getPersistentDataContainer(); 24 | return CompletableFuture.completedFuture(pdc.get(key, PersistentDataType.STRING)); 25 | } 26 | 27 | @Override 28 | public CompletableFuture save(ChunkPosition pos, String data) { 29 | PersistentDataContainer pdc = pos.getWorld().getChunkAt(pos.getX(), pos.getZ()).getPersistentDataContainer(); 30 | pdc.set(key, PersistentDataType.STRING, data); 31 | return CompletableFuture.completedFuture(null); 32 | } 33 | 34 | @Override 35 | public CompletableFuture remove(ChunkPosition pos) { 36 | PersistentDataContainer pdc = pos.getWorld().getChunkAt(pos.getX(), pos.getZ()).getPersistentDataContainer(); 37 | pdc.remove(key); 38 | return CompletableFuture.completedFuture(null); 39 | } 40 | 41 | @Override 42 | public CompletableFuture saveAll() { 43 | return CompletableFuture.completedFuture(null); 44 | } 45 | 46 | @Override 47 | public CompletableFuture close() { 48 | return CompletableFuture.completedFuture(null); 49 | } 50 | 51 | @Override 52 | public CompletableFuture> loadAll() { 53 | throw new UnsupportedOperationException("PDC backend cannot access all data blocks"); 54 | } 55 | 56 | @Override 57 | public boolean attemptMigration(BlockDataManager manager) { 58 | return false; 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/redempt/redlib/blockdata/backend/SQLiteBackend.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.blockdata.backend; 2 | 3 | import org.bukkit.block.Block; 4 | import redempt.redlib.blockdata.BlockDataManager; 5 | import redempt.redlib.blockdata.ChunkPosition; 6 | import redempt.redlib.blockdata.DataBlock; 7 | import redempt.redlib.json.JSONMap; 8 | import redempt.redlib.json.JSONParser; 9 | import redempt.redlib.misc.LocationUtils; 10 | import redempt.redlib.sql.SQLHelper; 11 | import redempt.redlib.sql.SQLHelper.Results; 12 | 13 | import java.io.IOException; 14 | import java.nio.file.Files; 15 | import java.nio.file.Path; 16 | import java.nio.file.StandardCopyOption; 17 | import java.sql.DatabaseMetaData; 18 | import java.sql.ResultSet; 19 | import java.sql.SQLException; 20 | import java.util.HashMap; 21 | import java.util.Map; 22 | import java.util.concurrent.CompletableFuture; 23 | import java.util.concurrent.Executor; 24 | import java.util.concurrent.Executors; 25 | 26 | class SQLiteBackend implements BlockDataBackend { 27 | 28 | private SQLHelper helper; 29 | private Executor exec = Executors.newSingleThreadExecutor(); 30 | private Path path; 31 | 32 | public SQLiteBackend(Path path) { 33 | this.path = path; 34 | try { 35 | Files.createDirectories(path.getParent()); 36 | } catch (IOException e) { 37 | e.printStackTrace(); 38 | } 39 | helper = new SQLHelper(SQLHelper.openSQLite(path)); 40 | helper.execute("PRAGMA synchronous = OFF;"); 41 | helper.executeUpdate("CREATE TABLE IF NOT EXISTS data (x INT, z INT, world STRING, data TEXT, PRIMARY KEY (x, z, world));"); 42 | helper.setCommitInterval(5 * 20 * 60); 43 | } 44 | 45 | @Override 46 | public boolean attemptMigration(BlockDataManager manager) { 47 | try { 48 | DatabaseMetaData metadata = helper.getConnection().getMetaData(); 49 | ResultSet results = metadata.getTables(null, null, "blocks", null); 50 | if (!results.next()) { 51 | return false; 52 | } 53 | results.close(); 54 | Files.copy(path, path.getParent().resolve(path.getFileName() + "_old"), StandardCopyOption.REPLACE_EXISTING); 55 | helper.queryResults("SELECT x, y, z, world, data FROM blocks;").forEach(r -> { 56 | int x = r.get(1); 57 | int y = r.get(2); 58 | int z = r.get(3); 59 | String worldName = r.getString(4); 60 | String data = r.getString(5); 61 | LocationUtils.waitForWorld(worldName, world -> { 62 | Block block = world.getBlockAt(x, y, z); 63 | DataBlock db = manager.getDataBlock(block); 64 | JSONMap map = JSONParser.parseMap(data); 65 | map.keySet().forEach(k -> db.set(k, map.get(k))); 66 | }); 67 | }); 68 | helper.executeUpdate("DROP TABLE blocks;"); 69 | manager.save(); 70 | return true; 71 | } catch (SQLException | IOException e) { 72 | e.printStackTrace(); 73 | return false; 74 | } 75 | } 76 | 77 | @Override 78 | public CompletableFuture load(ChunkPosition pos) { 79 | return CompletableFuture.supplyAsync(() -> { 80 | return helper.querySingleResultString("SELECT data FROM data WHERE x=? AND z=? AND world=?", pos.getX(), pos.getZ(), pos.getWorld().getName()); 81 | }, exec); 82 | } 83 | 84 | @Override 85 | public CompletableFuture save(ChunkPosition pos, String data) { 86 | return CompletableFuture.runAsync(() -> { 87 | helper.executeUpdate("REPLACE INTO data VALUES (?, ?, ?, ?);", pos.getX(), pos.getZ(), pos.getWorld().getName(), data); 88 | }, exec); 89 | } 90 | 91 | @Override 92 | public CompletableFuture remove(ChunkPosition pos) { 93 | return CompletableFuture.runAsync(() -> { 94 | helper.executeUpdate("DELETE FROM data WHERE x=? AND z=? AND world=?;", pos.getX(), pos.getZ(), pos.getWorld().getName()); 95 | }, exec); 96 | } 97 | 98 | @Override 99 | public CompletableFuture saveAll() { 100 | return CompletableFuture.runAsync(() -> { 101 | helper.commit(); 102 | }, exec); 103 | } 104 | 105 | @Override 106 | public CompletableFuture close() { 107 | return CompletableFuture.runAsync(() -> { 108 | saveAll(); 109 | helper.close(); 110 | }, exec); 111 | } 112 | 113 | @Override 114 | public CompletableFuture> loadAll() { 115 | return CompletableFuture.supplyAsync(() -> { 116 | Results results = helper.queryResults("SELECT * FROM data;"); 117 | Map map = new HashMap<>(); 118 | results.forEach(r -> { 119 | int x = r.get(1); 120 | int z = r.get(2); 121 | String world = r.getString(3); 122 | ChunkPosition pos = new ChunkPosition(x, z, world); 123 | String data = r.getString(4); 124 | map.put(pos, data); 125 | }); 126 | return map; 127 | }, exec); 128 | } 129 | 130 | } 131 | -------------------------------------------------------------------------------- /src/redempt/redlib/blockdata/custom/CustomBlock.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.blockdata.custom; 2 | 3 | import org.bukkit.block.Block; 4 | import org.bukkit.event.player.PlayerInteractEvent; 5 | import redempt.redlib.blockdata.DataBlock; 6 | 7 | /** 8 | * Represents an instance of a CustomBlockType. Effectively a wrapper for DataBlock. 9 | * 10 | * @author Redempt 11 | */ 12 | public class CustomBlock { 13 | 14 | private CustomBlockType type; 15 | private DataBlock db; 16 | 17 | protected CustomBlock(CustomBlockType type, DataBlock db) { 18 | this.type = type; 19 | this.db = db; 20 | } 21 | 22 | /** 23 | * @return The DataBlock this CustomBlock wraps 24 | */ 25 | public DataBlock getDataBlock() { 26 | return db; 27 | } 28 | 29 | /** 30 | * @return The Block this CustomBlock is at 31 | */ 32 | public Block getBlock() { 33 | return db.getBlock(); 34 | } 35 | 36 | /** 37 | * @return The CustomBlockType which created this CustomBlock 38 | */ 39 | public CustomBlockType getType() { 40 | return type; 41 | } 42 | 43 | /** 44 | * Called when this CustomBlock is clicked. Does nothing by default, override to define custom behavior. 45 | * 46 | * @param e The event 47 | */ 48 | public void click(PlayerInteractEvent e) { 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/redempt/redlib/blockdata/custom/CustomBlockType.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.blockdata.custom; 2 | 3 | import org.bukkit.Material; 4 | import org.bukkit.block.Block; 5 | import org.bukkit.entity.Player; 6 | import org.bukkit.event.Listener; 7 | import org.bukkit.inventory.ItemStack; 8 | import org.bukkit.plugin.Plugin; 9 | import redempt.redlib.blockdata.BlockDataManager; 10 | import redempt.redlib.blockdata.DataBlock; 11 | 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | 15 | /** 16 | * Represents a type of a CustomBlock that can be set 17 | * 18 | * @param The type of the CustomBlock represented by this CustomBlockType 19 | * @author Redempt 20 | */ 21 | public abstract class CustomBlockType implements Listener { 22 | 23 | private BlockDataManager manager; 24 | private String typeName; 25 | 26 | /** 27 | * Construct a CustomBlockType with the type name. You should only call this if you don't use 28 | * {@link CustomBlockRegistry#registerAll(Plugin)} to load custom block types. 29 | * 30 | * @param typeName The name of this type 31 | */ 32 | public CustomBlockType(String typeName) { 33 | this.typeName = typeName; 34 | } 35 | 36 | /** 37 | * Checks whether the item given matches the item for this CustomBlockType 38 | * 39 | * @param item The ItemStack to check 40 | * @return Whether the item matches 41 | */ 42 | public boolean itemMatches(ItemStack item) { 43 | return item.hasItemMeta() && getBaseItemName().equals(item.getItemMeta().getDisplayName()); 44 | } 45 | 46 | /** 47 | * Called when this CustomBlockType is placed. Use it to initialize any fields that are needed. 48 | * 49 | * @param player The player who placed the CustomBlock 50 | * @param item The ItemStack in their hand when it was placed 51 | * @param block The CustomBlock storing the data 52 | */ 53 | public abstract void place(Player player, ItemStack item, T block); 54 | 55 | /** 56 | * Gets the item to be dropped when this block is mined 57 | * 58 | * @param block The CustomBlock that was mined 59 | * @return The ItemStack to drop 60 | */ 61 | public abstract ItemStack getItem(T block); 62 | 63 | public List getDrops(T block) { 64 | return new ArrayList<>(); 65 | } 66 | 67 | /** 68 | * @return A unique item name that the item for this CustomBlockType will have 69 | */ 70 | public abstract String getBaseItemName(); 71 | 72 | protected final void register(BlockDataManager manager) { 73 | this.manager = manager; 74 | } 75 | 76 | /** 77 | * Checks whether the type of a block matches this CustomBlockType. Always returns true by default. 78 | * 79 | * @param material The Material to check 80 | * @return Whether this Material matches the type for this CustomBlockType 81 | */ 82 | public boolean typeMatches(Material material) { 83 | return true; 84 | } 85 | 86 | /** 87 | * @return The name of this CustomBlockType 88 | */ 89 | public String getName() { 90 | return typeName; 91 | } 92 | 93 | /** 94 | * Defines a custom return for a class extending {@link CustomBlock} 95 | * 96 | * @param db The DataBlock to be passed to the constructor 97 | * @return The CustomBlock sub-class instance 98 | */ 99 | public T getCustom(DataBlock db) { 100 | return null; 101 | } 102 | 103 | /** 104 | * Gets a {@link CustomBlock} of this type at the given block 105 | * 106 | * @param block The Block to get the CustomBlock at 107 | * @return The CustomBlock of this type at this Block, or null if it is not present 108 | */ 109 | public final T get(Block block) { 110 | return get(manager.getDataBlock(block, false)); 111 | } 112 | 113 | /** 114 | * Gets a {@link CustomBlock} of this type from the given DataBlock 115 | * 116 | * @param db The DataBlock to get the CustomBlock at 117 | * @return The CustomBlock of this type represented by this DataBlock, or null if it is not present 118 | */ 119 | public final T get(DataBlock db) { 120 | if (db == null || !db.getString("custom-type").equals(typeName) || !typeMatches(db.getBlock().getType()) 121 | || !db.getManager().equals(manager)) { 122 | return null; 123 | } 124 | CustomBlock custom = getCustom(db); 125 | if (custom != null) { 126 | return (T) custom; 127 | } 128 | return (T) new CustomBlock(this, db); 129 | } 130 | 131 | /** 132 | * Initializes the placement of this CustomBlockType for the given Block. Does not change the block's vanilla type. 133 | * 134 | * @param block The block to initialize 135 | * @return The initialized CustomBlock 136 | */ 137 | public final T initialize(Block block) { 138 | DataBlock db = manager.getDataBlock(block); 139 | db.set("custom-type", typeName); 140 | return get(db); 141 | } 142 | 143 | } 144 | -------------------------------------------------------------------------------- /src/redempt/redlib/blockdata/events/CustomBlockPlaceEvent.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.blockdata.events; 2 | 3 | import org.bukkit.block.Block; 4 | import org.bukkit.entity.Player; 5 | import org.bukkit.event.Cancellable; 6 | import org.bukkit.event.HandlerList; 7 | import org.bukkit.event.block.BlockEvent; 8 | import org.bukkit.inventory.ItemStack; 9 | import redempt.redlib.blockdata.custom.CustomBlockType; 10 | 11 | /** 12 | * Called when a CustomBlock is placed by a Player 13 | */ 14 | public class CustomBlockPlaceEvent extends BlockEvent implements Cancellable { 15 | 16 | private static HandlerList handlers = new HandlerList(); 17 | 18 | public static HandlerList getHandlerList() { 19 | return handlers; 20 | } 21 | 22 | private boolean cancelled = false; 23 | private CustomBlockType type; 24 | private Player player; 25 | private ItemStack item; 26 | 27 | /** 28 | * Constructs a new CustomBlockPlaceEvent 29 | * 30 | * @param block The block that was placed 31 | * @param item The item used to break the block 32 | * @param type The type of CustomBlock that is being placed 33 | * @param player The Player that placed the block 34 | */ 35 | public CustomBlockPlaceEvent(Block block, ItemStack item, CustomBlockType type, Player player) { 36 | super(block); 37 | this.item = item; 38 | this.type = type; 39 | this.player = player; 40 | } 41 | 42 | /** 43 | * @return The Player that placed the CustomBlock 44 | */ 45 | public Player getPlayer() { 46 | return player; 47 | } 48 | 49 | /** 50 | * @return The item that was in the player's hand when this block was placed 51 | */ 52 | public ItemStack getItem() { 53 | return item; 54 | } 55 | 56 | /** 57 | * @return The CustomBlockType that is being placed 58 | */ 59 | public CustomBlockType getCustomBlockType() { 60 | return type; 61 | } 62 | 63 | @Override 64 | public boolean isCancelled() { 65 | return cancelled; 66 | } 67 | 68 | @Override 69 | public void setCancelled(boolean cancel) { 70 | this.cancelled = cancel; 71 | } 72 | 73 | @Override 74 | public HandlerList getHandlers() { 75 | return handlers; 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /src/redempt/redlib/blockdata/events/DataBlockDestroyEvent.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.blockdata.events; 2 | 3 | import org.bukkit.block.Block; 4 | import org.bukkit.event.Cancellable; 5 | import org.bukkit.event.Event; 6 | import org.bukkit.event.HandlerList; 7 | import org.bukkit.event.block.BlockExplodeEvent; 8 | import org.bukkit.event.entity.EntityExplodeEvent; 9 | import redempt.redlib.blockdata.DataBlock; 10 | 11 | /** 12 | * Called when a DataBlock is destroyed 13 | * 14 | * @author Redempt 15 | */ 16 | public class DataBlockDestroyEvent extends Event implements Cancellable { 17 | 18 | private static HandlerList handlers = new HandlerList(); 19 | 20 | public static HandlerList getHandlerList() { 21 | return handlers; 22 | } 23 | 24 | private Event parent; 25 | private boolean cancelled = false; 26 | private DataBlock db; 27 | private DestroyCause cause; 28 | 29 | /** 30 | * Creates a new BlockDataDestroyEvent 31 | * 32 | * @param db The DataBlock that was destroyed 33 | * @param parent The Event which caused this one 34 | * @param cause The cause of the DataBlock being destroyed 35 | */ 36 | public DataBlockDestroyEvent(DataBlock db, Event parent, DestroyCause cause) { 37 | this.db = db; 38 | this.parent = parent; 39 | this.cause = cause; 40 | } 41 | 42 | /** 43 | * Sets whether the data should be removed from the block 44 | * 45 | * @param cancelled True to cancel removal of data from the block, false otherwise 46 | */ 47 | @Override 48 | public void setCancelled(boolean cancelled) { 49 | this.cancelled = cancelled; 50 | } 51 | 52 | /** 53 | * Cancels the event which caused this one - meaning the block will not be destroyed 54 | */ 55 | public void cancelParent() { 56 | setCancelled(true); 57 | if (parent instanceof Cancellable) { 58 | ((Cancellable) parent).setCancelled(true); 59 | } 60 | if (parent instanceof BlockExplodeEvent) { 61 | BlockExplodeEvent e = (BlockExplodeEvent) parent; 62 | Block block = db.getBlock(); 63 | e.blockList().remove(db.getBlock()); 64 | if (!cancelled) { 65 | e.blockList().add(block); 66 | } 67 | } 68 | if (parent instanceof EntityExplodeEvent) { 69 | EntityExplodeEvent e = (EntityExplodeEvent) parent; 70 | Block block = db.getBlock(); 71 | e.blockList().remove(db.getBlock()); 72 | if (!cancelled) { 73 | e.blockList().add(block); 74 | } 75 | } 76 | } 77 | 78 | /** 79 | * @return The reason the DataBlock was destroyed 80 | */ 81 | public DestroyCause getCause() { 82 | return cause; 83 | } 84 | 85 | /** 86 | * @return The event which caused this one 87 | */ 88 | public Event getParent() { 89 | return parent; 90 | } 91 | 92 | /** 93 | * @return Whether this event is cancelled 94 | */ 95 | @Override 96 | public boolean isCancelled() { 97 | return cancelled; 98 | } 99 | 100 | @Override 101 | public HandlerList getHandlers() { 102 | return handlers; 103 | } 104 | 105 | /** 106 | * @return The DataBlock being removed 107 | */ 108 | public DataBlock getDataBlock() { 109 | return db; 110 | } 111 | 112 | /** 113 | * @return The Block being destroyed 114 | */ 115 | public Block getBlock() { 116 | return db.getBlock(); 117 | } 118 | 119 | public static enum DestroyCause { 120 | 121 | PLAYER_BREAK, 122 | COMBUST, 123 | EXPLOSION, 124 | ENTITY 125 | 126 | } 127 | 128 | } 129 | -------------------------------------------------------------------------------- /src/redempt/redlib/blockdata/events/DataBlockMoveEvent.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.blockdata.events; 2 | 3 | import org.bukkit.block.Block; 4 | import org.bukkit.event.Cancellable; 5 | import org.bukkit.event.Event; 6 | import org.bukkit.event.HandlerList; 7 | import redempt.redlib.blockdata.DataBlock; 8 | 9 | /** 10 | * Called when a DataBlock is moved by pistons 11 | * 12 | * @author Redempt 13 | */ 14 | public class DataBlockMoveEvent extends Event implements Cancellable { 15 | 16 | private static HandlerList handlers = new HandlerList(); 17 | 18 | public static HandlerList getHandlerList() { 19 | return handlers; 20 | } 21 | 22 | private DataBlock db; 23 | private Block destination; 24 | private Event parent; 25 | private boolean cancelled = false; 26 | 27 | /** 28 | * Creates a DataBlockMoveEvent 29 | * 30 | * @param db The DataBlock being moved 31 | * @param destination The Block it is being moved to 32 | * @param parent The event which caused this one 33 | */ 34 | public DataBlockMoveEvent(DataBlock db, Block destination, Event parent) { 35 | this.db = db; 36 | this.parent = parent; 37 | this.destination = destination; 38 | } 39 | 40 | /** 41 | * @return The event which caused this one 42 | */ 43 | public Event getParent() { 44 | return parent; 45 | } 46 | 47 | /** 48 | * @return The Block the data is being moved to 49 | */ 50 | public Block getDestination() { 51 | return destination; 52 | } 53 | 54 | /** 55 | * @return The DataBlock being moved 56 | */ 57 | public DataBlock getDataBlock() { 58 | return db; 59 | } 60 | 61 | public Block getBlock() { 62 | return db.getBlock(); 63 | } 64 | 65 | /** 66 | * Sets whether to move the data to the new Block 67 | * 68 | * @param cancelled True to cancel the moving of data, false otherwise 69 | */ 70 | public void setCancelled(boolean cancelled) { 71 | this.cancelled = cancelled; 72 | } 73 | 74 | /** 75 | * Cancels the blocks from being moved altogether 76 | */ 77 | public void cancelParent() { 78 | if (parent instanceof Cancellable) { 79 | ((Cancellable) parent).setCancelled(true); 80 | } 81 | } 82 | 83 | /** 84 | * @return Whether the event is cancelled 85 | */ 86 | public boolean isCancelled() { 87 | return cancelled; 88 | } 89 | 90 | @Override 91 | public HandlerList getHandlers() { 92 | return handlers; 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /src/redempt/redlib/config/ConfigField.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.config; 2 | 3 | import redempt.redlib.config.annotations.ConfigName; 4 | import redempt.redlib.config.instantiation.FieldSummary; 5 | 6 | import java.lang.reflect.Field; 7 | import java.util.List; 8 | 9 | /** 10 | * Wraps a Field and stores the name which should be used to store its value in config 11 | * 12 | * @author Redempt 13 | */ 14 | public class ConfigField { 15 | 16 | private Field field; 17 | private String name; 18 | private List comments; 19 | 20 | /** 21 | * Constructs a ConfigField from a field 22 | * 23 | * @param field The Field 24 | */ 25 | public ConfigField(Field field) { 26 | this.field = field; 27 | ConfigName annotation = field.getAnnotation(ConfigName.class); 28 | name = annotation == null ? field.getName() : annotation.value(); 29 | comments = FieldSummary.getComments(field); 30 | } 31 | 32 | /** 33 | * @return The wrapped Field 34 | */ 35 | public Field getField() { 36 | return field; 37 | } 38 | 39 | /** 40 | * Attemps to set the value of the field for the target object to the value 41 | * 42 | * @param target The target object 43 | * @param value The value 44 | */ 45 | public void set(Object target, Object value) { 46 | try { 47 | field.set(target, value); 48 | } catch (IllegalAccessException e) { 49 | e.printStackTrace(); 50 | } 51 | } 52 | 53 | /** 54 | * Attemps to set the field in a static context to the given value 55 | * 56 | * @param value The value 57 | */ 58 | public void set(Object value) { 59 | set(null, value); 60 | } 61 | 62 | /** 63 | * Attempts to get the field's value for a given object 64 | * 65 | * @param target The target object to get the value from 66 | * @return The value 67 | */ 68 | public Object get(Object target) { 69 | try { 70 | return field.get(target); 71 | } catch (IllegalAccessException e) { 72 | e.printStackTrace(); 73 | return null; 74 | } 75 | } 76 | 77 | /** 78 | * Attemps to get the value of the field in a static context 79 | * 80 | * @return The value 81 | */ 82 | public Object get() { 83 | return get(null); 84 | } 85 | 86 | /** 87 | * @return The name for the field that should be used to store config values 88 | */ 89 | public String getName() { 90 | return name; 91 | } 92 | 93 | /** 94 | * @return Comments which should be applied to the path in config 95 | */ 96 | public List getComments() { 97 | return comments; 98 | } 99 | 100 | /** 101 | * Sets the comments which should be applied to the path in config 102 | * 103 | * @param comments The comments which should be applied 104 | */ 105 | public void setComments(List comments) { 106 | this.comments = comments; 107 | } 108 | 109 | /** 110 | * Sets the name of this ConfigField 111 | * 112 | * @param name The name to set 113 | */ 114 | public void setName(String name) { 115 | this.name = name; 116 | } 117 | 118 | public int hashCode() { 119 | return field.hashCode(); 120 | } 121 | 122 | public boolean equals(Object o) { 123 | if (!(o instanceof ConfigField)) { 124 | return false; 125 | } 126 | ConfigField cf = (ConfigField) o; 127 | return cf.field.equals(field); 128 | } 129 | 130 | } 131 | -------------------------------------------------------------------------------- /src/redempt/redlib/config/ConfigType.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.config; 2 | 3 | import java.lang.reflect.Field; 4 | import java.lang.reflect.Type; 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | import java.util.Objects; 8 | import java.util.stream.Collectors; 9 | 10 | /** 11 | * Represents a type and its type parameters 12 | * 13 | * @param The base type 14 | */ 15 | public class ConfigType { 16 | 17 | private Class clazz; 18 | private List> componentTypes; 19 | 20 | /** 21 | * Creates a ConfigType from an arbitrary Type 22 | * 23 | * @param type The Type 24 | * @return A ConfigType representation of the Type 25 | */ 26 | public static ConfigType create(Type type) { 27 | if (type instanceof Class) { 28 | return create((Class) type, new ArrayList<>()); 29 | } 30 | return create(type.getTypeName()); 31 | } 32 | 33 | /** 34 | * Gets the ConfigType of a specific Field 35 | * 36 | * @param field The field 37 | * @return A ConfigType for the field 38 | */ 39 | public static ConfigType get(Field field) { 40 | return create(field.getGenericType()); 41 | } 42 | 43 | private static ConfigType create(String typeName) { 44 | try { 45 | int ind = typeName.indexOf('<'); 46 | if (ind == -1) { 47 | return new ConfigType<>(Class.forName(typeName)); 48 | } 49 | Class clazz = Class.forName(typeName.substring(0, ind)); 50 | List> componentTypes = splitOnComma(typeName, ind + 1, typeName.length() - 1).stream().map(ConfigType::create).collect(Collectors.toList()); 51 | return create(clazz, componentTypes); 52 | } catch (ClassNotFoundException e) { 53 | throw new IllegalArgumentException("All parameter types for config must be known at compiletime", e); 54 | } 55 | } 56 | 57 | private static ConfigType create(Class clazz, List> componentTypes) { 58 | return new ConfigType((Class) clazz, componentTypes); 59 | } 60 | 61 | private static List splitOnComma(String str, int start, int end) { 62 | int depth = 0; 63 | StringBuilder current = new StringBuilder(); 64 | List split = new ArrayList<>(); 65 | for (int i = start; i < end; i++) { 66 | char c = str.charAt(i); 67 | switch (c) { 68 | case '<': 69 | depth++; 70 | break; 71 | case '>': 72 | depth--; 73 | break; 74 | case ',': 75 | if (depth != 0) { 76 | break; 77 | } 78 | split.add(current.toString().trim()); 79 | current = new StringBuilder(); 80 | continue; 81 | } 82 | current.append(c); 83 | } 84 | String last = current.toString().trim(); 85 | if (last.length() != 0) { 86 | split.add(last); 87 | } 88 | return split; 89 | } 90 | 91 | /** 92 | * Constructs a ConfigType from a base class type and a list of component ConfigTypes 93 | * 94 | * @param clazz The base class type 95 | * @param componentTypes The component types 96 | */ 97 | public ConfigType(Class clazz, List> componentTypes) { 98 | this.clazz = clazz; 99 | this.componentTypes = componentTypes; 100 | } 101 | 102 | /** 103 | * Constructs a ConfigType with no component types 104 | * 105 | * @param clazz The class type 106 | */ 107 | public ConfigType(Class clazz) { 108 | this(clazz, new ArrayList<>()); 109 | } 110 | 111 | /** 112 | * @return The base type of this ConfigType 113 | */ 114 | public Class getType() { 115 | return clazz; 116 | } 117 | 118 | /** 119 | * @return A list of all component types of this ConfigType 120 | */ 121 | public List> getComponentTypes() { 122 | return componentTypes; 123 | } 124 | 125 | @Override 126 | public int hashCode() { 127 | return Objects.hash(clazz, componentTypes); 128 | } 129 | 130 | @Override 131 | public boolean equals(Object o) { 132 | if (!(o instanceof ConfigType)) { 133 | return false; 134 | } 135 | ConfigType type = (ConfigType) o; 136 | return type.clazz.equals(clazz) && type.componentTypes.equals(componentTypes); 137 | } 138 | 139 | @Override 140 | public String toString() { 141 | String str = clazz.getName(); 142 | if (componentTypes.size() > 0) { 143 | str += "<" + componentTypes.stream().map(ConfigType::toString).collect(Collectors.joining(", ")) + ">"; 144 | } 145 | return str; 146 | } 147 | 148 | } 149 | -------------------------------------------------------------------------------- /src/redempt/redlib/config/annotations/Comment.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.config.annotations; 2 | 3 | import java.lang.annotation.*; 4 | 5 | /** 6 | * Used to denote comments which should be applied to a config path. Only supported in 1.18.1+ 7 | * 8 | * @author Redempt 9 | */ 10 | @Retention(RetentionPolicy.RUNTIME) 11 | @Target({ElementType.FIELD, ElementType.PARAMETER}) 12 | @Repeatable(Comments.class) 13 | @Inherited 14 | public @interface Comment { 15 | 16 | String value(); 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/redempt/redlib/config/annotations/Comments.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.config.annotations; 2 | 3 | import java.lang.annotation.*; 4 | 5 | /** 6 | * A wrapper for multiple {@link Comment} annotations 7 | * 8 | * @author Redempt 9 | */ 10 | @Retention(RetentionPolicy.RUNTIME) 11 | @Target({ElementType.FIELD, ElementType.PARAMETER}) 12 | @Inherited 13 | public @interface Comments { 14 | 15 | Comment[] value(); 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/redempt/redlib/config/annotations/ConfigConstructor.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.config.annotations; 2 | 3 | import java.lang.annotation.*; 4 | 5 | /** 6 | * Denotes a constructor which can be used to initialize the object from config. 7 | * Parameter names in the constructor must match field names in the class. 8 | */ 9 | @Inherited 10 | @Target(ElementType.CONSTRUCTOR) 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @Documented 13 | public @interface ConfigConstructor { 14 | } 15 | -------------------------------------------------------------------------------- /src/redempt/redlib/config/annotations/ConfigMappable.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.config.annotations; 2 | 3 | import java.lang.annotation.*; 4 | 5 | /** 6 | * Indicates that a class can be automatically serialized to config 7 | * 8 | * @author Redempt 9 | */ 10 | @Target(ElementType.TYPE) 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @Documented 13 | public @interface ConfigMappable { 14 | } 15 | -------------------------------------------------------------------------------- /src/redempt/redlib/config/annotations/ConfigName.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.config.annotations; 2 | 3 | import java.lang.annotation.*; 4 | 5 | /** 6 | * Specifies the name that should be used to access and set a value in config 7 | * 8 | * @author Redempt 9 | */ 10 | @Target({ElementType.FIELD, ElementType.PARAMETER}) 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @Documented 13 | @Inherited 14 | public @interface ConfigName { 15 | 16 | String value(); 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/redempt/redlib/config/annotations/ConfigPath.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.config.annotations; 2 | 3 | import java.lang.annotation.*; 4 | 5 | /** 6 | * Indicates that a field should be populated with the last element of the path to its location in config 7 | * 8 | * @author Redempt 9 | */ 10 | @Target({ElementType.FIELD, ElementType.PARAMETER}) 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @Documented 13 | @Inherited 14 | public @interface ConfigPath { 15 | } 16 | -------------------------------------------------------------------------------- /src/redempt/redlib/config/annotations/ConfigPostInit.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.config.annotations; 2 | 3 | import java.lang.annotation.*; 4 | 5 | /** 6 | * Indicates that a method should be run when an object is deserialized from config 7 | * 8 | * @author Redempt 9 | */ 10 | @Target(ElementType.METHOD) 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @Documented 13 | @Inherited 14 | public @interface ConfigPostInit { 15 | } 16 | -------------------------------------------------------------------------------- /src/redempt/redlib/config/annotations/ConfigSubclassable.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.config.annotations; 2 | 3 | import java.lang.annotation.*; 4 | 5 | /** 6 | * Indicates that this type can be subclassed and stored in config, its type will be stored alongside its metadata 7 | */ 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target(ElementType.TYPE) 10 | @Documented 11 | @Inherited 12 | public @interface ConfigSubclassable { 13 | } 14 | -------------------------------------------------------------------------------- /src/redempt/redlib/config/conversion/CollectionConverter.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.config.conversion; 2 | 3 | import redempt.redlib.config.ConfigType; 4 | import redempt.redlib.config.ConversionManager; 5 | import redempt.redlib.config.data.DataHolder; 6 | import redempt.redlib.config.data.ListDataHolder; 7 | 8 | import java.util.*; 9 | import java.util.function.Supplier; 10 | 11 | /** 12 | * A converter which saves and loads contents of a collection 13 | * 14 | * @author Redempt 15 | */ 16 | public class CollectionConverter { 17 | 18 | private static Map, Supplier>> defaults; 19 | 20 | static { 21 | defaults = new HashMap<>(); 22 | defaults.put(List.class, ArrayList::new); 23 | defaults.put(Set.class, LinkedHashSet::new); 24 | defaults.put(Queue.class, ArrayDeque::new); 25 | } 26 | 27 | /** 28 | * Creates a collection converter 29 | * 30 | * @param manager The ConfigManager handling the data 31 | * @param collectionType The ConfigType of the collection with full generic info 32 | * @param The component type of the collection 33 | * @param The collection type 34 | * @return A collection converter 35 | */ 36 | public static > TypeConverter create(ConversionManager manager, ConfigType collectionType) { 37 | ConfigType componentType = collectionType.getComponentTypes().get(0); 38 | TypeConverter converter = (TypeConverter) manager.getConverter(componentType); 39 | return new TypeConverter() { 40 | @Override 41 | public T loadFrom(DataHolder section, String path, T currentValue) { 42 | if (currentValue == null) { 43 | currentValue = (T) defaults.get(collectionType.getType()).get(); 44 | } else { 45 | currentValue.clear(); 46 | } 47 | DataHolder newSection = path == null ? section : section.getList(path); 48 | if (newSection == null) { 49 | return null; 50 | } 51 | T collection = currentValue; 52 | newSection.getKeys().forEach(k -> { 53 | V obj = converter.loadFrom(newSection, k, null); 54 | collection.add(obj); 55 | }); 56 | return collection; 57 | } 58 | 59 | @Override 60 | public void saveTo(T t, DataHolder section, String path) { 61 | DataHolder newSection = new ListDataHolder(); 62 | int pos = 0; 63 | if (t != null) { 64 | for (V obj : t) { 65 | converter.saveTo(obj, newSection, String.valueOf(pos)); 66 | pos++; 67 | } 68 | } 69 | section.set(path, newSection); 70 | } 71 | }; 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /src/redempt/redlib/config/conversion/EnumConverter.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.config.conversion; 2 | 3 | /** 4 | * A converter which converts enum values 5 | * 6 | * @author Redempt 7 | */ 8 | public class EnumConverter { 9 | 10 | /** 11 | * Creates an enum converter 12 | * 13 | * @param clazz The enum class 14 | * @param The type 15 | * @return A StringConverter for the given enum type 16 | */ 17 | public static StringConverter create(Class clazz) { 18 | Class enumClass = (Class) clazz; 19 | return new StringConverter() { 20 | @Override 21 | public T fromString(String str) { 22 | return str == null ? null : (T) Enum.valueOf(enumClass, str); 23 | } 24 | 25 | @Override 26 | public String toString(T t) { 27 | return t == null ? null : t.name(); 28 | } 29 | }; 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/redempt/redlib/config/conversion/MapConverter.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.config.conversion; 2 | 3 | import redempt.redlib.config.ConfigType; 4 | import redempt.redlib.config.ConversionManager; 5 | import redempt.redlib.config.data.DataHolder; 6 | 7 | import java.util.LinkedHashMap; 8 | import java.util.List; 9 | import java.util.Map; 10 | 11 | /** 12 | * A converter which saves and loads contents of a map 13 | * 14 | * @author Redempt 15 | */ 16 | public class MapConverter { 17 | 18 | /** 19 | * Creates a MapConverter 20 | * 21 | * @param manager The ConversionManager handling converters 22 | * @param type The ConfigType of the map with complete generic information 23 | * @param The key type of the map 24 | * @param The value type of the map 25 | * @param The map type 26 | * @return A MapConverter for the given type 27 | */ 28 | public static > TypeConverter create(ConversionManager manager, ConfigType type) { 29 | List> types = type.getComponentTypes(); 30 | StringConverter keyConverter = (StringConverter) manager.getStringConverter(types.get(0)); 31 | TypeConverter valueConverter = (TypeConverter) manager.getConverter(types.get(1)); 32 | return new TypeConverter() { 33 | @Override 34 | public M loadFrom(DataHolder section, String path, M currentValue) { 35 | if (currentValue == null) { 36 | currentValue = (M) new LinkedHashMap<>(); 37 | } else { 38 | currentValue.clear(); 39 | } 40 | M map = currentValue; 41 | DataHolder newSection = path == null ? section : section.getSubsection(path); 42 | if (newSection == null) { 43 | return null; 44 | } 45 | newSection.getKeys().forEach(k -> { 46 | K key = keyConverter.fromString(k); 47 | V value = valueConverter.loadFrom(newSection, k, null); 48 | map.put(key, value); 49 | }); 50 | return map; 51 | } 52 | 53 | @Override 54 | public void saveTo(M m, DataHolder section, String path) { 55 | DataHolder newSection = path == null ? section : section.createSubsection(path); 56 | if (m == null) { 57 | return; 58 | } 59 | m.forEach((k, v) -> { 60 | String keyPath = keyConverter.toString(k); 61 | valueConverter.saveTo(v, newSection, keyPath); 62 | }); 63 | } 64 | }; 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /src/redempt/redlib/config/conversion/NativeConverter.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.config.conversion; 2 | 3 | import redempt.redlib.config.data.DataHolder; 4 | 5 | /** 6 | * A converter which saves and loads directly to config without modifying the data 7 | * 8 | * @author Redempt 9 | */ 10 | public class NativeConverter { 11 | 12 | /** 13 | * Creates a native type converter 14 | * 15 | * @param The type 16 | * @return A native type converter 17 | */ 18 | public static TypeConverter create() { 19 | return new TypeConverter() { 20 | @Override 21 | public T loadFrom(DataHolder section, String path, T currentValue) { 22 | return (T) section.get(path); 23 | } 24 | 25 | @Override 26 | public void saveTo(T t, DataHolder section, String path) { 27 | section.set(path, t); 28 | } 29 | }; 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/redempt/redlib/config/conversion/ObjectConverter.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.config.conversion; 2 | 3 | import redempt.redlib.config.ConfigField; 4 | import redempt.redlib.config.ConfigType; 5 | import redempt.redlib.config.ConversionManager; 6 | import redempt.redlib.config.data.DataHolder; 7 | import redempt.redlib.config.instantiation.FieldSummary; 8 | import redempt.redlib.config.instantiation.Instantiator; 9 | 10 | import java.lang.reflect.Modifier; 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | /** 15 | * A converter which builds objects from configuration sections 16 | * 17 | * @author Redempt 18 | */ 19 | public class ObjectConverter { 20 | 21 | /** 22 | * Creates an object converter 23 | * 24 | * @param manager The ConversionManager handling converters 25 | * @param type The config type to convert 26 | * @param The type 27 | * @return An object converter for the given type 28 | */ 29 | public static TypeConverter create(ConversionManager manager, ConfigType type) { 30 | if (type.getType().isInterface() || Modifier.isAbstract(type.getType().getModifiers())) { 31 | throw new IllegalStateException("Cannot automatically convert abstract classe or interface " + type.getType()); 32 | } 33 | FieldSummary summary = FieldSummary.getFieldSummary(manager, type.getType(), false); 34 | Instantiator instantiator = Instantiator.getInstantiator(type.getType()); 35 | return new TypeConverter() { 36 | @Override 37 | public T loadFrom(DataHolder section, String path, T currentValue) { 38 | DataHolder newSection = path == null ? section : section.getSubsection(path); 39 | List objs = new ArrayList<>(summary.getFields().size()); 40 | for (ConfigField field : summary.getFields()) { 41 | Object value = summary.getConverters().get(field).loadFrom(newSection, field.getName(), null); 42 | objs.add(value); 43 | } 44 | return (T) instantiator.instantiate(manager, currentValue, type.getType(), objs, path, summary); 45 | } 46 | 47 | @Override 48 | public void saveTo(T t, DataHolder section, String path) { 49 | saveTo(t, section, path, true); 50 | } 51 | 52 | @Override 53 | public void saveTo(T t, DataHolder section, String path, boolean overwrite) { 54 | if (path != null && section.isSet(path) && !overwrite) { 55 | return; 56 | } 57 | DataHolder newSection = path == null ? section : section.createSubsection(path); 58 | if (t == null) { 59 | return; 60 | } 61 | for (ConfigField field : summary.getFields()) { 62 | saveWith(summary.getConverters().get(field), field.get(t), newSection, field.getName(), overwrite); 63 | } 64 | summary.applyComments(newSection); 65 | } 66 | }; 67 | } 68 | 69 | private static void saveWith(TypeConverter converter, Object obj, DataHolder section, String path, boolean overwrite) { 70 | converter.saveTo((T) obj, section, path, overwrite); 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /src/redempt/redlib/config/conversion/PrimitiveConverter.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.config.conversion; 2 | 3 | import redempt.redlib.config.data.DataHolder; 4 | 5 | import java.util.function.Function; 6 | 7 | /** 8 | * A converter which can convert to and from strings, but saves values directly instead of as strings 9 | * 10 | * @author Redempt 11 | */ 12 | public class PrimitiveConverter { 13 | 14 | /** 15 | * Creates a StringConverter which saves values directly rather than as strings 16 | * 17 | * @param loader The function to convert from strings 18 | * @param saver The function to convert to strings 19 | * @param The type to convert 20 | * @return The converter 21 | */ 22 | public static StringConverter create(Function loader, Function saver) { 23 | return new StringConverter() { 24 | @Override 25 | public T fromString(String str) { 26 | return loader.apply(str); 27 | } 28 | 29 | @Override 30 | public String toString(T t) { 31 | return saver.apply(t); 32 | } 33 | 34 | @Override 35 | public void saveTo(T t, DataHolder section, String path) { 36 | section.set(path, t); 37 | } 38 | }; 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/redempt/redlib/config/conversion/StaticRootConverter.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.config.conversion; 2 | 3 | import redempt.redlib.config.ConfigField; 4 | import redempt.redlib.config.ConversionManager; 5 | import redempt.redlib.config.data.DataHolder; 6 | import redempt.redlib.config.instantiation.FieldSummary; 7 | 8 | /** 9 | * A converter which saves to and loads from static fields and can only be used as a config target 10 | * 11 | * @author Redempt 12 | */ 13 | public class StaticRootConverter { 14 | 15 | /** 16 | * Creates a static root converter 17 | * 18 | * @param manager The ConfigManager handling the data 19 | * @param root The class to save and load from non-transient static fields of 20 | * @param The type 21 | * @return A static root converter 22 | */ 23 | public static TypeConverter create(ConversionManager manager, Class root) { 24 | FieldSummary summary = FieldSummary.getFieldSummary(manager, root, true); 25 | return new TypeConverter() { 26 | @Override 27 | public T loadFrom(DataHolder section, String path, T currentValue) { 28 | for (ConfigField field : summary.getFields()) { 29 | Object val = summary.getConverters().get(field).loadFrom(section, field.getName(), null); 30 | field.set(val); 31 | } 32 | return null; 33 | } 34 | 35 | @Override 36 | public void saveTo(T t, DataHolder section, String path) { 37 | saveTo(t, section, path, true); 38 | } 39 | 40 | @Override 41 | public void saveTo(T t, DataHolder section, String path, boolean overwrite) { 42 | for (ConfigField field : summary.getFields()) { 43 | Object obj = field.get(); 44 | saveWith(summary.getConverters().get(field), obj, section, field.getName(), overwrite); 45 | } 46 | summary.applyComments(section); 47 | } 48 | }; 49 | } 50 | 51 | private static void saveWith(TypeConverter converter, Object obj, DataHolder section, String path, boolean overwrite) { 52 | converter.saveTo((T) obj, section, path, overwrite); 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/redempt/redlib/config/conversion/StringConverter.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.config.conversion; 2 | 3 | import redempt.redlib.config.data.DataHolder; 4 | 5 | import java.util.function.Function; 6 | 7 | /** 8 | * A TypeConverter which can convert data directly to and from strings 9 | * 10 | * @param The type 11 | * @author Redempt 12 | */ 13 | public interface StringConverter extends TypeConverter { 14 | 15 | /** 16 | * Creates a StringConverter using a loading function and a saving function 17 | * 18 | * @param loader A loading function which can convert from a string to the type 19 | * @param saver A saving function which can convert from the type to a string 20 | * @param The type 21 | * @return The created StringConverter 22 | */ 23 | public static StringConverter create(Function loader, Function saver) { 24 | return new StringConverter() { 25 | @Override 26 | public T fromString(String str) { 27 | return loader.apply(str); 28 | } 29 | 30 | @Override 31 | public String toString(T t) { 32 | return saver.apply(t); 33 | } 34 | }; 35 | } 36 | 37 | /** 38 | * Converts from a string 39 | * 40 | * @param str The string 41 | * @return The resulting object 42 | */ 43 | public T fromString(String str); 44 | 45 | /** 46 | * Converts to a string 47 | * 48 | * @param t The object to convert 49 | * @return The string representation 50 | */ 51 | public String toString(T t); 52 | 53 | /** 54 | * @param section The ConfigurationSection to load from 55 | * @param path The path to the data in the ConfigurationSection 56 | * @param currentValue The current value, used for collections and maps 57 | * @return 58 | */ 59 | @Override 60 | public default T loadFrom(DataHolder section, String path, T currentValue) { 61 | String str = section.getString(path); 62 | return str == null ? null : fromString(str); 63 | } 64 | 65 | /** 66 | * @param t The object to save 67 | * @param section The ConfigurationSection to save to 68 | * @param path The path to the data that should be saved in the ConfigurationSection 69 | */ 70 | @Override 71 | public default void saveTo(T t, DataHolder section, String path) { 72 | section.set(path, t == null ? null : toString(t)); 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /src/redempt/redlib/config/conversion/SubclassConverter.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.config.conversion; 2 | 3 | import redempt.redlib.config.ConfigType; 4 | import redempt.redlib.config.ConversionManager; 5 | import redempt.redlib.config.data.DataHolder; 6 | 7 | /** 8 | * A converter which can convert subclasses of mappable classes 9 | * 10 | * @author Redempt 11 | */ 12 | public class SubclassConverter { 13 | 14 | /** 15 | * Creates a TypeConverter that can convert subclasses 16 | * 17 | * @param manager The ConversionManager handling converters 18 | * @param clazz The class to handle subclasses of 19 | * @param isAbstract Whether the class is abstract or an interface 20 | * @param The type 21 | * @return The converter 22 | */ 23 | public static TypeConverter create(ConversionManager manager, Class clazz, boolean isAbstract) { 24 | TypeConverter parent = !isAbstract ? ObjectConverter.create(manager, new ConfigType<>(clazz)) : null; 25 | return new TypeConverter() { 26 | @Override 27 | public T loadFrom(DataHolder section, String path, T currentValue) { 28 | String typeName = section.getSubsection(path).getString("=type"); 29 | if (typeName == null) { 30 | throw new IllegalStateException("Could not determine subclass for object with path " + path); 31 | } 32 | Class type = manager.loadClass(typeName); 33 | if (!clazz.isAssignableFrom(type)) { 34 | throw new IllegalStateException(type + " is not a subclass of " + clazz); 35 | } 36 | TypeConverter converter = type.equals(clazz) ? parent : (TypeConverter) manager.getConverter(new ConfigType<>(type)); 37 | return converter.loadFrom(section, path, currentValue); 38 | } 39 | 40 | @Override 41 | public void saveTo(T t, DataHolder section, String path) { 42 | Class type = t.getClass(); 43 | if (!clazz.isAssignableFrom(type)) { 44 | throw new IllegalStateException(type + " is not a subclass of " + clazz); 45 | } 46 | TypeConverter converter = type.equals(clazz) ? parent : (TypeConverter) manager.getConverter(new ConfigType<>(type)); 47 | converter.saveTo(t, section, path); 48 | section.getSubsection(path).set("=type", type.getName()); 49 | } 50 | }; 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/redempt/redlib/config/conversion/TypeConverter.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.config.conversion; 2 | 3 | import redempt.redlib.config.data.DataHolder; 4 | 5 | /** 6 | * An interface which converts data in config for a given type 7 | * 8 | * @param The type 9 | * @author Redempt 10 | */ 11 | public interface TypeConverter { 12 | 13 | /** 14 | * Attemps to load the object from config 15 | * 16 | * @param section The ConfigurationSection to load from 17 | * @param path The path to the data in the ConfigurationSection 18 | * @param currentValue The current value, used for collections and maps 19 | * @return The loaded object 20 | */ 21 | public T loadFrom(DataHolder section, String path, T currentValue); 22 | 23 | /** 24 | * Attemps to save the object to config 25 | * 26 | * @param t The object to save 27 | * @param section The ConfigurationSection to save to 28 | * @param path The path to the data that should be saved in the ConfigurationSection 29 | */ 30 | public void saveTo(T t, DataHolder section, String path); 31 | 32 | /** 33 | * Attemps to save the object to config 34 | * 35 | * @param t The object to save 36 | * @param section The ConfigurationSection to save to 37 | * @param path The path to the data that should be saved in the ConfigurationSection 38 | * @param overwrite Whether to overwrite existing data 39 | */ 40 | public default void saveTo(T t, DataHolder section, String path, boolean overwrite) { 41 | if (!overwrite && section.isSet(path)) { 42 | return; 43 | } 44 | saveTo(t, section, path); 45 | } 46 | 47 | } -------------------------------------------------------------------------------- /src/redempt/redlib/config/data/ConfigurationSectionDataHolder.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.config.data; 2 | 3 | import org.bukkit.configuration.ConfigurationSection; 4 | 5 | import java.util.HashMap; 6 | import java.util.List; 7 | import java.util.Map; 8 | import java.util.Set; 9 | 10 | public class ConfigurationSectionDataHolder implements DataHolder { 11 | 12 | private ConfigurationSection section; 13 | private Map> comments; 14 | 15 | public ConfigurationSectionDataHolder(ConfigurationSection section) { 16 | this(section, new HashMap<>()); 17 | } 18 | 19 | private ConfigurationSectionDataHolder(ConfigurationSection section, Map> comments) { 20 | this.section = section; 21 | this.comments = comments; 22 | } 23 | 24 | @Override 25 | public Object get(String path) { 26 | return section.get(path); 27 | } 28 | 29 | @Override 30 | public void set(String path, Object obj) { 31 | section.set(path, DataHolder.unwrap(obj)); 32 | } 33 | 34 | @Override 35 | public DataHolder getSubsection(String path) { 36 | ConfigurationSection subsection = section.getConfigurationSection(path); 37 | return subsection == null ? null : new ConfigurationSectionDataHolder(subsection, comments); 38 | } 39 | 40 | @Override 41 | public DataHolder createSubsection(String path) { 42 | return new ConfigurationSectionDataHolder(section.createSection(path), comments); 43 | } 44 | 45 | @Override 46 | public Set getKeys() { 47 | return section.getKeys(false); 48 | } 49 | 50 | @Override 51 | public boolean isSet(String path) { 52 | return section.isSet(path); 53 | } 54 | 55 | @Override 56 | public String getString(String path) { 57 | return section.getString(path); 58 | } 59 | 60 | @Override 61 | public DataHolder getList(String path) { 62 | return new ListDataHolder(section.getList(path)); 63 | } 64 | 65 | @Override 66 | public void remove(String path) { 67 | section.set(path, null); 68 | } 69 | 70 | @Override 71 | public Object unwrap() { 72 | return section; 73 | } 74 | 75 | public void clearComments() { 76 | comments.clear(); 77 | } 78 | 79 | public Map> getComments() { 80 | return comments; 81 | } 82 | 83 | @Override 84 | public void setComments(String path, List comments) { 85 | String key; 86 | String currentPath = section.getCurrentPath(); 87 | if (currentPath == null || currentPath.equals(".")) { 88 | key = path; 89 | } else { 90 | key = currentPath + "." + path; 91 | } 92 | this.comments.put(key, comments); 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /src/redempt/redlib/config/data/DataHolder.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.config.data; 2 | 3 | import java.util.List; 4 | import java.util.Set; 5 | 6 | /** 7 | * An arbitrary data structure which can map keys to values 8 | * 9 | * @author Redempt 10 | */ 11 | public interface DataHolder { 12 | 13 | /** 14 | * Unwraps the object a DataHolder wraps, if it is one 15 | * 16 | * @param obj The object 17 | * @return The unwrapped object, or the original object if it was not a DataHolder 18 | */ 19 | public static Object unwrap(Object obj) { 20 | if (obj instanceof DataHolder) { 21 | return ((DataHolder) obj).unwrap(); 22 | } 23 | return obj; 24 | } 25 | 26 | /** 27 | * Gets the object mapped to the given path 28 | * 29 | * @param path The path 30 | * @return The object mapped to the path 31 | */ 32 | public Object get(String path); 33 | 34 | /** 35 | * Sets the object at a given path 36 | * 37 | * @param path The path to the object 38 | * @param obj The object to set 39 | */ 40 | public void set(String path, Object obj); 41 | 42 | /** 43 | * Gets an existing subsection of this DataHolder 44 | * 45 | * @param path The path to the data 46 | * @return The subsection, or null 47 | */ 48 | public DataHolder getSubsection(String path); 49 | 50 | /** 51 | * Creates a subsection of this DataHolder 52 | * 53 | * @param path The path of the subsection to create 54 | * @return The created subsection 55 | */ 56 | public DataHolder createSubsection(String path); 57 | 58 | /** 59 | * @return All valid keys 60 | */ 61 | public Set getKeys(); 62 | 63 | /** 64 | * Checks whether a given path has a value associated 65 | * 66 | * @param path The path to check 67 | * @return Whether the path has an associated value 68 | */ 69 | public boolean isSet(String path); 70 | 71 | /** 72 | * Gets a string value from a path 73 | * 74 | * @param path The path to the string 75 | * @return The string 76 | */ 77 | public String getString(String path); 78 | 79 | /** 80 | * Gets a list subsection 81 | * 82 | * @param path The path to the subsection 83 | * @return The list subsection, or null 84 | */ 85 | public DataHolder getList(String path); 86 | 87 | /** 88 | * Removes a mapping 89 | * 90 | * @param path The path of the data to remove 91 | */ 92 | public void remove(String path); 93 | 94 | /** 95 | * Unwraps the object this DataHolder wraps 96 | * 97 | * @return The wrapped storage 98 | */ 99 | public Object unwrap(); 100 | 101 | /** 102 | * Sets comments on the given path, if it is supported 103 | * 104 | * @param path The path to apply comments to 105 | * @param comments The comments to apply 106 | */ 107 | public default void setComments(String path, List comments) { 108 | } 109 | 110 | } 111 | -------------------------------------------------------------------------------- /src/redempt/redlib/config/data/ListDataHolder.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.config.data; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.Map; 6 | import java.util.Set; 7 | import java.util.stream.Collectors; 8 | import java.util.stream.IntStream; 9 | 10 | public class ListDataHolder implements DataHolder { 11 | 12 | private List list; 13 | 14 | public ListDataHolder(List list) { 15 | this.list = (List) list; 16 | } 17 | 18 | public ListDataHolder() { 19 | this(new ArrayList<>()); 20 | } 21 | 22 | @Override 23 | public Object get(String path) { 24 | int index = Integer.parseInt(path); 25 | if (index < 0 || index >= list.size()) { 26 | return null; 27 | } 28 | return list.get(index); 29 | } 30 | 31 | @Override 32 | public void set(String path, Object obj) { 33 | int index = Integer.parseInt(path); 34 | obj = DataHolder.unwrap(obj); 35 | if (index >= list.size()) { 36 | list.add(obj); 37 | } else { 38 | list.set(index, obj); 39 | } 40 | } 41 | 42 | @Override 43 | public DataHolder getSubsection(String path) { 44 | Object obj = get(path); 45 | return obj instanceof Map ? new MapDataHolder((Map) obj) : null; 46 | } 47 | 48 | @Override 49 | public DataHolder createSubsection(String path) { 50 | int index = Integer.parseInt(path); 51 | MapDataHolder holder = new MapDataHolder(); 52 | if (index >= list.size()) { 53 | list.add(holder.unwrap()); 54 | } else { 55 | list.set(index, holder.unwrap()); 56 | } 57 | return holder; 58 | } 59 | 60 | @Override 61 | public Set getKeys() { 62 | return IntStream.range(0, list.size()).mapToObj(String::valueOf).collect(Collectors.toSet()); 63 | } 64 | 65 | @Override 66 | public boolean isSet(String path) { 67 | int index = Integer.parseInt(path); 68 | return index > 0 && index < list.size(); 69 | } 70 | 71 | @Override 72 | public String getString(String path) { 73 | Object val = get(path); 74 | return val == null ? null : String.valueOf(val); 75 | } 76 | 77 | @Override 78 | public DataHolder getList(String path) { 79 | Object obj = get(path); 80 | return obj instanceof List ? new ListDataHolder((List) obj) : null; 81 | } 82 | 83 | @Override 84 | public void remove(String path) { 85 | list.remove(Integer.parseInt(path)); 86 | } 87 | 88 | @Override 89 | public Object unwrap() { 90 | return list; 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /src/redempt/redlib/config/data/MapDataHolder.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.config.data; 2 | 3 | import java.util.HashMap; 4 | import java.util.List; 5 | import java.util.Map; 6 | import java.util.Set; 7 | 8 | public class MapDataHolder implements DataHolder { 9 | 10 | private Map map; 11 | 12 | public MapDataHolder(Map map) { 13 | this.map = map; 14 | } 15 | 16 | public MapDataHolder() { 17 | this(new HashMap<>()); 18 | } 19 | 20 | 21 | @Override 22 | public Object get(String path) { 23 | return map.get(path); 24 | } 25 | 26 | @Override 27 | public void set(String path, Object obj) { 28 | map.put(path, DataHolder.unwrap(obj)); 29 | } 30 | 31 | @Override 32 | public DataHolder getSubsection(String path) { 33 | Object obj = map.get(path); 34 | return obj instanceof Map ? new MapDataHolder((Map) obj) : null; 35 | } 36 | 37 | @Override 38 | public DataHolder createSubsection(String path) { 39 | MapDataHolder subsection = new MapDataHolder(); 40 | map.put(path, subsection.unwrap()); 41 | return subsection; 42 | } 43 | 44 | @Override 45 | public Set getKeys() { 46 | return map.keySet(); 47 | } 48 | 49 | @Override 50 | public boolean isSet(String path) { 51 | return map.containsKey(path); 52 | } 53 | 54 | @Override 55 | public String getString(String path) { 56 | Object val = get(path); 57 | return val == null ? null : String.valueOf(val); 58 | } 59 | 60 | @Override 61 | public DataHolder getList(String path) { 62 | Object obj = map.get(path); 63 | return obj instanceof List ? new ListDataHolder((List) obj) : null; 64 | } 65 | 66 | @Override 67 | public void remove(String path) { 68 | map.remove(path); 69 | } 70 | 71 | @Override 72 | public Object unwrap() { 73 | return map; 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /src/redempt/redlib/config/instantiation/ConstructorInstantiator.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.config.instantiation; 2 | 3 | import redempt.redlib.config.ConversionManager; 4 | import redempt.redlib.config.annotations.ConfigPath; 5 | 6 | import java.lang.reflect.Constructor; 7 | import java.lang.reflect.Field; 8 | import java.lang.reflect.InvocationTargetException; 9 | import java.lang.reflect.Parameter; 10 | import java.util.Arrays; 11 | import java.util.List; 12 | 13 | /** 14 | * An instantiator used for record types which passes in all necessary fields 15 | * 16 | * @author Redempt 17 | */ 18 | public class ConstructorInstantiator implements Instantiator { 19 | 20 | /** 21 | * Attempts to create an Instantiator for a record type, or a class which has a constructor taking all its fields 22 | * in the same order they appear in the class 23 | * 24 | * @param clazz The class to create an Instantiator for 25 | * @param The type 26 | * @return An Instantiator 27 | */ 28 | public static Instantiator createDefault(Class clazz) { 29 | try { 30 | Field[] fields = clazz.getDeclaredFields(); 31 | Constructor constructor = clazz.getDeclaredConstructor(Arrays.stream(fields).map(Field::getType).toArray(Class[]::new)); 32 | return new ConstructorInstantiator(constructor); 33 | } catch (NoSuchMethodException e) { 34 | throw new IllegalStateException("Class '" + clazz.getName() + "' does not have a constructor that takes all of its fields in order"); 35 | } 36 | } 37 | 38 | private Constructor constructor; 39 | private Parameter[] params; 40 | 41 | private ConstructorInstantiator(Constructor constructor) { 42 | this.constructor = constructor; 43 | params = constructor.getParameters(); 44 | } 45 | 46 | /** 47 | * Instantiates a new object using its constructor 48 | * 49 | * @param manager The ConversionManager handling converters 50 | * @param target The target object, always ignored by this type of Instantiator 51 | * @param clazz The class whose fields are being used 52 | * @param values The values for the fields 53 | * @param path The path in config 54 | * @param info Extra info about the instantiation 55 | * @param The type 56 | * @return The constructed object 57 | */ 58 | @Override 59 | public T instantiate(ConversionManager manager, Object target, Class clazz, List values, String path, FieldSummary info) { 60 | Object[] objs = new Object[params.length]; 61 | int valuePos = 0; 62 | for (int i = 0; i < params.length; i++) { 63 | Parameter param = params[i]; 64 | if (param.isAnnotationPresent(ConfigPath.class)) { 65 | objs[i] = info.getConfigPathConverter().fromString(path); 66 | continue; 67 | } 68 | objs[i] = values.get(valuePos); 69 | valuePos++; 70 | } 71 | try { 72 | return (T) constructor.newInstance(objs); 73 | } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { 74 | e.printStackTrace(); 75 | } 76 | return null; 77 | } 78 | 79 | 80 | } 81 | -------------------------------------------------------------------------------- /src/redempt/redlib/config/instantiation/EmptyInstantiator.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.config.instantiation; 2 | 3 | import redempt.redlib.config.ConversionManager; 4 | 5 | import java.lang.reflect.Constructor; 6 | import java.lang.reflect.InvocationTargetException; 7 | import java.util.List; 8 | 9 | /** 10 | * An Instantiator which uses an empty constructor, then populates fields and invokes a post-init method 11 | * 12 | * @author Redempt 13 | */ 14 | public class EmptyInstantiator implements Instantiator { 15 | 16 | /** 17 | * Creates an instance of a class if it has a no-args constructor 18 | * 19 | * @param clazz The class to instantiate 20 | * @param The type of the class 21 | * @return The instance 22 | */ 23 | public static T instantiate(Class clazz) { 24 | try { 25 | Constructor constructor = clazz.getDeclaredConstructor(); 26 | constructor.setAccessible(true); 27 | return (T) constructor.newInstance(); 28 | } catch (InstantiationException | InvocationTargetException | IllegalAccessException e) { 29 | e.printStackTrace(); 30 | return null; 31 | } catch (NoSuchMethodException e) { 32 | throw new IllegalStateException("Class must have a no-arg constructor", e); 33 | } 34 | } 35 | 36 | /** 37 | * Instantiates an object, loads values into its fields, and calls the post-init method 38 | * 39 | * @param manager The ConversionManager handling converters 40 | * @param target The object to load to, or null if creating a new one 41 | * @param clazz The class whose fields are being worked with 42 | * @param values The values for the fields 43 | * @param path The path in config 44 | * @param info Extra info used for post-instantiation steps 45 | * @param The type 46 | * @return The instantiated object, or the input object with its fields modified 47 | */ 48 | @Override 49 | public T instantiate(ConversionManager manager, Object target, Class clazz, List values, String path, FieldSummary info) { 50 | try { 51 | T t = target == null ? instantiate(clazz) : (T) target; 52 | for (int i = 0; i < info.getFields().size(); i++) { 53 | if (values.get(i) == null) { 54 | continue; 55 | } 56 | info.getFields().get(i).set(t, values.get(i)); 57 | } 58 | if (info.getConfigPath() != null) { 59 | Object pathValue = info.getConfigPathConverter().fromString(path); 60 | info.getConfigPath().set(t, pathValue); 61 | } 62 | if (info.getPostInit() != null) { 63 | info.getPostInit().invoke(t); 64 | } 65 | return t; 66 | } catch (IllegalAccessException | InvocationTargetException e) { 67 | e.printStackTrace(); 68 | return null; 69 | } 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /src/redempt/redlib/config/instantiation/Instantiator.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.config.instantiation; 2 | 3 | import redempt.redlib.config.ConversionManager; 4 | import redempt.redlib.config.annotations.ConfigConstructor; 5 | import redempt.redlib.config.annotations.ConfigMappable; 6 | 7 | import java.lang.reflect.Constructor; 8 | import java.util.Arrays; 9 | import java.util.List; 10 | import java.util.Optional; 11 | 12 | /** 13 | * A utility to instantiate objects from values loaded from config 14 | * 15 | * @author Redempt 16 | */ 17 | public interface Instantiator { 18 | 19 | public static boolean isRecord(Class clazz) { 20 | return clazz.getSuperclass() != null && clazz.getSuperclass().getName().equals("java.lang.Record"); 21 | } 22 | 23 | /** 24 | * Attemps to get the appropriate Instantiator for the given class type 25 | * 26 | * @param clazz The class type 27 | * @return An Instantiator 28 | * @throws IllegalArgumentException If the class cannot be instantiated by known methods 29 | */ 30 | public static Instantiator getInstantiator(Class clazz) { 31 | if (isRecord(clazz)) { 32 | return ConstructorInstantiator.createDefault(clazz); 33 | } 34 | if (clazz.isAnnotationPresent(ConfigMappable.class)) { 35 | Optional> constructor = Arrays.stream(clazz.getConstructors()).filter(c -> c.isAnnotationPresent(ConfigConstructor.class)).findFirst(); 36 | return constructor.map(value -> ConstructorInstantiator.createDefault(clazz)).orElseGet(EmptyInstantiator::new); 37 | } 38 | throw new IllegalArgumentException("Cannot create instantiator for class which is not a record type and not annotated with ConfigMappable (" + clazz + ")"); 39 | } 40 | 41 | /** 42 | * Instantiates and/or loads data into an object 43 | * 44 | * @param manager The ConversionManager handling converters 45 | * @param target The target object, or null 46 | * @param clazz The class whose fields are being used 47 | * @param values The values for the fields 48 | * @param path The path in config 49 | * @param info Extra info about the instantiation 50 | * @param The type 51 | * @return An instantiated object, or the input object with its fields modified 52 | */ 53 | public T instantiate(ConversionManager manager, Object target, Class clazz, List values, String path, FieldSummary info); 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/redempt/redlib/dev/ChainCommand.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.dev; 2 | 3 | import org.bukkit.Bukkit; 4 | import org.bukkit.command.Command; 5 | import org.bukkit.command.CommandSender; 6 | import org.bukkit.command.SimpleCommandMap; 7 | import redempt.redlib.commandmanager.ArgType; 8 | import redempt.redlib.commandmanager.CommandHook; 9 | 10 | import java.lang.reflect.Field; 11 | import java.util.ArrayList; 12 | import java.util.Arrays; 13 | import java.util.Map; 14 | 15 | public class ChainCommand { 16 | 17 | static { 18 | try { 19 | Field field = Bukkit.getPluginManager().getClass().getDeclaredField("commandMap"); 20 | field.setAccessible(true); 21 | SimpleCommandMap map = (SimpleCommandMap) field.get(Bukkit.getPluginManager()); 22 | Class clazz = map.getClass(); 23 | while (!clazz.getSimpleName().equals("SimpleCommandMap")) { 24 | clazz = clazz.getSuperclass(); 25 | } 26 | Field mapField = clazz.getDeclaredField("knownCommands"); 27 | mapField.setAccessible(true); 28 | knownCommands = (Map) mapField.get(map); 29 | } catch (NoSuchFieldException | IllegalAccessException e) { 30 | e.printStackTrace(); 31 | } 32 | } 33 | 34 | private static Map knownCommands; 35 | 36 | @CommandHook("commandchain") 37 | public void commandChain(CommandSender sender, String command) { 38 | String[] split = command.split(";"); 39 | for (String cmd : split) { 40 | Bukkit.dispatchCommand(sender, cmd.trim()); 41 | } 42 | } 43 | 44 | public ArgType getArgType() { 45 | return new ArgType<>("commandchain", s -> s).setTab((c, s) -> { 46 | int i = s.length - 1; 47 | while (i > 0 && !s[i].endsWith(";")) { 48 | i--; 49 | } 50 | if (i + 1 < s.length && s[i].endsWith(";")) { 51 | i++; 52 | } 53 | if (s.length - i == 0 || s.length - i == 1) { 54 | return new ArrayList<>(knownCommands.keySet()); 55 | } 56 | Command cmd = knownCommands.get(s[i]); 57 | if (cmd != null) { 58 | return cmd.tabComplete(c, s[i], Arrays.copyOfRange(s, i + 1, s.length)); 59 | } 60 | return new ArrayList<>(); 61 | }); 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /src/redempt/redlib/dev/profiler/BurstProfiler.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.dev.profiler; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.concurrent.ArrayBlockingQueue; 6 | import java.util.concurrent.Executors; 7 | import java.util.concurrent.ScheduledExecutorService; 8 | import java.util.concurrent.TimeUnit; 9 | 10 | /** 11 | * A profiler best used in bursts. Uses a lot of memory to profile even short periods of time, 12 | * but allows you to select certain timeframes to inspect 13 | * 14 | * @author Redempt 15 | */ 16 | public class BurstProfiler extends Profiler { 17 | 18 | private Thread server; 19 | private ArrayBlockingQueue samples; 20 | private ScheduledExecutorService scheduler; 21 | private int size; 22 | 23 | /** 24 | * Create a new BurstProfiler with an initial size, being the number of milliseconds 25 | * it will be able to record 26 | * 27 | * @param size The size of the sample queue to create, 1ms = 1 element 28 | */ 29 | public BurstProfiler(int size) { 30 | this.size = size; 31 | samples = new ArrayBlockingQueue<>(size); 32 | } 33 | 34 | /** 35 | * Create a new BurstProfiler with a default size of 10,000 (10 seconds) 36 | */ 37 | public BurstProfiler() { 38 | this(10000); 39 | } 40 | 41 | /** 42 | * Start this profiler. Must be run from the thread you intend to profile. 43 | */ 44 | @Override 45 | public void start() { 46 | samples.clear(); 47 | if (server != null) { 48 | return; 49 | } 50 | server = Thread.currentThread(); 51 | scheduler = Executors.newSingleThreadScheduledExecutor(); 52 | scheduler.scheduleAtFixedRate(() -> { 53 | new Thread(() -> { 54 | if (server == null) { 55 | return; 56 | } 57 | Sample sample = new Sample(server.getStackTrace(), System.currentTimeMillis()); 58 | while (!samples.offer(sample)) { 59 | samples.poll(); 60 | } 61 | }).start(); 62 | }, 1, 1, TimeUnit.MILLISECONDS); 63 | } 64 | 65 | protected void end() { 66 | if (scheduler != null) { 67 | scheduler.shutdown(); 68 | scheduler = null; 69 | } 70 | server = null; 71 | } 72 | 73 | /** 74 | * Gets a summary of the last X milliseconds of profiling, 75 | * with X being the size this BurstProfiler was initialized with 76 | * 77 | * @return The summary 78 | */ 79 | public SampleSummary getSummary() { 80 | return new SampleSummary(samples); 81 | } 82 | 83 | /** 84 | * Gets a summary of the profiling after the specified time. Cannot go back further 85 | * than X milliseconds, with X being the size this BrustProfiler was initialized with 86 | * 87 | * @param after The timestamp after which summary data should be included 88 | * @return The summary 89 | */ 90 | public SampleSummary getSummary(long after) { 91 | long start = samples.element().getTime(); 92 | long diff = after - start; 93 | List list = new ArrayList<>(); 94 | samples.stream().skip(Math.max(diff, 0)).forEach(list::add); 95 | return new SampleSummary(list); 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /src/redempt/redlib/dev/profiler/PassiveProfiler.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.dev.profiler; 2 | 3 | import java.util.concurrent.Executors; 4 | import java.util.concurrent.ScheduledExecutorService; 5 | import java.util.concurrent.TimeUnit; 6 | 7 | /** 8 | * A profiler best used over long periods of time. Uses very little memory even when running 9 | * for very long periods, but cannot retrieve data from specific timeframes - only allows 10 | * summaries of the entire time that was profiled. 11 | * 12 | * @author Redempt 13 | */ 14 | public class PassiveProfiler extends Profiler { 15 | 16 | private Thread server; 17 | private ScheduledExecutorService scheduler; 18 | private SampleSummary summary; 19 | 20 | /** 21 | * Create a new PassiveProfiler with an empty summary 22 | */ 23 | public PassiveProfiler() { 24 | summary = new SampleSummary(); 25 | } 26 | 27 | /** 28 | * Start this profiler. Must be run from the thread you intend to profile. 29 | */ 30 | public void start() { 31 | summary = new SampleSummary(); 32 | if (server != null) { 33 | return; 34 | } 35 | server = Thread.currentThread(); 36 | scheduler = Executors.newSingleThreadScheduledExecutor(); 37 | scheduler.scheduleAtFixedRate(() -> { 38 | new Thread(() -> { 39 | if (server == null) { 40 | return; 41 | } 42 | summary.add(server.getStackTrace()); 43 | }).start(); 44 | }, 1, 1, TimeUnit.MILLISECONDS); 45 | } 46 | 47 | protected void end() { 48 | if (scheduler != null) { 49 | scheduler.shutdown(); 50 | scheduler = null; 51 | } 52 | server = null; 53 | } 54 | 55 | /** 56 | * @return A summary of all of the data collected by this profiler, up to the time this method was called. 57 | * The returned summary will not be updated with new data after it is returned. 58 | */ 59 | public SampleSummary getSummary() { 60 | return summary.clone(); 61 | } 62 | 63 | /** 64 | * @return A summary of all the data collected by this profiler. The returned summary will be updated 65 | * with new data after it is returned if the profiler is still running. 66 | */ 67 | public SampleSummary getRunningSummary() { 68 | return summary; 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /src/redempt/redlib/dev/profiler/Profiler.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.dev.profiler; 2 | 3 | import java.util.HashSet; 4 | import java.util.Set; 5 | 6 | /** 7 | * A base class of a Profiler which can be used to analyze the performance of the server. Runs asynchronously 8 | * in its own thread. 9 | * 10 | * @author Redempt 11 | */ 12 | public abstract class Profiler { 13 | 14 | private static Set profilers = new HashSet<>(); 15 | 16 | /** 17 | * Stop all running profilers 18 | */ 19 | public static void stopAll() { 20 | profilers.forEach(Profiler::end); 21 | profilers.clear(); 22 | } 23 | 24 | public Profiler() { 25 | profilers.add(this); 26 | } 27 | 28 | /** 29 | * Start this profiler. Must be run from the thread you intend to profile. 30 | */ 31 | public abstract void start(); 32 | 33 | protected abstract void end(); 34 | 35 | /** 36 | * @return A SampleSummary representing all of the data collected by this profiler 37 | */ 38 | public abstract SampleSummary getSummary(); 39 | 40 | /** 41 | * Stop this profiler 42 | */ 43 | public final void stop() { 44 | end(); 45 | profilers.remove(this); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/redempt/redlib/dev/profiler/Sample.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.dev.profiler; 2 | 3 | class Sample { 4 | 5 | private long time; 6 | private StackTraceElement[] stack; 7 | 8 | public Sample(StackTraceElement[] stack, long time) { 9 | this.time = time; 10 | this.stack = stack; 11 | } 12 | 13 | public StackTraceElement[] getStackTrace() { 14 | return stack; 15 | } 16 | 17 | public long getTime() { 18 | return time; 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/redempt/redlib/dev/profiler/TickMonitorProfiler.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.dev.profiler; 2 | 3 | import redempt.redlib.RedLib; 4 | import redempt.redlib.misc.Task; 5 | 6 | import java.util.*; 7 | 8 | public class TickMonitorProfiler { 9 | 10 | private static int tick = -1; 11 | private static Deque times = new ArrayDeque<>(); 12 | private static BurstProfiler burst; 13 | private static List reports = new ArrayList<>(15); 14 | private static long tickMinimum = 100; 15 | private static Task task; 16 | 17 | public static void setTickMinimum(long tickMinimum) { 18 | TickMonitorProfiler.tickMinimum = tickMinimum; 19 | } 20 | 21 | public static void start() { 22 | if (tick != -1) { 23 | return; 24 | } 25 | burst = new BurstProfiler(10000); 26 | burst.start(); 27 | tick = 0; 28 | task = Task.syncRepeating(RedLib.getInstance(), () -> { 29 | if (times.size() >= 999) { 30 | times.poll(); 31 | } 32 | tick++; 33 | long current = System.currentTimeMillis(); 34 | if (times.size() > 0) { 35 | long time = times.getLast(); 36 | if (current - time >= tickMinimum) { 37 | SampleSummary summary = burst.getSummary(time); 38 | if (reports.size() >= 15) { 39 | reports.stream().min(Comparator.comparingLong(SampleSummary::getDuration)).ifPresent(s -> { 40 | reports.remove(s); 41 | }); 42 | } 43 | reports.add(summary); 44 | } 45 | } 46 | times.add(current); 47 | }, 1, 1); 48 | } 49 | 50 | public static void stop() { 51 | if (task != null) { 52 | task.cancel(); 53 | } 54 | tick = -1; 55 | burst.stop(); 56 | } 57 | 58 | public static void clear() { 59 | reports.clear(); 60 | } 61 | 62 | public static List getReports() { 63 | return reports; 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/redempt/redlib/enchants/EnchantInfo.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.enchants; 2 | 3 | /** 4 | * Represents a CustomEnchant and level 5 | * 6 | * @author Redempt 7 | */ 8 | public class EnchantInfo { 9 | 10 | private CustomEnchant ench; 11 | private int level; 12 | 13 | /** 14 | * Constructs an EnchantInfo from a CustomEnchant and level 15 | * 16 | * @param ench The CustomEnchant 17 | * @param level The level 18 | */ 19 | public EnchantInfo(CustomEnchant ench, int level) { 20 | this.ench = ench; 21 | this.level = level; 22 | } 23 | 24 | /** 25 | * @return The level stored in this EnchantInfo 26 | */ 27 | public int getLevel() { 28 | return level; 29 | } 30 | 31 | /** 32 | * @return The CustomEnchant stored in this EnchantInfo 33 | */ 34 | public CustomEnchant getEnchant() { 35 | return ench; 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/redempt/redlib/enchants/EnchantListener.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.enchants; 2 | 3 | import org.bukkit.event.Event; 4 | 5 | import java.util.function.BiConsumer; 6 | 7 | class EnchantListener { 8 | 9 | private BiConsumer activate; 10 | private BiConsumer deactivate; 11 | 12 | public EnchantListener(BiConsumer activate, BiConsumer deactivate) { 13 | this.activate = activate; 14 | this.deactivate = deactivate; 15 | } 16 | 17 | public void activate(Event event, int level) { 18 | activate.accept((T) event, level); 19 | } 20 | 21 | public void deactivate(Event event, int level) { 22 | deactivate.accept((T) event, level); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/redempt/redlib/enchants/EventItems.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.enchants; 2 | 3 | import org.bukkit.event.Event; 4 | import org.bukkit.inventory.ItemStack; 5 | 6 | /** 7 | * Represents the items related to an event, for handling by enchantments 8 | * 9 | * @author Redempt 10 | */ 11 | public class EventItems { 12 | 13 | private Event event; 14 | private ItemStack[] before; 15 | private ItemStack[] after; 16 | 17 | /** 18 | * @param event The event 19 | * @param before The array of items before the event executes 20 | * @param after The array of items after the event executes 21 | */ 22 | public EventItems(Event event, ItemStack[] before, ItemStack[] after) { 23 | if (before == null) { 24 | before = new ItemStack[after == null ? 1 : after.length]; 25 | } 26 | if (after == null) { 27 | after = new ItemStack[before.length]; 28 | } 29 | this.before = before; 30 | this.after = after; 31 | this.event = event; 32 | } 33 | 34 | /** 35 | * @param event The event 36 | * @param before The item before the event executes 37 | * @param after The item after the event executes 38 | */ 39 | public EventItems(Event event, ItemStack before, ItemStack after) { 40 | this(event, new ItemStack[]{before}, new ItemStack[]{after}); 41 | } 42 | 43 | /** 44 | * @param event The event 45 | * @param after The array of items related to the event 46 | */ 47 | public EventItems(Event event, ItemStack[] after) { 48 | this(event, new ItemStack[after.length], after); 49 | } 50 | 51 | /** 52 | * @param event The event 53 | * @param after The item related to the event 54 | */ 55 | public EventItems(Event event, ItemStack after) { 56 | this(event, new ItemStack[]{null}, new ItemStack[]{after}); 57 | } 58 | 59 | /** 60 | * @return The array of items before the event executes 61 | */ 62 | public ItemStack[] getBefore() { 63 | return before; 64 | } 65 | 66 | /** 67 | * @return The array of items after the event executes 68 | */ 69 | public ItemStack[] getAfter() { 70 | return after; 71 | } 72 | 73 | /** 74 | * @return The event 75 | */ 76 | public Event getEvent() { 77 | return event; 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /src/redempt/redlib/enchants/events/PlayerChangedArmorEvent.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.enchants.events; 2 | 3 | import org.bukkit.Bukkit; 4 | import org.bukkit.entity.Player; 5 | import org.bukkit.event.HandlerList; 6 | import org.bukkit.event.player.PlayerEvent; 7 | import org.bukkit.inventory.ItemStack; 8 | import redempt.redlib.RedLib; 9 | import redempt.redlib.misc.Task; 10 | 11 | /** 12 | * Called when a player changes any piece of armor 13 | * 14 | * @author Redempt 15 | */ 16 | public class PlayerChangedArmorEvent extends PlayerEvent { 17 | 18 | private static HandlerList handlers = new HandlerList(); 19 | 20 | static { 21 | register(); 22 | } 23 | 24 | public static HandlerList getHandlerList() { 25 | return handlers; 26 | } 27 | 28 | private static void register() { 29 | Task.syncRepeating(RedLib.getInstance(), () -> Bukkit.getOnlinePlayers().forEach(PlayerChangedArmorEvent::check), 1, 1); 30 | } 31 | 32 | private static void check(Player player) { 33 | ItemStack[] armor = player.getInventory().getArmorContents().clone(); 34 | Task.syncDelayed(RedLib.getInstance(), () -> { 35 | ItemStack[] newArmor = player.getInventory().getArmorContents(); 36 | for (int i = 0; i < armor.length; i++) { 37 | if (armor[i] == null && newArmor[i] == null) { 38 | continue; 39 | } 40 | if ((armor[i] == null) || (newArmor[i] == null) || !armor[i].equals(newArmor[i])) { 41 | Bukkit.getPluginManager().callEvent(new PlayerChangedArmorEvent(player, armor, newArmor)); 42 | } 43 | } 44 | }, 1); 45 | } 46 | 47 | private ItemStack[] previous; 48 | private ItemStack[] current; 49 | 50 | /** 51 | * Constructs a new PlayerChangedArmorEvent 52 | * 53 | * @param player The Player who changed their armor 54 | * @param previous The armor the Player was previously wearing 55 | * @param current The armor the Player is now wearing 56 | */ 57 | public PlayerChangedArmorEvent(Player player, ItemStack[] previous, ItemStack[] current) { 58 | super(player); 59 | this.previous = previous; 60 | this.current = current; 61 | } 62 | 63 | /** 64 | * @return The armor the Player was previously wearing 65 | */ 66 | public ItemStack[] getPreviousArmor() { 67 | return previous; 68 | } 69 | 70 | /** 71 | * @return The armor the Player is now wearing 72 | */ 73 | public ItemStack[] getNewArmor() { 74 | return current; 75 | } 76 | 77 | 78 | public HandlerList getHandlers() { 79 | return handlers; 80 | } 81 | 82 | } -------------------------------------------------------------------------------- /src/redempt/redlib/enchants/events/PlayerChangedHeldItemEvent.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.enchants.events; 2 | 3 | import org.bukkit.Bukkit; 4 | import org.bukkit.entity.Player; 5 | import org.bukkit.event.HandlerList; 6 | import org.bukkit.event.player.PlayerEvent; 7 | import org.bukkit.inventory.ItemStack; 8 | import redempt.redlib.RedLib; 9 | import redempt.redlib.misc.Task; 10 | 11 | /** 12 | * Called when a player changes the item they are holding, or a property of their held item changes 13 | * 14 | * @author Redempt 15 | */ 16 | public class PlayerChangedHeldItemEvent extends PlayerEvent { 17 | 18 | private static HandlerList handlers = new HandlerList(); 19 | 20 | static { 21 | register(); 22 | } 23 | 24 | public static HandlerList getHandlerList() { 25 | return handlers; 26 | } 27 | 28 | private static void register() { 29 | Task.syncRepeating(RedLib.getInstance(), () -> Bukkit.getOnlinePlayers().forEach(PlayerChangedHeldItemEvent::check), 1, 1); 30 | } 31 | 32 | private static void check(Player player) { 33 | ItemStack held = player.getItemInHand(); 34 | Task.syncDelayed(RedLib.getInstance(), () -> { 35 | ItemStack now = player.getItemInHand(); 36 | if ((held == null && now != null) || !held.equals(now)) { 37 | Bukkit.getPluginManager().callEvent(new PlayerChangedHeldItemEvent(player, held, now)); 38 | } 39 | }, 1); 40 | } 41 | 42 | private ItemStack oldItem; 43 | private ItemStack newItem; 44 | 45 | /** 46 | * Constructs a new PlayerChangedHeldItemEvent 47 | * 48 | * @param player The Player who changed their held item 49 | * @param oldItem The item they were previously holding 50 | * @param newItem The item they are now holding 51 | */ 52 | public PlayerChangedHeldItemEvent(Player player, ItemStack oldItem, ItemStack newItem) { 53 | super(player); 54 | this.oldItem = oldItem; 55 | this.newItem = newItem; 56 | } 57 | 58 | /** 59 | * @return The item the player was holding previously 60 | */ 61 | public ItemStack getPreviousItem() { 62 | return oldItem; 63 | } 64 | 65 | /** 66 | * @return The item the player is now holding 67 | */ 68 | public ItemStack getNewItem() { 69 | return newItem; 70 | } 71 | 72 | /** 73 | * @return The player 74 | */ 75 | 76 | @Override 77 | public HandlerList getHandlers() { 78 | return handlers; 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /src/redempt/redlib/enchants/trigger/AttackEntityTrigger.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.enchants.trigger; 2 | 3 | import org.bukkit.Material; 4 | import org.bukkit.entity.Player; 5 | import org.bukkit.event.EventPriority; 6 | import org.bukkit.event.entity.EntityDamageByEntityEvent; 7 | import redempt.redlib.enchants.EventItems; 8 | 9 | class AttackEntityTrigger extends EnchantTrigger { 10 | 11 | @Override 12 | protected void register() { 13 | addListener(EntityDamageByEntityEvent.class, e -> { 14 | if (!(e.getDamager() instanceof Player)) { 15 | return null; 16 | } 17 | return new EventItems(e, ((Player) e.getDamager()).getItemInHand()); 18 | }); 19 | } 20 | 21 | @Override 22 | public EventPriority getPriority() { 23 | return EventPriority.MONITOR; 24 | } 25 | 26 | @Override 27 | public boolean defaultAppliesTo(Material type) { 28 | return type.toString().endsWith("_SWORD"); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/redempt/redlib/enchants/trigger/EquipArmorTrigger.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.enchants.trigger; 2 | 3 | import org.bukkit.Material; 4 | import org.bukkit.event.player.PlayerJoinEvent; 5 | import org.bukkit.event.player.PlayerQuitEvent; 6 | import org.bukkit.inventory.ItemStack; 7 | import redempt.redlib.enchants.EventItems; 8 | import redempt.redlib.enchants.events.PlayerChangedArmorEvent; 9 | 10 | class EquipArmorTrigger extends EnchantTrigger { 11 | 12 | @Override 13 | protected void register() { 14 | addListener(PlayerChangedArmorEvent.class, e -> new EventItems(e, e.getPreviousArmor(), e.getNewArmor())); 15 | addListener(PlayerJoinEvent.class, e -> 16 | new EventItems(new PlayerChangedArmorEvent(e.getPlayer(), null, e.getPlayer().getInventory().getArmorContents()), 17 | null, e.getPlayer().getInventory().getArmorContents())); 18 | addListener(PlayerQuitEvent.class, e -> 19 | new EventItems(new PlayerChangedArmorEvent(e.getPlayer(), e.getPlayer().getInventory().getArmorContents(), new ItemStack[4]), 20 | e.getPlayer().getInventory().getArmorContents(), null)); 21 | } 22 | 23 | @Override 24 | public boolean defaultAppliesTo(Material type) { 25 | String str = type.toString(); 26 | return str.endsWith("_BOOTS") || str.endsWith("_CHESTPLATE") || str.endsWith("_LEGGINGS") || str.endsWith("_HELMET"); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/redempt/redlib/enchants/trigger/HoldItemTrigger.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.enchants.trigger; 2 | 3 | import org.bukkit.Material; 4 | import org.bukkit.event.player.PlayerJoinEvent; 5 | import org.bukkit.event.player.PlayerQuitEvent; 6 | import redempt.redlib.enchants.EventItems; 7 | import redempt.redlib.enchants.events.PlayerChangedHeldItemEvent; 8 | 9 | class HoldItemTrigger extends EnchantTrigger { 10 | 11 | @Override 12 | protected void register() { 13 | addListener(PlayerChangedHeldItemEvent.class, e -> new EventItems(e, e.getPreviousItem(), e.getNewItem())); 14 | addListener(PlayerJoinEvent.class, e -> 15 | new EventItems(new PlayerChangedHeldItemEvent(e.getPlayer(), null, e.getPlayer().getItemInHand()), null, e.getPlayer().getItemInHand())); 16 | addListener(PlayerQuitEvent.class, e -> 17 | new EventItems(new PlayerChangedHeldItemEvent(e.getPlayer(), e.getPlayer().getItemInHand(), null), e.getPlayer().getItemInHand(), null)); 18 | } 19 | 20 | @Override 21 | public boolean defaultAppliesTo(Material type) { 22 | String str = type.toString(); 23 | return str.endsWith("_PICKAXE"); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/redempt/redlib/enchants/trigger/KillEntityTrigger.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.enchants.trigger; 2 | 3 | import org.bukkit.Material; 4 | import org.bukkit.event.entity.EntityDeathEvent; 5 | import redempt.redlib.enchants.EventItems; 6 | 7 | public class KillEntityTrigger extends EnchantTrigger { 8 | 9 | @Override 10 | protected void register() { 11 | addListener(EntityDeathEvent.class, e -> { 12 | if (e.getEntity().getKiller() == null) { 13 | return null; 14 | } 15 | return new EventItems(e, e.getEntity().getKiller().getItemInHand()); 16 | }); 17 | } 18 | 19 | @Override 20 | public boolean defaultAppliesTo(Material type) { 21 | return type.toString().endsWith("_SWORD"); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/redempt/redlib/enchants/trigger/MineBlockTrigger.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.enchants.trigger; 2 | 3 | import org.bukkit.Material; 4 | import org.bukkit.event.EventPriority; 5 | import org.bukkit.event.block.BlockBreakEvent; 6 | import redempt.redlib.enchants.EventItems; 7 | 8 | class MineBlockTrigger extends EnchantTrigger { 9 | 10 | @Override 11 | protected void register() { 12 | addListener(BlockBreakEvent.class, e -> new EventItems(e, e.getPlayer().getItemInHand())); 13 | } 14 | 15 | @Override 16 | public EventPriority getPriority() { 17 | return EventPriority.MONITOR; 18 | } 19 | 20 | @Override 21 | public boolean defaultAppliesTo(Material type) { 22 | return type.toString().endsWith("_PICKAXE"); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/redempt/redlib/enchants/trigger/ShootArrowTrigger.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.enchants.trigger; 2 | 3 | import org.bukkit.Material; 4 | import org.bukkit.entity.Player; 5 | import org.bukkit.event.entity.ProjectileLaunchEvent; 6 | import redempt.redlib.enchants.EventItems; 7 | 8 | class ShootArrowTrigger extends EnchantTrigger { 9 | 10 | @Override 11 | protected void register() { 12 | addListener(ProjectileLaunchEvent.class, e -> { 13 | if (!(e.getEntity().getShooter() instanceof Player)) { 14 | return null; 15 | } 16 | return new EventItems(e, ((Player) e.getEntity().getShooter()).getItemInHand()); 17 | }); 18 | } 19 | 20 | @Override 21 | public boolean defaultAppliesTo(Material type) { 22 | return type == Material.BOW; 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/redempt/redlib/enchants/trigger/TakeDamageTrigger.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.enchants.trigger; 2 | 3 | import org.bukkit.Material; 4 | import org.bukkit.entity.Player; 5 | import org.bukkit.event.EventPriority; 6 | import org.bukkit.event.entity.EntityDamageByBlockEvent; 7 | import org.bukkit.event.entity.EntityDamageByEntityEvent; 8 | import org.bukkit.event.entity.EntityDamageEvent; 9 | import redempt.redlib.enchants.EventItems; 10 | 11 | class TakeDamageTrigger extends EnchantTrigger { 12 | 13 | @Override 14 | protected void register() { 15 | addListener(EntityDamageEvent.class, e -> { 16 | if (!(e.getEntity() instanceof Player)) { 17 | return null; 18 | } 19 | return new EventItems(e, ((Player) e.getEntity()).getInventory().getArmorContents()); 20 | }); 21 | addListener(EntityDamageByEntityEvent.class, e -> { 22 | if (!(e.getEntity() instanceof Player)) { 23 | return null; 24 | } 25 | return new EventItems(e, ((Player) e.getEntity()).getInventory().getArmorContents()); 26 | }); 27 | addListener(EntityDamageByBlockEvent.class, e -> { 28 | if (!(e.getEntity() instanceof Player)) { 29 | return null; 30 | } 31 | return new EventItems(e, ((Player) e.getEntity()).getInventory().getArmorContents()); 32 | }); 33 | } 34 | 35 | @Override 36 | public boolean defaultAppliesTo(Material type) { 37 | String str = type.toString(); 38 | return str.endsWith("_BOOTS") || str.endsWith("_CHESTPLATE") || str.endsWith("_LEGGINGS") || str.endsWith("_HELMET"); 39 | } 40 | 41 | @Override 42 | public EventPriority getPriority() { 43 | return EventPriority.MONITOR; 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/redempt/redlib/inventorygui/ItemButton.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.inventorygui; 2 | 3 | import java.util.function.BiConsumer; 4 | import java.util.function.Consumer; 5 | 6 | import org.bukkit.event.inventory.InventoryClickEvent; 7 | import org.bukkit.inventory.ItemStack; 8 | 9 | /** 10 | * @author Redempt 11 | */ 12 | public abstract class ItemButton { 13 | 14 | protected ItemStack item; 15 | private int slot; 16 | 17 | /** 18 | * Create an ItemButton from the given ItemStack and listener. 19 | * Useful if you, like most people, would rather use lambdas than the anonymous class definition. 20 | * 21 | * @param item The ItemStack to be used as this button's icon 22 | * @param listener The listener which will be called whenever this button is clicked 23 | * @return The ItemButton, which can be added to an InventoryGUI 24 | */ 25 | public static ItemButton create(ItemStack item, Consumer listener) { 26 | return new ItemButton(item) { 27 | 28 | @Override 29 | public void onClick(InventoryClickEvent e) { 30 | listener.accept(e); 31 | } 32 | 33 | }; 34 | } 35 | 36 | /** 37 | * Create an ItemButton from the given ItemStack and listener. 38 | * Useful if you, like most people, would rather use lambdas than the anonymous class definition. 39 | * 40 | * @param item The ItemStack to be used as this button's icon 41 | * @param listener The listener which will be called whenever this button is clicked and accepts the event and button 42 | * @return The ItemButton, which can be added to an InventoryGUI 43 | */ 44 | public static ItemButton create(ItemStack item, BiConsumer listener) { 45 | return new ItemButton(item) { 46 | 47 | @Override 48 | public void onClick(InventoryClickEvent e) { 49 | listener.accept(e, this); 50 | } 51 | 52 | }; 53 | } 54 | 55 | /** 56 | * Create a new ItemButton with the given ItemStack as the icon 57 | * 58 | * @param item The ItemStack to be used as the icon 59 | */ 60 | public ItemButton(ItemStack item) { 61 | this.item = item; 62 | } 63 | 64 | /** 65 | * Get the ItemStack representing the icon for this button 66 | * 67 | * @return The ItemStack 68 | */ 69 | public ItemStack getItem() { 70 | return item; 71 | } 72 | 73 | protected int getSlot() { 74 | return slot; 75 | } 76 | 77 | protected void setSlot(int slot) { 78 | this.slot = slot; 79 | } 80 | 81 | /** 82 | * Update the item of this button. Does not refresh the InventoryGUI; you must call {@link InventoryGUI#update()} for this change to be reflected in the GUI. 83 | * 84 | * @param item The item to become the icon for this button 85 | */ 86 | public void setItem(ItemStack item) { 87 | this.item = item; 88 | } 89 | 90 | public abstract void onClick(InventoryClickEvent e); 91 | 92 | } 93 | -------------------------------------------------------------------------------- /src/redempt/redlib/itemutils/CustomItem.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.itemutils; 2 | 3 | import org.bukkit.inventory.ItemStack; 4 | import org.bukkit.plugin.Plugin; 5 | import redempt.redlib.RedLib; 6 | 7 | import java.lang.reflect.Constructor; 8 | import java.lang.reflect.InvocationTargetException; 9 | import java.util.HashMap; 10 | import java.util.List; 11 | import java.util.Map; 12 | 13 | /** 14 | * Represents a custom item with special behavior 15 | * 16 | * @author Redempt 17 | */ 18 | public abstract class CustomItem { 19 | 20 | /** 21 | * Gets an instance of every class which extends CustomItem in your plugin, and puts them in a map by name 22 | * Note: Custom item classes MUST have a default constructor which takes no arguments to be loaded by this method 23 | * 24 | * @param plugin The plugin to get the custom items from 25 | * @return A map of the custom items by name 26 | */ 27 | public static Map getAll(Plugin plugin) { 28 | List> list = RedLib.getExtendingClasses(plugin, CustomItem.class); 29 | Map map = new HashMap<>(); 30 | for (Class clazz : list) { 31 | try { 32 | Constructor constructor = clazz.getDeclaredConstructor(); 33 | constructor.setAccessible(true); 34 | CustomItem citem = (CustomItem) constructor.newInstance(); 35 | map.put(citem.getName(), citem); 36 | } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | 37 | InvocationTargetException e) { 38 | throw new IllegalStateException("Class " + clazz.getName() + " does not have a default constructor or could not be loaded", e); 39 | } 40 | } 41 | return map; 42 | } 43 | 44 | private String name; 45 | private ItemStack item; 46 | 47 | /** 48 | * A constructor that should only be called by {@link CustomItem#getAll(Plugin)} 49 | * 50 | * @param name The name of the CustomItem - insert a constant when overriding this constructor, do not take it as a parameter of the overridden constructor 51 | */ 52 | protected CustomItem(String name) { 53 | this.name = name; 54 | item = getDefaultItem(); 55 | } 56 | 57 | /** 58 | * @return Whether the item should be cloned before being returned 59 | */ 60 | protected boolean cloneOnGet() { 61 | return false; 62 | } 63 | 64 | /** 65 | * @return The default item for this CustomItem 66 | */ 67 | public abstract ItemStack getDefaultItem(); 68 | 69 | /** 70 | * @return The name of this custom item 71 | */ 72 | public final String getName() { 73 | return name; 74 | } 75 | 76 | /** 77 | * @return The item 78 | */ 79 | public ItemStack getItem() { 80 | if (cloneOnGet()) { 81 | return item.clone(); 82 | } 83 | return item; 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /src/redempt/redlib/itemutils/ItemSerializer.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.itemutils; 2 | 3 | import org.bukkit.configuration.serialization.ConfigurationSerializable; 4 | import org.bukkit.configuration.serialization.DelegateDeserialization; 5 | import redempt.redlib.json.JSONList; 6 | import redempt.redlib.json.JSONMap; 7 | import redempt.redlib.nms.NMSHelper; 8 | 9 | import java.lang.reflect.Constructor; 10 | import java.lang.reflect.Method; 11 | import java.util.HashMap; 12 | import java.util.List; 13 | import java.util.Map; 14 | import java.util.function.Function; 15 | 16 | class ItemSerializer { 17 | 18 | private static Map, Function, ?>> deserializers = new HashMap<>(); 19 | 20 | private static interface EFunction { 21 | 22 | public static Function wrap(EFunction func) { 23 | return a -> { 24 | try { 25 | return func.apply(a); 26 | } catch (Exception e) { 27 | e.printStackTrace(); 28 | return null; 29 | } 30 | }; 31 | } 32 | 33 | B apply(A a) throws Exception; 34 | 35 | } 36 | 37 | private static Object invokeDeserialize(Class clazz, Map data) { 38 | find: 39 | if (!deserializers.containsKey(clazz)) { 40 | DelegateDeserialization annotation = clazz.getAnnotation(DelegateDeserialization.class); 41 | Class target = annotation == null ? clazz : annotation.value(); 42 | boolean found = false; 43 | try { 44 | Method method = target.getDeclaredMethod("deserialize", Map.class); 45 | deserializers.put(clazz, EFunction.wrap(m -> method.invoke(null, m))); 46 | found = true; 47 | } catch (NoSuchMethodException e) { 48 | } 49 | if (found) { 50 | break find; 51 | } 52 | try { 53 | Constructor con = target.getDeclaredConstructor(Map.class); 54 | deserializers.put(clazz, EFunction.wrap(con::newInstance)); 55 | found = true; 56 | } catch (NoSuchMethodException e) { 57 | } 58 | if (!found) { 59 | throw new IllegalStateException("No suitable deserialization method found for " + clazz); 60 | } 61 | } 62 | return deserializers.get(clazz).apply(data); 63 | } 64 | 65 | 66 | private static Object deserializeObject(JSONMap map) { 67 | try { 68 | Class clazz = Class.forName(map.getString("==").replace("%version%", NMSHelper.getNMSVersion())); 69 | return invokeDeserialize(clazz, map); 70 | } catch (Exception e) { 71 | e.printStackTrace(); 72 | return null; 73 | } 74 | } 75 | 76 | public static Object recursiveDeserialize(Object obj) { 77 | if (obj instanceof JSONMap) { 78 | JSONMap map = (JSONMap) obj; 79 | map.keySet().forEach(k -> { 80 | map.put(k, recursiveDeserialize(map.get(k))); 81 | }); 82 | if (map.containsKey("==")) { 83 | return deserializeObject(map); 84 | } 85 | } 86 | if (obj instanceof JSONList) { 87 | JSONList list = (JSONList) obj; 88 | for (int i = 0; i < list.size(); i++) { 89 | list.set(i, recursiveDeserialize(list.get(i))); 90 | } 91 | } 92 | return obj; 93 | } 94 | 95 | public static JSONMap toJSON(ConfigurationSerializable s, Class clazz) { 96 | Map map = s.serialize(); 97 | JSONMap json = new JSONMap(); 98 | json.put("==", clazz.getName().replace(NMSHelper.getNMSVersion(), "%version%")); 99 | map.forEach((k, v) -> { 100 | json.put(k, serialize(v)); 101 | }); 102 | return json; 103 | } 104 | 105 | public static Object serialize(Object o) { 106 | if (o instanceof ConfigurationSerializable) { 107 | Class clazz = o.getClass(); 108 | return toJSON((ConfigurationSerializable) o, clazz); 109 | } else if (o instanceof Map) { 110 | Map map = (Map) o; 111 | JSONMap json = new JSONMap(); 112 | map.forEach((k, v) -> { 113 | json.put(k.toString(), serialize(v)); 114 | }); 115 | return json; 116 | } else if (o instanceof List) { 117 | List list = (List) o; 118 | JSONList json = new JSONList(); 119 | list.stream().map(ItemSerializer::serialize).forEach(json::add); 120 | return json; 121 | } 122 | return o; 123 | } 124 | 125 | } 126 | -------------------------------------------------------------------------------- /src/redempt/redlib/itemutils/ItemTrait.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.itemutils; 2 | 3 | import org.bukkit.inventory.ItemStack; 4 | import org.bukkit.inventory.meta.ItemMeta; 5 | 6 | import java.util.Optional; 7 | import java.util.function.BiPredicate; 8 | 9 | /** 10 | * A set of comparable traits items have and can be compared with {@link ItemUtils#compare(ItemStack, ItemStack, ItemTrait...)} 11 | * 12 | * @author Redempt 13 | */ 14 | public enum ItemTrait { 15 | 16 | /** 17 | * For comparing the durability of two items 18 | */ 19 | DURABILITY((a, b) -> a.getDurability() == b.getDurability()), 20 | /** 21 | * For comparing the amount of two items 22 | */ 23 | AMOUNT((a, b) -> a.getAmount() == b.getAmount()), 24 | /** 25 | * For comparing the display name of two items 26 | */ 27 | NAME((a, b) -> Optional.ofNullable(a.getItemMeta()).map(ItemMeta::getDisplayName).equals(Optional.ofNullable(b.getItemMeta()).map(ItemMeta::getDisplayName))), 28 | /** 29 | * For comparing the lore of two items 30 | */ 31 | LORE((a, b) -> Optional.ofNullable(a.getItemMeta()).map(ItemMeta::getLore).equals(Optional.ofNullable(b.getItemMeta()).map(ItemMeta::getLore))), 32 | /** 33 | * For comparing the enchantments of two items 34 | */ 35 | ENCHANTMENTS((a, b) -> a.getEnchantments().equals(b.getEnchantments())), 36 | /** 37 | * For comparing the types of two items 38 | */ 39 | TYPE((a, b) -> a.getType() == b.getType()); 40 | 41 | private BiPredicate compare; 42 | 43 | ItemTrait(BiPredicate compare) { 44 | this.compare = compare; 45 | } 46 | 47 | /** 48 | * Compares this trait on the two items 49 | * 50 | * @param a The first item 51 | * @param b The second item 52 | * @return True if the trait is the same on both items, false otherwise 53 | */ 54 | public boolean compare(ItemStack a, ItemStack b) { 55 | return compare.test(a, b); 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/redempt/redlib/json/JSONList.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.json; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.StringJoiner; 6 | import java.util.stream.Collectors; 7 | 8 | /** 9 | * Represents a list which can be serialized to JSON and deserialized back to this form, 10 | * assuming all of the values it stores are serializable 11 | */ 12 | public class JSONList extends ArrayList { 13 | 14 | public Integer getInt(int key) { 15 | Object o = get(key); 16 | if (o instanceof Long) { 17 | return (int) (long) o; 18 | } 19 | return (Integer) o; 20 | } 21 | 22 | public Boolean getBoolean(int key) { 23 | return (Boolean) get(key); 24 | } 25 | 26 | public Long getLong(int key) { 27 | return (Long) get(key); 28 | } 29 | 30 | public Double getDouble(int key) { 31 | return (Double) get(key); 32 | } 33 | 34 | public JSONList getList(int key) { 35 | return (JSONList) get(key); 36 | } 37 | 38 | public JSONMap getMap(int key) { 39 | return (JSONMap) get(key); 40 | } 41 | 42 | public String getString(int key) { 43 | return (String) get(key); 44 | } 45 | 46 | public List cast(Class clazz) { 47 | return stream().map(clazz::cast).collect(Collectors.toList()); 48 | } 49 | 50 | /** 51 | * @return A JSON string representing this JSONList 52 | */ 53 | @Override 54 | public String toString() { 55 | StringJoiner joiner = new StringJoiner(", ", "[", "]"); 56 | forEach(o -> joiner.add(JSONParser.toJSONString(o))); 57 | return joiner.toString(); 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/redempt/redlib/json/JSONMap.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.json; 2 | 3 | import java.util.HashMap; 4 | import java.util.StringJoiner; 5 | 6 | /** 7 | * Represents a map which can be serialized to JSON and deserialized back to this form, 8 | * assuming all of the values it stores are serializable 9 | */ 10 | public class JSONMap extends HashMap { 11 | 12 | public Integer getInt(String key) { 13 | Object o = get(key); 14 | if (o instanceof Long) { 15 | return (int) (long) o; 16 | } 17 | return (Integer) o; 18 | } 19 | 20 | public Boolean getBoolean(String key) { 21 | return (Boolean) get(key); 22 | } 23 | 24 | public Double getDouble(String key) { 25 | return (Double) get(key); 26 | } 27 | 28 | public Long getLong(String key) { 29 | return (Long) get(key); 30 | } 31 | 32 | public JSONList getList(String key) { 33 | return (JSONList) get(key); 34 | } 35 | 36 | public JSONMap getMap(String key) { 37 | return (JSONMap) get(key); 38 | } 39 | 40 | public String getString(String key) { 41 | return (String) get(key); 42 | } 43 | 44 | /** 45 | * @return A JSON string representing this JSONMap 46 | */ 47 | @Override 48 | public String toString() { 49 | StringJoiner joiner = new StringJoiner(", ", "{", "}"); 50 | forEach((key, value) -> joiner.add(JSONParser.toJSONString(key) + ": " + JSONParser.toJSONString(value))); 51 | return joiner.toString(); 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/redempt/redlib/misc/EntityPersistor.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.misc; 2 | 3 | import org.bukkit.Bukkit; 4 | import org.bukkit.World; 5 | import org.bukkit.entity.Entity; 6 | 7 | import java.lang.reflect.InvocationHandler; 8 | import java.lang.reflect.Method; 9 | import java.lang.reflect.Proxy; 10 | 11 | /** 12 | * An Entity in Spigot may not persist if the entity it refers to is unloaded, then loaded again at a later time. 13 | * This can make development very annoying, as you have to constantly check whether the Entity is still valid or not. 14 | * EntityPersistor wraps an Entity using a proxy, and anytime a method is called on the Entity, it will check if 15 | * the Entity instance is still valid. If it isn't, it will attempt to replace it with a valid instance by re-fetching 16 | * the Entity from Bukkit. 17 | */ 18 | public class EntityPersistor { 19 | 20 | /** 21 | * Wraps an Entity object with a proxy which will attempt to ensure the Entity object remains valid 22 | * even if the entity's chunk is unloaded, then loaded again. Helpful if you need a reference to an 23 | * Entity over a long period of time which must not be broken. Note that any wrapped Entity will not 24 | * interact with {@link Object#equals(Object)} reflexively. You must call .equals() on the Entity 25 | * which has been wrapped, not on another Entity comparing it to this one. This could not be avoided, 26 | * unfortunately, but as long as you are aware of that, it should work fine. 27 | * Seems to break in 1.8 because of API fuckery. Use {@link EntityPersistor#wrap(Entity)} 28 | * 29 | * @param entity The Entity to wrap 30 | * @param The type of the Entity 31 | * @return The wrapped Entity 32 | */ 33 | public static T persist(T entity) { 34 | Class clazz = entity.getClass(); 35 | boolean foundInterface = false; 36 | for (Class iface : clazz.getInterfaces()) { 37 | if (Entity.class.isAssignableFrom(iface)) { 38 | clazz = iface; 39 | foundInterface = true; 40 | break; 41 | } 42 | } 43 | if (!foundInterface) { 44 | throw new IllegalArgumentException("The provided object cannot be wrapped!"); 45 | } 46 | return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, new InvocationHandler() { 47 | 48 | private T instance = entity; 49 | 50 | @Override 51 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 52 | if (!instance.isValid()) { 53 | T replace = (T) Bukkit.getEntity(instance.getUniqueId()); 54 | if (replace != null) { 55 | instance = replace; 56 | } 57 | } 58 | if (method.getName().equals("equals") && method.getParameters().length == 1 && method.getParameters()[0].getName().equals("Object")) { 59 | if (args[0] instanceof Entity) { 60 | return ((Entity) args[0]).getUniqueId().equals(instance.getUniqueId()); 61 | } 62 | return false; 63 | } 64 | return method.invoke(instance, args); 65 | } 66 | 67 | }); 68 | } 69 | 70 | /** 71 | * Wraps an Entity in an EntityPersistor. Calling {@link EntityPersistor#get()} will refresh the reference 72 | * if it is invalid. Use for 1.8 73 | * 74 | * @param entity The Entity to wrap 75 | * @param The type of the Entity 76 | * @return An EntityPersistor wrapping the given Entity 77 | */ 78 | public static EntityPersistor wrap(T entity) { 79 | return new EntityPersistor(entity); 80 | } 81 | 82 | private T entity; 83 | 84 | private EntityPersistor(T entity) { 85 | this.entity = entity; 86 | } 87 | 88 | /** 89 | * Gets the Entity held in this EntityPersistor. If the reference is invalid, the EntityPersistor will attempt 90 | * to refresh it. 91 | * 92 | * @return The wrapped Entity 93 | */ 94 | public T get() { 95 | refresh: 96 | if (!entity.isValid()) { 97 | for (Entity entity : this.entity.getLocation().getChunk().getEntities()) { 98 | if (entity.getUniqueId().equals(this.entity.getUniqueId())) { 99 | this.entity = (T) entity; 100 | break refresh; 101 | } 102 | } 103 | for (World world : Bukkit.getWorlds()) { 104 | for (Entity entity : world.getEntities()) { 105 | if (this.entity.getUniqueId().equals(entity.getUniqueId())) { 106 | this.entity = (T) entity; 107 | break; 108 | } 109 | } 110 | } 111 | } 112 | return entity; 113 | } 114 | 115 | } 116 | -------------------------------------------------------------------------------- /src/redempt/redlib/misc/EventListener.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.misc; 2 | 3 | import org.bukkit.Bukkit; 4 | import org.bukkit.event.*; 5 | import org.bukkit.plugin.Plugin; 6 | import redempt.redlib.RedLib; 7 | 8 | import java.util.function.BiConsumer; 9 | import java.util.function.Consumer; 10 | 11 | /** 12 | * A compact way to define a Listener using a lambda 13 | * 14 | * @param The event being listened for 15 | * @author Redempt 16 | */ 17 | public class EventListener implements Listener { 18 | 19 | private BiConsumer, T> handler; 20 | private Class eventClass; 21 | 22 | /** 23 | * Creates and registers a Listener for the given event 24 | * 25 | * @param eventClass The class of the event being listened for 26 | * @param priority The EventPriority for this listener 27 | * @param handler The callback to receive the event and this EventListener 28 | */ 29 | public EventListener(Class eventClass, EventPriority priority, BiConsumer, T> handler) { 30 | this(RedLib.getCallingPlugin(), eventClass, priority, handler); 31 | } 32 | 33 | /** 34 | * Creates and registers a Listener for the given event 35 | * 36 | * @param plugin The plugin registering the listener 37 | * @param eventClass The class of the event being listened for 38 | * @param priority The EventPriority for this listener 39 | * @param handler The callback to receive the event and this EventListener 40 | */ 41 | public EventListener(Plugin plugin, Class eventClass, EventPriority priority, BiConsumer, T> handler) { 42 | this.handler = handler; 43 | this.eventClass = eventClass; 44 | Bukkit.getPluginManager().registerEvent(eventClass, this, priority, (l, e) -> handleEvent((T) e), plugin); 45 | } 46 | 47 | /** 48 | * Creates and registers a Listener for the given event 49 | * 50 | * @param eventClass The class of the event being listened for 51 | * @param priority The EventPriority for this listener 52 | * @param handler The callback to receive the event 53 | */ 54 | public EventListener(Class eventClass, EventPriority priority, Consumer handler) { 55 | this(RedLib.getCallingPlugin(), eventClass, priority, handler); 56 | } 57 | 58 | /** 59 | * Creates and registers a Listener for the given event 60 | * 61 | * @param plugin The plugin registering the listener 62 | * @param eventClass The class of the event being listened for 63 | * @param priority The EventPriority for this listener 64 | * @param handler The callback to receive the event 65 | */ 66 | public EventListener(Plugin plugin, Class eventClass, EventPriority priority, Consumer handler) { 67 | this(plugin, eventClass, priority, (l, e) -> handler.accept(e)); 68 | } 69 | 70 | /** 71 | * Creates and registers a Listener for the given event 72 | * 73 | * @param eventClass The class of the event being listened for 74 | * @param handler The callback to receive the event and this EventListener 75 | */ 76 | public EventListener(Class eventClass, BiConsumer, T> handler) { 77 | this(RedLib.getCallingPlugin(), eventClass, handler); 78 | } 79 | 80 | /** 81 | * Creates and registers a Listener for the given event 82 | * 83 | * @param plugin The plugin registering the listener 84 | * @param eventClass The class of the event being listened for 85 | * @param handler The callback to receive the event and this EventListener 86 | */ 87 | public EventListener(Plugin plugin, Class eventClass, BiConsumer, T> handler) { 88 | this(plugin, eventClass, EventPriority.NORMAL, handler); 89 | } 90 | 91 | /** 92 | * Creates and registers a Listener for the given event 93 | * 94 | * @param eventClass The class of the event being listened for 95 | * @param handler The callback to receive the event 96 | */ 97 | public EventListener(Class eventClass, Consumer handler) { 98 | this(RedLib.getCallingPlugin(), eventClass, handler); 99 | } 100 | 101 | /** 102 | * Creates and registers a Listener for the given event 103 | * 104 | * @param plugin The plugin registering the listener 105 | * @param eventClass The class of the event being listened for 106 | * @param handler The callback to receive the event 107 | */ 108 | public EventListener(Plugin plugin, Class eventClass, Consumer handler) { 109 | this(plugin, eventClass, EventPriority.NORMAL, handler); 110 | } 111 | 112 | @EventHandler 113 | public void handleEvent(T event) { 114 | if (eventClass.isAssignableFrom(event.getClass())) { 115 | handler.accept(this, event); 116 | } 117 | } 118 | 119 | /** 120 | * Unregisters this listener 121 | */ 122 | public void unregister() { 123 | HandlerList.unregisterAll(this); 124 | } 125 | 126 | } 127 | -------------------------------------------------------------------------------- /src/redempt/redlib/misc/Path.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.misc; 2 | 3 | import org.bukkit.Location; 4 | import org.bukkit.util.Vector; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | public class Path { 10 | 11 | /** 12 | * Get the locations between the start and end location 13 | * 14 | * @param start The start location 15 | * @param end The end location 16 | * @param step The step size to use 17 | * @return A list of all the locations between the locations 18 | */ 19 | public static List getPath(Location start, Location end, double step) { 20 | List locs = new ArrayList<>(); 21 | locs.add(start); 22 | Vector v = end.clone().subtract(start).toVector(); 23 | v = v.normalize().multiply(step); 24 | Location current = start.clone(); 25 | while (current.distance(end) > step) { 26 | locs.add(current.clone()); 27 | current = current.add(v); 28 | } 29 | locs.add(end); 30 | return locs; 31 | } 32 | 33 | /** 34 | * Get the locations between the start and end location 35 | * 36 | * @param start The start location 37 | * @param end The end location 38 | * @return A list of all the locations between the locations, equidistant 39 | */ 40 | public static List getPath(Location start, Location end) { 41 | return getPath(start, end, 1); 42 | } 43 | 44 | /** 45 | * Get the locations from the start along a vector 46 | * 47 | * @param start The start location 48 | * @param direction The vector indicating direction 49 | * @param distance The length of the path 50 | * @param step The step size to use 51 | * @return A list of all the locations between the locations, equidistant 52 | */ 53 | public static List getPath(Location start, Vector direction, double distance, double step) { 54 | direction = direction.clone().normalize().multiply(distance); 55 | Location end = start.clone().add(direction); 56 | return getPath(start, end, step); 57 | } 58 | 59 | /** 60 | * Get the locations from the start along a vector 61 | * 62 | * @param start The start location 63 | * @param direction The vector indicating direction 64 | * @param distance The max distance to step 65 | * @return A list of all the locations between the locations, equidistant 66 | */ 67 | public static List getPath(Location start, Vector direction, double distance) { 68 | return getPath(start, direction, distance, 1); 69 | } 70 | 71 | /** 72 | * Get the locations from the start along a vector 73 | * 74 | * @param start The start location 75 | * @param direction The vector indicating direction and length 76 | * @return A list of all the locations between the locations, equidistant 77 | */ 78 | public static List getPath(Location start, Vector direction) { 79 | return getPath(start, direction, direction.length(), 1); 80 | } 81 | 82 | /** 83 | * Get the locations from the start along a vector 84 | * 85 | * @param start The start location whose direction vector will be used for direction and length 86 | * @return A list of all the locations between the locations, equidistant 87 | */ 88 | public static List getPath(Location start) { 89 | return getPath(start, start.getDirection(), start.getDirection().length(), 1); 90 | } 91 | 92 | /** 93 | * Get the locations from the start along a vector 94 | * 95 | * @param start The start location whose direction vector will be used for direction and length 96 | * @param step The step size to use 97 | * @return A list of all the locations between the locations, equidistant 98 | */ 99 | public static List getPath(Location start, double step) { 100 | return getPath(start, start.getDirection(), start.getDirection().length(), step); 101 | } 102 | 103 | } 104 | -------------------------------------------------------------------------------- /src/redempt/redlib/misc/PlayerWrapper.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.misc; 2 | 3 | import org.bukkit.entity.Player; 4 | 5 | import java.lang.reflect.Proxy; 6 | import java.util.Arrays; 7 | 8 | public class PlayerWrapper { 9 | 10 | /** 11 | * Wraps a player using a Proxy, disabling some methods from being called. Very hacky, do not use if it can be avoided. 12 | * Meant for when you want to send a fake event to test if it will be cancelled, but don't want the plugins to be able to do certain things with the player based on the event. 13 | * 14 | * @param player The player to wrap 15 | * @param disable The names of the methods to disable 16 | * @return The wrapped player 17 | */ 18 | public static Player wrap(Player player, String... disable) { 19 | return (Player) Proxy.newProxyInstance(player.getClass().getClassLoader(), new Class[]{Player.class}, (proxy, method, args) -> { 20 | if (Arrays.stream(disable).anyMatch(s -> s.equals(method.getName()))) { 21 | return null; 22 | } 23 | return method.invoke(player, args); 24 | }); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/redempt/redlib/misc/UserCache.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.misc; 2 | 3 | import org.bukkit.Bukkit; 4 | import org.bukkit.OfflinePlayer; 5 | import org.bukkit.entity.Player; 6 | import org.bukkit.event.EventPriority; 7 | import org.bukkit.event.player.PlayerJoinEvent; 8 | import redempt.redlib.RedLib; 9 | 10 | import java.util.Locale; 11 | import java.util.Map; 12 | import java.util.concurrent.CompletableFuture; 13 | import java.util.concurrent.ConcurrentHashMap; 14 | import java.util.concurrent.ExecutionException; 15 | 16 | /** 17 | * A cache of offline users by name which can be queried without worrying about web requests 18 | * 19 | * @author Redempt 20 | */ 21 | public class UserCache { 22 | 23 | private static Map nameCache; 24 | private static CompletableFuture initTask; 25 | 26 | /** 27 | * Initializes the user cache asynchronously 28 | * 29 | * @param onComplete A Runnable to be run when the initialization is complete 30 | */ 31 | public static synchronized void asyncInit(Runnable onComplete) { 32 | if (initTask != null || nameCache != null) { 33 | onComplete.run(); 34 | return; 35 | } 36 | initTask = CompletableFuture.runAsync(() -> { 37 | if (nameCache != null) { 38 | return; 39 | } 40 | nameCache = new ConcurrentHashMap<>(); 41 | for (OfflinePlayer player : Bukkit.getOfflinePlayers()) { 42 | nameCache.put(player.getName().toLowerCase(Locale.ROOT), player); 43 | } 44 | Task.syncDelayed(() -> new EventListener<>(RedLib.getInstance(), PlayerJoinEvent.class, EventPriority.LOWEST, e -> { 45 | Player player = e.getPlayer(); 46 | nameCache.put(player.getName().toLowerCase(Locale.ROOT), player); 47 | })); 48 | }); 49 | } 50 | 51 | /** 52 | * Initializes the user cache asynchronously 53 | */ 54 | public static void asyncInit() { 55 | asyncInit(() -> { 56 | }); 57 | } 58 | 59 | /** 60 | * Initializes the user cache synchronously 61 | */ 62 | public static void init() { 63 | asyncInit(); 64 | if (!initTask.isDone()) { 65 | try { 66 | initTask.get(); 67 | } catch (InterruptedException | ExecutionException e) { 68 | e.printStackTrace(); 69 | } 70 | } 71 | } 72 | 73 | /** 74 | * Gets an OfflinePlayer by name, initializing the cache synchronously if it has not been initialized yet 75 | * 76 | * @param name The name of the player, case insensitive 77 | * @return The OfflinePlayer, or null 78 | */ 79 | public static OfflinePlayer getOfflinePlayer(String name) { 80 | if (nameCache == null) { 81 | init(); 82 | } 83 | return nameCache.get(name.toLowerCase(Locale.ROOT)); 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /src/redempt/redlib/multiblock/StructureData.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.multiblock; 2 | 3 | import org.bukkit.Bukkit; 4 | import org.bukkit.Location; 5 | import org.bukkit.Material; 6 | import org.bukkit.block.Block; 7 | import org.bukkit.block.BlockState; 8 | import org.bukkit.block.data.BlockData; 9 | import org.bukkit.entity.Player; 10 | import org.bukkit.material.MaterialData; 11 | import redempt.redlib.RedLib; 12 | 13 | /** 14 | * Cross-version wrapper for block data - {@link BlockData} in 1.13+, {@link MaterialData} otherwise 15 | * 16 | * @author Redempt 17 | */ 18 | public class StructureData { 19 | 20 | private BlockData data; 21 | private MaterialData mdata; 22 | 23 | protected StructureData(String string) { 24 | if (RedLib.MID_VERSION >= 13) { 25 | data = Bukkit.createBlockData(string); 26 | } else { 27 | String[] split = string.split(":"); 28 | Material type = Material.valueOf(split[0]); 29 | byte data = 0; 30 | if (split.length == 2) { 31 | data = Byte.parseByte(split[1]); 32 | } 33 | mdata = new MaterialData(type, data); 34 | } 35 | } 36 | 37 | public StructureData(Material type) { 38 | if (RedLib.MID_VERSION >= 13) { 39 | data = type.createBlockData(); 40 | } else { 41 | mdata = new MaterialData(type, (byte) 0); 42 | } 43 | } 44 | 45 | /** 46 | * Creates a StructureData from a BlockData, for 1.13+ 47 | * 48 | * @param data The BlockData 49 | */ 50 | public StructureData(BlockData data) { 51 | this.data = data; 52 | } 53 | 54 | public StructureData getRotated(Rotator rotator) { 55 | if (RedLib.MID_VERSION >= 13) { 56 | return new StructureData(rotator.rotate(data)); 57 | } else { 58 | return this; 59 | } 60 | } 61 | 62 | /** 63 | * Creates a StructureData from a Material and byte data, for 1.12 and below 64 | * 65 | * @param type The block type 66 | * @param data The data byte 67 | */ 68 | public StructureData(Material type, byte data) { 69 | mdata = new MaterialData(type, data); 70 | } 71 | 72 | /** 73 | * Sets this StructureData at the given location 74 | * 75 | * @param block The block to set 76 | */ 77 | public void setBlock(Block block) { 78 | if (RedLib.MID_VERSION >= 13) { 79 | block.setBlockData(data, false); 80 | } else { 81 | BlockState state = block.getState(); 82 | state.setType(mdata.getItemType()); 83 | state.setRawData(mdata.getData()); 84 | state.update(true, false); 85 | } 86 | } 87 | 88 | /** 89 | * Gets the BlockState to set for a given block 90 | * 91 | * @param block The Block to get the BlockState at 92 | * @return The BlockState that would be set 93 | */ 94 | public BlockState getState(Block block) { 95 | if (RedLib.MID_VERSION >= 13) { 96 | BlockState state = block.getState(); 97 | state.setBlockData(data); 98 | return state; 99 | } else { 100 | BlockState state = block.getState(); 101 | state.setType(mdata.getItemType()); 102 | state.setRawData(mdata.getData()); 103 | return state; 104 | } 105 | } 106 | 107 | /** 108 | * Sends a fake block change to a Player 109 | * 110 | * @param player The Player to send the fake block change to 111 | * @param loc The Location of the fake block 112 | */ 113 | public void sendBlock(Player player, Location loc) { 114 | if (RedLib.MID_VERSION >= 13) { 115 | player.sendBlockChange(loc, data); 116 | } else { 117 | player.sendBlockChange(loc, mdata.getItemType(), mdata.getData()); 118 | } 119 | } 120 | 121 | /** 122 | * @return Whether the Material is air - for 1.15+, AIR, CAVE_AIR, or VOID_AIR 123 | */ 124 | public boolean isAir() { 125 | return RedLib.MID_VERSION >= 15 ? getType().isAir() : getType() == Material.AIR; 126 | } 127 | 128 | /** 129 | * @return The type of this StructureData 130 | */ 131 | public Material getType() { 132 | return RedLib.MID_VERSION >= 13 ? data.getMaterial() : mdata.getItemType(); 133 | } 134 | 135 | /** 136 | * Compares this StructureData to a Block 137 | * 138 | * @param block The Block to compare with 139 | * @param strict Whether to compare strictly 140 | * @param ignoreAir Whether to return true automatically if this StructureData is air 141 | * @return Whether the block matches this StructureData within the given parameters 142 | */ 143 | public boolean compare(Block block, boolean strict, boolean ignoreAir) { 144 | if (ignoreAir && isAir()) { 145 | return true; 146 | } 147 | if (!strict) { 148 | return block.getType() == getType(); 149 | } 150 | if (RedLib.MID_VERSION >= 13) { 151 | return block.getBlockData().matches(data); 152 | } else { 153 | return block.getData() == mdata.getData() && block.getType() == mdata.getItemType(); 154 | } 155 | } 156 | 157 | } 158 | -------------------------------------------------------------------------------- /src/redempt/redlib/multiblock/StructureFinder.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.multiblock; 2 | 3 | import org.bukkit.Material; 4 | import org.bukkit.block.Block; 5 | 6 | import java.util.ArrayList; 7 | import java.util.Collections; 8 | import java.util.Comparator; 9 | import java.util.HashMap; 10 | import java.util.List; 11 | import java.util.Map; 12 | 13 | class StructureFinder { 14 | 15 | private MultiBlockStructure type; 16 | private Map> materialMap = new HashMap<>(); 17 | private List materials = new ArrayList<>(); 18 | 19 | public StructureFinder(MultiBlockStructure type) { 20 | this.type = type; 21 | } 22 | 23 | private void initializeMap() { 24 | for (int x = 0; x < type.dimX; x++) { 25 | for (int y = 0; y < type.dimY; y++) { 26 | for (int z = 0; z < type.dimZ; z++) { 27 | Material type = this.type.data[x][y][z].getType(); 28 | List list = materialMap.get(type); 29 | if (list == null) { 30 | list = new ArrayList<>(); 31 | materialMap.put(type, list); 32 | materials.add(type); 33 | } 34 | list.add(new int[]{x, y, z}); 35 | } 36 | } 37 | } 38 | boolean air = materials.remove(Material.AIR); 39 | materials.sort(Comparator.comparingInt(m -> materialMap.get(m).size())); 40 | if (air) { 41 | materials.add(Material.AIR); 42 | } 43 | } 44 | 45 | public Structure getAt(Block block) { 46 | if (materialMap.size() == 0) { 47 | initializeMap(); 48 | } 49 | List locations = materialMap.get(block.getType()); 50 | if (locations == null) { 51 | if (!type.ignoreAir) { 52 | return null; 53 | } 54 | locations = materialMap.get(Material.AIR); 55 | if (locations == null) { 56 | return null; 57 | } 58 | } 59 | Rotator rotator = new Rotator(1, false); 60 | for (int rot = 0; rot < 4; rot++) { 61 | for (int mirror = 0; mirror < 2; mirror++) { 62 | rotator.setRotation(rot); 63 | rotator.setMirrored(mirror == 1); 64 | Structure struct = getAt(block, rotator); 65 | if (struct != null) { 66 | return struct; 67 | } 68 | } 69 | } 70 | return null; 71 | } 72 | 73 | private Structure getAt(Block block, Rotator rotator) { 74 | int maxX = type.dimX; 75 | int maxY = type.dimY; 76 | int maxZ = type.dimZ; 77 | Structure struct; 78 | boolean foundY; 79 | for (int x = 0; x < maxX; x++) { 80 | for (int y = 0; y < maxY; y++) { 81 | foundY = false; 82 | for (int z = 0; z < maxZ; z++) { 83 | rotator.setLocation(x, z); 84 | Block b = block.getRelative(-rotator.getRotatedBlockX(), -y, -rotator.getRotatedBlockZ()); 85 | if (!type.ignoreAir && !materialMap.containsKey(b.getType())) { 86 | if (foundY) { 87 | maxZ = z; 88 | } 89 | break; 90 | } 91 | foundY = true; 92 | struct = getExact(b, rotator); 93 | if (struct != null) { 94 | return struct; 95 | } 96 | } 97 | if (!foundY) { 98 | maxY = y; 99 | } 100 | } 101 | } 102 | return null; 103 | } 104 | 105 | private Structure getExact(Block block, Rotator rotator) { 106 | for (Material mat : materials) { 107 | List instances = materialMap.get(mat); 108 | for (int[] pos : instances) { 109 | StructureData data = type.data[pos[0]][pos[1]][pos[2]]; 110 | rotator.setLocation(pos[0], pos[2]); 111 | Block b = block.getRelative(rotator.getRotatedBlockX(), pos[1], rotator.getRotatedBlockZ()); 112 | if (!type.compare(data, b, rotator)) { 113 | return null; 114 | } 115 | } 116 | } 117 | return new Structure(type, block.getLocation(), rotator); 118 | } 119 | 120 | } 121 | -------------------------------------------------------------------------------- /src/redempt/redlib/nms/NMSArray.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.nms; 2 | 3 | import java.lang.reflect.Array; 4 | 5 | /** 6 | * Wraps any type of Array and provides easy reflection access 7 | * 8 | * @author Redempt 9 | */ 10 | public class NMSArray { 11 | 12 | private Object array; 13 | 14 | public NMSArray(Object array) { 15 | if (!array.getClass().isArray()) { 16 | throw new IllegalArgumentException("Object passed is not an array!"); 17 | } 18 | this.array = array; 19 | } 20 | 21 | /** 22 | * Gets a wrapped NMSObject with the value at a certain index in the array 23 | * 24 | * @param index The index to get 25 | * @return An NMSObject wrapping the object at the index 26 | */ 27 | public NMSObject get(int index) { 28 | return new NMSObject(Array.get(array, index)); 29 | } 30 | 31 | /** 32 | * Gets the object at the given index in the wrapped array 33 | * 34 | * @param index The index to get 35 | * @return The object at the index 36 | */ 37 | public Object getDirect(int index) { 38 | return Array.get(array, index); 39 | } 40 | 41 | /** 42 | * Sets the object at the index of the wrapped array 43 | * 44 | * @param index The index to set 45 | * @param obj The object to set. If it is an {@link NMSObject}, it will be unwrapped automatically. 46 | */ 47 | public void set(int index, Object obj) { 48 | if (obj instanceof NMSObject) { 49 | obj = ((NMSObject) obj).getObject(); 50 | } 51 | Array.set(array, index, obj); 52 | } 53 | 54 | public int length() { 55 | return Array.getLength(array); 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/redempt/redlib/nms/NMSClass.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.nms; 2 | 3 | import java.lang.reflect.Array; 4 | import java.lang.reflect.Field; 5 | import java.lang.reflect.InvocationTargetException; 6 | import java.lang.reflect.Method; 7 | 8 | /** 9 | * Wraps any class and provides methods for easy reflection 10 | * 11 | * @author Redempt 12 | */ 13 | public class NMSClass { 14 | 15 | private Class clazz; 16 | 17 | /** 18 | * Constructs an NMSClass wrapping the given class 19 | * 20 | * @param clazz The class to wrap 21 | */ 22 | public NMSClass(Class clazz) { 23 | this.clazz = clazz; 24 | } 25 | 26 | /** 27 | * @return The simple name of the wrapped class 28 | */ 29 | public String getName() { 30 | return clazz.getSimpleName(); 31 | } 32 | 33 | /** 34 | * @return The wrapped class 35 | */ 36 | public Class getWrappedClass() { 37 | return clazz; 38 | } 39 | 40 | /** 41 | * @return The wrapped superclass 42 | */ 43 | public NMSClass getSuperclass() { 44 | return new NMSClass(clazz.getSuperclass()); 45 | } 46 | 47 | /** 48 | * Calls a constructor of this class with the given arguments 49 | * 50 | * @param args The arguments to pass to the constructor 51 | * @return An NMSObject wrapping the returned value 52 | */ 53 | public NMSObject getInstance(Object... args) { 54 | try { 55 | NMSHelper.unwrapArgs(args); 56 | return new NMSObject(NMSHelper.getConstructor(clazz, NMSHelper.getArgTypes(args)).newInstance(args)); 57 | } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { 58 | throw new IllegalArgumentException(e); 59 | } 60 | } 61 | 62 | /** 63 | * Creates an array of this class type 64 | * 65 | * @param size The size of the array 66 | * @return An NMSArray wrapping the array 67 | */ 68 | public NMSArray createArray(int size) { 69 | return new NMSArray(Array.newInstance(clazz, size)); 70 | } 71 | 72 | /** 73 | * Calls a static method of this class 74 | * 75 | * @param methodName The name of the static method 76 | * @param args The arguments to pass to the static method 77 | * @return An NMSObject wrapping the returned value from the method 78 | */ 79 | public NMSObject callStaticMethod(String methodName, Object... args) { 80 | try { 81 | Method method = NMSHelper.getMethod(clazz, methodName, NMSHelper.getArgTypes(args)); 82 | return new NMSObject(method.invoke(null, args)); 83 | } catch (IllegalAccessException | InvocationTargetException e) { 84 | throw new IllegalArgumentException(e); 85 | } 86 | } 87 | 88 | /** 89 | * Gets the value of a static field in the wrapped class 90 | * 91 | * @param name The name of the field 92 | * @return An NMSObject wrapping the field 93 | */ 94 | public NMSObject getStaticField(String name) { 95 | try { 96 | Field field = clazz.getDeclaredField(name); 97 | field.setAccessible(true); 98 | return new NMSObject(field.get(null)); 99 | } catch (NoSuchFieldException | IllegalAccessException e) { 100 | throw new IllegalArgumentException(e); 101 | } 102 | } 103 | 104 | } 105 | -------------------------------------------------------------------------------- /src/redempt/redlib/nms/NMSObject.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.nms; 2 | 3 | import java.lang.reflect.Field; 4 | import java.lang.reflect.InvocationTargetException; 5 | import java.lang.reflect.Method; 6 | 7 | /** 8 | * Wraps any Object and provides easy access to reflection methods 9 | * 10 | * @author Redempt 11 | */ 12 | public class NMSObject { 13 | 14 | private Object obj; 15 | 16 | /** 17 | * Constructs an NMSObject with the object it should wrap 18 | * 19 | * @param obj The object to wrap 20 | */ 21 | public NMSObject(Object obj) { 22 | this.obj = obj; 23 | } 24 | 25 | /** 26 | * @return The wrapped object 27 | */ 28 | public Object getObject() { 29 | return obj; 30 | } 31 | 32 | /** 33 | * @return The name of the class of the wrapped object 34 | */ 35 | public String getTypeName() { 36 | return obj.getClass().getSimpleName(); 37 | } 38 | 39 | /** 40 | * @return A wrapped NMSClass of the class of the wrapped object 41 | */ 42 | public NMSClass getType() { 43 | return new NMSClass(obj.getClass()); 44 | } 45 | 46 | /** 47 | * @return Whether this NMSObject is wrapping null 48 | */ 49 | public boolean isNull() { 50 | return obj == null; 51 | } 52 | 53 | /** 54 | * Calls a method on the wrapped object 55 | * 56 | * @param name The name of the method 57 | * @param args The arguments to pass to the method 58 | * @param supers The number of superclasses to move up before getting the declared method 59 | * @return An NMSObject which is the returned value from the method 60 | */ 61 | public NMSObject callMethod(int supers, String name, Object... args) { 62 | try { 63 | Method method = NMSHelper.getMethod(getSuperclass(obj.getClass(), supers), name, NMSHelper.getArgTypes(args)); 64 | return new NMSObject(method.invoke(obj, args)); 65 | } catch (IllegalAccessException | InvocationTargetException e) { 66 | throw new IllegalArgumentException(e); 67 | } 68 | } 69 | 70 | /** 71 | * Calls a method on the wrapped object 72 | * 73 | * @param name The name of the method 74 | * @param args The arguments to pass to the method 75 | * @return An NMSObject which is the returned value from the method 76 | */ 77 | public NMSObject callMethod(String name, Object... args) { 78 | return callMethod(0, name, args); 79 | } 80 | 81 | /** 82 | * Gets the value stored in a field in the wrapped object 83 | * 84 | * @param name The name of the field 85 | * @param supers The number of superclasses to move up before getting the declared field 86 | * @return A wrapped NMSObject with the value of the field 87 | */ 88 | public NMSObject getField(int supers, String name) { 89 | try { 90 | Field field = getSuperclass(obj.getClass(), supers).getDeclaredField(name); 91 | field.setAccessible(true); 92 | return new NMSObject(field.get(obj)); 93 | } catch (IllegalAccessException | NoSuchFieldException e) { 94 | throw new IllegalArgumentException(e); 95 | } 96 | } 97 | 98 | /** 99 | * Gets the value stored in a field in the wrapped object 100 | * 101 | * @param name The name of the field 102 | * @return A wrapped NMSObject with the value of the field 103 | */ 104 | public NMSObject getField(String name) { 105 | return getField(0, name); 106 | } 107 | 108 | /** 109 | * Sets the value stored in a field in the wrapped object 110 | * 111 | * @param name The name of the field 112 | * @param supers The number of superclasses to move up before getting the declared field 113 | * @param obj The object to set. Will be unwrapped if it is an NMSObject. 114 | */ 115 | public void setField(int supers, String name, Object obj) { 116 | if (obj instanceof NMSObject) { 117 | obj = ((NMSObject) obj).getObject(); 118 | } 119 | try { 120 | Field field = getSuperclass(this.obj.getClass(), supers).getDeclaredField(name); 121 | field.setAccessible(true); 122 | field.set(this.obj, obj); 123 | } catch (IllegalAccessException | NoSuchFieldException e) { 124 | throw new IllegalArgumentException(e); 125 | } 126 | } 127 | 128 | /** 129 | * Sets the value stored in a field in the wrapped object 130 | * 131 | * @param name The name of the field 132 | * @param obj The object to set. Will be unwrapped if it is an NMSObject. 133 | */ 134 | public void setField(String name, Object obj) { 135 | setField(0, name, obj); 136 | } 137 | 138 | private Class getSuperclass(Class clazz, int levels) { 139 | for (int i = 0; i < levels; i++) { 140 | clazz = clazz.getSuperclass(); 141 | } 142 | return clazz; 143 | } 144 | 145 | } 146 | -------------------------------------------------------------------------------- /src/redempt/redlib/protection/BypassPolicy.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.protection; 2 | 3 | import org.bukkit.block.Block; 4 | import org.bukkit.entity.Player; 5 | 6 | import redempt.redlib.protection.ProtectionPolicy.ProtectionType; 7 | 8 | /** 9 | * Represents a policy that allows players to bypass certain protection types for certain blocks 10 | * 11 | * @author Redempt 12 | */ 13 | public interface BypassPolicy { 14 | 15 | /** 16 | * Checks whether a player can bypass the given protection type for the given block 17 | * 18 | * @param player The player attempting an action 19 | * @param type The type of action being attempted 20 | * @param block The block the action is being performed on 21 | * @return Whether this player can bypass the protection type for the given block 22 | */ 23 | public boolean canBypass(Player player, ProtectionType type, Block block); 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/redempt/redlib/protection/ProtectedRegion.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.protection; 2 | 3 | import org.bukkit.plugin.Plugin; 4 | import redempt.redlib.RedLib; 5 | import redempt.redlib.protection.ProtectionPolicy.ProtectionType; 6 | import redempt.redlib.region.CuboidRegion; 7 | import redempt.redlib.region.Region; 8 | 9 | /** 10 | * Represents a Region which has been protected using a ProtectionPolicy 11 | * 12 | * @author Redempt 13 | */ 14 | public class ProtectedRegion { 15 | 16 | private Region region; 17 | private ProtectionPolicy policy; 18 | 19 | /** 20 | * Creates a ProtectedRegion 21 | * 22 | * @param plugin The Plugin registering the ProtectedRegion 23 | * @param region The Region to protect 24 | * @param types The ProtectionTypes to protect the Region with 25 | */ 26 | public ProtectedRegion(Plugin plugin, Region region, ProtectionType... types) { 27 | this.region = region; 28 | this.policy = new ProtectionPolicy(plugin, region.toCuboid(), region::contains, types); 29 | } 30 | 31 | /** 32 | * Creates a ProtectedRegion 33 | * 34 | * @param region The Region to protect 35 | * @param types The ProtectionTypes to protect the Region with 36 | */ 37 | public ProtectedRegion(Region region, ProtectionType... types) { 38 | this(RedLib.getCallingPlugin(), region, types); 39 | } 40 | 41 | /** 42 | * @return The region being protected 43 | */ 44 | public Region getRegion() { 45 | return region; 46 | } 47 | 48 | /** 49 | * @return The {@link ProtectionPolicy} protecting the region 50 | */ 51 | public ProtectionPolicy getPolicy() { 52 | return policy; 53 | } 54 | 55 | /** 56 | * Disables all protections for this region 57 | */ 58 | public void unprotect() { 59 | policy.disable(); 60 | } 61 | 62 | } -------------------------------------------------------------------------------- /src/redempt/redlib/region/MultiRegionMeta.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.region; 2 | 3 | import org.bukkit.block.BlockFace; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | import java.util.TreeSet; 8 | 9 | class MultiRegionMeta { 10 | 11 | private List> points = new ArrayList<>(); 12 | 13 | public MultiRegionMeta(List regions) { 14 | for (int i = 0; i < 6; i++) { 15 | TreeSet direction = new TreeSet<>(); 16 | for (Region region : regions) { 17 | direction.add(getCoordinate(region, i)); 18 | } 19 | points.add(direction); 20 | } 21 | } 22 | 23 | public double getNextStep(BlockFace face, double current) { 24 | int index = getBlockFaceIndex(face); 25 | TreeSet list = points.get(index); 26 | Double next = index >= 3 ? list.lower(current) : list.higher(current); 27 | return next == null ? current : next; 28 | } 29 | 30 | public double getCurrentStep(Region region, BlockFace face) { 31 | return getCoordinate(region, getBlockFaceIndex(face)); 32 | } 33 | 34 | private int getBlockFaceIndex(BlockFace face) { 35 | switch (face) { 36 | case EAST: 37 | return 0; 38 | case UP: 39 | return 1; 40 | case SOUTH: 41 | return 2; 42 | case WEST: 43 | return 3; 44 | case DOWN: 45 | return 4; 46 | case NORTH: 47 | return 5; 48 | } 49 | return -1; 50 | } 51 | 52 | private double getCoordinate(Region region, int coord) { 53 | switch (coord) { 54 | case 0: 55 | return region.getEnd().getX(); 56 | case 1: 57 | return region.getEnd().getY(); 58 | case 2: 59 | return region.getEnd().getZ(); 60 | case 3: 61 | return region.getStart().getX(); 62 | case 4: 63 | return region.getStart().getY(); 64 | case 5: 65 | return region.getStart().getZ(); 66 | } 67 | return 0; 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /src/redempt/redlib/region/Overlappable.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.region; 2 | 3 | /** 4 | * Represents a Region which supports the overlap and intersect operations 5 | * 6 | * @author Redempt 7 | */ 8 | public abstract class Overlappable extends Region { 9 | 10 | /** 11 | * Checks whether this Overlappable overlaps another Overlappable 12 | * 13 | * @param other The Overlappable Region to check 14 | * @return Whether this Overlappable overlaps the given Overlappable 15 | */ 16 | public abstract boolean overlaps(Overlappable other); 17 | 18 | /** 19 | * Gets the intersection of this Overlappable with another 20 | * 21 | * @param other The Overlappable Region to check 22 | * @return The intersection of this Region with the provided Overlappable Region 23 | */ 24 | public abstract Region getIntersection(Overlappable other); 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/redempt/redlib/region/RegionEnterExitListener.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.region; 2 | 3 | import org.bukkit.Bukkit; 4 | import org.bukkit.event.entity.PlayerDeathEvent; 5 | import org.bukkit.event.player.*; 6 | import redempt.redlib.RedLib; 7 | import redempt.redlib.misc.EventListener; 8 | import redempt.redlib.region.events.RegionEnterEvent; 9 | import redempt.redlib.region.events.RegionEnterEvent.EnterCause; 10 | import redempt.redlib.region.events.RegionExitEvent; 11 | import redempt.redlib.region.events.RegionExitEvent.ExitCause; 12 | 13 | /** 14 | * @author Redempt 15 | */ 16 | public class RegionEnterExitListener { 17 | 18 | private static RegionMap regionMap = new RegionMap<>(); 19 | 20 | static { 21 | register(); 22 | } 23 | 24 | private static void register() { 25 | new EventListener<>(RedLib.getInstance(), PlayerMoveEvent.class, e -> { 26 | regionMap.get(e.getFrom()).forEach(r -> { 27 | if (r.contains(e.getFrom()) && !r.contains(e.getTo())) { 28 | Bukkit.getPluginManager().callEvent(new RegionExitEvent(e.getPlayer(), r, ExitCause.MOVE, e)); 29 | } 30 | }); 31 | regionMap.get(e.getTo()).forEach(r -> { 32 | if (!r.contains(e.getFrom()) && r.contains(e.getTo())) { 33 | Bukkit.getPluginManager().callEvent(new RegionEnterEvent(e.getPlayer(), r, EnterCause.MOVE, e)); 34 | } 35 | }); 36 | }); 37 | new EventListener<>(RedLib.getInstance(), PlayerTeleportEvent.class, e -> { 38 | regionMap.get(e.getFrom()).forEach(r -> { 39 | if (r.contains(e.getFrom()) && !r.contains(e.getTo())) { 40 | Bukkit.getPluginManager().callEvent(new RegionExitEvent(e.getPlayer(), r, ExitCause.TELEPORT, e)); 41 | } 42 | }); 43 | regionMap.get(e.getTo()).forEach(r -> { 44 | if (!r.contains(e.getFrom()) && r.contains(e.getTo())) { 45 | Bukkit.getPluginManager().callEvent(new RegionEnterEvent(e.getPlayer(), r, EnterCause.TELEPORT, e)); 46 | } 47 | }); 48 | }); 49 | new EventListener<>(RedLib.getInstance(), PlayerQuitEvent.class, e -> { 50 | regionMap.get(e.getPlayer().getLocation()).forEach(r -> { 51 | if (r.contains(e.getPlayer().getLocation())) { 52 | Bukkit.getPluginManager().callEvent(new RegionExitEvent(e.getPlayer(), r, ExitCause.QUIT, null)); 53 | } 54 | }); 55 | }); 56 | new EventListener<>(RedLib.getInstance(), PlayerJoinEvent.class, e -> { 57 | regionMap.get(e.getPlayer().getLocation()).forEach(r -> { 58 | if (r.contains(e.getPlayer().getLocation())) { 59 | Bukkit.getPluginManager().callEvent(new RegionEnterEvent(e.getPlayer(), r, EnterCause.JOIN, null)); 60 | } 61 | }); 62 | }); 63 | new EventListener<>(RedLib.getInstance(), PlayerDeathEvent.class, e -> { 64 | regionMap.get(e.getEntity().getLocation()).forEach(r -> { 65 | if (r.contains(e.getEntity().getLocation())) { 66 | Bukkit.getPluginManager().callEvent(new RegionExitEvent(e.getEntity(), r, ExitCause.DEATH, null)); 67 | } 68 | }); 69 | }); 70 | new EventListener<>(RedLib.getInstance(), PlayerRespawnEvent.class, e -> { 71 | regionMap.get(e.getPlayer().getLocation()).forEach(r -> { 72 | if (r.contains(e.getPlayer().getLocation())) { 73 | Bukkit.getPluginManager().callEvent(new RegionEnterEvent(e.getPlayer(), r, EnterCause.RESPAWN, null)); 74 | } 75 | }); 76 | }); 77 | } 78 | 79 | protected static RegionMap getRegionMap() { 80 | return regionMap; 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /src/redempt/redlib/region/RegionUtils.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.region; 2 | 3 | import org.bukkit.block.Block; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | /** 9 | * Utilities for niche use cases of Regions 10 | * 11 | * @author Redempt 12 | */ 13 | public class RegionUtils { 14 | 15 | /** 16 | * Finds the spaces within a Region that can be considered "inside" - space that is surrounded 17 | * by other blocks 18 | * 19 | * @param region The region to find the interior space of 20 | * @return A MultiRegion representing all of the "inside" space within the given region 21 | */ 22 | public static MultiRegion findInside(CuboidRegion region) { 23 | int[] dim = region.getBlockDimensions(); 24 | MultiRegion multi = null; 25 | multi = addAll(multi, iter(region, dim, new int[]{0, 1, 2})); 26 | multi = addAll(multi, iter(region, dim, new int[]{0, 2, 1})); 27 | multi = addAll(multi, iter(region, dim, new int[]{2, 1, 0})); 28 | if (multi != null) { 29 | multi.recalculate(); 30 | } 31 | return multi; 32 | } 33 | 34 | private static MultiRegion addAll(MultiRegion region, List reg) { 35 | int count = 0; 36 | for (CuboidRegion r : reg) { 37 | count++; 38 | if (region == null) { 39 | region = new MultiRegion(r); 40 | continue; 41 | } 42 | region.add(r); 43 | if (count >= 10) { 44 | region.recalculate(); 45 | count = 0; 46 | } 47 | } 48 | return region; 49 | } 50 | 51 | private static List iter(CuboidRegion region, int[] dim, int[] order) { 52 | int[] pos = new int[3]; 53 | List regions = new ArrayList<>(); 54 | for (pos[order[0]] = 0; pos[order[0]] < dim[order[0]]; pos[order[0]]++) { 55 | for (pos[order[1]] = 0; pos[order[1]] < dim[order[1]]; pos[order[1]]++) { 56 | Block block = null; 57 | for (pos[order[2]] = 0; pos[order[2]] < dim[order[2]]; pos[order[2]]++) { 58 | Block b = region.getStart().getBlock().getRelative(pos[0], pos[1], pos[2]); 59 | if (b.getType().isSolid()) { 60 | block = b; 61 | break; 62 | } 63 | } 64 | if (block == null) { 65 | continue; 66 | } 67 | for (pos[order[2]] = dim[order[2]] - 1; pos[order[2]] >= 0; pos[order[2]]--) { 68 | Block b = region.getStart().getBlock().getRelative(pos[0], pos[1], pos[2]); 69 | if (b.equals(block)) { 70 | break; 71 | } 72 | if (b.getType().isSolid()) { 73 | CuboidRegion reg = new CuboidRegion(block.getLocation(), b.getLocation()); 74 | reg.expand(1, 0, 1, 0, 1, 0); 75 | regions.add(reg); 76 | break; 77 | } 78 | } 79 | } 80 | } 81 | return regions; 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /src/redempt/redlib/region/SelectionTool.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.region; 2 | 3 | import org.bukkit.Bukkit; 4 | import org.bukkit.Location; 5 | import org.bukkit.event.EventHandler; 6 | import org.bukkit.event.HandlerList; 7 | import org.bukkit.event.Listener; 8 | import org.bukkit.event.block.Action; 9 | import org.bukkit.event.player.PlayerInteractEvent; 10 | import org.bukkit.event.server.PluginDisableEvent; 11 | import org.bukkit.inventory.ItemStack; 12 | import org.bukkit.plugin.Plugin; 13 | import org.bukkit.plugin.java.JavaPlugin; 14 | import redempt.redlib.RedLib; 15 | import redempt.redlib.itemutils.ItemUtils; 16 | import redempt.redlib.misc.Path; 17 | 18 | import java.util.HashMap; 19 | import java.util.List; 20 | import java.util.Map; 21 | import java.util.UUID; 22 | 23 | /** 24 | * A tool which can be given to players and used to select a Region, or just any two points 25 | * 26 | * @author Redempt 27 | */ 28 | public class SelectionTool implements Listener { 29 | 30 | private ItemStack item; 31 | private Map selections = new HashMap<>(); 32 | private Plugin plugin; 33 | 34 | /** 35 | * Create a SelectionTool with the given item 36 | * 37 | * @param item The item to use 38 | */ 39 | public SelectionTool(ItemStack item) { 40 | this.item = item; 41 | Bukkit.getPluginManager().registerEvents(this, RedLib.getInstance()); 42 | try { 43 | plugin = JavaPlugin.getProvidingPlugin(Class.forName(new Exception().getStackTrace()[1].getClassName())); 44 | } catch (ClassNotFoundException e) { 45 | e.printStackTrace(); 46 | } 47 | } 48 | 49 | @EventHandler 50 | public void onPluginDisable(PluginDisableEvent e) { 51 | if (e.getPlugin().equals(plugin)) { 52 | HandlerList.unregisterAll(this); 53 | } 54 | } 55 | 56 | @EventHandler 57 | public void onClick(PlayerInteractEvent e) { 58 | if (e.getAction() != Action.RIGHT_CLICK_BLOCK || e.getItem() == null) { 59 | return; 60 | } 61 | if (!ItemUtils.compare(e.getItem(), item)) { 62 | return; 63 | } 64 | Location[] locations = selections.getOrDefault(e.getPlayer().getUniqueId(), new Location[2]); 65 | if (locations[0] != null && locations[1] != null) { 66 | locations[0] = null; 67 | locations[1] = null; 68 | } 69 | if (locations[0] == null) { 70 | locations[0] = e.getClickedBlock().getLocation(); 71 | e.getPlayer().sendMessage(RedLib.msg("firstLocationSet")); 72 | } else if (locations[1] == null) { 73 | locations[1] = e.getClickedBlock().getLocation(); 74 | e.getPlayer().sendMessage(RedLib.msg("secondLocationSet")); 75 | } 76 | selections.put(e.getPlayer().getUniqueId(), locations); 77 | } 78 | 79 | /** 80 | * Gets the item used by this SelectionTool 81 | * 82 | * @return The item 83 | */ 84 | public ItemStack getItem() { 85 | return item.clone(); 86 | } 87 | 88 | /** 89 | * Get the locations selected by the given player 90 | * 91 | * @param uuid The UUID of the player 92 | * @return The locations selected by the given player 93 | */ 94 | public Location[] getLocations(UUID uuid) { 95 | return selections.getOrDefault(uuid, new Location[2]); 96 | } 97 | 98 | /** 99 | * Creates and returns a Region based on the locations selected by the player 100 | * 101 | * @param uuid The UUID of the player 102 | * @return The Region selected by the player, or null if the player has not selected 2 locations 103 | */ 104 | public CuboidRegion getRegion(UUID uuid) { 105 | Location[] locations = selections.get(uuid); 106 | if (locations == null || locations[0] == null || locations[1] == null) { 107 | return null; 108 | } 109 | CuboidRegion region = new CuboidRegion(locations[0], locations[1]); 110 | region.expand(1, 0, 1, 0, 1, 0); 111 | return region; 112 | } 113 | 114 | /** 115 | * Creates a path of Locations, one block apart, based on the locations selected by the player 116 | * 117 | * @param uuid The UUID of the player 118 | * @return The Path selected by the player, or null if the player has not selected 2 locations 119 | */ 120 | public List getPath(UUID uuid) { 121 | Location[] locations = selections.get(uuid); 122 | if (locations[0] == null || locations[1] == null) { 123 | return null; 124 | } 125 | return Path.getPath(locations[0], locations[1]); 126 | } 127 | 128 | } 129 | -------------------------------------------------------------------------------- /src/redempt/redlib/region/events/RegionEnterEvent.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.region.events; 2 | 3 | import org.bukkit.entity.Player; 4 | import org.bukkit.event.Cancellable; 5 | import org.bukkit.event.Event; 6 | import org.bukkit.event.HandlerList; 7 | import redempt.redlib.region.Region; 8 | 9 | /** 10 | * Called when a player enters a region with events enabled 11 | * 12 | * @author Redempt 13 | */ 14 | public class RegionEnterEvent extends Event implements Cancellable { 15 | 16 | private static final HandlerList handlers = new HandlerList(); 17 | 18 | public static HandlerList getHandlerList() { 19 | return handlers; 20 | } 21 | 22 | @Override 23 | public HandlerList getHandlers() { 24 | return handlers; 25 | } 26 | 27 | private Region region; 28 | private Player player; 29 | private EnterCause cause; 30 | private Cancellable parent; 31 | 32 | /** 33 | * Constructs a new RegionEnterEvent 34 | * 35 | * @param player The player who entered the region 36 | * @param region The region that was entered 37 | * @param cause What caused the player to enter the region 38 | * @param parent The event which caused this RegionEnterEvent to fire 39 | */ 40 | public RegionEnterEvent(Player player, Region region, EnterCause cause, Cancellable parent) { 41 | this.region = region; 42 | this.player = player; 43 | this.cause = cause; 44 | this.parent = parent; 45 | } 46 | 47 | /** 48 | * @return The player who entered the region 49 | */ 50 | public Player getPlayer() { 51 | return player; 52 | } 53 | 54 | /** 55 | * @return The region that was entered 56 | */ 57 | public Region getRegion() { 58 | return region; 59 | } 60 | 61 | /** 62 | * @return What caused the player to enter the region 63 | */ 64 | public EnterCause getCause() { 65 | return cause; 66 | } 67 | 68 | /** 69 | * @return Whether or not the event has been cancelled. Always false if the parent event cannot be cancelled. 70 | */ 71 | @Override 72 | public boolean isCancelled() { 73 | if (parent == null) { 74 | return false; 75 | } 76 | return parent.isCancelled(); 77 | } 78 | 79 | /** 80 | * Set whether or not to cancel the player entering the Region. 81 | * Not all causes can be cancelled - check {@link RegionEnterEvent#getCause()} first, 82 | * you can't cancel a player joining 83 | * 84 | * @param cancel Whether to cancel this event 85 | */ 86 | @Override 87 | public void setCancelled(boolean cancel) { 88 | if (parent != null) { 89 | parent.setCancelled(cancel); 90 | } 91 | } 92 | 93 | public static enum EnterCause { 94 | /** 95 | * When a player moves into a region 96 | */ 97 | MOVE, 98 | /** 99 | * When a player teleports into a region 100 | */ 101 | TELEPORT, 102 | /** 103 | * When a player joins into a region 104 | */ 105 | JOIN, 106 | /** 107 | * When a players respawns into a region 108 | */ 109 | RESPAWN 110 | } 111 | 112 | } 113 | -------------------------------------------------------------------------------- /src/redempt/redlib/region/events/RegionExitEvent.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.region.events; 2 | 3 | import org.bukkit.entity.Player; 4 | import org.bukkit.event.Cancellable; 5 | import org.bukkit.event.Event; 6 | import org.bukkit.event.HandlerList; 7 | import redempt.redlib.region.Region; 8 | 9 | /** 10 | * Called when a player exits a region with events enabled 11 | * 12 | * @author Redempt 13 | */ 14 | public class RegionExitEvent extends Event implements Cancellable { 15 | 16 | private static final HandlerList handlers = new HandlerList(); 17 | 18 | public static HandlerList getHandlerList() { 19 | return handlers; 20 | } 21 | 22 | @Override 23 | public HandlerList getHandlers() { 24 | return handlers; 25 | } 26 | 27 | private Region region; 28 | private Player player; 29 | private ExitCause cause; 30 | private Cancellable parent; 31 | 32 | /** 33 | * Constructs a new RegionExitEvent 34 | * 35 | * @param player The player that exited the region 36 | * @param region The region that was exited 37 | * @param cause What caused the player to enter the region 38 | * @param parent The event that caused this RegionExitEvent to fire 39 | */ 40 | public RegionExitEvent(Player player, Region region, ExitCause cause, Cancellable parent) { 41 | this.region = region; 42 | this.player = player; 43 | this.cause = cause; 44 | this.parent = parent; 45 | } 46 | 47 | /** 48 | * @return The player who exited the region 49 | */ 50 | public Player getPlayer() { 51 | return player; 52 | } 53 | 54 | /** 55 | * @return The region that was exited 56 | */ 57 | public Region getRegion() { 58 | return region; 59 | } 60 | 61 | /** 62 | * @return What caused the player to exit the region 63 | */ 64 | public ExitCause getCause() { 65 | return cause; 66 | } 67 | 68 | /** 69 | * @return Whether or not the event has been cancelled. Always false if the parent event cannot be cancelled. 70 | */ 71 | @Override 72 | public boolean isCancelled() { 73 | if (parent == null) { 74 | return false; 75 | } 76 | return parent.isCancelled(); 77 | } 78 | 79 | /** 80 | * Set whether or not to cancel the player entering the Region. 81 | * Not all causes can be cancelled - check {@link RegionExitEvent#getCause()} first, 82 | * you can't cancel a player leaving 83 | * 84 | * @param cancel Whether to cancel this event 85 | */ 86 | @Override 87 | public void setCancelled(boolean cancel) { 88 | if (parent != null) { 89 | parent.setCancelled(cancel); 90 | } 91 | } 92 | 93 | public static enum ExitCause { 94 | /** 95 | * When a player moves out of a region 96 | */ 97 | MOVE, 98 | /** 99 | * When a player teleports out of a region 100 | */ 101 | TELEPORT, 102 | /** 103 | * When a player leaves the game whilst in a region 104 | */ 105 | QUIT, 106 | /** 107 | * When a player dies in a region 108 | */ 109 | DEATH 110 | } 111 | 112 | } 113 | -------------------------------------------------------------------------------- /src/redempt/redlib/sql/SQLCacheEntry.java: -------------------------------------------------------------------------------- 1 | package redempt.redlib.sql; 2 | 3 | import java.util.Arrays; 4 | import java.util.Objects; 5 | 6 | class SQLCacheEntry { 7 | 8 | private Object[] params; 9 | 10 | public SQLCacheEntry(Object[] params) { 11 | this.params = params; 12 | } 13 | 14 | public Object[] getParams() { 15 | return params; 16 | } 17 | 18 | @Override 19 | public int hashCode() { 20 | return Objects.hash(params); 21 | } 22 | 23 | @Override 24 | public boolean equals(Object o) { 25 | if (!(o instanceof SQLCacheEntry)) { 26 | return false; 27 | } 28 | return Arrays.equals(params, ((SQLCacheEntry) o).params); 29 | } 30 | 31 | } 32 | --------------------------------------------------------------------------------