├── .github └── workflows │ ├── autobuild.yml │ └── package-publish.yml ├── .gitignore ├── Jenkinsfile ├── LICENSE ├── NyaaCoreTester ├── README.md ├── build.gradle └── src │ └── main │ ├── java │ └── cat │ │ └── nyaa │ │ └── nyaacoretester │ │ ├── DemoTests.java │ │ ├── NyaaCoreTester.java │ │ └── orm │ │ ├── SQLiteDatabaseTest.java │ │ ├── TableAllTypes.java │ │ ├── TableTest1.java │ │ ├── TableTest2.java │ │ ├── TableTest3.java │ │ ├── TableTest4.java │ │ ├── TableTest5.java │ │ ├── TableTest6.java │ │ ├── TableTest7.java │ │ └── TableTest8.java │ └── resources │ ├── plugin.yml │ └── sql │ ├── table3_4_insert.sql │ ├── table3_update.sql │ └── table5_query.sql ├── README.md ├── build.gradle.kts ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle.kts └── src ├── main ├── java │ └── cat │ │ └── nyaa │ │ └── nyaacore │ │ ├── BasicItemMatcher.java │ │ ├── ILocalizer.java │ │ ├── LanguageRepository.java │ │ ├── Message.java │ │ ├── NyaaCoreLoader.java │ │ ├── Pair.java │ │ ├── cmdreceiver │ │ ├── ArgumentParsingException.java │ │ ├── Arguments.java │ │ ├── BadCommandException.java │ │ ├── CommandReceiver.java │ │ ├── NoItemInHandException.java │ │ ├── NoPermissionException.java │ │ ├── NotPlayerException.java │ │ └── SubCommand.java │ │ ├── configuration │ │ ├── FileConfigure.java │ │ ├── Getter.java │ │ ├── ISerializable.java │ │ ├── NbtItemStack.java │ │ ├── PluginConfigure.java │ │ ├── PropertyHelper.java │ │ ├── Setter.java │ │ └── annotation │ │ │ ├── Deserializer.java │ │ │ └── Serializer.java │ │ ├── orm │ │ ├── BundledSQLUtils.java │ │ ├── DataTypeMapping.java │ │ ├── DatabaseUtils.java │ │ ├── NonUniqueResultException.java │ │ ├── ObjectFieldModifier.java │ │ ├── ObjectModifier.java │ │ ├── RollbackGuard.java │ │ ├── WhereClause.java │ │ ├── annotations │ │ │ ├── Column.java │ │ │ └── Table.java │ │ └── backends │ │ │ ├── BackendConfig.java │ │ │ ├── BaseTypedTable.java │ │ │ ├── IConnectedDatabase.java │ │ │ ├── ITable.java │ │ │ ├── ITypedTable.java │ │ │ ├── MysqlDatabase.java │ │ │ └── SQLiteDatabase.java │ │ └── utils │ │ ├── ClassPathUtils.java │ │ ├── ClickSelectionUtils.java │ │ ├── ConcurrentUtils.java │ │ ├── EntityUtils.java │ │ ├── ExperienceUtils.java │ │ ├── HexColorUtils.java │ │ ├── InventoryUtils.java │ │ ├── ItemStackUtils.java │ │ ├── ItemTagUtils.java │ │ ├── LocaleUtils.java │ │ ├── MathUtils.java │ │ ├── NmsUtils.java │ │ ├── OfflinePlayerUtils.java │ │ ├── RayTraceUtils.java │ │ ├── ReflectionUtils.java │ │ ├── TeleportUtils.java │ │ ├── TridentUtils.java │ │ └── VersionUtils.java └── resources │ ├── lang │ └── en_US.yml │ └── plugin.yml └── test └── java └── cat └── nyaa └── nyaacore ├── ArgumentsTest.java ├── LanguageRepositoryTest.java ├── MessageTest.java ├── TableCreationTest.java ├── cmdreceiver └── dispatchtest │ ├── CmdRoot.java │ ├── CmdSub1.java │ ├── CmdSub2.java │ ├── CmdSub2A.java │ └── DispatchTest.java ├── configuration └── ISerializableTest.java └── utils └── VersionUtilsTest.java /.github/workflows/autobuild.yml: -------------------------------------------------------------------------------- 1 | # ref: 2 | # Writing Workflows: 3 | # https://docs.github.com/en/actions/writing-workflows 4 | # Setup Java JDK: 5 | # https://github.com/marketplace/actions/setup-java-jdk 6 | # And the github action env is clean by default so the project need to be pulled and checkout by job 7 | # https://github.com/marketplace/actions/checkout 8 | # otherwise there is nothing in the workspace folder (can also run git pull but this one make thing easy) 9 | # @v4 seems like the latest version matches v4.x.x for a job 10 | # 11 | name: AutoBuilder 12 | run-name: 'Auto build on ${{github.ref_type}} ${{github.ref_name}} #${{github.run_number}}' 13 | on: [push] 14 | jobs: 15 | Build: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: setup jdk 19 | uses: actions/setup-java@v4 20 | with: 21 | distribution: 'oracle' 22 | java-version: '21' 23 | - name: checkout repo 24 | uses: actions/checkout@v4 25 | - name: build project with gradle 26 | run: ./gradlew build -i 27 | - name: upload artifacts 28 | uses: actions/upload-artifact@v4 29 | with: 30 | name: ${{github.event.repository.name}}-autobuild-${{github.run_number}}-git-${{github.sha}}.zip 31 | path: ./build/libs/*.jar -------------------------------------------------------------------------------- /.github/workflows/package-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a package using Maven and then publish it to GitHub packages when a release is created 2 | # For more information see: https://github.com/actions/setup-java/blob/main/docs/advanced-usage.md#apache-maven-with-a-settings-path 3 | # also ref: 4 | # Publishing packages to GitHub Packages: 5 | # https://docs.github.com/en/actions/use-cases-and-examples/publishing-packages/publishing-java-packages-with-maven#publishing-packages-to-github-packages 6 | # 7 | # Upgrade to Automatic token authentication, no need for personal access token anymore 8 | # ref: https://docs.github.com/en/packages/managing-github-packages-using-github-actions-workflows/publishing-and-installing-a-package-with-github-actions#upgrading-a-workflow-that-accesses-a-registry-using-a-personal-access-token 9 | # 10 | 11 | name: Publish to GitHub Packages 12 | 13 | run-name: 'Package Publish #${{github.run_number}}' 14 | 15 | on: 16 | workflow_dispatch: 17 | 18 | jobs: 19 | build: 20 | runs-on: ubuntu-latest 21 | 22 | permissions: 23 | packages: write 24 | contents: read 25 | 26 | steps: 27 | - name: Checkout Code 28 | uses: actions/checkout@v4 29 | 30 | - name: Set up JDK 31 | uses: actions/setup-java@v4 32 | with: 33 | java-version: '21' 34 | distribution: 'oracle' 35 | 36 | - name: Publish to GitHub Packages Apache Maven 37 | run: ./gradlew publishToGithubPackage 38 | env: 39 | GITHUB_MAVEN_URL: https://maven.pkg.github.com/${{github.repository}} 40 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### JetBrains template 3 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 4 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 5 | 6 | .vscode 7 | 8 | # User-specific stuff: 9 | .idea/workspace.xml 10 | .idea/tasks.xml 11 | .idea/dictionaries 12 | .idea/vcs.xml 13 | .idea/jsLibraryMappings.xml 14 | 15 | # Sensitive or high-churn files: 16 | .idea/dataSources.ids 17 | .idea/dataSources.xml 18 | .idea/dataSources.local.xml 19 | .idea/sqlDataSources.xml 20 | .idea/dynamic.xml 21 | .idea/uiDesigner.xml 22 | 23 | # Gradle: 24 | .idea/gradle.xml 25 | .idea/libraries 26 | 27 | # Mongo Explorer plugin: 28 | .idea/mongoSettings.xml 29 | 30 | ## File-based project format: 31 | *.iws 32 | 33 | ## Plugin-specific files: 34 | 35 | # IntelliJ 36 | /out/ 37 | 38 | # mpeltonen/sbt-idea plugin 39 | .idea_modules/ 40 | 41 | # JIRA plugin 42 | atlassian-ide-plugin.xml 43 | 44 | # Crashlytics plugin (for Android Studio and IntelliJ) 45 | com_crashlytics_export_strings.xml 46 | crashlytics.properties 47 | crashlytics-build.properties 48 | fabric.properties 49 | ### Example user template template 50 | ### Example user template 51 | 52 | # IntelliJ project files 53 | .idea 54 | *.iml 55 | out 56 | gen### Java template 57 | *.class 58 | 59 | # Mobile Tools for Java (J2ME) 60 | .mtj.tmp/ 61 | 62 | # Package Files # 63 | *.jar 64 | *.war 65 | *.ear 66 | 67 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 68 | hs_err_pid* 69 | 70 | .DS_Store 71 | .gradle 72 | build 73 | 74 | !lib/LocketteProAPI.jar 75 | !lib/EssentialsX-2.0.1-468.jar 76 | !/gradle/wrapper/gradle-wrapper.jar 77 | /testdb.db 78 | /testdb.db-journal 79 | *.log 80 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | pipeline { 2 | agent any 3 | stages { 4 | stage('Build') { 5 | tools { 6 | jdk "jdk17" 7 | } 8 | steps { 9 | sh './gradlew publishToNyaaCatCILocal' 10 | } 11 | } 12 | } 13 | 14 | post { 15 | always { 16 | archiveArtifacts artifacts: 'build/libs/*.jar', fingerprint: true 17 | cleanWs() 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 NyaaCat Community 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 | -------------------------------------------------------------------------------- /NyaaCoreTester/README.md: -------------------------------------------------------------------------------- 1 | # NyaaCore Test Suit 2 | 3 | This project contains the test cases for NyaaCore plugin. It automatically runs all the tests and 4 | shutdown the server when complete. 5 | 6 | It shall be used for debugging and demonstration purpose but should never be installed on a production server. 7 | 8 | You need to specify `-Dnyaacore.tester.enabled=true` to make this plugin effective. 9 | 10 | -------------------------------------------------------------------------------- /NyaaCoreTester/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java-library' 2 | apply plugin: 'com.github.johnrengelman.shadow' 3 | 4 | sourceCompatibility = 1.8 5 | targetCompatibility = 1.8 6 | 7 | repositories { 8 | jcenter() 9 | maven { name 'Spigot'; url 'https://hub.spigotmc.org/nexus/content/repositories/snapshots/' } 10 | maven { name 'Sonatype'; url 'https://oss.sonatype.org/content/groups/public' } 11 | } 12 | 13 | dependencies { 14 | compileOnly 'org.spigotmc:spigot-api:1.14.3-R0.1-SNAPSHOT' 15 | compileOnly project(":NyaaCore") 16 | 17 | // shadow JUnit into the production jar 18 | api 'junit:junit:4.12' 19 | api 'org.mockito:mockito-core:2.18.3' 20 | shadow 'junit:junit:4.12' 21 | shadow 'org.mockito:mockito-core:2.18.3' 22 | } 23 | -------------------------------------------------------------------------------- /NyaaCoreTester/src/main/java/cat/nyaa/nyaacoretester/DemoTests.java: -------------------------------------------------------------------------------- 1 | package cat.nyaa.nyaacoretester; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.junit.jupiter.api.Assertions.assertEquals; 6 | 7 | public class DemoTests { 8 | @Test 9 | public void arithmeticTest1() { 10 | assertEquals(2, 1 + 1); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /NyaaCoreTester/src/main/java/cat/nyaa/nyaacoretester/NyaaCoreTester.java: -------------------------------------------------------------------------------- 1 | package cat.nyaa.nyaacoretester; 2 | 3 | import cat.nyaa.nyaacoretester.cmdreceiver.CmdRoot; 4 | import cat.nyaa.nyaacoretester.cmdreceiver.CommandReceiverTest; 5 | import cat.nyaa.nyaacoretester.orm.SQLiteDatabaseTest; 6 | import org.bukkit.plugin.java.JavaPlugin; 7 | import org.junit.internal.TextListener; 8 | import org.junit.runner.JUnitCore; 9 | import org.junit.runner.Result; 10 | import org.junit.runner.RunWith; 11 | import org.junit.runners.Suite; 12 | 13 | public class NyaaCoreTester extends JavaPlugin { 14 | public static NyaaCoreTester instance; 15 | public static CmdRoot cmd; 16 | 17 | @Override 18 | public void onEnable() { 19 | instance = this; 20 | saveConfig(); 21 | boolean enabled = Boolean.parseBoolean(System.getProperty("nyaacore.tester.enabled", "false")); 22 | if (enabled) { 23 | getCommand("nyaacoretester").setExecutor(cmd); 24 | getCommand("nyaacoretester").setTabCompleter(cmd); 25 | } 26 | getServer().getScheduler().scheduleSyncDelayedTask(this, new Runnable() { 27 | @Override 28 | public void run() { 29 | 30 | if (!enabled) { 31 | getLogger().warning("NyaaCoreTester installed but \"-Dnyaacore.tester.enabled=true\" not set. Shutdown the server."); 32 | getServer().shutdown(); 33 | } else { 34 | getLogger().info("Ready for testing..."); 35 | 36 | try { 37 | // https://www.baeldung.com/junit-tests-run-programmatically-from-java 38 | JUnitCore junit = new JUnitCore(); 39 | junit.addListener(new TextListener(System.out)); 40 | Result result = junit.run(NyaaCoreTestSuite.class); 41 | System.out.println(String.format("Finished. Result: Failures: %d. Ignored: %d. Tests run: %d. Time: %dms.", 42 | result.getFailureCount(), 43 | result.getIgnoreCount(), 44 | result.getRunCount(), 45 | result.getRunTime() 46 | )); 47 | } catch (Exception ex) { 48 | ex.printStackTrace(); 49 | } 50 | 51 | getLogger().info("Testing complete."); 52 | getServer().shutdown(); 53 | } 54 | } 55 | }); 56 | } 57 | 58 | @RunWith(Suite.class) 59 | @Suite.SuiteClasses({ 60 | DemoTests.class, 61 | SQLiteDatabaseTest.class, 62 | CommandReceiverTest.class 63 | }) 64 | public static class NyaaCoreTestSuite { 65 | 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /NyaaCoreTester/src/main/java/cat/nyaa/nyaacoretester/orm/TableAllTypes.java: -------------------------------------------------------------------------------- 1 | package cat.nyaa.nyaacoretester.orm; 2 | 3 | import cat.nyaa.nyaacore.orm.annotations.Column; 4 | import cat.nyaa.nyaacore.orm.annotations.Table; 5 | import org.bukkit.Material; 6 | import org.bukkit.inventory.ItemStack; 7 | 8 | import java.time.ZonedDateTime; 9 | import java.time.temporal.ChronoUnit; 10 | import java.util.Objects; 11 | import java.util.UUID; 12 | 13 | @Table("all_types") 14 | public class TableAllTypes { 15 | /* 16 | * There are all accepted java types: 17 | * 1. bool/Boolean => INTEGER [true=1, false=0] 18 | * 2. int/Integer => INTEGER [no conversion] 19 | * 3. long/Long => BIGINT [no conversion] 20 | * 4. float/Float => FLOAT [no conversion] 21 | * 5. double/Double => DOUBLE [no conversion] 22 | * 6. String => MEDIUMTEXT [no conversion] 23 | * 7. Enum => MEDIUMTEXT [name() and valueOf()] 24 | * 8. ItemStack => MEDIUMTEXT [nbt(de)serializebase64()] 25 | * 9. Any type can be serialized/deserialized using toString() and fromString()/parse() (e.g. ZonedDateTime) 26 | * => MEDIUMTEXT [toString() and fromString()/parse()] 27 | */ 28 | @Column 29 | boolean p_field_bool; 30 | @Column 31 | Boolean o_field_bool; 32 | boolean p_method_bool; 33 | Boolean o_method_bool; 34 | 35 | @Column 36 | int p_field_int; 37 | @Column 38 | Integer o_field_int; 39 | int p_method_int; 40 | Integer o_method_int; 41 | 42 | @Column 43 | long p_field_long; 44 | @Column 45 | Long o_field_long; 46 | long p_method_long; 47 | Long o_method_long; 48 | 49 | @Column 50 | float p_field_float; 51 | @Column 52 | Float o_field_float; 53 | float p_method_float; 54 | Float o_method_float; 55 | 56 | @Column 57 | double p_field_double; 58 | @Column 59 | Double o_field_double; 60 | double p_method_double; 61 | Double o_method_double; 62 | 63 | @Column 64 | String o_field_string; 65 | String o_method_string; 66 | 67 | @Column 68 | DemoEnums o_field_enum; 69 | DemoEnums o_method_enum; 70 | 71 | @Column 72 | ItemStack o_field_itemstack; 73 | ItemStack o_method_itemstack; 74 | 75 | @Column 76 | UUID o_field_uuid; 77 | UUID o_method_uuid; 78 | 79 | @Column 80 | ZonedDateTime o_field_zoneddatetime; 81 | ZonedDateTime o_method_zoneddatetime; 82 | 83 | public static TableAllTypes instance() { 84 | TableAllTypes ret = new TableAllTypes(); 85 | ret.p_field_bool = false; 86 | ret.o_field_bool = true; 87 | ret.p_method_bool = true; 88 | ret.o_method_bool = false; 89 | ret.p_field_int = 43; 90 | ret.o_field_int = 44; 91 | ret.p_method_int = 45; 92 | ret.o_method_int = 46; 93 | ret.p_field_long = 47; 94 | ret.o_field_long = 48L; 95 | ret.p_method_long = 49; 96 | ret.o_method_long = 50L; 97 | ret.p_field_float = 51.51F; 98 | ret.o_field_float = 52.52F; 99 | ret.p_method_float = 53.53F; 100 | ret.o_method_float = 54.54F; 101 | ret.p_field_double = 55.55D; 102 | ret.o_field_double = 56.56D; 103 | ret.p_method_double = 57.57D; 104 | ret.o_method_double = 58.58D; 105 | ret.o_field_string = "FFFS"; 106 | ret.o_method_string = "FFFE"; 107 | ret.o_field_enum = DemoEnums.VALUE_1; 108 | ret.o_method_enum = DemoEnums.VALUE_2; 109 | ret.o_field_itemstack = new ItemStack(Material.OAK_WOOD); 110 | ret.o_method_itemstack = new ItemStack(Material.CHEST, 32); 111 | ret.o_field_uuid = UUID.randomUUID(); 112 | ret.o_method_uuid = UUID.randomUUID(); 113 | ret.o_field_zoneddatetime = ZonedDateTime.now(); 114 | ret.o_method_zoneddatetime = ret.o_field_zoneddatetime.plus(442L, ChronoUnit.SECONDS); 115 | return ret; 116 | } 117 | 118 | @Column 119 | public boolean getP_method_bool() { 120 | return p_method_bool; 121 | } 122 | 123 | public void setP_method_bool(boolean p_method_bool) { 124 | this.p_method_bool = p_method_bool; 125 | } 126 | 127 | @Column 128 | public Boolean getO_method_bool() { 129 | return o_method_bool; 130 | } 131 | 132 | public void setO_method_bool(Boolean o_method_bool) { 133 | this.o_method_bool = o_method_bool; 134 | } 135 | 136 | @Column 137 | public int getP_method_int() { 138 | return p_method_int; 139 | } 140 | 141 | public void setP_method_int(int p_method_int) { 142 | this.p_method_int = p_method_int; 143 | } 144 | 145 | @Column 146 | public Integer getO_method_int() { 147 | return o_method_int; 148 | } 149 | 150 | public void setO_method_int(Integer o_method_int) { 151 | this.o_method_int = o_method_int; 152 | } 153 | 154 | @Column 155 | public long getP_method_long() { 156 | return p_method_long; 157 | } 158 | 159 | public void setP_method_long(long p_method_long) { 160 | this.p_method_long = p_method_long; 161 | } 162 | 163 | @Column 164 | public Long getO_method_long() { 165 | return o_method_long; 166 | } 167 | 168 | public void setO_method_long(Long o_method_long) { 169 | this.o_method_long = o_method_long; 170 | } 171 | 172 | @Column 173 | public float getP_method_float() { 174 | return p_method_float; 175 | } 176 | 177 | public void setP_method_float(float p_method_float) { 178 | this.p_method_float = p_method_float; 179 | } 180 | 181 | @Column 182 | public Float getO_method_float() { 183 | return o_method_float; 184 | } 185 | 186 | public void setO_method_float(Float o_method_float) { 187 | this.o_method_float = o_method_float; 188 | } 189 | 190 | @Column 191 | public double getP_method_double() { 192 | return p_method_double; 193 | } 194 | 195 | public void setP_method_double(double p_method_double) { 196 | this.p_method_double = p_method_double; 197 | } 198 | 199 | @Column 200 | public Double getO_method_double() { 201 | return o_method_double; 202 | } 203 | 204 | public void setO_method_double(Double o_method_double) { 205 | this.o_method_double = o_method_double; 206 | } 207 | 208 | @Column 209 | public String getO_method_string() { 210 | return o_method_string; 211 | } 212 | 213 | public void setO_method_string(String o_method_string) { 214 | this.o_method_string = o_method_string; 215 | } 216 | 217 | @Column 218 | public DemoEnums getO_method_enum() { 219 | return o_method_enum; 220 | } 221 | 222 | public void setO_method_enum(DemoEnums o_method_enum) { 223 | this.o_method_enum = o_method_enum; 224 | } 225 | 226 | @Column 227 | public ItemStack getO_method_itemstack() { 228 | return o_method_itemstack; 229 | } 230 | 231 | public void setO_method_itemstack(ItemStack o_method_itemstack) { 232 | this.o_method_itemstack = o_method_itemstack; 233 | } 234 | 235 | @Column 236 | public UUID getO_method_uuid() { 237 | return o_method_uuid; 238 | } 239 | 240 | public void setO_method_uuid(UUID o_method_uuid) { 241 | this.o_method_uuid = o_method_uuid; 242 | } 243 | 244 | @Column 245 | public ZonedDateTime getO_method_zoneddatetime() { 246 | return o_method_zoneddatetime; 247 | } 248 | 249 | public void setO_method_zoneddatetime(ZonedDateTime o_method_zoneddatetime) { 250 | this.o_method_zoneddatetime = o_method_zoneddatetime; 251 | } 252 | 253 | @Override 254 | public boolean equals(Object o) { 255 | if (this == o) return true; 256 | if (o == null || getClass() != o.getClass()) return false; 257 | TableAllTypes that = (TableAllTypes) o; 258 | return p_field_bool == that.p_field_bool && 259 | p_method_bool == that.p_method_bool && 260 | p_field_int == that.p_field_int && 261 | p_method_int == that.p_method_int && 262 | p_field_long == that.p_field_long && 263 | p_method_long == that.p_method_long && 264 | Float.compare(that.p_field_float, p_field_float) == 0 && 265 | Float.compare(that.p_method_float, p_method_float) == 0 && 266 | Double.compare(that.p_field_double, p_field_double) == 0 && 267 | Double.compare(that.p_method_double, p_method_double) == 0 && 268 | Objects.equals(o_field_bool, that.o_field_bool) && 269 | Objects.equals(o_method_bool, that.o_method_bool) && 270 | Objects.equals(o_field_int, that.o_field_int) && 271 | Objects.equals(o_method_int, that.o_method_int) && 272 | Objects.equals(o_field_long, that.o_field_long) && 273 | Objects.equals(o_method_long, that.o_method_long) && 274 | Objects.equals(o_field_float, that.o_field_float) && 275 | Objects.equals(o_method_float, that.o_method_float) && 276 | Objects.equals(o_field_double, that.o_field_double) && 277 | Objects.equals(o_method_double, that.o_method_double) && 278 | Objects.equals(o_field_string, that.o_field_string) && 279 | Objects.equals(o_method_string, that.o_method_string) && 280 | o_field_enum == that.o_field_enum && 281 | o_method_enum == that.o_method_enum && 282 | Objects.equals(o_field_itemstack, that.o_field_itemstack) && 283 | Objects.equals(o_method_itemstack, that.o_method_itemstack) && 284 | Objects.equals(o_field_uuid, that.o_field_uuid) && 285 | Objects.equals(o_method_uuid, that.o_method_uuid) && 286 | Objects.equals(o_field_zoneddatetime, that.o_field_zoneddatetime) && 287 | Objects.equals(o_method_zoneddatetime, that.o_method_zoneddatetime); 288 | } 289 | 290 | public enum DemoEnums { 291 | VALUE_1, 292 | VALUE_2, 293 | VALUE_3; 294 | } 295 | } 296 | -------------------------------------------------------------------------------- /NyaaCoreTester/src/main/java/cat/nyaa/nyaacoretester/orm/TableTest1.java: -------------------------------------------------------------------------------- 1 | package cat.nyaa.nyaacoretester.orm; 2 | 3 | import cat.nyaa.nyaacore.orm.annotations.Column; 4 | import cat.nyaa.nyaacore.orm.annotations.Table; 5 | 6 | import java.util.Objects; 7 | import java.util.UUID; 8 | 9 | @Table("test1") 10 | public class TableTest1 { 11 | 12 | @Column(primary = true) 13 | public Long id; 14 | 15 | @Column 16 | public String string; 17 | 18 | @Column 19 | public UUID uuid; 20 | 21 | public UUID uuid_indirect; 22 | 23 | public TableTest1() { 24 | } 25 | 26 | public TableTest1(Long id, String string, UUID uuid, UUID uuid_indirect) { 27 | this.id = id; 28 | this.string = string; 29 | this.uuid = uuid; 30 | this.uuid_indirect = uuid_indirect; 31 | } 32 | 33 | @Column(name = "uuid_indirect") 34 | public String getUuidIndirect() { 35 | return uuid_indirect.toString(); 36 | } 37 | 38 | public void setUuidIndirect(String uuid_indirect) { 39 | this.uuid_indirect = UUID.fromString(uuid_indirect); 40 | } 41 | 42 | @Override 43 | public boolean equals(Object o) { 44 | if (this == o) return true; 45 | if (o == null || getClass() != o.getClass()) return false; 46 | TableTest1 that = (TableTest1) o; 47 | return Objects.equals(id, that.id) && 48 | Objects.equals(string, that.string) && 49 | Objects.equals(uuid, that.uuid) && 50 | Objects.equals(uuid_indirect, that.uuid_indirect); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /NyaaCoreTester/src/main/java/cat/nyaa/nyaacoretester/orm/TableTest2.java: -------------------------------------------------------------------------------- 1 | package cat.nyaa.nyaacoretester.orm; 2 | 3 | import cat.nyaa.nyaacore.orm.annotations.Column; 4 | import cat.nyaa.nyaacore.orm.annotations.Table; 5 | import org.bukkit.inventory.ItemStack; 6 | 7 | import java.util.Objects; 8 | 9 | @Table("test2") 10 | public class TableTest2 { 11 | @Column(primary = true) 12 | public long id; 13 | 14 | @Column 15 | public ItemStack item; 16 | 17 | @Override 18 | public boolean equals(Object o) { 19 | if (this == o) return true; 20 | if (o == null || getClass() != o.getClass()) return false; 21 | TableTest2 that = (TableTest2) o; 22 | return id == that.id && 23 | Objects.equals(item, that.item); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /NyaaCoreTester/src/main/java/cat/nyaa/nyaacoretester/orm/TableTest3.java: -------------------------------------------------------------------------------- 1 | package cat.nyaa.nyaacoretester.orm; 2 | 3 | import cat.nyaa.nyaacore.orm.annotations.Column; 4 | import cat.nyaa.nyaacore.orm.annotations.Table; 5 | 6 | @Table("test3") 7 | public class TableTest3 { 8 | @Column(primary = true) 9 | public long key; 10 | 11 | @Column 12 | public long data1; 13 | 14 | @Column 15 | public long data2; 16 | 17 | @Override 18 | public boolean equals(Object o) { 19 | if (this == o) return true; 20 | if (o == null || getClass() != o.getClass()) return false; 21 | TableTest3 that = (TableTest3) o; 22 | return key == that.key && 23 | data1 == that.data1 && 24 | data2 == that.data2; 25 | } 26 | } -------------------------------------------------------------------------------- /NyaaCoreTester/src/main/java/cat/nyaa/nyaacoretester/orm/TableTest4.java: -------------------------------------------------------------------------------- 1 | package cat.nyaa.nyaacoretester.orm; 2 | 3 | import cat.nyaa.nyaacore.orm.annotations.Column; 4 | import cat.nyaa.nyaacore.orm.annotations.Table; 5 | 6 | @Table("test4") 7 | public class TableTest4 { 8 | @Column(primary = true) 9 | public long key; 10 | 11 | @Column 12 | public long data1; 13 | 14 | @Column 15 | public long data2; 16 | 17 | @Override 18 | public boolean equals(Object o) { 19 | if (this == o) return true; 20 | if (o == null || getClass() != o.getClass()) return false; 21 | TableTest4 that = (TableTest4) o; 22 | return key == that.key && 23 | data1 == that.data1 && 24 | data2 == that.data2; 25 | } 26 | } -------------------------------------------------------------------------------- /NyaaCoreTester/src/main/java/cat/nyaa/nyaacoretester/orm/TableTest5.java: -------------------------------------------------------------------------------- 1 | package cat.nyaa.nyaacoretester.orm; 2 | 3 | import cat.nyaa.nyaacore.orm.annotations.Column; 4 | import cat.nyaa.nyaacore.orm.annotations.Table; 5 | 6 | import java.util.Objects; 7 | 8 | @Table("test5") 9 | public class TableTest5 { 10 | @Column(primary = true) 11 | String player_name; 12 | @Column 13 | int balance; 14 | @Column 15 | String group_name; 16 | 17 | public TableTest5() { 18 | } 19 | 20 | public TableTest5(String playerName, int balance, String group) { 21 | this.player_name = playerName; 22 | this.balance = balance; 23 | this.group_name = group; 24 | } 25 | 26 | @Table 27 | public static class CollectedReport { 28 | @Column 29 | String group_name; 30 | @Column 31 | int max_balance; 32 | @Column 33 | int head_count; 34 | 35 | public CollectedReport() { 36 | } 37 | 38 | public CollectedReport(String group, int max_balance, int head_count) { 39 | this.group_name = group; 40 | this.max_balance = max_balance; 41 | this.head_count = head_count; 42 | } 43 | 44 | @Override 45 | public boolean equals(Object o) { 46 | if (this == o) return true; 47 | if (o == null || getClass() != o.getClass()) return false; 48 | CollectedReport that = (CollectedReport) o; 49 | return max_balance == that.max_balance && 50 | head_count == that.head_count && 51 | Objects.equals(group_name, that.group_name); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /NyaaCoreTester/src/main/java/cat/nyaa/nyaacoretester/orm/TableTest6.java: -------------------------------------------------------------------------------- 1 | package cat.nyaa.nyaacoretester.orm; 2 | 3 | import cat.nyaa.nyaacore.orm.annotations.Column; 4 | import cat.nyaa.nyaacore.orm.annotations.Table; 5 | 6 | import java.util.Objects; 7 | 8 | @Table("test6") 9 | public class TableTest6 { 10 | @Column(primary = true) 11 | String key; 12 | @Column(nullable = true) 13 | String value; 14 | 15 | public TableTest6() { 16 | } 17 | 18 | public TableTest6(String key, String value) { 19 | this.key = key; 20 | this.value = value; 21 | } 22 | 23 | @Override 24 | public boolean equals(Object o) { 25 | if (this == o) return true; 26 | if (o == null || getClass() != o.getClass()) return false; 27 | TableTest6 that = (TableTest6) o; 28 | return Objects.equals(key, that.key) && 29 | Objects.equals(value, that.value); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /NyaaCoreTester/src/main/java/cat/nyaa/nyaacoretester/orm/TableTest7.java: -------------------------------------------------------------------------------- 1 | package cat.nyaa.nyaacoretester.orm; 2 | 3 | import cat.nyaa.nyaacore.orm.annotations.Column; 4 | import cat.nyaa.nyaacore.orm.annotations.Table; 5 | 6 | 7 | @Table("test7") 8 | public class TableTest7 { 9 | @Column(primary = true) 10 | int id; 11 | @Column(unique = false) 12 | long val; 13 | @Column(unique = true) 14 | long val_uniq; 15 | 16 | public TableTest7() { 17 | } 18 | 19 | public TableTest7(int id, long val, long val_uniq) { 20 | this.id = id; 21 | this.val = val; 22 | this.val_uniq = val_uniq; 23 | } 24 | 25 | @Override 26 | public boolean equals(Object o) { 27 | if (this == o) return true; 28 | if (o == null || getClass() != o.getClass()) return false; 29 | TableTest7 that = (TableTest7) o; 30 | return id == that.id && 31 | val == that.val && 32 | val_uniq == that.val_uniq; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /NyaaCoreTester/src/main/java/cat/nyaa/nyaacoretester/orm/TableTest8.java: -------------------------------------------------------------------------------- 1 | package cat.nyaa.nyaacoretester.orm; 2 | 3 | import cat.nyaa.nyaacore.orm.annotations.Column; 4 | import cat.nyaa.nyaacore.orm.annotations.Table; 5 | 6 | import java.util.Objects; 7 | 8 | @Table("test8") 9 | public class TableTest8 { 10 | @Column(primary = true) 11 | Integer x; 12 | @Column 13 | String y; 14 | 15 | public TableTest8() { 16 | } 17 | 18 | public TableTest8(Integer x, String y) { 19 | this.x = x; 20 | this.y = y; 21 | } 22 | 23 | @Override 24 | public boolean equals(Object o) { 25 | if (this == o) return true; 26 | if (o == null || getClass() != o.getClass()) return false; 27 | TableTest8 that = (TableTest8) o; 28 | return Objects.equals(x, that.x) && 29 | Objects.equals(y, that.y); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /NyaaCoreTester/src/main/resources/plugin.yml: -------------------------------------------------------------------------------- 1 | name: NyaaCoreTester 2 | main: cat.nyaa.nyaacoretester.NyaaCoreTester 3 | description: "NyaaCoreTester" 4 | version: 7.0.0 5 | depend: [NyaaCore] 6 | authors: [RecursiveG] 7 | website: "https://github.com/NyaaCat/NyaaCoreTester" 8 | api-version: 1.13 9 | 10 | commands: 11 | nyaacoretester: 12 | description: The command for NyaaCoreTester 13 | aliases: nct 14 | -------------------------------------------------------------------------------- /NyaaCoreTester/src/main/resources/sql/table3_4_insert.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO {{table_name}}(key,data1,data2) VALUES(?,?,?) -------------------------------------------------------------------------------- /NyaaCoreTester/src/main/resources/sql/table3_update.sql: -------------------------------------------------------------------------------- 1 | UPDATE test3 2 | SET data1=(SELECT key FROM test4 WHERE test4.data1 = test3.data2) 3 | WHERE test3.data2 IN (SELECT data1 FROM test4); -------------------------------------------------------------------------------- /NyaaCoreTester/src/main/resources/sql/table5_query.sql: -------------------------------------------------------------------------------- 1 | SELECT group_name, COUNT(player_name) AS head_count, MAX(balance) AS max_balance FROM test5 GROUP BY group_name ORDER BY group_name ASC -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## NyaaCore 2 | Common library used for all NyaaCat plugin. 3 | Provides infrastructures to simplify plugin development. 4 | 5 | [![Build Status](https://ci.nyaacat.com/job/NyaaCore/job/main/badge/icon)](https://ci.nyaacat.com/job/NyaaCore/job/main/) 6 | ## Component List 7 | 8 | - Annotation based command dispatcher 9 | - Annotation based configuration serializer 10 | - Annotation based database serializer 11 | - JSON message builder 12 | - Database middleware 13 | - Http client 14 | - Http server based on [timboudreau/netty-http-client](https://github.com/timboudreau/netty-http-client) 15 | 16 | ## Use as dependency in Gradle 17 | 18 | ``` 19 | repositories { 20 | maven { 21 | name 'NyaaCat' 22 | url 'https://ci.nyaacat.com/maven/' 23 | } 24 | maven { 25 | name 'aikar'; 26 | url 'https://repo.aikar.co/content/groups/aikar/' 27 | } 28 | } 29 | 30 | dependencies { 31 | compile('cat.nyaa:nyaacore:9.0-SNAPSHOT') 32 | } 33 | ``` 34 | 35 | ## Dependencies 36 | - EssentialsX: soft depend. Used by `TeleportUtils`, with Essentials install, players can use `/back` command to return to the position before teleportion. 37 | - Vault: soft depend. It's fine as long as you don't touch `VaultUtils`. If any downstream plugins what to use that, that plugin **MUST** list `Vault` as a required dependency. 38 | 39 | ## Version History 40 | - 6.4.x: Minecraft 1.14.3 41 | - 7.0.x: Minecraft 1.14.4 42 | - 7.1.x: Minecraft 1.15.1 since build 350 43 | - 9.x: Minecraft 1.21+ 44 | 45 | Older versions can be found in [Github Release Page](https://github.com/NyaaCat/NyaaCore/releases) 46 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | import java.net.URI 2 | 3 | plugins { 4 | `java-library` 5 | `maven-publish` 6 | id("io.papermc.paperweight.userdev") version "2.0.0-beta.17" 7 | id("xyz.jpenilla.run-paper") version "2.3.0" // Adds runServer and runMojangMappedServer tasks for testing 8 | } 9 | 10 | // = = = 11 | 12 | val pluginName = "NyaaCore" 13 | val paperApiName = "1.21.5-R0.1-SNAPSHOT" 14 | 15 | // = = = 16 | 17 | group = "cat.nyaa" 18 | version ="9.6" 19 | 20 | java { 21 | // Configure the java toolchain. This allows gradle to auto-provision JDK 21 on systems that only have JDK 8 installed for example. 22 | toolchain.languageVersion.set(JavaLanguageVersion.of(21)) 23 | } 24 | 25 | repositories { 26 | mavenCentral() 27 | maven ("https://papermc.io/repo/repository/maven-public/") //paper 28 | maven ("https://libraries.minecraft.net") // mojang 29 | maven ("https://repo.essentialsx.net/releases/") // essentials 30 | // maven { url = uri("https://ci.nyaacat.com/maven/") } // nyaacat 31 | } 32 | 33 | dependencies { 34 | paperweight.paperDevBundle(paperApiName) 35 | compileOnly("net.essentialsx:EssentialsX:2.21.1") { 36 | exclude(group = "org.spigotmc", module = "spigot-api") 37 | } // soft dep 38 | compileOnly("org.jetbrains:annotations:24.1.0") 39 | // Testing 40 | testImplementation(platform("org.junit:junit-bom:5.10.3")) 41 | testImplementation("org.junit.jupiter:junit-jupiter") 42 | testImplementation("com.github.seeseemelk:MockBukkit-v1.21:3.95.1") 43 | testImplementation("org.mockito:mockito-core:5.12.0") 44 | testImplementation("org.mockito:mockito-junit-jupiter:5.12.0") 45 | testImplementation("org.xerial:sqlite-jdbc:3.46.0.0") 46 | testImplementation("ch.vorburger.mariaDB4j:mariaDB4j:3.1.0") 47 | } 48 | 49 | publishing { 50 | publications { 51 | create("mavenJava") { 52 | from(components["java"]) 53 | groupId = group.toString() 54 | artifactId = pluginName.lowercase() 55 | version = project.version.toString() 56 | } 57 | } 58 | repositories { 59 | maven { 60 | name = "GithubPackage" 61 | url = URI(System.getenv("GITHUB_MAVEN_URL") ?: "https://github.com") 62 | credentials { 63 | username = System.getenv("GITHUB_ACTOR") 64 | password = System.getenv("GITHUB_TOKEN") 65 | } 66 | } 67 | maven { 68 | name = "NyaaCatCILocal" 69 | //local maven repository 70 | url = uri("file://${System.getenv("MAVEN_DIR")}") 71 | } 72 | } 73 | } 74 | 75 | // Custom tasks for publishing to specific repositories 76 | tasks.register("publishToGithubPackage") { 77 | dependsOn("publishMavenJavaPublicationToGithubPackageRepository") 78 | // auto generated task: publishPublicationToRepository 79 | } 80 | 81 | tasks.register("publishToNyaaCatCILocal") { 82 | dependsOn("publishMavenJavaPublicationToNyaaCatCILocalRepository") 83 | } 84 | 85 | 86 | /* 87 | reobfJar { 88 | // This is an example of how you might change the output location for reobfJar. It's recommended not to do this 89 | // for a variety of reasons, however it's asked frequently enough that an example of how to do it is included here. 90 | outputJar.set(layout.buildDirectory.file("libs/PaperweightTestPlugin-${project.version}.jar")) 91 | } 92 | */ 93 | 94 | 95 | tasks { 96 | // ref: https://docs.papermc.io/paper/dev/userdev 97 | // 1) 98 | // For >=1.20.5 when you don't care about supporting spigot 99 | // set: 100 | paperweight.reobfArtifactConfiguration = io.papermc.paperweight.userdev.ReobfArtifactConfiguration.MOJANG_PRODUCTION 101 | // and it will eliminate the -dev suffix from the artifact name which indicates the artifact is 102 | // mojang-mapped and won't work on the most of the server but at 1.20.5 the paper 103 | // uses mojang-mapping by default, so it doesn't matter anymore. 104 | // 105 | 106 | // 2) 107 | // For 1.20.4 or below, or when you care about supporting Spigot on >=1.20.5 108 | // Configure reobfJar to run when invoking the build task 109 | /* 110 | assemble { 111 | dependsOn(reobfJar) 112 | } 113 | */ 114 | 115 | withType { 116 | val newProperties = project.properties.toMutableMap() 117 | newProperties["api_version"] = getMcVersion(paperApiName) 118 | filesMatching("plugin.yml") { 119 | expand(newProperties) 120 | } 121 | } 122 | 123 | compileJava { 124 | options.encoding = Charsets.UTF_8.name() // We want UTF-8 for everything 125 | // Set the release flag. This configures what version bytecode the compiler will emit, as well as what JDK APIs are usable. 126 | // See https://openjdk.java.net/jeps/247 for more information. 127 | options.release.set(21) 128 | } 129 | 130 | javadoc { 131 | with((options as StandardJavadocDocletOptions)) { 132 | options.encoding = 133 | Charsets.UTF_8.name() // We want UTF-8 for everything 134 | links("https://docs.oracle.com/en/java/javase/21/docs/api/") 135 | links("https://guava.dev/releases/21.0/api/docs/") 136 | links("https://ci.md-5.net/job/BungeeCord/ws/chat/target/apidocs/") 137 | links("https://jd.papermc.io/paper/1.21/") 138 | options.locale = "en_US" 139 | options.encoding = "UTF-8" 140 | (options as StandardJavadocDocletOptions).addBooleanOption( 141 | "keywords", 142 | true 143 | ) 144 | (options as StandardJavadocDocletOptions).addStringOption( 145 | "Xdoclint:none", 146 | "-quiet" 147 | ) 148 | (options as StandardJavadocDocletOptions).addBooleanOption( 149 | "html5", 150 | true 151 | ) 152 | options.windowTitle = "$pluginName Javadoc" 153 | } 154 | } 155 | } 156 | 157 | private fun getMcVersion(apiNameString: String): String { 158 | val version = apiNameString.split('-')[0] 159 | val versionInArray = version.split('.') 160 | return "${versionInArray[0]}.${versionInArray[1]}" 161 | } 162 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NyaaCat/NyaaCore/2494fe7f75f22c36163f5d1481c6e70c32bd6831/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.1-bin.zip 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStorePath=wrapper/dists 5 | zipStoreBase=GRADLE_USER_HOME 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 84 | 85 | APP_NAME="Gradle" 86 | APP_BASE_NAME=${0##*/} 87 | 88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | MAX_FD=$( ulimit -H -n ) || 147 | warn "Could not query maximum file descriptor limit" 148 | esac 149 | case $MAX_FD in #( 150 | '' | soft) :;; #( 151 | *) 152 | ulimit -n "$MAX_FD" || 153 | warn "Could not set maximum file descriptor limit to $MAX_FD" 154 | esac 155 | fi 156 | 157 | # Collect all arguments for the java command, stacking in reverse order: 158 | # * args from the command line 159 | # * the main class name 160 | # * -classpath 161 | # * -D...appname settings 162 | # * --module-path (only if needed) 163 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 164 | 165 | # For Cygwin or MSYS, switch paths to Windows format before running java 166 | if "$cygwin" || "$msys" ; then 167 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 168 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 169 | 170 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 171 | 172 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 173 | for arg do 174 | if 175 | case $arg in #( 176 | -*) false ;; # don't mess with options #( 177 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 178 | [ -e "$t" ] ;; #( 179 | *) false ;; 180 | esac 181 | then 182 | arg=$( cygpath --path --ignore --mixed "$arg" ) 183 | fi 184 | # Roll the args list around exactly as many times as the number of 185 | # args, so each arg winds up back in the position where it started, but 186 | # possibly modified. 187 | # 188 | # NB: a `for` loop captures its iteration list before it begins, so 189 | # changing the positional parameters here affects neither the number of 190 | # iterations, nor the values presented in `arg`. 191 | shift # remove old arg 192 | set -- "$@" "$arg" # push replacement arg 193 | done 194 | fi 195 | 196 | # Collect all arguments for the java command; 197 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 198 | # shell script including quotes and variable substitutions, so put them in 199 | # double quotes to make sure that they get re-expanded; and 200 | # * put everything else in single quotes, so that it's not re-expanded. 201 | 202 | set -- \ 203 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 204 | -classpath "$CLASSPATH" \ 205 | org.gradle.wrapper.GradleWrapperMain \ 206 | "$@" 207 | 208 | # Use "xargs" to parse quoted args. 209 | # 210 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 211 | # 212 | # In Bash we could simply go: 213 | # 214 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 215 | # set -- "${ARGS[@]}" "$@" 216 | # 217 | # but POSIX shell has neither arrays nor command substitution, so instead we 218 | # post-process each arg (as a line of input to sed) to backslash-escape any 219 | # character that might be a shell metacharacter, then use eval to reverse 220 | # that process (while maintaining the separation between arguments), and wrap 221 | # the whole thing up as a single "set" statement. 222 | # 223 | # This will of course break if any of these variables contains a newline or 224 | # an unmatched quote. 225 | # 226 | 227 | eval "set -- $( 228 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 229 | xargs -n1 | 230 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 231 | tr '\n' ' ' 232 | )" '"$@"' 233 | 234 | exec "$JAVACMD" "$@" 235 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | maven ("https://repo.papermc.io/repository/maven-public/" ) 5 | } 6 | } 7 | 8 | rootProject.name = "NyaaCore" -------------------------------------------------------------------------------- /src/main/java/cat/nyaa/nyaacore/BasicItemMatcher.java: -------------------------------------------------------------------------------- 1 | package cat.nyaa.nyaacore; 2 | 3 | import cat.nyaa.nyaacore.configuration.ISerializable; 4 | import org.bukkit.ChatColor; 5 | import org.bukkit.enchantments.Enchantment; 6 | import org.bukkit.inventory.ItemStack; 7 | import org.bukkit.inventory.meta.Damageable; 8 | import org.bukkit.inventory.meta.ItemMeta; 9 | import org.bukkit.inventory.meta.Repairable; 10 | 11 | import java.util.*; 12 | 13 | /* TODO: support NBT matching */ 14 | /* TODO: item attribute modifiers */ 15 | /* TODO: item flags */ 16 | public class BasicItemMatcher implements ISerializable { 17 | @Serializable 18 | public ItemStack itemTemplate = null; 19 | @Serializable 20 | public boolean requireExact = false; // Require item to be exactly the same. e.g. can stack together. Ignore all other rules. 21 | @Serializable 22 | public int minDamageValue = -2; // `-1` means `arbitrary`; `-2` means `same as template` 23 | @Serializable 24 | public int maxDamageValue = -2; // `-1` means `arbitrary`; `-2` means `same as template` 25 | @Serializable 26 | public MatchingMode enchantMatch = MatchingMode.CONTAINS; 27 | @Serializable 28 | public MatchingMode loreMatch = MatchingMode.EXACT_TEXT; 29 | @Serializable 30 | public MatchingMode nameMatch = MatchingMode.ARBITRARY; 31 | @Serializable 32 | public MatchingMode repairCostMatch = MatchingMode.EXACT; 33 | 34 | public static boolean containsMatch(Collection list, ItemStack item) { 35 | for (BasicItemMatcher m : list) { 36 | if (m.matches(item)) { 37 | return true; 38 | } 39 | } 40 | return false; 41 | } 42 | 43 | public boolean matches(ItemStack anotherItem) { 44 | ItemStack base = itemTemplate.clone(); 45 | ItemStack given = anotherItem.clone(); 46 | base.setAmount(1); 47 | given.setAmount(1); 48 | if (requireExact) return base.equals(given); 49 | if (!base.getType().equals(given.getType())) return false; 50 | 51 | ItemMeta baseItemMeta = base.getItemMeta(); 52 | ItemMeta givenItemMeta = given.getItemMeta(); 53 | if (repairCostMatch == MatchingMode.EXACT && 54 | baseItemMeta instanceof Repairable && givenItemMeta instanceof Repairable && 55 | !(((Repairable) givenItemMeta).getRepairCost() == ((Repairable) baseItemMeta).getRepairCost())) { 56 | return false; 57 | } 58 | 59 | int baseDamage = ((Damageable) baseItemMeta).getDamage(); 60 | int givenDamage = ((Damageable) givenItemMeta).getDamage(); 61 | if (minDamageValue == -2 && givenDamage < baseDamage) return false; 62 | if (minDamageValue >= 0 && givenDamage < minDamageValue) return false; 63 | if (maxDamageValue == -2 && givenDamage > baseDamage) return false; 64 | if (maxDamageValue >= 0 && givenDamage > maxDamageValue) return false; 65 | 66 | String baseDisplay = getDisplayName(base); 67 | String givenDisplay = getDisplayName(given); 68 | if (nameMatch == MatchingMode.EXACT && !baseDisplay.equals(givenDisplay)) return false; 69 | if (nameMatch == MatchingMode.EXACT_TEXT && !ChatColor.stripColor(baseDisplay).equals(ChatColor.stripColor(givenDisplay))) 70 | return false; 71 | if (nameMatch == MatchingMode.CONTAINS && !givenDisplay.contains(baseDisplay)) return false; 72 | if (nameMatch == MatchingMode.CONTAINS_TEXT && !ChatColor.stripColor(givenDisplay).contains(ChatColor.stripColor(baseDisplay))) 73 | return false; 74 | 75 | Map baseEnch = base.getEnchantments(); 76 | Map givenEnch = given.getEnchantments(); 77 | if (enchantMatch == MatchingMode.EXACT || enchantMatch == MatchingMode.EXACT_TEXT) { 78 | if (!baseEnch.equals(givenEnch)) return false; 79 | } else if (enchantMatch == MatchingMode.CONTAINS || enchantMatch == MatchingMode.CONTAINS_TEXT) { 80 | for (Map.Entry e : baseEnch.entrySet()) { 81 | if (!givenEnch.containsKey(e.getKey()) || givenEnch.get(e.getKey()) < e.getValue()) 82 | return false; 83 | } 84 | } 85 | 86 | String[] baseLore = getLore(base); 87 | String[] givenLore = getLore(given); 88 | if (loreMatch == MatchingMode.EXACT && !Arrays.deepEquals(baseLore, givenLore)) return false; 89 | if (loreMatch == MatchingMode.CONTAINS && !containStrArr(givenLore, baseLore, false)) return false; 90 | if (loreMatch == MatchingMode.EXACT_TEXT) { 91 | for (int i = 0; i < baseLore.length; i++) baseLore[i] = ChatColor.stripColor(baseLore[i]); 92 | for (int i = 0; i < givenLore.length; i++) givenLore[i] = ChatColor.stripColor(givenLore[i]); 93 | if (!Arrays.deepEquals(baseLore, givenLore)) return false; 94 | } 95 | return loreMatch != MatchingMode.CONTAINS_TEXT || containStrArr(givenLore, baseLore, true); 96 | } 97 | 98 | private String getDisplayName(ItemStack i) { 99 | if (i.hasItemMeta() && i.getItemMeta().hasDisplayName()) return i.getItemMeta().getDisplayName(); 100 | return i.getType().name(); 101 | } 102 | 103 | private String[] getLore(ItemStack i) { 104 | if (!i.hasItemMeta() || !i.getItemMeta().hasLore()) return new String[0]; 105 | return i.getItemMeta().getLore().toArray(new String[0]); 106 | } 107 | 108 | private boolean containStrArr(String[] sample, String[] pattern, boolean stripColor) { 109 | Set sampleSet = new HashSet<>(); 110 | for (String s : sample) { 111 | sampleSet.add(stripColor ? ChatColor.stripColor(s) : s); 112 | } 113 | for (String s : pattern) { 114 | if (!sampleSet.contains(s)) 115 | return false; 116 | } 117 | return true; 118 | } 119 | 120 | public enum MatchingMode { 121 | EXACT, 122 | EXACT_TEXT, // ignore the control chars for strings. 123 | CONTAINS, 124 | CONTAINS_TEXT, // ignore the control chars for strings. 125 | ARBITRARY 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/main/java/cat/nyaa/nyaacore/ILocalizer.java: -------------------------------------------------------------------------------- 1 | package cat.nyaa.nyaacore; 2 | 3 | public interface ILocalizer { 4 | /** 5 | * Get the language item then format with `para` by {@link String#format(String, Object...)} 6 | */ 7 | String getFormatted(String key, Object... para); 8 | 9 | boolean hasKey(String key); 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/cat/nyaa/nyaacore/NyaaCoreLoader.java: -------------------------------------------------------------------------------- 1 | package cat.nyaa.nyaacore; 2 | 3 | import cat.nyaa.nyaacore.configuration.NbtItemStack; 4 | import cat.nyaa.nyaacore.utils.ClickSelectionUtils; 5 | import cat.nyaa.nyaacore.utils.OfflinePlayerUtils; 6 | import net.minecraft.SharedConstants; 7 | import org.bukkit.Bukkit; 8 | import org.bukkit.configuration.serialization.ConfigurationSerialization; 9 | import org.bukkit.plugin.PluginDescriptionFile; 10 | import org.bukkit.plugin.java.JavaPlugin; 11 | import org.bukkit.plugin.java.JavaPluginLoader; 12 | 13 | import javax.annotation.Nullable; 14 | import java.io.File; 15 | import java.io.InputStream; 16 | import java.util.logging.Logger; 17 | 18 | public class NyaaCoreLoader extends JavaPlugin { 19 | private static NyaaCoreLoader instance; 20 | 21 | static { 22 | ConfigurationSerialization.registerClass(NbtItemStack.class); 23 | } 24 | 25 | private boolean isTest = false; 26 | 27 | // public NyaaCoreLoader() { 28 | // super(); 29 | // } 30 | // 31 | // protected NyaaCoreLoader(JavaPluginLoader loader, PluginDescriptionFile description, File dataFolder, File file) { 32 | // super(loader, description, dataFolder, file); 33 | // } 34 | // 35 | // protected NyaaCoreLoader(JavaPluginLoader loader, PluginDescriptionFile description, File dataFolder, File file, Boolean isTest) { 36 | // super(loader, description, dataFolder, file); 37 | // this.isTest = isTest; 38 | // } 39 | 40 | public static NyaaCoreLoader getInstance() { 41 | return instance; 42 | } 43 | 44 | @Override 45 | public void onLoad() { 46 | instance = this; 47 | } 48 | 49 | @Override 50 | public void onEnable() { 51 | if (!isTest) { 52 | String serverVersion = "", targetVersion; 53 | try { 54 | serverVersion = SharedConstants.getCurrentVersion().getName(); 55 | } catch (Exception e) { 56 | getLogger().severe(e.getMessage()); 57 | Bukkit.getPluginManager().disablePlugin(this); 58 | } 59 | var pluginDescription = getDescription(); 60 | targetVersion = pluginDescription.getAPIVersion(); 61 | getLogger().info("target minecraft version:" + targetVersion + " ,server version:" + serverVersion); 62 | Bukkit.getPluginManager().registerEvents(new ClickSelectionUtils._Listener(), this); 63 | Bukkit.getPluginManager().registerEvents(new OfflinePlayerUtils._Listener(), this); 64 | OfflinePlayerUtils.init(); 65 | } 66 | } 67 | 68 | public static class checkVersion { 69 | private static checkVersion instance; 70 | private static boolean bypass = false; 71 | 72 | public static checkVersion getInstance() { 73 | if (checkVersion.instance == null) { 74 | checkVersion.instance = new checkVersion(); 75 | } 76 | return instance; 77 | } 78 | 79 | public void enable(@Nullable InputStream VersionResource, Logger logger) { 80 | if (bypass) { 81 | } 82 | 83 | } 84 | 85 | public void setOff() { // test only 86 | bypass = true; 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/cat/nyaa/nyaacore/Pair.java: -------------------------------------------------------------------------------- 1 | package cat.nyaa.nyaacore; 2 | 3 | import java.util.Map; 4 | import java.util.Objects; 5 | 6 | public class Pair implements Map.Entry { 7 | 8 | private K key; 9 | private V value; 10 | 11 | public Pair(K key, V value) { 12 | this.key = key; 13 | this.value = value; 14 | } 15 | 16 | public Pair(Map.Entry entry) { 17 | this.key = entry.getKey(); 18 | this.value = entry.getValue(); 19 | } 20 | 21 | public static Pair of(Ks key, Vs value) { 22 | return new Pair<>(key, value); 23 | } 24 | 25 | @Override 26 | public K getKey() { 27 | return key; 28 | } 29 | 30 | public K setKey(K key) { 31 | K oldKey = this.key; 32 | this.key = key; 33 | return oldKey; 34 | } 35 | 36 | @Override 37 | public V getValue() { 38 | return value; 39 | } 40 | 41 | @Override 42 | public V setValue(V value) { 43 | V oldValue = this.value; 44 | this.value = value; 45 | return oldValue; 46 | } 47 | 48 | @Override 49 | public int hashCode() { 50 | return (key == null ? 0 : key.hashCode()) * 17 + (value == null ? 0 : value.hashCode()); 51 | } 52 | 53 | @Override 54 | public boolean equals(Object o) { 55 | if (this == o) return true; 56 | if (o instanceof Pair pair) { 57 | return Objects.equals(key, pair.getKey()) && Objects.equals(value, pair.getValue()); 58 | } 59 | return false; 60 | } 61 | 62 | @Override 63 | public String toString() { 64 | return key + "=" + value; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/cat/nyaa/nyaacore/cmdreceiver/ArgumentParsingException.java: -------------------------------------------------------------------------------- 1 | package cat.nyaa.nyaacore.cmdreceiver; 2 | 3 | public class ArgumentParsingException extends Exception { 4 | public String cmdline; 5 | public int index; 6 | public String reason; 7 | 8 | public ArgumentParsingException(String cmdline, int index, String reason) { 9 | this.cmdline = cmdline; 10 | this.index = index; 11 | this.reason = reason; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/cat/nyaa/nyaacore/cmdreceiver/BadCommandException.java: -------------------------------------------------------------------------------- 1 | package cat.nyaa.nyaacore.cmdreceiver; 2 | 3 | public class BadCommandException extends RuntimeException { 4 | public final Object[] objs; 5 | 6 | public BadCommandException() { 7 | super(""); 8 | objs = null; 9 | } 10 | 11 | /** 12 | * show formatted error message to player 13 | * 14 | * @param msg_internal msg template key. e.g. `internal.warn.***' 15 | * @param args arguments 16 | */ 17 | public BadCommandException(String msg_internal, Object... args) { 18 | super(msg_internal); 19 | objs = args; 20 | } 21 | 22 | public BadCommandException(String msg_internal, Throwable cause, Object... args) { 23 | super(msg_internal, cause); 24 | objs = args; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/cat/nyaa/nyaacore/cmdreceiver/NoItemInHandException.java: -------------------------------------------------------------------------------- 1 | package cat.nyaa.nyaacore.cmdreceiver; 2 | 3 | class NoItemInHandException extends RuntimeException { 4 | public final boolean isOffHand; 5 | 6 | /** 7 | * @param ifh true if require item in offhand 8 | */ 9 | public NoItemInHandException(boolean ifh) { 10 | isOffHand = ifh; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/cat/nyaa/nyaacore/cmdreceiver/NoPermissionException.java: -------------------------------------------------------------------------------- 1 | package cat.nyaa.nyaacore.cmdreceiver; 2 | 3 | class NoPermissionException extends RuntimeException { 4 | /** 5 | * @param permission name for the permission node 6 | */ 7 | public NoPermissionException(String permission) { 8 | super(permission); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/cat/nyaa/nyaacore/cmdreceiver/NotPlayerException.java: -------------------------------------------------------------------------------- 1 | package cat.nyaa.nyaacore.cmdreceiver; 2 | 3 | class NotPlayerException extends RuntimeException { 4 | 5 | } -------------------------------------------------------------------------------- /src/main/java/cat/nyaa/nyaacore/cmdreceiver/SubCommand.java: -------------------------------------------------------------------------------- 1 | package cat.nyaa.nyaacore.cmdreceiver; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * SubCommands ARE CASE-SENSITIVE 10 | */ 11 | @Target({ElementType.METHOD, ElementType.FIELD}) 12 | @Retention(RetentionPolicy.RUNTIME) 13 | public @interface SubCommand { 14 | String value() default ""; 15 | 16 | String[] alias() default {}; 17 | 18 | boolean isDefaultCommand() default false; 19 | 20 | String permission() default ""; 21 | 22 | String tabCompleter() default ""; 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/cat/nyaa/nyaacore/configuration/FileConfigure.java: -------------------------------------------------------------------------------- 1 | package cat.nyaa.nyaacore.configuration; 2 | 3 | import org.bukkit.configuration.InvalidConfigurationException; 4 | import org.bukkit.configuration.file.YamlConfiguration; 5 | import org.bukkit.plugin.java.JavaPlugin; 6 | 7 | import java.io.File; 8 | import java.io.IOException; 9 | 10 | public abstract class FileConfigure implements ISerializable { 11 | protected abstract String getFileName(); 12 | 13 | protected abstract JavaPlugin getPlugin(); 14 | 15 | protected void ensureConfigIntegrity() { 16 | 17 | } 18 | 19 | private File ensureFile() { 20 | File cfgFile = new File(getPlugin().getDataFolder(), getFileName()); 21 | if (!cfgFile.exists()) { 22 | cfgFile.getParentFile().mkdirs(); 23 | try { 24 | cfgFile.createNewFile(); 25 | } catch (IOException ex) { 26 | throw new RuntimeException(ex); 27 | } 28 | } 29 | return cfgFile; 30 | } 31 | 32 | public void save() { 33 | ensureConfigIntegrity(); 34 | YamlConfiguration cfg = new YamlConfiguration(); 35 | serialize(cfg); 36 | try { 37 | cfg.save(ensureFile()); 38 | } catch (IOException ex) { 39 | getPlugin().getLogger().severe("Cannot save " + getFileName() + ". Emergency dump:"); 40 | getPlugin().getLogger().severe("\n" + cfg.saveToString()); 41 | getPlugin().getLogger().severe("Cannot save " + getFileName() + ". Emergency dump End."); 42 | throw new RuntimeException(ex); 43 | } 44 | } 45 | 46 | public void load() { 47 | YamlConfiguration cfg = new YamlConfiguration(); 48 | try { 49 | cfg.load(ensureFile()); 50 | } catch (IOException | InvalidConfigurationException ex) { 51 | throw new RuntimeException(ex); 52 | } 53 | deserialize(cfg); 54 | save(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/cat/nyaa/nyaacore/configuration/Getter.java: -------------------------------------------------------------------------------- 1 | package cat.nyaa.nyaacore.configuration; 2 | 3 | import java.lang.reflect.InvocationTargetException; 4 | 5 | @FunctionalInterface 6 | public interface Getter { 7 | static Getter from(T p, Class> cls) { 8 | return getAccessor(p, cls); 9 | } 10 | 11 | static T getAccessor(E p, Class cls) { 12 | try { 13 | return cls.getDeclaredConstructor().newInstance(); 14 | } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { 15 | throw new RuntimeException(e); 16 | } catch (NoSuchMethodException e) { 17 | try { 18 | return cls.getDeclaredConstructor(cls.getEnclosingClass()).newInstance(p); 19 | } catch (InstantiationException | IllegalAccessException | InvocationTargetException | 20 | NoSuchMethodException ex) { 21 | throw new RuntimeException(ex); 22 | } 23 | } 24 | } 25 | 26 | /** 27 | * @param object Object to serialize to String 28 | * @return String representation of the object 29 | */ 30 | String get(T object); 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/cat/nyaa/nyaacore/configuration/NbtItemStack.java: -------------------------------------------------------------------------------- 1 | package cat.nyaa.nyaacore.configuration; 2 | 3 | import cat.nyaa.nyaacore.utils.ItemStackUtils; 4 | import org.bukkit.configuration.serialization.ConfigurationSerializable; 5 | import org.bukkit.inventory.ItemStack; 6 | 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | 10 | /** 11 | * Store NBT-represented ItemStacks in config file, rather than default yaml-representation. 12 | */ 13 | public class NbtItemStack implements ConfigurationSerializable { 14 | public ItemStack it; 15 | 16 | public NbtItemStack(ItemStack it) { 17 | this.it = it; 18 | } 19 | 20 | public static NbtItemStack deserialize(Map map) { 21 | try { 22 | String nbt = (String) map.getOrDefault("nbt", 0); 23 | if (nbt == null || "".equalsIgnoreCase(nbt)) return new NbtItemStack(null); 24 | return new NbtItemStack(ItemStackUtils.itemFromBase64(nbt)); 25 | } catch (Exception ex) { 26 | ex.printStackTrace(); 27 | return new NbtItemStack(null); 28 | } 29 | } 30 | 31 | @Override 32 | public Map serialize() { 33 | Map ret = new HashMap<>(); 34 | if (it != null) { 35 | try { 36 | ret.put("nbt", ItemStackUtils.itemToBase64(it)); 37 | } catch (Exception ex) { 38 | ex.printStackTrace(); 39 | ret.put("nbt", ""); 40 | } 41 | } else { 42 | ret.put("nbt", ""); 43 | } 44 | 45 | return ret; 46 | } 47 | } -------------------------------------------------------------------------------- /src/main/java/cat/nyaa/nyaacore/configuration/PluginConfigure.java: -------------------------------------------------------------------------------- 1 | package cat.nyaa.nyaacore.configuration; 2 | 3 | public abstract class PluginConfigure extends FileConfigure { 4 | @Override 5 | protected final String getFileName() { 6 | return "config.yml"; 7 | } 8 | 9 | @Override 10 | public void save() { 11 | ensureConfigIntegrity(); 12 | serialize(getPlugin().getConfig()); 13 | getPlugin().saveConfig(); 14 | } 15 | 16 | @Override 17 | public void load() { 18 | getPlugin().saveDefaultConfig(); 19 | getPlugin().reloadConfig(); 20 | deserialize(getPlugin().getConfig()); 21 | save(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/cat/nyaa/nyaacore/configuration/Setter.java: -------------------------------------------------------------------------------- 1 | package cat.nyaa.nyaacore.configuration; 2 | 3 | import java.util.Optional; 4 | 5 | import static cat.nyaa.nyaacore.configuration.Getter.getAccessor; 6 | 7 | public interface Setter { 8 | 9 | static Setter from(T p, Class> cls) { 10 | return getAccessor(p, cls); 11 | } 12 | 13 | /** 14 | * @param value String representation of the object 15 | * @return The object to be set to field, or empty if field are already set by this setter 16 | * @throws IllegalArgumentException {@code value} is not a valid representation of the object 17 | */ 18 | Optional set(Object value) throws IllegalArgumentException; 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/cat/nyaa/nyaacore/configuration/annotation/Deserializer.java: -------------------------------------------------------------------------------- 1 | package cat.nyaa.nyaacore.configuration.annotation; 2 | 3 | import cat.nyaa.nyaacore.configuration.ISerializable; 4 | import cat.nyaa.nyaacore.configuration.Setter; 5 | 6 | import java.lang.annotation.ElementType; 7 | import java.lang.annotation.Retention; 8 | import java.lang.annotation.RetentionPolicy; 9 | import java.lang.annotation.Target; 10 | 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @Target(ElementType.FIELD) 13 | public @interface Deserializer { 14 | Class> value(); 15 | 16 | String message() default ""; 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/cat/nyaa/nyaacore/configuration/annotation/Serializer.java: -------------------------------------------------------------------------------- 1 | package cat.nyaa.nyaacore.configuration.annotation; 2 | 3 | import cat.nyaa.nyaacore.configuration.Getter; 4 | import cat.nyaa.nyaacore.configuration.ISerializable; 5 | 6 | import java.lang.annotation.ElementType; 7 | import java.lang.annotation.Retention; 8 | import java.lang.annotation.RetentionPolicy; 9 | import java.lang.annotation.Target; 10 | 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @Target(ElementType.FIELD) 13 | public @interface Serializer { 14 | Class> value(); 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/cat/nyaa/nyaacore/orm/BundledSQLUtils.java: -------------------------------------------------------------------------------- 1 | package cat.nyaa.nyaacore.orm; 2 | 3 | import org.bukkit.plugin.Plugin; 4 | 5 | import java.io.BufferedInputStream; 6 | import java.io.ByteArrayOutputStream; 7 | import java.io.IOException; 8 | import java.io.InputStream; 9 | import java.nio.charset.StandardCharsets; 10 | import java.sql.Connection; 11 | import java.sql.PreparedStatement; 12 | import java.sql.ResultSet; 13 | import java.sql.SQLException; 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | import java.util.Map; 17 | 18 | public final class BundledSQLUtils { 19 | /** 20 | * build a statement using provided parameters 21 | * 22 | * @param sql SQL string 23 | * @param replacementMap {{key}} in the file will be replaced by value. Ignored if null. NOTE: sql injection will happen 24 | * @param parameters JDBC's positional parametrized query. Java types. 25 | * @return statement 26 | */ 27 | private static PreparedStatement buildStatement(Connection conn, String sql, Map replacementMap, Object... parameters) { 28 | if (replacementMap != null) { 29 | for (String key : replacementMap.keySet()) { 30 | sql = sql.replace("{{" + key + "}}", replacementMap.get(key)); 31 | } 32 | } 33 | try { 34 | PreparedStatement stmt = conn.prepareStatement(sql); 35 | for (int i = 0; i < parameters.length; i++) { 36 | Object javaObj = parameters[i]; 37 | stmt.setObject(i + 1, DataTypeMapping.getDataTypeConverter(javaObj.getClass()).toSqlType(javaObj)); 38 | } 39 | return stmt; 40 | } catch (SQLException ex) { 41 | throw new RuntimeException(sql, ex); 42 | } 43 | } 44 | 45 | /** 46 | * Convert a result set to a list of java objects 47 | * 48 | * @param rs the result set 49 | * @param cls record type 50 | * @param record type, do not have to be registered in getTables() 51 | * @return java object list 52 | */ 53 | public static List parseResultSet(ResultSet rs, Class cls) { 54 | try { 55 | if (rs == null) return new ArrayList<>(); 56 | ObjectModifier table = ObjectModifier.fromClass(cls); 57 | List results = new ArrayList(); 58 | while (rs.next()) { 59 | T obj = table.getObjectFromResultSet(rs); 60 | results.add(obj); 61 | } 62 | return results; 63 | } catch (SQLException | ReflectiveOperationException ex) { 64 | throw new RuntimeException(ex); 65 | } 66 | } 67 | 68 | /** 69 | * Execute a SQL file bundled with the plugin 70 | * 71 | * @param plugin java plugin object, for resource access 72 | * @param conn connected database 73 | * @param filename full file name, including extension, in resources/sql folder 74 | * @param replacementMap {{key}} in the file will be replaced by value. Ignored if null. NOTE: sql injection will happen 75 | * @param cls class of desired object 76 | * @param parameters JDBC's positional parametrized query. Java type. 77 | * @param Type of record object 78 | * @return the result set, null if cls is null. 79 | */ 80 | public static List queryBundledAs(Plugin plugin, Connection conn, String filename, Map replacementMap, Class cls, Object... parameters) { 81 | String sql; 82 | try ( 83 | InputStream inputStream = plugin.getResource("sql/" + filename); 84 | BufferedInputStream bis = new BufferedInputStream(inputStream); 85 | ByteArrayOutputStream buf = new ByteArrayOutputStream() 86 | ) { 87 | int result = bis.read(); 88 | while (result != -1) { 89 | buf.write((byte) result); 90 | result = bis.read(); 91 | } 92 | sql = buf.toString(StandardCharsets.UTF_8); 93 | } catch (IOException ex) { 94 | throw new RuntimeException(ex); 95 | } 96 | 97 | try (PreparedStatement stat = buildStatement(conn, sql, replacementMap, parameters)) { 98 | boolean hasResult = stat.execute(); 99 | if (cls == null) { 100 | return null; 101 | } else if (hasResult) { 102 | return parseResultSet(stat.getResultSet(), cls); 103 | } else { 104 | return new ArrayList<>(); 105 | } 106 | } catch (SQLException ex) { 107 | throw new RuntimeException(ex); 108 | } 109 | } 110 | 111 | public static void queryBundled(Plugin plugin, Connection conn, String filename, Map replacementMap, Object... parameters) { 112 | queryBundledAs(plugin, conn, filename, replacementMap, null, parameters); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/main/java/cat/nyaa/nyaacore/orm/DatabaseUtils.java: -------------------------------------------------------------------------------- 1 | package cat.nyaa.nyaacore.orm; 2 | 3 | import cat.nyaa.nyaacore.orm.backends.BackendConfig; 4 | import cat.nyaa.nyaacore.orm.backends.IConnectedDatabase; 5 | import cat.nyaa.nyaacore.orm.backends.SQLiteDatabase; 6 | import org.bukkit.plugin.Plugin; 7 | 8 | import java.io.File; 9 | import java.sql.Connection; 10 | import java.sql.DriverManager; 11 | import java.sql.SQLException; 12 | 13 | public class DatabaseUtils { 14 | /** 15 | * @param cfg 16 | * @return 17 | */ 18 | public static IConnectedDatabase connect(Plugin plugin, BackendConfig cfg) throws ClassNotFoundException, SQLException { 19 | if ("sqlite".equalsIgnoreCase(cfg.provider)) { 20 | return new SQLiteDatabase(newJdbcConnection(plugin, cfg)); 21 | } else if ("mysql".equalsIgnoreCase(cfg.provider)) { 22 | throw new RuntimeException("NyaaCore ORM MySQL backend is not implemented"); 23 | } else { 24 | throw new IllegalArgumentException("Invalid provider: " + cfg.provider); 25 | } 26 | } 27 | 28 | 29 | /** 30 | * @param plugin 31 | * @param cfg 32 | * @return 33 | * @throws ClassNotFoundException 34 | * @throws SQLException 35 | * @throws IllegalArgumentException 36 | */ 37 | public static Connection newJdbcConnection(Plugin plugin, BackendConfig cfg) throws ClassNotFoundException, SQLException, IllegalArgumentException { 38 | String provider = cfg.provider; 39 | if ("sqlite".equalsIgnoreCase(provider)) { 40 | 41 | Class.forName("org.sqlite.JDBC"); 42 | File f = new File(plugin.getDataFolder(), cfg.sqlite_file); 43 | return DriverManager.getConnection("jdbc:sqlite:" + f.getAbsolutePath()); 44 | 45 | } else if ("mysql".equalsIgnoreCase(provider)) { 46 | 47 | Class.forName(cfg.mysql_jdbc_driver); 48 | return DriverManager.getConnection(cfg.mysql_url, cfg.mysql_username, cfg.mysql_password); 49 | 50 | } else { 51 | throw new IllegalArgumentException("Invalid provider: " + provider); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/cat/nyaa/nyaacore/orm/NonUniqueResultException.java: -------------------------------------------------------------------------------- 1 | package cat.nyaa.nyaacore.orm; 2 | 3 | public class NonUniqueResultException extends Exception { 4 | public NonUniqueResultException(String s) { 5 | super(s); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/cat/nyaa/nyaacore/orm/ObjectFieldModifier.java: -------------------------------------------------------------------------------- 1 | package cat.nyaa.nyaacore.orm; 2 | 3 | import cat.nyaa.nyaacore.orm.annotations.Column; 4 | import com.google.common.base.Strings; 5 | 6 | import java.lang.reflect.Field; 7 | import java.lang.reflect.Method; 8 | import java.lang.reflect.Modifier; 9 | 10 | /** 11 | * A java field is converted to a database column in two steps: 12 | * 1. determine the access method 13 | * 2. determine the SQL type decl and convertion rule by the object type 14 | * There are two access methods: 15 | * 1. directly get/set to a class field, the type is the field's type 16 | * 2. get/set through a pair of getter/setter with matching return/parameter type, the type is the return/parameter type. 17 | * There are several accepted java types: 18 | * {@link DataTypeMapping} 19 | */ 20 | public class ObjectFieldModifier { 21 | 22 | public final String name; 23 | public final boolean nullable; 24 | public final boolean unique; 25 | public final boolean primary; 26 | public final boolean autoIncrement; 27 | public final String columnDefinition; 28 | public final int length; 29 | public final AccessMethod accessMethod; 30 | public final Field field; // used if access method is DIRECT_FIELD 31 | public final Method setter; // used if access method is GETTER_SETTER 32 | public final Method getter; // used if access method is GETTER_SETTER 33 | public final Class javaType; 34 | public final DataTypeMapping.IDataTypeConverter typeConverter; 35 | 36 | /** 37 | * Constructor for field based table columns 38 | */ 39 | public ObjectFieldModifier(ObjectModifier table, Field dataField, Column anno) { 40 | if (anno == null) throw new IllegalArgumentException(); 41 | if (anno.name().isEmpty()) { 42 | name = dataField.getName(); 43 | } else { 44 | name = anno.name(); 45 | } 46 | this.nullable = anno.nullable(); 47 | this.unique = anno.unique(); 48 | this.primary = anno.primary(); 49 | this.autoIncrement = anno.autoIncrement(); 50 | accessMethod = AccessMethod.DIRECT_FIELD; 51 | field = dataField; 52 | field.setAccessible(true); 53 | setter = null; 54 | getter = null; 55 | 56 | javaType = field.getType(); 57 | typeConverter = DataTypeMapping.getDataTypeConverter(javaType); 58 | this.columnDefinition = Strings.isNullOrEmpty(anno.columnDefinition()) ? typeConverter.getSqlType().getName() : anno.columnDefinition(); 59 | this.length = anno.length(); 60 | } 61 | 62 | /** 63 | * Constructor for method based table columns 64 | */ 65 | public ObjectFieldModifier(ObjectModifier table, Method dataMethod, Column anno) { 66 | if (anno == null) throw new IllegalArgumentException(); 67 | this.nullable = anno.nullable(); 68 | this.unique = anno.unique(); 69 | this.primary = anno.primary(); 70 | this.autoIncrement = anno.autoIncrement(); 71 | 72 | String methodName = dataMethod.getName(); 73 | if (!methodName.startsWith("get") && !methodName.startsWith("set")) 74 | throw new IllegalArgumentException("Method is neither a setter nor a getter: " + dataMethod); 75 | String methodSuffix = methodName.substring(3); 76 | String name = ("".equals(anno.name())) ? methodSuffix : anno.name(); 77 | Class methodType; 78 | if (methodName.startsWith("get")) { 79 | methodType = dataMethod.getReturnType(); 80 | } else { 81 | methodType = dataMethod.getParameterCount() == 1 ? dataMethod.getParameterTypes()[0] : null; 82 | } 83 | if (methodType == null || methodType == Void.class || methodType == Void.TYPE) 84 | throw new IllegalArgumentException("Cannot determine getter/setter type: " + dataMethod); 85 | //if (methodType != String.class && methodType != Long.class && methodType != Double.class) 86 | // throw new IllegalArgumentException("Only three types are supported for getter/setter columns: String/Long/Double"); 87 | 88 | Method getter, setter; 89 | try { 90 | getter = dataMethod.getDeclaringClass().getDeclaredMethod("get" + methodSuffix); 91 | setter = dataMethod.getDeclaringClass().getDeclaredMethod("set" + methodSuffix, methodType); 92 | if (getter.getParameterCount() != 0 || getter.getReturnType() != methodType || Modifier.isStatic(getter.getModifiers())) 93 | throw new RuntimeException("getter signature mismatch"); 94 | if (setter.getParameterCount() != 1 || setter.getParameterTypes()[0] != methodType || 95 | (setter.getReturnType() != Void.class && setter.getReturnType() != Void.TYPE) || 96 | Modifier.isStatic(setter.getModifiers())) 97 | throw new RuntimeException("setter signature mismatch"); 98 | getter.setAccessible(true); 99 | setter.setAccessible(true); 100 | } catch (ReflectiveOperationException ex) { 101 | ex.printStackTrace(); 102 | throw new RuntimeException(ex); 103 | } 104 | 105 | this.name = name; 106 | 107 | this.accessMethod = AccessMethod.GETTER_SETTER; 108 | this.field = null; 109 | this.getter = getter; 110 | this.setter = setter; 111 | 112 | this.javaType = methodType; 113 | this.typeConverter = DataTypeMapping.getDataTypeConverter(this.javaType); 114 | this.columnDefinition = Strings.isNullOrEmpty(anno.columnDefinition()) ? typeConverter.getSqlType().getName() : anno.columnDefinition(); 115 | this.length = anno.length(); 116 | } 117 | 118 | public int getLength() { 119 | return length; 120 | } 121 | 122 | public String getName() { 123 | return name; 124 | } 125 | 126 | public Object getJavaObject(Object entityObj) { 127 | try { 128 | if (accessMethod == AccessMethod.DIRECT_FIELD) { 129 | return field.get(entityObj); 130 | } else { 131 | return getter.invoke(entityObj); 132 | } 133 | } catch (ReflectiveOperationException ex) { 134 | throw new RuntimeException(ex); 135 | } 136 | } 137 | 138 | public void setJavaObject(Object entityObj, Object obj) { 139 | try { 140 | if (accessMethod == AccessMethod.DIRECT_FIELD) { 141 | field.set(entityObj, obj); 142 | } else { 143 | setter.invoke(entityObj, obj); 144 | } 145 | } catch (ReflectiveOperationException ex) { 146 | throw new RuntimeException(ex); 147 | } 148 | } 149 | 150 | public Object getSqlObject(Object entityObj) { 151 | Object javaObj = getJavaObject(entityObj); 152 | if (javaObj == null) { 153 | return null; 154 | } else { 155 | return typeConverter.toSqlType(javaObj); 156 | } 157 | } 158 | 159 | public void setSqlObject(Object entityObj, Object obj) { 160 | if (obj == null) { 161 | setJavaObject(entityObj, null); 162 | } else { 163 | setJavaObject(entityObj, typeConverter.toJavaType(obj)); 164 | } 165 | } 166 | 167 | public enum AccessMethod { 168 | DIRECT_FIELD, // directly get from field 169 | GETTER_SETTER // use getter and setter 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /src/main/java/cat/nyaa/nyaacore/orm/ObjectModifier.java: -------------------------------------------------------------------------------- 1 | package cat.nyaa.nyaacore.orm; 2 | 3 | import cat.nyaa.nyaacore.orm.annotations.Column; 4 | import cat.nyaa.nyaacore.orm.annotations.Table; 5 | 6 | import java.lang.reflect.Constructor; 7 | import java.lang.reflect.Field; 8 | import java.lang.reflect.Method; 9 | import java.sql.ResultSet; 10 | import java.sql.SQLException; 11 | import java.util.*; 12 | 13 | 14 | /** 15 | * Access a Java object like a database record 16 | * One type needs only one ObjectModifier instance and the instance can be used 17 | * on many different objects. 18 | * All ObjectModifier instances are stored permanently in {@link ObjectModifier#structured_tables}. 19 | * 20 | * @param type of the java object, must have default constructor 21 | */ 22 | public class ObjectModifier { 23 | /* class -> TableStructure cache */ 24 | private static final Map, ObjectModifier> structured_tables = new HashMap<>(); 25 | // Java object type info 26 | public final Class clz; 27 | public final Constructor ctor; 28 | // SQL table info 29 | public final String tableName; 30 | public final List orderedColumnName = new ArrayList<>(); 31 | public final Map columns = new HashMap<>(); 32 | public final String primaryKey; // null if no primary key 33 | 34 | private ObjectModifier(Class tableClass) throws NoSuchMethodException { 35 | Table annotationTable = tableClass.getDeclaredAnnotation(Table.class); 36 | 37 | this.clz = tableClass; 38 | Constructor ctor; 39 | try { 40 | ctor = tableClass.getConstructor(); 41 | } catch (NoSuchMethodException e) { 42 | ctor = tableClass.getDeclaredConstructor(); 43 | } 44 | this.ctor = ctor; 45 | this.ctor.setAccessible(true); 46 | if (annotationTable == null || annotationTable.value().isEmpty()) { 47 | this.tableName = tableClass.getSimpleName(); 48 | } else { 49 | this.tableName = annotationTable.value(); 50 | } 51 | 52 | String pkColumn = null; 53 | 54 | // load all the fields 55 | for (Field f : tableClass.getDeclaredFields()) { 56 | Column columnAnnotation = f.getAnnotation(Column.class); 57 | if (columnAnnotation == null) continue; 58 | ObjectFieldModifier structure = new ObjectFieldModifier(this, f, columnAnnotation); 59 | if (columns.containsKey(structure.getName())) 60 | throw new RuntimeException("Duplicated column name: " + structure.getName()); 61 | if (structure.primary) { 62 | if (pkColumn != null) throw new RuntimeException("Duplicated primary key at: " + f.getName()); 63 | pkColumn = structure.getName(); 64 | } 65 | columns.put(structure.getName(), structure); 66 | } 67 | 68 | // load all the getter/setter 69 | for (Method m : tableClass.getDeclaredMethods()) { 70 | Column columnAnnotation = m.getAnnotation(Column.class); 71 | if (columnAnnotation == null) continue; 72 | ObjectFieldModifier structure = new ObjectFieldModifier(this, m, columnAnnotation); 73 | if (columns.containsKey(structure.getName())) 74 | throw new RuntimeException("Duplicated column name: " + structure.getName()); 75 | if (structure.primary) { 76 | if (pkColumn != null) throw new RuntimeException("Duplicated primary key at: " + m.getName()); 77 | pkColumn = structure.getName(); 78 | } 79 | columns.put(structure.getName(), structure); 80 | } 81 | 82 | primaryKey = pkColumn; 83 | orderedColumnName.addAll(columns.keySet()); 84 | orderedColumnName.sort(String::compareTo); 85 | } 86 | 87 | @SuppressWarnings("unchecked") 88 | public static ObjectModifier fromClass(Class cls) { 89 | if (structured_tables.containsKey(cls)) return (ObjectModifier) structured_tables.get(cls); 90 | ObjectModifier ts; 91 | try { 92 | ts = new ObjectModifier<>(cls); 93 | } catch (NoSuchMethodException e) { 94 | throw new RuntimeException(e); 95 | } 96 | structured_tables.put(cls, ts); 97 | return ts; 98 | } 99 | 100 | 101 | 102 | 103 | 104 | 105 | /* ********** * 106 | * class info * 107 | * ********** */ 108 | 109 | public Class getJavaClass() { 110 | return clz; 111 | } 112 | 113 | /** 114 | * @return table name, nullable 115 | */ 116 | public String getTableName() { 117 | return tableName; 118 | } 119 | 120 | /** 121 | * @return a readonly list of column names 122 | */ 123 | public List getColNames() { 124 | return Collections.unmodifiableList(orderedColumnName); 125 | } 126 | 127 | /** 128 | * Get primary key column name 129 | * 130 | * @return the name of primary key, null if no primary key 131 | */ 132 | public String getPkColName() { 133 | return primaryKey; 134 | } 135 | 136 | /** 137 | * Check if a column exists, may be faster than getColNames().contains() 138 | * 139 | * @param column column name 140 | * @return exists or not 141 | */ 142 | public boolean hasColumn(String column) { 143 | return columns.containsKey(column); 144 | } 145 | 146 | /** 147 | * return comma separated list of column names 148 | */ 149 | public String getColumnNamesString() { 150 | if (orderedColumnName.size() == 0) return ""; 151 | StringBuilder sb = new StringBuilder(orderedColumnName.get(0)); 152 | for (int i = 1; i < orderedColumnName.size(); i++) { 153 | sb.append(","); 154 | sb.append(orderedColumnName.get(i)); 155 | } 156 | return sb.toString(); 157 | } 158 | 159 | public DataTypeMapping.IDataTypeConverter getTypeConvertorForColumn(String columnName) { 160 | ObjectFieldModifier fm = columns.get(columnName); 161 | if (fm == null) throw new IllegalArgumentException("no such column: " + columnName); 162 | return fm.typeConverter; 163 | } 164 | 165 | 166 | 167 | 168 | 169 | /* ************************ * 170 | * Object read/modification * 171 | * ************************ */ 172 | 173 | public Object getSqlValue(T obj, String columnName) { 174 | ObjectFieldModifier fm = columns.get(columnName); 175 | if (fm == null) throw new IllegalArgumentException("no such column: " + columnName); 176 | return fm.getSqlObject(obj); 177 | } 178 | 179 | public void setSqlValue(T obj, String columnName, Object newSqlValue) { 180 | ObjectFieldModifier fm = columns.get(columnName); 181 | if (fm == null) throw new IllegalArgumentException("no such column: " + columnName); 182 | fm.setSqlObject(obj, newSqlValue); 183 | 184 | } 185 | 186 | /** 187 | * Construct ONE table object from Java ResultSet. 188 | * Only CURRENT result row will be picked 189 | */ 190 | public T getObjectFromResultSet(ResultSet rs) throws ReflectiveOperationException, SQLException { 191 | T obj = ctor.newInstance(); 192 | for (String colName : getColNames()) { 193 | Object colValue = rs.getObject(colName); 194 | setSqlValue(obj, colName, colValue); 195 | } 196 | return obj; 197 | } 198 | 199 | /** 200 | * Get certain columns(fields) from a table object 201 | * and the column objects should have been converted to database acceptable objects: long/float/string 202 | * 203 | * @param obj the java object 204 | * @param columns the column names, or null for all columns 205 | * @return columnName to columnData map 206 | */ 207 | public Map getColumnObjectMap(T obj, String... columns) { 208 | List columnList = new ArrayList<>(); 209 | Map objects = new HashMap<>(); 210 | if (columns == null || columns.length == 0) { 211 | columnList.addAll(orderedColumnName); 212 | } else { 213 | columnList.addAll(Arrays.asList(columns)); 214 | } 215 | for (String colName : columnList) { 216 | if (!this.columns.containsKey(colName)) 217 | throw new RuntimeException("column " + colName + " not in object " + getJavaClass().getCanonicalName()); 218 | objects.put(colName, this.columns.get(colName).getSqlObject(obj)); 219 | } 220 | return objects; 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /src/main/java/cat/nyaa/nyaacore/orm/RollbackGuard.java: -------------------------------------------------------------------------------- 1 | package cat.nyaa.nyaacore.orm; 2 | 3 | import cat.nyaa.nyaacore.orm.backends.IConnectedDatabase; 4 | 5 | import java.sql.Connection; 6 | import java.sql.SQLException; 7 | 8 | /** 9 | * This provides a easy way to rollback database in case of Java program exceptions. 10 | * You are not expected to use this for real transactions. 11 | *

12 | * Here is the use case: 13 | *

14 |  *     try (RollbackGuard guard = new RollbackGuard(db)) {
15 |  * 	        // something could throw exception
16 |  * 	        guard.commit();
17 |  *     }
18 |  * 
19 | *

20 | * If exception is thrown and guard is not committed, the guard will automatically rollback the connection. 21 | */ 22 | public class RollbackGuard implements AutoCloseable { 23 | private final Connection conn; 24 | private boolean needRollbackOnClose = false; 25 | 26 | public RollbackGuard(IConnectedDatabase db) { 27 | conn = db.getConnection(); 28 | try { 29 | conn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE); 30 | conn.setAutoCommit(false); 31 | } catch (SQLException ex) { 32 | throw new RuntimeException("Failed to create ConnectedDatabaseTransactionGuard", ex); 33 | } 34 | needRollbackOnClose = true; 35 | } 36 | 37 | public void commit() { 38 | try { 39 | conn.commit(); 40 | conn.setAutoCommit(true); 41 | needRollbackOnClose = false; 42 | } catch (SQLException ex) { 43 | needRollbackOnClose = true; 44 | } 45 | } 46 | 47 | @Override 48 | public void close() throws Exception { 49 | if (needRollbackOnClose) conn.rollback(); 50 | conn.setAutoCommit(true); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/cat/nyaa/nyaacore/orm/WhereClause.java: -------------------------------------------------------------------------------- 1 | package cat.nyaa.nyaacore.orm; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | public class WhereClause { 7 | public static final WhereClause EMPTY = new WhereClause(); 8 | 9 | private final List columns = new ArrayList<>(); 10 | private final List comparators = new ArrayList<>(); 11 | private final List javaObjects = new ArrayList<>(); 12 | 13 | public WhereClause() { 14 | } 15 | 16 | public WhereClause(String columnName, String comparator, Object obj) { 17 | this.where(columnName, comparator, obj); 18 | } 19 | 20 | public static WhereClause EQ(String columnName, Object obj) { 21 | return new WhereClause().whereEq(columnName, obj); 22 | } 23 | 24 | public WhereClause whereEq(String columnName, Object obj) { 25 | if (obj == null) throw new IllegalArgumentException("please use `IS' to compare NULL value"); 26 | return where(columnName, "=", obj); 27 | } 28 | 29 | /** 30 | * comparator can be any SQL comparator. 31 | * e.g. =, >, < 32 | * 33 | * @param columnName 34 | * @param comparator if it's a keyword, you need to add spaces before and after, e.g. " LIKE ", if obj is null, this should use " IS " instead of "=" 35 | * @param obj the java object, can be null 36 | */ 37 | public WhereClause where(String columnName, String comparator, Object obj) { 38 | if (columnName == null || comparator == null) throw new IllegalArgumentException(); 39 | columns.add(columnName); 40 | comparators.add(comparator); 41 | javaObjects.add(obj); 42 | return this; 43 | } 44 | 45 | /** 46 | * sql in e.g. "DELETE FROM table" 47 | * sql out e.g. "DELETE FROM table WHERE col1=? AND col2=? AND col3=?" 48 | * 49 | * @param sql 50 | * @param positionalParameterHolder 51 | * @param columnTypeMapping 52 | * @return 53 | */ 54 | public String appendWhereClause(String sql, List positionalParameterHolder, ObjectModifier columnTypeMapping) { 55 | if (columns.size() > 0) { 56 | sql += " WHERE"; 57 | for (int idx = 0; idx < columns.size(); idx++) { 58 | if (idx > 0) sql += " AND"; 59 | sql += " " + columns.get(idx) + comparators.get(idx) + "?"; 60 | positionalParameterHolder.add(columnTypeMapping.getTypeConvertorForColumn(columns.get(idx)).toSqlType(javaObjects.get(idx))); 61 | } 62 | } 63 | return sql; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/cat/nyaa/nyaacore/orm/annotations/Column.java: -------------------------------------------------------------------------------- 1 | package cat.nyaa.nyaacore.orm.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * A partial copy of javax.persistence.Column 10 | * because I don't like shadow whole JPA 11 | */ 12 | @Target({ElementType.FIELD, ElementType.METHOD}) 13 | @Retention(RetentionPolicy.RUNTIME) 14 | public @interface Column { 15 | String name() default ""; // column name, use field name if empty 16 | 17 | String columnDefinition() default ""; // if this is not empty, everything else except name() will be ignored 18 | 19 | boolean nullable() default false; 20 | 21 | boolean unique() default false; 22 | 23 | boolean primary() default false; 24 | 25 | boolean autoIncrement() default false; 26 | 27 | int length() default -1; 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/cat/nyaa/nyaacore/orm/annotations/Table.java: -------------------------------------------------------------------------------- 1 | package cat.nyaa.nyaacore.orm.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target(ElementType.TYPE) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface Table { 11 | String value() default ""; // table name 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/cat/nyaa/nyaacore/orm/backends/BackendConfig.java: -------------------------------------------------------------------------------- 1 | package cat.nyaa.nyaacore.orm.backends; 2 | 3 | import cat.nyaa.nyaacore.configuration.ISerializable; 4 | 5 | public class BackendConfig implements ISerializable { 6 | @Serializable 7 | public String provider; 8 | @Serializable 9 | public String sqlite_file; 10 | @Serializable 11 | public String mysql_url; 12 | @Serializable 13 | public String mysql_username; 14 | @Serializable 15 | public String mysql_password; 16 | @Serializable 17 | public String mysql_jdbc_driver; 18 | 19 | public BackendConfig() { 20 | } 21 | 22 | public BackendConfig(String provider, String sqlite_file, String mysql_url, String mysql_username, String mysql_password, String mysql_jdbc_driver) { 23 | this.provider = provider; 24 | this.sqlite_file = sqlite_file; 25 | this.mysql_url = mysql_url; 26 | this.mysql_username = mysql_username; 27 | this.mysql_password = mysql_password; 28 | this.mysql_jdbc_driver = mysql_jdbc_driver; 29 | } 30 | 31 | public static BackendConfig sqliteBackend(String dbFileName) { 32 | return new BackendConfig("sqlite", dbFileName, null, null, null, null); 33 | } 34 | 35 | public static BackendConfig mysqlBackend(String url) { 36 | return mysqlBackend(url, null, null); 37 | } 38 | 39 | public static BackendConfig mysqlBackend(String url, String username, String password) { 40 | return new BackendConfig("mysql", null, url, username, password, null); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/cat/nyaa/nyaacore/orm/backends/BaseTypedTable.java: -------------------------------------------------------------------------------- 1 | package cat.nyaa.nyaacore.orm.backends; 2 | 3 | import cat.nyaa.nyaacore.orm.DataTypeMapping; 4 | import cat.nyaa.nyaacore.orm.NonUniqueResultException; 5 | import cat.nyaa.nyaacore.orm.WhereClause; 6 | 7 | import java.sql.*; 8 | import java.util.ArrayList; 9 | import java.util.Arrays; 10 | import java.util.List; 11 | import java.util.Map; 12 | 13 | /** 14 | * A typed table is a table whose schema is determined by a Java type. 15 | * Default JDBC-based implementations. 16 | * 17 | * @param the table type 18 | */ 19 | abstract class BaseTypedTable implements ITypedTable { 20 | 21 | /** 22 | * Downstream plugins should *NEVER* use this. YOU'VE BEEN WARNED! 23 | * 24 | * @return underlying implementation-specific connection 25 | */ 26 | protected abstract Connection getConnection(); 27 | 28 | @Override 29 | public void delete(WhereClause where) { 30 | String sql = "DELETE FROM " + getTableName(); 31 | List objects = new ArrayList<>(); 32 | sql = where.appendWhereClause(sql, objects, getJavaTypeModifier()); 33 | try (PreparedStatement stmt = getConnection().prepareStatement(sql)) { 34 | int x = 1; 35 | for (Object obj : objects) { 36 | stmt.setObject(x, obj); 37 | x++; 38 | } 39 | stmt.execute(); 40 | } catch (SQLException ex) { 41 | throw new RuntimeException(sql, ex); 42 | } 43 | } 44 | 45 | @Override 46 | public void insert(T object) { 47 | String sql = String.format("INSERT INTO %s(%s) VALUES(?", getTableName(), getJavaTypeModifier().getColumnNamesString()); 48 | for (int i = 1; i < getJavaTypeModifier().getColNames().size(); i++) sql += ",?"; 49 | sql += ")"; 50 | Map objMap = getJavaTypeModifier().getColumnObjectMap(object); 51 | try (PreparedStatement stmt = getConnection().prepareStatement(sql)) { 52 | for (int i = 1; i <= getJavaTypeModifier().getColNames().size(); i++) { 53 | String colName = getJavaTypeModifier().getColNames().get(i - 1); 54 | if (!objMap.containsKey(colName) || objMap.get(colName) == null) { 55 | stmt.setNull(i, Types.NULL); 56 | } else { 57 | stmt.setObject(i, objMap.get(colName)); 58 | } 59 | } 60 | stmt.execute(); 61 | } catch (SQLException ex) { 62 | throw new RuntimeException(sql + "\n" + objMap.toString(), ex); 63 | } 64 | } 65 | 66 | @Override 67 | public List select(WhereClause where) { 68 | String sql = "SELECT " + getJavaTypeModifier().getColumnNamesString() + " FROM " + getTableName(); 69 | List objects = new ArrayList<>(); 70 | sql = where.appendWhereClause(sql, objects, getJavaTypeModifier()); 71 | try (PreparedStatement stmt = getConnection().prepareStatement(sql)) { 72 | int x = 1; 73 | for (Object obj : objects) { 74 | stmt.setObject(x, obj); 75 | x++; 76 | } 77 | List results = new ArrayList(); 78 | try (ResultSet rs = stmt.executeQuery()) { 79 | while (rs.next()) { 80 | T obj = getJavaTypeModifier().getObjectFromResultSet(rs); 81 | results.add(obj); 82 | } 83 | } 84 | return results; 85 | } catch (SQLException | ReflectiveOperationException ex) { 86 | throw new RuntimeException(sql, ex); 87 | } 88 | } 89 | 90 | @Override 91 | public T selectUnique(WhereClause where) throws NonUniqueResultException { 92 | T result = selectUniqueUnchecked(where); 93 | if (result == null) { 94 | throw new NonUniqueResultException("SQL Selection has no result or not unique"); 95 | } 96 | return result; 97 | } 98 | 99 | 100 | @Override 101 | public T selectUniqueUnchecked(WhereClause where) { 102 | String sql = "SELECT " + getJavaTypeModifier().getColumnNamesString() + " FROM " + getTableName(); 103 | List objects = new ArrayList<>(); 104 | sql = where.appendWhereClause(sql, objects, getJavaTypeModifier()); 105 | sql += " LIMIT 2"; 106 | try (PreparedStatement stmt = getConnection().prepareStatement(sql)) { 107 | int x = 1; 108 | for (Object obj : objects) { 109 | stmt.setObject(x, obj); 110 | x++; 111 | } 112 | T result = null; 113 | try (ResultSet rs = stmt.executeQuery()) { 114 | if (rs.next()) { 115 | result = getJavaTypeModifier().getObjectFromResultSet(rs); 116 | if (rs.next()) result = null; // if more than one results, then return null; 117 | } 118 | } 119 | return result; 120 | } catch (SQLException | ReflectiveOperationException ex) { 121 | throw new RuntimeException(sql, ex); 122 | } 123 | } 124 | 125 | @Override 126 | public int count(WhereClause where) { 127 | String sql = "SELECT COUNT(*) AS C FROM " + getTableName(); 128 | List objects = new ArrayList<>(); 129 | sql = where.appendWhereClause(sql, objects, getJavaTypeModifier()); 130 | try (PreparedStatement stmt = getConnection().prepareStatement(sql)) { 131 | int x = 1; 132 | for (Object obj : objects) { 133 | stmt.setObject(x, obj); 134 | x++; 135 | } 136 | try (ResultSet rs = stmt.executeQuery()) { 137 | if (rs.next()) { 138 | int count = rs.getInt("C"); 139 | return count; 140 | } else { 141 | throw new RuntimeException("COUNT() returns empty result"); 142 | } 143 | } 144 | } catch (SQLException ex) { 145 | throw new RuntimeException(sql, ex); 146 | } 147 | } 148 | 149 | @Override 150 | public void update(T obj, WhereClause where, String... columns) { 151 | List updatedColumns = new ArrayList<>(); 152 | Map newValues = getJavaTypeModifier().getColumnObjectMap(obj, columns); 153 | if (columns == null || columns.length <= 0) { 154 | updatedColumns.addAll(getJavaTypeModifier().getColNames()); 155 | } else { 156 | updatedColumns.addAll(Arrays.asList(columns)); 157 | } 158 | 159 | List parameters = new ArrayList<>(); 160 | String sql = "UPDATE " + getTableName() + " SET "; 161 | for (int i = 0; i < updatedColumns.size(); i++) { 162 | if (i > 0) sql += ","; 163 | sql += updatedColumns.get(i) + "=?"; 164 | parameters.add(newValues.get(updatedColumns.get(i))); 165 | } 166 | 167 | sql = where.appendWhereClause(sql, parameters, getJavaTypeModifier()); 168 | try (PreparedStatement stmt = getConnection().prepareStatement(sql)) { 169 | int idx = 1; 170 | for (Object o : parameters) { 171 | if (o == null) { 172 | stmt.setNull(idx, Types.NULL); 173 | } else { 174 | stmt.setObject(idx, o); 175 | } 176 | idx++; 177 | } 178 | stmt.execute(); 179 | } catch (SQLException ex) { 180 | throw new RuntimeException(sql, ex); 181 | } 182 | } 183 | 184 | @Override 185 | public R selectSingleton(String query, DataTypeMapping.IDataTypeConverter resultTypeConverter) { 186 | String sql = String.format("SELECT %s FROM %s", query, getTableName()); 187 | try (Statement st = getConnection().createStatement(); 188 | ResultSet rs = st.executeQuery(sql)) { 189 | if (rs.getMetaData().getColumnCount() != 1) { 190 | throw new RuntimeException("result has multiple columns"); 191 | } 192 | if (rs.next()) { 193 | R ret = resultTypeConverter.toJavaType(rs.getObject(1)); 194 | if (rs.next()) { 195 | throw new RuntimeException("result has multiple rows"); 196 | } 197 | return ret; 198 | } else { 199 | return null; 200 | } 201 | } catch (SQLException ex) { 202 | throw new RuntimeException(ex); 203 | } 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /src/main/java/cat/nyaa/nyaacore/orm/backends/IConnectedDatabase.java: -------------------------------------------------------------------------------- 1 | package cat.nyaa.nyaacore.orm.backends; 2 | 3 | import org.bukkit.plugin.Plugin; 4 | 5 | import java.sql.Connection; 6 | import java.sql.SQLException; 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | /** 11 | * IDatabase contains an internal JDBC connection. see {@link cat.nyaa.nyaacore.orm.DatabaseUtils#connect(Plugin, BackendConfig)} 12 | * So the user *MUST* disconnect this 13 | *

14 | * This interface is *NOT* thread safe. 15 | * If you what multiple connections, call {@link cat.nyaa.nyaacore.orm.DatabaseUtils#connect(Plugin, BackendConfig)} to create more connections. 16 | * AND **NEVER** FORGET TO {@link IConnectedDatabase#close()} 17 | *

18 | * All ITable returned by {@link IConnectedDatabase#getTable(Class)} share this same connection. 19 | * NEVER operate on different tables simultaneously. 20 | *

21 | * General rule: 22 | * when in double, use {@link cat.nyaa.nyaacore.orm.DatabaseUtils#connect(Plugin, BackendConfig)} to get a new connection 23 | */ 24 | public interface IConnectedDatabase extends AutoCloseable { 25 | /** 26 | * Get underlying JDBC connection. Easily gets messed up. Avoid if you can. 27 | * 28 | * @return 29 | */ 30 | Connection getConnection(); 31 | 32 | ITypedTable getTable(Class recordClass); 33 | 34 | /** 35 | * @param recordClass 36 | * @param 37 | * @return 38 | */ 39 | ITypedTable getUnverifiedTable(Class recordClass); 40 | 41 | @Override 42 | void close() throws SQLException; 43 | 44 | boolean verifySchema(String tableName, Class recordClass); 45 | 46 | /** 47 | * Execute a SQL file bundled with some plugin, using the default Connection. 48 | * 49 | * @param filename full file name, including extension, in resources/sql folder 50 | * @param replacementMap {{key}} in the file will be replaced by value. Ignored if null. NOTE: sql injection will happen 51 | * @param cls class of desired object 52 | * @param parameters JDBC's positional parametrized query. Java Type 53 | * @return the result set, null if cls is null. 54 | */ 55 | List queryBundledAs(Plugin plugin, String filename, Map replacementMap, Class cls, Object... parameters); 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/cat/nyaa/nyaacore/orm/backends/ITable.java: -------------------------------------------------------------------------------- 1 | package cat.nyaa.nyaacore.orm.backends; 2 | 3 | import cat.nyaa.nyaacore.orm.DataTypeMapping; 4 | 5 | /** 6 | * Some low-level APIs to interact with data tables. 7 | */ 8 | public interface ITable { 9 | default T selectSingleton(String query, DataTypeMapping.IDataTypeConverter resultTypeConverter) { 10 | throw new UnsupportedOperationException(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/cat/nyaa/nyaacore/orm/backends/ITypedTable.java: -------------------------------------------------------------------------------- 1 | package cat.nyaa.nyaacore.orm.backends; 2 | 3 | import cat.nyaa.nyaacore.orm.NonUniqueResultException; 4 | import cat.nyaa.nyaacore.orm.ObjectModifier; 5 | import cat.nyaa.nyaacore.orm.WhereClause; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * A typed table is a table whose schema is determined by a Java type. 11 | * 12 | * @param the table type 13 | */ 14 | public interface ITypedTable extends ITable { 15 | 16 | /** 17 | * @return name of the table 18 | */ 19 | String getTableName(); 20 | 21 | /** 22 | * @return Java object modifier for the type of this table 23 | */ 24 | ObjectModifier getJavaTypeModifier(); 25 | 26 | /** 27 | * the where clauses are ignored 28 | */ 29 | void insert(T newRecord); 30 | 31 | /** 32 | * SELECT * FROM this_table WHERE ... 33 | * 34 | * @return all select rows 35 | */ 36 | List select(WhereClause where); 37 | 38 | /** 39 | * remove records matching the where clauses 40 | */ 41 | void delete(WhereClause where); 42 | 43 | /** 44 | * Update record according to the where clauses 45 | * 46 | * @param newRecord new values for columns 47 | * @param columns columns need to be updated, update all columns if empty 48 | */ 49 | void update(T newRecord, WhereClause where, String... columns); 50 | 51 | /** 52 | * Select only one record. 53 | * 54 | * @return the record, or throw exception if not unique 55 | */ 56 | T selectUnique(WhereClause where) throws NonUniqueResultException; 57 | 58 | /** 59 | * Select only one record. 60 | *

61 | * This method is called "unchecked" because it does not throw 62 | * checked exception {@link NonUniqueResultException} 63 | *

64 | * Instead, it returns null if not unique 65 | * 66 | * @param where 67 | * @return implNote: has small performance advantage over {@link ITypedTable#select(WhereClause)} because it does not convert all records to java objects 68 | */ 69 | T selectUniqueUnchecked(WhereClause where); 70 | 71 | /** 72 | * A short hand for select().size(); 73 | * 74 | * @return number of records to be selected. 75 | * implNote: has small performance advantage over {@link ITypedTable#select(WhereClause)}.size() because it "SELECT COUNT(*)" 76 | */ 77 | int count(WhereClause where); 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/cat/nyaa/nyaacore/orm/backends/MysqlDatabase.java: -------------------------------------------------------------------------------- 1 | package cat.nyaa.nyaacore.orm.backends; 2 | 3 | import org.bukkit.plugin.Plugin; 4 | 5 | import java.sql.Connection; 6 | import java.sql.SQLException; 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | /** 11 | * @deprecated stub code, not implemented 12 | */ 13 | @Deprecated 14 | public class MysqlDatabase implements IConnectedDatabase { 15 | 16 | public MysqlDatabase(Connection conn) { 17 | throw new RuntimeException("NyaaCore ORM MySQL backend is not implemented"); 18 | } 19 | 20 | @Override 21 | public Connection getConnection() { 22 | return null; 23 | } 24 | 25 | @Override 26 | public ITypedTable getTable(Class recordClass) { 27 | return null; 28 | } 29 | 30 | @Override 31 | public ITypedTable getUnverifiedTable(Class recordClass) { 32 | return null; 33 | } 34 | 35 | @Override 36 | public void close() throws SQLException { 37 | 38 | } 39 | 40 | @Override 41 | public List queryBundledAs(Plugin plugin, String filename, Map replacementMap, Class cls, Object... parameters) { 42 | return null; 43 | } 44 | 45 | @Override 46 | public boolean verifySchema(String tableName, Class recordClass) { 47 | return false; 48 | } 49 | 50 | // public String getCreateTableSQL() { 51 | // StringJoiner colStr = new StringJoiner(","); 52 | // for (String colName : orderedColumnName) { 53 | // colStr.add(columns.get(colName).getTableCreationScheme()); 54 | // } 55 | // if (primKeyStructure != null) { 56 | // if (primKeyStructure.sqlType.isBlobOrText() && primKeyStructure.getLength() > 0) { 57 | // colStr.add(String.format("CONSTRAINT constraint_PK PRIMARY KEY (%s(%d))", primaryKey, primKeyStructure.getLength())); 58 | // } else { 59 | // colStr.add(String.format("CONSTRAINT constraint_PK PRIMARY KEY (%s)", primaryKey)); 60 | // } 61 | // } 62 | // return String.format("CREATE TABLE IF NOT EXISTS %s(%s)", tableName, colStr.toString()); 63 | // } 64 | // public void createTable(Class cls) { 65 | // Validate.notNull(cls); 66 | // if (createdTableClasses.contains(cls)) return; 67 | // try { 68 | // if (!mainConnLock.tryAcquire(10, TimeUnit.SECONDS)) { 69 | // throw new IllegalStateException(); 70 | // } 71 | // } catch (InterruptedException e) { 72 | // throw new RuntimeException(e); 73 | // } 74 | // try { 75 | // createTable(cls, getConnection()); 76 | // } finally { 77 | // mainConnLock.release(); 78 | // } 79 | // } 80 | // 81 | // @Override 82 | // public void createTable(Class cls) { 83 | // Validate.notNull(cls); 84 | // if (createdTableClasses.contains(cls)) return; 85 | // ObjectModifier ts = ObjectModifier.fromClass(cls); 86 | // String sql = ts.getCreateTableSQL(); 87 | // try (Statement smt = getConnection().createStatement()) { 88 | // smt.executeUpdate(sql); 89 | // createdTableClasses.add(cls); 90 | // } catch (SQLException ex) { 91 | // throw new RuntimeException(sql, ex); 92 | // } 93 | // } 94 | } 95 | -------------------------------------------------------------------------------- /src/main/java/cat/nyaa/nyaacore/orm/backends/SQLiteDatabase.java: -------------------------------------------------------------------------------- 1 | package cat.nyaa.nyaacore.orm.backends; 2 | 3 | import cat.nyaa.nyaacore.orm.BundledSQLUtils; 4 | import cat.nyaa.nyaacore.orm.ObjectFieldModifier; 5 | import cat.nyaa.nyaacore.orm.ObjectModifier; 6 | import org.bukkit.Bukkit; 7 | import org.bukkit.plugin.Plugin; 8 | 9 | import java.sql.*; 10 | import java.util.HashMap; 11 | import java.util.List; 12 | import java.util.Map; 13 | import java.util.StringJoiner; 14 | 15 | public class SQLiteDatabase implements IConnectedDatabase { 16 | 17 | private final Connection dbConn; 18 | 19 | public SQLiteDatabase(Connection sqlConnection) { 20 | if (sqlConnection == null) throw new IllegalArgumentException(); 21 | dbConn = sqlConnection; 22 | try { 23 | dbConn.setAutoCommit(true); 24 | dbConn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE); 25 | } catch (SQLException ex) { 26 | throw new RuntimeException(ex); 27 | } 28 | } 29 | 30 | private static String getTableCreationScheme(ObjectFieldModifier fm) { 31 | String ret = fm.name + " " + fm.columnDefinition; 32 | if (fm.primary) ret += " PRIMARY KEY"; 33 | if (!fm.nullable) ret += " NOT NULL"; 34 | if (fm.autoIncrement) ret += " AUTOINCREMENT"; 35 | if (fm.unique) ret += " UNIQUE"; 36 | return ret; 37 | } 38 | 39 | private static String getTableCreationSql(Class recordClass) { 40 | ObjectModifier objMod = ObjectModifier.fromClass(recordClass); 41 | StringJoiner colStr = new StringJoiner(","); 42 | for (String colName : objMod.getColNames()) { 43 | ObjectFieldModifier ct = objMod.columns.get(colName); 44 | colStr.add(getTableCreationScheme(ct)); 45 | } 46 | return String.format("CREATE TABLE IF NOT EXISTS %s(%s)", objMod.tableName, colStr); 47 | } 48 | 49 | @Override 50 | public Connection getConnection() { 51 | return dbConn; 52 | } 53 | 54 | @Override 55 | public void close() throws SQLException { 56 | dbConn.close(); 57 | } 58 | 59 | @Override 60 | public boolean verifySchema(String tableName, Class recordClass) { 61 | if (tableName == null || recordClass == null) throw new IllegalArgumentException(); 62 | if (tableName.equals(recordClass.getName())) throw new IllegalArgumentException("table name must match"); 63 | ObjectModifier objectModifier = ObjectModifier.fromClass(recordClass); 64 | 65 | boolean matches = true; 66 | try { 67 | Map objectColumns = new HashMap<>(objectModifier.columns); 68 | 69 | ResultSet columnRS = dbConn.getMetaData().getColumns(null, null, tableName, "%"); 70 | while (columnRS.next()) { 71 | String colName = columnRS.getString("COLUMN_NAME"); 72 | int colType = columnRS.getInt("DATA_TYPE"); 73 | int nullable = columnRS.getInt("NULLABLE"); // 0=NotNull 1=Nullable 2=Unknown 74 | 75 | ObjectFieldModifier tmp = objectColumns.remove(colName); 76 | if (tmp == null) { 77 | Bukkit.getLogger().info(String.format("table column %s.%s not exists in class %s", tableName, colName, recordClass.getCanonicalName())); 78 | matches = false; 79 | } else { 80 | JDBCType tableColType = JDBCType.valueOf(colType); 81 | SQLType objColType = tmp.typeConverter.getSqlType(); 82 | if (objColType == JDBCType.BIGINT) 83 | objColType = JDBCType.INTEGER; // FIXME: it seems that SQLite returns INTEGER for BIGINT columns 84 | if (objColType == JDBCType.DOUBLE) 85 | objColType = JDBCType.FLOAT; // and returns FLOAT for DOUBLE columns 86 | if (!objColType.equals(tableColType)) { 87 | Bukkit.getLogger().info(String.format("table column %s.%s type mismatch. db:%s java:%s", 88 | tableName, colName, tableColType, objColType)); 89 | matches = false; 90 | } else if (nullable == 0 && tmp.nullable || nullable == 1 && !tmp.nullable) { 91 | Bukkit.getLogger().info(String.format("table column %s.%s nullable mismatch db:%d java:%s", 92 | tableName, colName, nullable, tmp.nullable)); 93 | matches = false; 94 | } 95 | } 96 | } 97 | columnRS.close(); 98 | 99 | for (String col : objectColumns.keySet()) { 100 | Bukkit.getLogger().info(String.format("table column %s.%s not in database", tableName, col)); 101 | matches = false; 102 | } 103 | 104 | ResultSet pkRs = dbConn.getMetaData().getPrimaryKeys(null, null, tableName); 105 | if (pkRs.next()) { 106 | String pkColName = pkRs.getString("COLUMN_NAME"); 107 | if (objectModifier.primaryKey == null) { 108 | Bukkit.getLogger().info(String.format("table column %s.%s is primary key but java does not", 109 | tableName, pkColName)); 110 | matches = false; 111 | } else if (!objectModifier.primaryKey.equals(pkColName)) { 112 | Bukkit.getLogger().info(String.format("table column %s.%s is primary key but java use %s", 113 | tableName, pkColName, objectModifier.primaryKey)); 114 | matches = false; 115 | } else if (pkRs.next()) { 116 | pkRs.close(); 117 | throw new RuntimeException("multiple primary keys wtf ???"); 118 | } 119 | } else if (objectModifier.primaryKey != null) { 120 | Bukkit.getLogger().info(String.format("table column %s.%s is not primary key but java says so", 121 | tableName, objectModifier.primaryKey)); 122 | matches = false; 123 | } 124 | pkRs.close(); 125 | 126 | return matches; 127 | } catch (SQLException ex) { 128 | throw new RuntimeException(ex); 129 | } 130 | } 131 | 132 | @Override 133 | public List queryBundledAs(Plugin plugin, String filename, Map replacementMap, Class cls, Object... parameters) { 134 | return BundledSQLUtils.queryBundledAs(plugin, dbConn, filename, replacementMap, cls, parameters); 135 | } 136 | 137 | private boolean tableExists(String tableName) throws SQLException { 138 | try (ResultSet rs = dbConn.getMetaData().getTables(null, null, tableName, new String[]{"TABLE"})) { 139 | return rs.next(); 140 | } 141 | } 142 | 143 | @Override 144 | public ITypedTable getUnverifiedTable(Class recordClass) { 145 | if (recordClass == null) throw new IllegalArgumentException(); 146 | ObjectModifier om = ObjectModifier.fromClass(recordClass); 147 | 148 | try { 149 | if (tableExists(om.tableName)) { 150 | return this.new SQLiteTypedTable<>(om); 151 | } else { 152 | createTable(recordClass); 153 | return this.new SQLiteTypedTable<>(om); 154 | } 155 | } catch (SQLException ex) { 156 | throw new RuntimeException(ex); 157 | } 158 | } 159 | 160 | @Override 161 | public ITypedTable getTable(Class recordClass) { 162 | if (recordClass == null) throw new IllegalArgumentException(); 163 | ObjectModifier om = ObjectModifier.fromClass(recordClass); 164 | 165 | try { 166 | if (tableExists(om.tableName)) { 167 | if (!verifySchema(om.tableName, recordClass)) { 168 | throw new RuntimeException("table schema not match"); 169 | } else { 170 | return this.new SQLiteTypedTable<>(om); 171 | } 172 | } else { 173 | createTable(recordClass); 174 | return this.new SQLiteTypedTable<>(om); 175 | } 176 | } catch (SQLException ex) { 177 | throw new RuntimeException(ex); 178 | } 179 | } 180 | 181 | private void createTable(Class cls) { 182 | if (cls == null) throw new IllegalArgumentException(); 183 | ObjectModifier om = ObjectModifier.fromClass(cls); 184 | String sql = getTableCreationSql(cls); 185 | try (Statement smt = dbConn.createStatement()) { 186 | smt.executeUpdate(sql); 187 | } catch (SQLException ex) { 188 | throw new RuntimeException(sql, ex); 189 | } 190 | } 191 | 192 | public class SQLiteTypedTable extends BaseTypedTable { 193 | private final ObjectModifier javaObjectModifier; 194 | private final String tableName; 195 | 196 | public SQLiteTypedTable(ObjectModifier javaObjectModifier) { 197 | this.javaObjectModifier = javaObjectModifier; 198 | this.tableName = javaObjectModifier.tableName; 199 | } 200 | 201 | @Override 202 | public String getTableName() { 203 | return tableName; 204 | } 205 | 206 | @Override 207 | public ObjectModifier getJavaTypeModifier() { 208 | return javaObjectModifier; 209 | } 210 | 211 | @Override 212 | protected Connection getConnection() { 213 | return dbConn; 214 | } 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /src/main/java/cat/nyaa/nyaacore/utils/ClickSelectionUtils.java: -------------------------------------------------------------------------------- 1 | package cat.nyaa.nyaacore.utils; 2 | 3 | import org.bukkit.Location; 4 | import org.bukkit.block.Block; 5 | import org.bukkit.event.EventHandler; 6 | import org.bukkit.event.EventPriority; 7 | import org.bukkit.event.Listener; 8 | import org.bukkit.event.block.Action; 9 | import org.bukkit.event.player.PlayerInteractEvent; 10 | import org.bukkit.plugin.Plugin; 11 | import org.bukkit.scheduler.BukkitRunnable; 12 | 13 | import java.util.HashMap; 14 | import java.util.Map; 15 | import java.util.UUID; 16 | import java.util.function.Consumer; 17 | 18 | public class ClickSelectionUtils { 19 | private static final Map> callbackMap = new HashMap<>(); 20 | private static final Map timeoutListener = new HashMap<>(); 21 | 22 | /** 23 | * Callback will be invoked if the player right clicked on a block, or the timer goes off. 24 | * 25 | * @param player the player 26 | * @param timeout seconds, must positive 27 | * @param callback if timeout, the parameter will be null 28 | */ 29 | public static void registerRightClickBlock(UUID player, int timeout, Consumer callback, Plugin plugin) { 30 | // force timeout any existing listeners 31 | Consumer cb = callbackMap.remove(player); 32 | BukkitRunnable tl = timeoutListener.remove(player); 33 | if (cb != null) cb.accept(null); 34 | if (tl != null) tl.cancel(); 35 | 36 | // add new callback 37 | callbackMap.put(player, callback); 38 | BukkitRunnable runnable = new BukkitRunnable() { 39 | @Override 40 | public void run() { 41 | if (callbackMap.containsKey(player)) 42 | callbackMap.remove(player).accept(null); 43 | timeoutListener.remove(player); 44 | } 45 | }; 46 | runnable.runTaskLater(plugin, timeout * 20L); 47 | timeoutListener.put(player, runnable); 48 | } 49 | 50 | public static class _Listener implements Listener { 51 | @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) 52 | public void onRightClickBlock(PlayerInteractEvent ev) { 53 | if (callbackMap.containsKey(ev.getPlayer().getUniqueId()) && ev.hasBlock() && ev.getAction() == Action.RIGHT_CLICK_BLOCK) { 54 | Block b = ev.getClickedBlock(); 55 | Consumer cb = callbackMap.remove(ev.getPlayer().getUniqueId()); 56 | BukkitRunnable tl = timeoutListener.remove(ev.getPlayer().getUniqueId()); 57 | 58 | if (tl == null || !tl.isCancelled()) { 59 | cb.accept(b.getLocation()); 60 | ev.setCancelled(true); 61 | } 62 | } 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/cat/nyaa/nyaacore/utils/ConcurrentUtils.java: -------------------------------------------------------------------------------- 1 | package cat.nyaa.nyaacore.utils; 2 | 3 | import org.bukkit.Bukkit; 4 | import org.bukkit.plugin.Plugin; 5 | 6 | import java.util.function.Consumer; 7 | import java.util.function.Function; 8 | 9 | public final class ConcurrentUtils { 10 | /** 11 | * Execute a task asynchronously then execute the callback synchronously 12 | */ 13 | public static void runAsyncTask(Plugin plugin, P parameter, Function asyncTask, Consumer callback) { 14 | Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { 15 | final Q ret = asyncTask.apply(parameter); 16 | Bukkit.getScheduler().runTask(plugin, () -> { 17 | callback.accept(ret); 18 | }); 19 | }); 20 | } 21 | 22 | /** 23 | * @deprecated caller can use {@link org.bukkit.scheduler.BukkitScheduler#runTaskAsynchronously(Plugin, Runnable)} directly 24 | */ 25 | @Deprecated 26 | public static

void runAsyncTask(Plugin plugin, P parameter, Consumer

asyncTask) { 27 | Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { 28 | asyncTask.accept(parameter); 29 | }); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/cat/nyaa/nyaacore/utils/EntityUtils.java: -------------------------------------------------------------------------------- 1 | package cat.nyaa.nyaacore.utils; 2 | 3 | 4 | import net.minecraft.world.entity.EntityType; 5 | 6 | import java.util.Optional; 7 | import java.util.concurrent.atomic.AtomicInteger; 8 | 9 | public class EntityUtils { 10 | public static AtomicInteger FAKE_ENTITY_COUNTER = new AtomicInteger(0xffcc); 11 | 12 | private static Optional> getNmsEntityTypes(org.bukkit.entity.EntityType bukkitEntityType) { 13 | return EntityType.byString(bukkitEntityType.getKey().getKey()); 14 | } 15 | 16 | public static int getUpdateInterval(org.bukkit.entity.EntityType bukkitEntityType) { 17 | return getNmsEntityTypes(bukkitEntityType).map(EntityType::updateInterval).orElse(3); 18 | } 19 | 20 | public static int getClientTrackingRange(org.bukkit.entity.EntityType bukkitEntityType) { 21 | return getNmsEntityTypes(bukkitEntityType).map(EntityType::clientTrackingRange).orElse(5); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/cat/nyaa/nyaacore/utils/ExperienceUtils.java: -------------------------------------------------------------------------------- 1 | package cat.nyaa.nyaacore.utils; 2 | 3 | import com.google.common.primitives.Ints; 4 | import org.bukkit.entity.Player; 5 | 6 | public final class ExperienceUtils { 7 | /** 8 | * How much exp points at least needed to reach this level. 9 | * i.e. getLevel() = level && getExp() == 0 10 | */ 11 | public static int getExpForLevel(int level) { 12 | if (level < 0) throw new IllegalArgumentException(); 13 | else if (level <= 16) return (level + 6) * level; 14 | else if (level < 32) return Ints.checkedCast(Math.round(2.5 * level * level - 40.5 * level + 360)); 15 | else return Ints.checkedCast(Math.round(4.5 * level * level - 162.5 * level + 2220)); 16 | } 17 | 18 | /** 19 | * The true exp point for a player at this time. 20 | */ 21 | public static int getExpPoints(Player p) { 22 | int pointForCurrentLevel = Math.round(p.getExpToLevel() * p.getExp()); 23 | return getExpForLevel(p.getLevel()) + pointForCurrentLevel; 24 | } 25 | 26 | public static void subtractExpPoints(Player p, int points) { 27 | if (points < 0) throw new IllegalArgumentException(); 28 | if (points == 0) return; 29 | int total = getExpPoints(p); 30 | if (total < points) throw new IllegalArgumentException("Negative Exp Left"); 31 | int newLevel = getLevelForExp(total - points); 32 | int remPoint = total - points - getExpForLevel(newLevel); 33 | p.setLevel(newLevel); 34 | p.setExp(0); 35 | p.giveExp(remPoint); 36 | } 37 | 38 | /** 39 | * Which level the player at if he/she has this mount of exp points 40 | * TODO optimization 41 | */ 42 | public static int getLevelForExp(int exp) { 43 | if (exp < 0) throw new IllegalArgumentException(); 44 | for (int lv = 1; lv < 21000; lv++) { 45 | if (getExpForLevel(lv) > exp) return lv - 1; 46 | } 47 | throw new IllegalArgumentException("exp too large"); 48 | } 49 | 50 | /** 51 | * Change the player's experience (not experience level) 52 | * Related events may be triggered. 53 | * 54 | * @param p the target player 55 | * @param exp amount of xp to be added to the player, 56 | * if negative, then subtract from the player. 57 | * @throws IllegalArgumentException if the player ended with negative xp 58 | */ 59 | public static void addPlayerExperience(Player p, int exp) { 60 | if (exp > 0) { 61 | p.giveExp(exp); 62 | } else if (exp < 0) { 63 | subtractExpPoints(p, -exp); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/cat/nyaa/nyaacore/utils/HexColorUtils.java: -------------------------------------------------------------------------------- 1 | package cat.nyaa.nyaacore.utils; 2 | 3 | import java.util.regex.MatchResult; 4 | import java.util.regex.Pattern; 5 | 6 | public class HexColorUtils { 7 | // Extract from SimpleLanguageLoader 8 | 9 | /** 10 | * hex color pattern looks like &#RRGGBB 11 | */ 12 | public static final Pattern hexColorPattern = Pattern.compile("&#[0-9A-Fa-f]{6}"); 13 | 14 | /** 15 | * Replace the convenient RGB color code with expanded color code in a string. 16 | * 17 | * @param text text to be proceeded 18 | * @return text with expanded color code 19 | */ 20 | public static String extractColorCode(String text) { 21 | var textParts = hexColorPattern.split(text); 22 | if (textParts.length == 0) 23 | textParts = new String[]{"", ""}; 24 | var colorCodes = hexColorPattern.matcher(text).results().map(MatchResult::group).toArray(String[]::new); 25 | var textBuilder = new StringBuilder(textParts[0]); 26 | for (int i = 0; i < colorCodes.length; i++) { 27 | textBuilder.append(convertToTraditionalColorCode(colorCodes[i])).append(textParts[i + 1]); 28 | } 29 | return convertToLegacyColorCode(textBuilder.toString()); 30 | } 31 | 32 | // for old api compatibility 33 | public static String hexColored(String text){ 34 | return extractColorCode(text); 35 | } 36 | 37 | /** 38 | * Expand color code looks like &#RRGGBB to &x&R&R&G&G&B&B. 39 | * 40 | * @param x convenient color code 41 | * @return expanded color code 42 | */ 43 | private static String convertToTraditionalColorCode(String x) { 44 | var hexColorCode = x.substring(2); 45 | var colorCodeBuilder = new StringBuilder("&x"); 46 | hexColorCode.chars().forEach(c -> colorCodeBuilder.append("&").append((char) c)); 47 | return colorCodeBuilder.toString(); 48 | } 49 | 50 | private static String convertToLegacyColorCode(String text) { 51 | return text.replace('&', '§').replace(String.valueOf('&') + '&', "§"); 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/cat/nyaa/nyaacore/utils/InventoryUtils.java: -------------------------------------------------------------------------------- 1 | package cat.nyaa.nyaacore.utils; 2 | 3 | 4 | import org.bukkit.Material; 5 | import org.bukkit.entity.Player; 6 | import org.bukkit.inventory.Inventory; 7 | import org.bukkit.inventory.ItemStack; 8 | import org.bukkit.inventory.PlayerInventory; 9 | 10 | import java.util.ArrayList; 11 | import java.util.Collections; 12 | import java.util.List; 13 | import java.util.stream.Collectors; 14 | 15 | public final class InventoryUtils { 16 | 17 | public static boolean hasItem(Player player, ItemStack item, int amount) { 18 | return hasItem(player.getInventory(), item, amount); 19 | } 20 | 21 | public static boolean hasItem(Inventory inv, ItemStack item, int amount) { 22 | return inv.containsAtLeast(item, amount); 23 | } 24 | 25 | public static boolean addItem(Player player, ItemStack item) { 26 | return addItem(player.getInventory(), item.clone(), item.getAmount()); 27 | } 28 | 29 | public static boolean addItem(Inventory inventory, ItemStack item) { 30 | return addItem(inventory, item.clone(), item.getAmount()); 31 | } 32 | 33 | private static boolean addItem(Inventory inventory, ItemStack item, int amount) { 34 | ItemStack i = item.clone(); 35 | i.setAmount(amount); 36 | return addItems(inventory, Collections.singletonList(i)); 37 | } 38 | 39 | public static boolean addItems(Inventory inventory, List items) { 40 | return _addItems(inventory, items.stream().filter(i -> i != null && i.getType() != Material.AIR).map(ItemStack::clone).collect(Collectors.toList())); 41 | } 42 | 43 | private static boolean _addItems(Inventory inventory, List items) { 44 | ItemStack[] tmpInv = new ItemStack[inventory.getSize()]; 45 | for (int i = 0; i < inventory.getSize(); i++) { 46 | if (i >= 36 && i <= 39 && inventory instanceof PlayerInventory) { 47 | tmpInv[i] = null; 48 | continue; 49 | } 50 | if (inventory.getItem(i) != null && inventory.getItem(i).getType() != Material.AIR) { 51 | tmpInv[i] = inventory.getItem(i).clone(); 52 | } else { 53 | tmpInv[i] = new ItemStack(Material.AIR); 54 | } 55 | } 56 | for (ItemStack item : items) { 57 | int amount = item.getAmount(); 58 | for (int slot = 0; slot < tmpInv.length; slot++) { 59 | ItemStack tmp = tmpInv[slot]; 60 | if (tmp == null) { 61 | continue; 62 | } 63 | if (tmp.getAmount() < item.getMaxStackSize() && item.isSimilar(tmp)) { 64 | if ((tmp.getAmount() + amount) <= item.getMaxStackSize()) { 65 | tmp.setAmount(amount + tmp.getAmount()); 66 | amount = 0; 67 | tmpInv[slot] = tmp; 68 | break; 69 | } else { 70 | amount = amount - (item.getMaxStackSize() - tmp.getAmount()); 71 | tmp.setAmount(item.getMaxStackSize()); 72 | tmpInv[slot] = tmp; 73 | continue; 74 | } 75 | } 76 | } 77 | if (amount > 0) { 78 | for (int i = 0; i < tmpInv.length; i++) { 79 | if (tmpInv[i] != null && tmpInv[i].getType() == Material.AIR) { 80 | item.setAmount(amount); 81 | tmpInv[i] = item.clone(); 82 | amount = 0; 83 | break; 84 | } 85 | } 86 | } 87 | if (amount > 0) { 88 | return false; 89 | } 90 | } 91 | for (int i = 0; i < tmpInv.length; i++) { 92 | if (tmpInv[i] != null && !tmpInv[i].equals(inventory.getItem(i))) { 93 | inventory.setItem(i, tmpInv[i]); 94 | } 95 | } 96 | return true; 97 | } 98 | 99 | public static boolean removeItem(Player player, ItemStack item, int amount) { 100 | return removeItem(player.getInventory(), item, amount); 101 | } 102 | 103 | public static boolean removeItem(Inventory inventory, ItemStack item, int amount) { 104 | ItemStack[] items = new ItemStack[inventory.getSize()]; 105 | for (int i = 0; i < inventory.getSize(); i++) { 106 | if (inventory.getItem(i) != null && 107 | inventory.getItem(i).getType() != Material.AIR) { 108 | items[i] = inventory.getItem(i).clone(); 109 | } else { 110 | items[i] = new ItemStack(Material.AIR); 111 | } 112 | } 113 | boolean success = false; 114 | for (int slot = 0; slot < items.length; slot++) { 115 | ItemStack tmp = items[slot]; 116 | if (tmp != null && tmp.isSimilar(item) && tmp.getAmount() > 0) { 117 | if (tmp.getAmount() < amount) { 118 | amount = amount - tmp.getAmount(); 119 | items[slot] = new ItemStack(Material.AIR); 120 | continue; 121 | } else if (tmp.getAmount() > amount) { 122 | tmp.setAmount(tmp.getAmount() - amount); 123 | amount = 0; 124 | success = true; 125 | break; 126 | } else { 127 | items[slot] = new ItemStack(Material.AIR); 128 | amount = 0; 129 | success = true; 130 | break; 131 | } 132 | } 133 | } 134 | if (success) { 135 | for (int i = 0; i < items.length; i++) { 136 | if (!items[i].equals(inventory.getItem(i))) { 137 | inventory.setItem(i, items[i]); 138 | } 139 | } 140 | return true; 141 | } 142 | return false; 143 | } 144 | 145 | public static int getAmount(Player p, ItemStack item) { 146 | return getAmount(p.getInventory(), item); 147 | } 148 | 149 | public static int getAmount(Inventory inventory, ItemStack item) { 150 | int amount = 0; 151 | for (int i = 0; i < inventory.getSize(); i++) { 152 | if (inventory.getItem(i) != null && 153 | inventory.getItem(i).getType() != Material.AIR && 154 | inventory.getItem(i).isSimilar(item)) { 155 | amount += inventory.getItem(i).getAmount(); 156 | } 157 | } 158 | return amount; 159 | } 160 | 161 | public static boolean hasEnoughSpace(Player player, ItemStack item, int amount) { 162 | return hasEnoughSpace(player.getInventory(), item, amount); 163 | } 164 | 165 | public static boolean hasEnoughSpace(Inventory inventory, ItemStack item) { 166 | return hasEnoughSpace(inventory, item, item.getAmount()); 167 | } 168 | 169 | public static boolean hasEnoughSpace(Inventory inventory, ItemStack item, int amount) { 170 | for (int i = 0; i < inventory.getSize(); i++) { 171 | if (i >= 36 && i <= 39 && inventory instanceof PlayerInventory) { 172 | continue; 173 | } 174 | if (inventory.getItem(i) != null && item.isSimilar(inventory.getItem(i)) && 175 | inventory.getItem(i).getAmount() < item.getMaxStackSize()) { 176 | amount -= item.getMaxStackSize() - inventory.getItem(i).getAmount(); 177 | } else if (inventory.getItem(i) == null || inventory.getItem(i).getType() == Material.AIR) { 178 | amount = 0; 179 | } 180 | if (amount < 1) { 181 | return true; 182 | } 183 | } 184 | return false; 185 | } 186 | 187 | /** 188 | * Remove items from inventory. 189 | * Either all removed or none removed. 190 | * 191 | * @param inv the inventory 192 | * @param itemToBeTaken items to be removed 193 | * @return If null, then all designated items are removed. If not null, it contains the items missing 194 | */ 195 | public static List withdrawInventoryAtomic(Inventory inv, List itemToBeTaken) { 196 | ItemStack[] itemStacks = inv.getContents(); 197 | ItemStack[] cloneStacks = new ItemStack[itemStacks.length]; 198 | for (int i = 0; i < itemStacks.length; i++) { 199 | cloneStacks[i] = itemStacks[i] == null ? null : itemStacks[i].clone(); 200 | } 201 | 202 | List ret = new ArrayList<>(); 203 | 204 | for (ItemStack item : itemToBeTaken) { 205 | int sizeReq = item.getAmount(); 206 | 207 | for (int i = 0; i < cloneStacks.length; i++) { 208 | if (cloneStacks[i] == null) continue; 209 | if (cloneStacks[i].isSimilar(item)) { 210 | int sizeSupp = cloneStacks[i].getAmount(); 211 | if (sizeSupp > sizeReq) { 212 | cloneStacks[i].setAmount(sizeSupp - sizeReq); 213 | sizeReq = 0; 214 | break; 215 | } else { 216 | cloneStacks[i] = null; 217 | sizeReq -= sizeSupp; 218 | if (sizeReq == 0) break; 219 | } 220 | } 221 | } 222 | 223 | if (sizeReq > 0) { 224 | ItemStack n = item.clone(); 225 | item.setAmount(sizeReq); 226 | ret.add(n); 227 | } 228 | } 229 | 230 | if (ret.size() == 0) { 231 | inv.setContents(cloneStacks); 232 | return null; 233 | } else { 234 | return ret; 235 | } 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /src/main/java/cat/nyaa/nyaacore/utils/ItemTagUtils.java: -------------------------------------------------------------------------------- 1 | package cat.nyaa.nyaacore.utils; 2 | 3 | import net.minecraft.core.component.DataComponentMap; 4 | import net.minecraft.core.component.DataComponents; 5 | import net.minecraft.nbt.CompoundTag; 6 | import net.minecraft.world.item.component.CustomData; 7 | import org.bukkit.craftbukkit.inventory.CraftItemStack; 8 | import org.bukkit.inventory.ItemStack; 9 | 10 | import java.lang.reflect.Field; 11 | import java.util.Optional; 12 | 13 | public class ItemTagUtils { 14 | 15 | public static Optional getString(ItemStack item, String key) { 16 | net.minecraft.world.item.ItemStack nmsItem = CraftItemStack.unwrap(item); 17 | CompoundTag tag = getTag(nmsItem); 18 | if (tag == null) return Optional.empty(); 19 | return !tag.contains(key) ? Optional.empty() : tag.getString(key); 20 | } 21 | 22 | public static Optional setString(ItemStack item, String key, String value) throws NoSuchFieldException, IllegalAccessException { 23 | net.minecraft.world.item.ItemStack nmsItem = null; 24 | if(item instanceof CraftItemStack) { 25 | nmsItem = CraftItemStack.unwrap(item); 26 | } 27 | if (nmsItem == null) { 28 | return Optional.empty(); 29 | } 30 | CustomData.update(DataComponents.CUSTOM_DATA, nmsItem, (tag) -> tag.putString(key, value)); 31 | return Optional.of(value); 32 | } 33 | 34 | public static Optional getInt(ItemStack item, String key) { 35 | net.minecraft.world.item.ItemStack nmsItem = CraftItemStack.unwrap(item); 36 | CompoundTag tag = getTag(nmsItem); 37 | if (tag == null) return Optional.empty(); 38 | return !tag.contains(key) ? Optional.empty() : tag.getInt(key); 39 | } 40 | 41 | public static Optional setInt(ItemStack item, String key, int value) { 42 | net.minecraft.world.item.ItemStack nmsItem = null; 43 | if(item instanceof CraftItemStack) { 44 | nmsItem = CraftItemStack.unwrap(item); 45 | } 46 | if (nmsItem == null) { 47 | return Optional.empty(); 48 | } 49 | CustomData.update(DataComponents.CUSTOM_DATA, nmsItem, (tag) -> tag.putInt(key, value)); 50 | return Optional.of(value); 51 | } 52 | 53 | public static Optional getDouble(ItemStack item, String key) { 54 | net.minecraft.world.item.ItemStack nmsItem = CraftItemStack.unwrap(item); 55 | CompoundTag tag = getTag(nmsItem); 56 | if (tag == null) return Optional.empty(); 57 | return !tag.contains(key) ? Optional.empty() : tag.getDouble(key); 58 | } 59 | 60 | public static Optional setDouble(ItemStack item, String key, double value) { 61 | net.minecraft.world.item.ItemStack nmsItem = null; 62 | if(item instanceof CraftItemStack) { 63 | nmsItem = CraftItemStack.unwrap(item); 64 | } 65 | if (nmsItem == null) { 66 | return Optional.empty(); 67 | } 68 | CustomData.update(DataComponents.CUSTOM_DATA, nmsItem, (tag) -> tag.putDouble(key, value)); 69 | return Optional.of(value); 70 | } 71 | 72 | public static Optional getShort(ItemStack item, String key) { 73 | net.minecraft.world.item.ItemStack nmsItem = CraftItemStack.unwrap(item); 74 | CompoundTag tag = getTag(nmsItem); 75 | if (tag == null) return Optional.empty(); 76 | return !tag.contains(key) ? Optional.empty() : tag.getShort(key); 77 | } 78 | 79 | public static Optional setShort(ItemStack item, String key, short value) { 80 | net.minecraft.world.item.ItemStack nmsItem = null; 81 | if(item instanceof CraftItemStack) { 82 | nmsItem = CraftItemStack.unwrap(item); 83 | } 84 | if (nmsItem == null) { 85 | return Optional.empty(); 86 | } 87 | CustomData.update(DataComponents.CUSTOM_DATA, nmsItem, (tag) -> tag.putShort(key, value)); 88 | return Optional.of(value); 89 | } 90 | 91 | public static Optional getByte(ItemStack item, String key) { 92 | net.minecraft.world.item.ItemStack nmsItem = CraftItemStack.unwrap(item); 93 | CompoundTag tag = getTag(nmsItem); 94 | if (tag == null) return Optional.empty(); 95 | return !tag.contains(key) ? Optional.empty() : tag.getByte(key); 96 | } 97 | 98 | public static Optional setByte(ItemStack item, String key, byte value) { 99 | net.minecraft.world.item.ItemStack nmsItem = null; 100 | if(item instanceof CraftItemStack) { 101 | nmsItem = CraftItemStack.unwrap(item); 102 | } 103 | if (nmsItem == null) { 104 | return Optional.empty(); 105 | } 106 | CustomData.update(DataComponents.CUSTOM_DATA, nmsItem, (tag) -> tag.putByte(key, value)); 107 | return Optional.of(value); 108 | } 109 | 110 | public static Optional getLong(ItemStack item, String key) { 111 | net.minecraft.world.item.ItemStack nmsItem = CraftItemStack.unwrap(item); 112 | CompoundTag tag = getTag(nmsItem); 113 | if (tag == null) return Optional.empty(); 114 | return !tag.contains(key) ? Optional.empty() : tag.getLong(key); 115 | } 116 | 117 | public static Optional setLong(ItemStack item, String key, long value) { 118 | net.minecraft.world.item.ItemStack nmsItem = null; 119 | if(item instanceof CraftItemStack) { 120 | nmsItem = CraftItemStack.unwrap(item); 121 | } 122 | if (nmsItem == null) { 123 | return Optional.empty(); 124 | } 125 | CustomData.update(DataComponents.CUSTOM_DATA, nmsItem, (tag) -> tag.putLong(key, value)); 126 | return Optional.of(value); 127 | } 128 | 129 | public static Optional getLongArray(ItemStack item, String key) { 130 | net.minecraft.world.item.ItemStack nmsItem = CraftItemStack.unwrap(item); 131 | CompoundTag tag = getTag(nmsItem); 132 | if (tag == null) return Optional.empty(); 133 | return !tag.contains(key) ? Optional.empty() : tag.getLongArray(key); 134 | } 135 | 136 | public static Optional setLongArray(ItemStack item, String key, long[] value) { 137 | net.minecraft.world.item.ItemStack nmsItem = null; 138 | if(item instanceof CraftItemStack) { 139 | nmsItem = CraftItemStack.unwrap(item); 140 | } 141 | if (nmsItem == null) { 142 | return Optional.empty(); 143 | } 144 | CustomData.update(DataComponents.CUSTOM_DATA, nmsItem, (tag) -> tag.putLongArray(key, value)); 145 | return Optional.of(value); 146 | } 147 | 148 | public static Optional getIntArray(ItemStack item, String key) { 149 | net.minecraft.world.item.ItemStack nmsItem = CraftItemStack.unwrap(item); 150 | CompoundTag tag = getTag(nmsItem); 151 | if (tag == null) return Optional.empty(); 152 | return !tag.contains(key) ? Optional.empty() : tag.getIntArray(key); 153 | } 154 | 155 | public static Optional setIntArray(ItemStack item, String key, int[] value) { 156 | net.minecraft.world.item.ItemStack nmsItem = null; 157 | if(item instanceof CraftItemStack) { 158 | nmsItem = CraftItemStack.unwrap(item); 159 | } 160 | if (nmsItem == null) { 161 | return Optional.empty(); 162 | } 163 | CustomData.update(DataComponents.CUSTOM_DATA, nmsItem, (tag) -> tag.putIntArray(key, value)); 164 | return Optional.of(value); 165 | } 166 | 167 | public static Optional getByteArray(ItemStack item, String key) { 168 | net.minecraft.world.item.ItemStack nmsItem = CraftItemStack.unwrap(item); 169 | CompoundTag tag = getTag(nmsItem); 170 | if (tag == null) return Optional.empty(); 171 | return !tag.contains(key) ? Optional.empty() : tag.getByteArray(key); 172 | } 173 | 174 | public static Optional setByteArray(ItemStack item, String key, byte[] value) { 175 | net.minecraft.world.item.ItemStack nmsItem = null; 176 | if(item instanceof CraftItemStack) { 177 | nmsItem = CraftItemStack.unwrap(item); 178 | } 179 | if (nmsItem == null) { 180 | return Optional.empty(); 181 | } 182 | CustomData.update(DataComponents.CUSTOM_DATA, nmsItem, (tag) -> tag.putByteArray(key, value)); 183 | return Optional.of(value); 184 | } 185 | 186 | public static Optional getBoolean(ItemStack item, String key) { 187 | net.minecraft.world.item.ItemStack nmsItem = CraftItemStack.unwrap(item); 188 | CompoundTag tag = getTag(nmsItem); 189 | if (tag == null) return Optional.empty(); 190 | return !tag.contains(key) ? Optional.empty() : tag.getBoolean(key); 191 | } 192 | 193 | public static Optional setBoolean(ItemStack item, String key, boolean value) { 194 | net.minecraft.world.item.ItemStack nmsItem = null; 195 | if(item instanceof CraftItemStack) { 196 | nmsItem = CraftItemStack.unwrap(item); 197 | } 198 | if (nmsItem == null) { 199 | return Optional.empty(); 200 | } 201 | CustomData.update(DataComponents.CUSTOM_DATA, nmsItem, (tag) -> tag.putBoolean(key, value)); 202 | return Optional.of(value); 203 | } 204 | 205 | public static Optional getFloat(ItemStack item, String key) { 206 | net.minecraft.world.item.ItemStack nmsItem = CraftItemStack.unwrap(item); 207 | CompoundTag tag = getTag(nmsItem); 208 | if (tag == null) return Optional.empty(); 209 | return !tag.contains(key) ? Optional.empty() : tag.getFloat(key); 210 | } 211 | 212 | public static Optional setFloat(ItemStack item, String key, float value) { 213 | net.minecraft.world.item.ItemStack nmsItem = null; 214 | if(item instanceof CraftItemStack) { 215 | nmsItem = CraftItemStack.unwrap(item); 216 | } 217 | if (nmsItem == null) { 218 | return Optional.empty(); 219 | } 220 | CustomData.update(DataComponents.CUSTOM_DATA, nmsItem, (tag) -> tag.putFloat(key, value)); 221 | return Optional.of(value); 222 | } 223 | 224 | private static CompoundTag getTag(net.minecraft.world.item.ItemStack itemStack) { 225 | DataComponentMap components = itemStack.getComponents(); 226 | CustomData customData = components.get(DataComponents.CUSTOM_DATA); 227 | if(customData == null) { 228 | return null; 229 | } 230 | return customData.getUnsafe(); 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /src/main/java/cat/nyaa/nyaacore/utils/LocaleUtils.java: -------------------------------------------------------------------------------- 1 | package cat.nyaa.nyaacore.utils; 2 | 3 | import net.md_5.bungee.api.chat.BaseComponent; 4 | import net.md_5.bungee.api.chat.TextComponent; 5 | import net.md_5.bungee.api.chat.TranslatableComponent; 6 | import org.bukkit.Material; 7 | import org.bukkit.NamespacedKey; 8 | import org.bukkit.craftbukkit.inventory.CraftItemStack; 9 | import org.bukkit.enchantments.Enchantment; 10 | import org.bukkit.inventory.ItemStack; 11 | import org.bukkit.inventory.meta.SkullMeta; 12 | 13 | /** 14 | * A wrapper for LangUtils 15 | */ 16 | public final class LocaleUtils { 17 | public static String getUnlocalizedName(Material material) { 18 | if (material == null) throw new IllegalArgumentException(); 19 | return namespaceKeyToTranslationKey(material.isBlock() ? "block" : "item", material.getKey()); 20 | } 21 | 22 | public static String getUnlocalizedName(ItemStack itemStack) { 23 | if (itemStack == null) throw new IllegalArgumentException(); 24 | net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack); 25 | return nmsItemStack.getItem().getDescriptionId(); 26 | } 27 | 28 | public static BaseComponent getNameComponent(ItemStack item) { 29 | if (item == null) throw new IllegalArgumentException(); 30 | if (item.hasItemMeta() && item.getItemMeta().hasDisplayName()) 31 | return new TextComponent(item.getItemMeta().getDisplayName()); 32 | if (item.getItemMeta() instanceof SkullMeta && ((SkullMeta) item.getItemMeta()).hasOwner()) { 33 | String key = getUnlocalizedName(item.getType()) + ".named"; 34 | return new TranslatableComponent(key, ((SkullMeta) item.getItemMeta()).getOwningPlayer().getName()); 35 | } 36 | net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(item); 37 | return new TranslatableComponent(nmsItemStack.getItem().getDescriptionId()); 38 | } 39 | 40 | public static String getUnlocalizedName(Enchantment ench) { 41 | return namespaceKeyToTranslationKey("enchantment", ench.getKey()); 42 | } 43 | 44 | public static BaseComponent getNameComponent(Enchantment ench) { 45 | return new TranslatableComponent(getUnlocalizedName(ench)); 46 | } 47 | 48 | public static String namespaceKeyToTranslationKey(String category, NamespacedKey namespacedKey) { 49 | return category + "." + namespacedKey.getNamespace() + "." + namespacedKey.getKey(); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/cat/nyaa/nyaacore/utils/MathUtils.java: -------------------------------------------------------------------------------- 1 | package cat.nyaa.nyaacore.utils; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | 5 | import java.util.ArrayList; 6 | import java.util.Collections; 7 | import java.util.List; 8 | import java.util.Random; 9 | 10 | public final class MathUtils { 11 | public static final int MILLI_IN_SEC = 1000; 12 | private static final Random rng = new Random(); 13 | 14 | /** 15 | * Generate a random number in [min,max] 16 | */ 17 | public static int uniformRangeInclusive(int minInclusive, int maxInclusive) { 18 | if (maxInclusive < minInclusive) throw new IllegalArgumentException(); 19 | return rng.nextInt(maxInclusive - minInclusive + 1) + minInclusive; 20 | } 21 | 22 | /** 23 | * Select n from candidates. Return all candidates if n > candidates.size() 24 | * Order is not preserved. 25 | */ 26 | public static ImmutableList randomSelect(List candidates, int n) { 27 | if (candidates == null || n < 0) throw new IllegalArgumentException(); 28 | if (n > candidates.size()) n = candidates.size(); 29 | if (n == 0) return ImmutableList.of(); 30 | if (n == candidates.size()) return ImmutableList.copyOf(candidates); 31 | List clone = new ArrayList<>(candidates); 32 | Collections.shuffle(clone, rng); 33 | 34 | ImmutableList.Builder b = ImmutableList.builder(); 35 | for (int i = 0; i < n; i++) b.add(clone.get(i)); 36 | return b.build(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/cat/nyaa/nyaacore/utils/NmsUtils.java: -------------------------------------------------------------------------------- 1 | package cat.nyaa.nyaacore.utils; 2 | 3 | import com.mojang.brigadier.exceptions.CommandSyntaxException; 4 | import net.minecraft.advancements.critereon.NbtPredicate; 5 | import net.minecraft.core.BlockPos; 6 | import net.minecraft.nbt.CompoundTag; 7 | import net.minecraft.nbt.TagParser; 8 | import net.minecraft.world.entity.player.Player; 9 | import net.minecraft.world.level.Level; 10 | import net.minecraft.world.level.block.entity.BlockEntity; 11 | import org.bukkit.World; 12 | import org.bukkit.block.Block; 13 | import org.bukkit.block.BlockState; 14 | import org.bukkit.craftbukkit.CraftWorld; 15 | import org.bukkit.craftbukkit.entity.CraftEntity; 16 | import org.bukkit.craftbukkit.entity.CraftLivingEntity; 17 | import org.bukkit.entity.Entity; 18 | import org.bukkit.entity.LivingEntity; 19 | 20 | import java.util.List; 21 | import java.util.Map; 22 | import java.util.UUID; 23 | import java.util.stream.Collectors; 24 | 25 | /** 26 | * A collection of operations that cannot be done with NMS. 27 | * Downstream plugin authors can add methods here, so that 28 | * their plugins do not need to depend on NMS for just a 29 | * single function. It also makes upgrade a bit easier, 30 | * since all NMS codes are here. 31 | */ 32 | public final class NmsUtils { 33 | /* see CommandEntityData.java */ 34 | public static void setEntityTag(Entity e, String tag) { 35 | net.minecraft.world.entity.Entity nmsEntity = ((CraftEntity) e).getHandle(); 36 | 37 | if (nmsEntity instanceof Player) { 38 | throw new IllegalArgumentException("Player NBT cannot be edited"); 39 | } else { 40 | CompoundTag nbtToBeMerged; 41 | 42 | try { 43 | nbtToBeMerged = TagParser.parseCompoundFully(tag); 44 | } catch (CommandSyntaxException ex) { 45 | throw new IllegalArgumentException("Invalid NBTTag string"); 46 | } 47 | 48 | CompoundTag nmsOrigNBT = NbtPredicate.getEntityTagToCompare(nmsEntity); // entity to nbt 49 | CompoundTag nmsClonedNBT = nmsOrigNBT.copy(); // clone 50 | nmsClonedNBT.merge(nbtToBeMerged); // merge NBT 51 | if (nmsClonedNBT.equals(nmsOrigNBT)) { 52 | } else { 53 | UUID uuid = nmsEntity.getUUID(); // store UUID 54 | nmsEntity.load(nmsClonedNBT); // set nbt 55 | nmsEntity.setUUID(uuid); // set uuid 56 | } 57 | } 58 | } 59 | 60 | public static boolean createExplosion(World world, Entity entity, double x, double y, double z, float power, boolean setFire, boolean breakBlocks) { 61 | return world.createExplosion(x, y, z, power, setFire, breakBlocks, entity); 62 | } 63 | 64 | /** 65 | * fromMobSpawner is removed in 1.15.2 Spigot 66 | * use {Mob.isAware} instead. 67 | */ 68 | @Deprecated 69 | public static boolean isFromMobSpawner(Entity entity) { 70 | return false; 71 | } 72 | 73 | /** 74 | * fromMobSpawner is removed in 1.15.2 Spigot 75 | * use {Mob.isAware} instead. 76 | */ 77 | @Deprecated 78 | public static void setFromMobSpawner(Entity entity, boolean fromMobSpawner) { 79 | // if (entity instanceof CraftEntity) { 80 | // ((CraftEntity) entity).getHandle().fromMobSpawner = fromMobSpawner; 81 | // } 82 | } 83 | 84 | /** 85 | * Update the yaw & pitch of entities. Can be used to set head orientation. 86 | * 87 | * @param entity the living entity 88 | * @param newYaw can be null if not to be modified 89 | * @param newPitch can be null if not to be modified 90 | */ 91 | public static void updateEntityYawPitch(LivingEntity entity, Float newYaw, Float newPitch) { 92 | if (entity == null) throw new IllegalArgumentException(); 93 | if (newYaw == null && newPitch == null) return; 94 | CraftLivingEntity nmsEntity = (CraftLivingEntity) entity; 95 | if (newYaw != null) { 96 | nmsEntity.getHandle().setYRot(newYaw); 97 | } 98 | 99 | if (newPitch != null) { 100 | nmsEntity.getHandle().setXRot(newPitch); 101 | } 102 | } 103 | 104 | /** 105 | * Set "OnGround" flag for an entity 106 | * 107 | * @param e the entity 108 | * @param isOnGround new OnGround value 109 | */ 110 | public static void setEntityOnGround(Entity e, boolean isOnGround) { 111 | if (e == null) throw new IllegalArgumentException(); 112 | CraftEntity nmsEntity = (CraftEntity) e; 113 | nmsEntity.getHandle().setOnGround(isOnGround); //nms method renamed 114 | } 115 | 116 | public static List getTileEntities(World world) { 117 | Map BlockEntityList = ((CraftWorld) world).getHandle().capturedTileEntities; 118 | // Safe to parallelize getPosition and getBlockAt 119 | return BlockEntityList.entrySet().stream().parallel().map(Map.Entry::getKey).map(p -> world.getBlockAt(p.getX(), p.getY(), p.getZ())).collect(Collectors.toList()); 120 | } 121 | 122 | public static List getBlockEntityBlockStates(World world) { 123 | Map BlockEntityList = ((CraftWorld) world).getHandle().capturedTileEntities; 124 | // Safe to parallelize getPosition and getBlockAt 125 | return BlockEntityList.entrySet().stream().parallel().map(Map.Entry::getKey).map(p -> world.getBlockAt(p.getX(), p.getY(), p.getZ())).map(Block::getState).collect(Collectors.toList()); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/main/java/cat/nyaa/nyaacore/utils/OfflinePlayerUtils.java: -------------------------------------------------------------------------------- 1 | package cat.nyaa.nyaacore.utils; 2 | 3 | import cat.nyaa.nyaacore.NyaaCoreLoader; 4 | import com.google.common.collect.BiMap; 5 | import com.google.common.collect.HashBiMap; 6 | import com.google.common.collect.ImmutableBiMap; 7 | import com.google.gson.Gson; 8 | import com.google.gson.reflect.TypeToken; 9 | import org.bukkit.Bukkit; 10 | import org.bukkit.OfflinePlayer; 11 | import org.bukkit.event.EventHandler; 12 | import org.bukkit.event.EventPriority; 13 | import org.bukkit.event.Listener; 14 | import org.bukkit.event.player.PlayerJoinEvent; 15 | import org.bukkit.event.player.PlayerQuitEvent; 16 | 17 | import java.net.URI; 18 | import java.net.http.HttpClient; 19 | import java.net.http.HttpRequest; 20 | import java.net.http.HttpResponse; 21 | import java.util.*; 22 | import java.util.concurrent.CompletableFuture; 23 | import java.util.concurrent.ConcurrentMap; 24 | import java.util.function.BinaryOperator; 25 | import java.util.function.Function; 26 | import java.util.logging.Level; 27 | import java.util.stream.Collectors; 28 | 29 | public class OfflinePlayerUtils { 30 | private static final String UNDASHED = "(\\w{8})(\\w{4})(\\w{4})(\\w{4})(\\w{12})"; 31 | private static final String DASHED = "$1-$2-$3-$4-$5"; 32 | private static final TypeToken>> typeTokenListMap = 33 | new TypeToken>>() { 34 | }; 35 | private static ConcurrentMap playerCache; 36 | private static ConcurrentMap nameCache; 37 | 38 | private OfflinePlayerUtils() { 39 | } 40 | 41 | public static void init() { 42 | playerCache = Arrays.stream(Bukkit.getOfflinePlayers()) 43 | .filter(p -> p.getName() != null) 44 | .collect(Collectors.toConcurrentMap(p -> p.getName().toLowerCase(Locale.ENGLISH), Function.identity(), 45 | BinaryOperator.maxBy(Comparator.comparing(OfflinePlayer::getLastPlayed)))); 46 | nameCache = playerCache.entrySet().stream().collect( 47 | Collectors.toConcurrentMap(e -> e.getValue().getUniqueId(), Map.Entry::getKey)); 48 | } 49 | 50 | public static OfflinePlayer lookupPlayer(String name) { 51 | OfflinePlayer player = Bukkit.getPlayerExact(name); 52 | if (player != null) return player; 53 | return playerCache.get(name.toLowerCase(Locale.ENGLISH)); 54 | } 55 | 56 | public static CompletableFuture> lookupPlayerNamesOnline(String... names) { 57 | List nameList = new LinkedList<>(Arrays.asList(names)); 58 | BiMap ret = HashBiMap.create(); 59 | Iterator iterator = nameList.iterator(); 60 | while (iterator.hasNext()) { 61 | String n = iterator.next(); 62 | OfflinePlayer player = playerCache.get(n.toLowerCase(Locale.ENGLISH)); 63 | if (player != null) { 64 | iterator.remove(); 65 | ret.put(n, player.getUniqueId()); 66 | } 67 | } 68 | 69 | HttpClient client = HttpClient.newHttpClient(); 70 | HttpRequest req = HttpRequest.newBuilder() 71 | .uri(URI.create("https://api.mojang.com/profiles/minecraft")) 72 | .header("Content-Type", "application/json") 73 | .POST(HttpRequest.BodyPublishers.ofString(new Gson().toJson(List.of(names)))) 74 | .build(); 75 | return client.sendAsync(req, HttpResponse.BodyHandlers.ofString()) 76 | .thenApply((response) -> { 77 | NyaaCoreLoader.getInstance().getLogger().log(Level.FINER, 78 | "request name -> uuid api " + response.statusCode()); 79 | if (response.statusCode() > 299 || response.body() == null) { 80 | return HashBiMap.create(); 81 | } 82 | List> result = 83 | new Gson().fromJson(response.body(), typeTokenListMap.getType()); 84 | return result.stream().collect(ImmutableBiMap.toImmutableBiMap(m -> { 85 | m.get("id"); 86 | return UUID.fromString(((String) m.get("id")).replaceAll(UNDASHED, DASHED)); 87 | }, 88 | m -> (String) m.get("name"))); 89 | }).thenApply(u -> { 90 | nameCache.putAll(u); 91 | ret.putAll(u.inverse()); 92 | return ret; 93 | }).exceptionally((e) -> { 94 | NyaaCoreLoader.getInstance().getLogger().log(Level.INFO, "failed to request name -> uuid api", e); 95 | return ret; 96 | }); 97 | } 98 | 99 | public static CompletableFuture lookupPlayerNameByUuidOnline(UUID uuid) { 100 | String s = nameCache.get(uuid); 101 | if (s != null) { 102 | return CompletableFuture.completedFuture(s); 103 | } 104 | HttpClient client = HttpClient.newHttpClient(); 105 | HttpRequest req = HttpRequest.newBuilder() 106 | .uri(URI.create( 107 | "https://api.mojang.com/user/profiles/" 108 | + uuid.toString().toLowerCase().replace("-", "") 109 | + "/names")) 110 | .GET() 111 | .build(); 112 | return client.sendAsync(req, HttpResponse.BodyHandlers.ofString()).thenApply((r) -> { 113 | NyaaCoreLoader.getInstance().getLogger().log(Level.FINER, 114 | "request uuid -> name api " + r.statusCode()); 115 | if (r.statusCode() > 299 || r.body() == null) { 116 | return null; 117 | } 118 | List> nameMapsList = 119 | new Gson().fromJson(r.body(), typeTokenListMap.getType()); 120 | if (nameMapsList.isEmpty()) { 121 | return null; 122 | } 123 | nameCache.put(uuid, nameMapsList.get(nameMapsList.size() - 1).get("name").toString()); 124 | return nameCache.get(uuid); 125 | }).exceptionally((e) -> { 126 | NyaaCoreLoader.getInstance().getLogger().log(Level.INFO, "failed to request uuid -> name api", e); 127 | return null; 128 | }); 129 | } 130 | 131 | public static class _Listener implements Listener { 132 | @EventHandler(priority = EventPriority.MONITOR) 133 | public void onPlayerJoin(PlayerJoinEvent event) { 134 | playerCache.put(event.getPlayer().getName().toLowerCase(Locale.ENGLISH), event.getPlayer()); 135 | } 136 | 137 | @EventHandler(priority = EventPriority.MONITOR) 138 | public void onPlayerJoin(PlayerQuitEvent event) { 139 | playerCache.put(event.getPlayer().getName().toLowerCase(Locale.ENGLISH), event.getPlayer()); 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/main/java/cat/nyaa/nyaacore/utils/RayTraceUtils.java: -------------------------------------------------------------------------------- 1 | package cat.nyaa.nyaacore.utils; 2 | 3 | import net.minecraft.world.phys.Vec3; 4 | import org.bukkit.FluidCollisionMode; 5 | import org.bukkit.GameMode; 6 | import org.bukkit.block.Block; 7 | import org.bukkit.craftbukkit.entity.CraftEntity; 8 | import org.bukkit.entity.Entity; 9 | import org.bukkit.entity.EntityType; 10 | import org.bukkit.entity.LivingEntity; 11 | import org.bukkit.entity.Player; 12 | import org.bukkit.util.RayTraceResult; 13 | import org.bukkit.util.Vector; 14 | 15 | import java.util.ArrayList; 16 | import java.util.List; 17 | import java.util.Optional; 18 | import java.util.function.Predicate; 19 | 20 | public class RayTraceUtils { 21 | public static Block rayTraceBlock(Player player) { 22 | float distance = player.getGameMode() == GameMode.CREATIVE ? 5.0F : 4.5F; 23 | RayTraceResult r = player.getWorld().rayTraceBlocks(player.getEyeLocation(), player.getEyeLocation().getDirection(), distance, FluidCollisionMode.NEVER, false); 24 | if (r != null) { 25 | return r.getHitBlock(); 26 | } 27 | return null; 28 | } 29 | 30 | public static List rayTraceEntities(Player player, float distance) { 31 | return rayTraceEntities(player, distance, not(player).and(canInteract())); 32 | } 33 | 34 | public static List rayTraceEntities(LivingEntity player, float distance, Predicate predicate) { 35 | List result = new ArrayList<>(); 36 | Vector start = player.getEyeLocation().toVector(); 37 | Vector end = start.clone().add(player.getEyeLocation().getDirection().multiply(distance)); 38 | for (Entity e : player.getWorld().getNearbyEntities(player.getEyeLocation(), distance, distance, distance, predicate)) { 39 | if (e instanceof LivingEntity && e instanceof CraftEntity && e.isValid()) { 40 | net.minecraft.world.entity.Entity nmsEntity = ((CraftEntity) e).getHandle(); 41 | Optional hit = nmsEntity.getBoundingBox().clip(toVec3DInternal(start), toVec3DInternal(end)); 42 | if (hit.isPresent()) { 43 | result.add((LivingEntity) e); 44 | } 45 | } 46 | } 47 | return result; 48 | } 49 | 50 | public static Object toVec3D(Vector v) { 51 | return toVec3DInternal(v); 52 | } 53 | 54 | private static Vec3 toVec3DInternal(Vector v) { 55 | return new Vec3(v.getX(), v.getY(), v.getZ()); 56 | } 57 | 58 | public static Predicate isAPlayer() { 59 | return entity -> entity instanceof Player; 60 | } 61 | 62 | public static Predicate not(Entity e) { 63 | return entity -> !entity.getUniqueId().equals(e.getUniqueId()); 64 | } 65 | 66 | public static Predicate canInteract() { 67 | return input -> { 68 | if (input instanceof Player && ((Player) input).getGameMode() == GameMode.SPECTATOR) { 69 | return false; 70 | } 71 | return input instanceof LivingEntity && ((LivingEntity) input).isCollidable(); 72 | }; 73 | } 74 | 75 | public static Entity getTargetEntity(Player p) { 76 | return getTargetEntity(p, getDistanceToBlock(p, p.getGameMode() == GameMode.CREATIVE ? 6.0F : 4.5F)); 77 | } 78 | 79 | public static Entity getTargetEntity(LivingEntity p, float maxDistance, boolean ignoreBlocks) { 80 | if (!ignoreBlocks) { 81 | maxDistance = getDistanceToBlock(p, maxDistance); 82 | } 83 | return getTargetEntity(p, maxDistance); 84 | } 85 | 86 | public static float getDistanceToBlock(LivingEntity entity, float maxDistance) { 87 | RayTraceResult r = entity.getWorld().rayTraceBlocks(entity.getEyeLocation(), entity.getEyeLocation().getDirection(), maxDistance); 88 | if (r != null) { 89 | return (float) entity.getEyeLocation().distance(r.getHitPosition().toLocation(entity.getWorld())); 90 | } 91 | return maxDistance; 92 | } 93 | 94 | public static Entity getTargetEntity(LivingEntity entity, float maxDistance) { 95 | RayTraceResult r = entity.getWorld().rayTraceEntities(entity.getEyeLocation(), entity.getEyeLocation().getDirection(), maxDistance, 96 | e -> e != null && 97 | (e instanceof LivingEntity || e.getType() == EntityType.ITEM_FRAME || e.getType() == EntityType.GLOW_ITEM_FRAME) && 98 | !(e instanceof LivingEntity && !((LivingEntity) e).isCollidable()) && 99 | e.getUniqueId() != entity.getUniqueId() && 100 | !(e instanceof Player && ((Player) e).getGameMode() == GameMode.SPECTATOR)); 101 | if (r != null) { 102 | return r.getHitEntity(); 103 | } 104 | return null; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/main/java/cat/nyaa/nyaacore/utils/ReflectionUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) SainttX 3 | * Copyright (C) contributors 4 | * 5 | * This file is part of Auctions. 6 | * 7 | * Auctions is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * Auctions is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with Auctions. If not, see . 19 | */ 20 | package cat.nyaa.nyaacore.utils; 21 | 22 | import org.bukkit.Bukkit; 23 | 24 | import java.lang.reflect.Field; 25 | import java.lang.reflect.Method; 26 | import java.util.*; 27 | 28 | public final class ReflectionUtils { 29 | 30 | /* 31 | * Cache of NMS classes that we've searched for 32 | */ 33 | private static final Map> loadedNMSClasses = new HashMap<>(); 34 | /* 35 | * Cache of OBS classes that we've searched for 36 | */ 37 | private static final Map> loadedOBCClasses = new HashMap<>(); 38 | /* 39 | * Cache of methods that we've found in particular classes 40 | */ 41 | private static final Map, Map> loadedMethods = new HashMap<>(); 42 | /* 43 | * The server version string to location NMS & OBC classes 44 | */ 45 | private static String versionString; 46 | 47 | /** 48 | * Gets the version string for NMS & OBC class paths 49 | * 50 | * @return The version string of OBC and NMS packages 51 | */ 52 | public static String getVersion() { 53 | if (versionString == null) { 54 | String name = Bukkit.getServer().getClass().getPackage().getName(); 55 | versionString = name.substring(name.lastIndexOf('.') + 1) + "."; 56 | } 57 | 58 | return versionString; 59 | } 60 | 61 | /** 62 | * Get a class from the org.bukkit.craftbukkit package 63 | * 64 | * @param obcClassName the path to the class 65 | * @return the found class at the specified path 66 | */ 67 | public static Class getOBCClass(String obcClassName) { 68 | if (loadedOBCClasses.containsKey(obcClassName)) { 69 | return loadedOBCClasses.get(obcClassName); 70 | } 71 | 72 | String clazzName = "org.bukkit.craftbukkit." + getVersion() + obcClassName; 73 | Class clazz; 74 | 75 | try { 76 | clazz = Class.forName(clazzName); 77 | } catch (Throwable t) { 78 | t.printStackTrace(); 79 | loadedOBCClasses.put(obcClassName, null); 80 | return null; 81 | } 82 | 83 | loadedOBCClasses.put(obcClassName, clazz); 84 | return clazz; 85 | } 86 | 87 | /** 88 | * Get a method from a class that has the specific parameters 89 | * 90 | * @param clazz The class we are searching 91 | * @param methodName The name of the method 92 | * @param params Any parameters that the method has 93 | * @return The method with appropriate parameters 94 | */ 95 | public static Method getMethod(Class clazz, String methodName, Class... params) { 96 | if (!loadedMethods.containsKey(clazz)) { 97 | loadedMethods.put(clazz, new HashMap<>()); 98 | } 99 | 100 | Map methods = loadedMethods.get(clazz); 101 | 102 | if (methods.containsKey(methodName)) { 103 | return methods.get(methodName); 104 | } 105 | 106 | try { 107 | Method method = clazz.getDeclaredMethod(methodName, params); 108 | if (method != null) method.setAccessible(true); 109 | methods.put(methodName, method); 110 | loadedMethods.put(clazz, methods); 111 | return method; 112 | } catch (Exception e) { 113 | e.printStackTrace(); 114 | methods.put(methodName, null); 115 | loadedMethods.put(clazz, methods); 116 | return null; 117 | } 118 | } 119 | 120 | 121 | /** 122 | * get all declared fields in a class and its' super class. 123 | * 124 | * @param clz target class 125 | * @return a List of Field objects declared by clz. 126 | * @since 7.2 127 | */ 128 | public static List getAllFields(Class clz) { 129 | List fields = new ArrayList<>(); 130 | return getAllFields(clz, fields); 131 | } 132 | 133 | private static List getAllFields(Class clz, List list) { 134 | Collections.addAll(list, clz.getDeclaredFields()); 135 | 136 | Class supClz = clz.getSuperclass(); 137 | if (supClz == null) { 138 | return list; 139 | } else { 140 | return getAllFields(supClz, list); 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/main/java/cat/nyaa/nyaacore/utils/TeleportUtils.java: -------------------------------------------------------------------------------- 1 | package cat.nyaa.nyaacore.utils; 2 | 3 | import com.earth2me.essentials.Essentials; 4 | import org.bukkit.Bukkit; 5 | import org.bukkit.Location; 6 | import org.bukkit.entity.Player; 7 | import org.bukkit.event.player.PlayerTeleportEvent; 8 | 9 | import java.util.List; 10 | import java.util.concurrent.CompletableFuture; 11 | 12 | public final class TeleportUtils { 13 | @Deprecated 14 | public static boolean Teleport(Player player, Location loc) { 15 | if (!player.isOnline() || loc == null || loc.getWorld() == null) { 16 | return false; 17 | } 18 | Essentials ess = (Essentials) Bukkit.getServer().getPluginManager().getPlugin("Essentials"); 19 | if (ess != null) { 20 | try { 21 | ess.getUser(player).getAsyncTeleport().now(loc, false, PlayerTeleportEvent.TeleportCause.PLUGIN, CompletableFuture.completedFuture(true)); 22 | return true; 23 | } catch (Exception e) { 24 | return false; 25 | } 26 | } else { 27 | player.setFallDistance(0); 28 | player.teleportAsync(loc, PlayerTeleportEvent.TeleportCause.PLUGIN); 29 | return true; 30 | } 31 | } 32 | 33 | @Deprecated 34 | public static void Teleport(List players, Location loc) { 35 | for (Player p : players) { 36 | Teleport(p, loc); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/cat/nyaa/nyaacore/utils/TridentUtils.java: -------------------------------------------------------------------------------- 1 | package cat.nyaa.nyaacore.utils; 2 | 3 | import net.minecraft.world.entity.projectile.ThrownTrident; 4 | import org.bukkit.craftbukkit.entity.CraftEntity; 5 | import org.bukkit.entity.Trident; 6 | import org.bukkit.inventory.ItemStack; 7 | 8 | import java.lang.reflect.Field; 9 | 10 | public final class TridentUtils { 11 | @Deprecated 12 | public static ItemStack getTridentItemStack(Trident entity) { 13 | return entity.getItem(); 14 | } 15 | 16 | @Deprecated 17 | public static void setTridentItemStack(Trident entity, ItemStack itemStack) { 18 | entity.setItem(itemStack); 19 | } 20 | 21 | public static boolean getTridentDealtDamage(Trident entity) { 22 | ThrownTrident thrownTrident = (ThrownTrident) ((CraftEntity) entity).getHandle(); 23 | return (boolean) thrownTrident.dealtDamage; 24 | } 25 | 26 | public static void setTridentDealtDamage(Trident entity, boolean dealtDamage) { 27 | ThrownTrident thrownTrident = (ThrownTrident) ((CraftEntity) entity).getHandle(); 28 | thrownTrident.dealtDamage = dealtDamage; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/cat/nyaa/nyaacore/utils/VersionUtils.java: -------------------------------------------------------------------------------- 1 | package cat.nyaa.nyaacore.utils; 2 | 3 | import org.bukkit.Bukkit; 4 | 5 | import java.util.*; 6 | 7 | public class VersionUtils { 8 | protected static int MAX_CACHE_SIZE = 10; 9 | private static final LinkedHashMap VersionCache = new LinkedHashMap<>((int) Math.ceil(MAX_CACHE_SIZE / 0.75) + 1, 0.75f, true) { 10 | @Override 11 | protected boolean removeEldestEntry(Map.Entry eldest) { 12 | return size() > MAX_CACHE_SIZE; 13 | } 14 | }; 15 | 16 | public static String getCurrentVersion() { 17 | return Bukkit.getBukkitVersion(); 18 | } 19 | 20 | public static boolean isVersionEqual(String versionStr, String otherStr) { 21 | return Arrays.equals(splitVersionStringToIntArray(versionStr), splitVersionStringToIntArray(otherStr)); 22 | } 23 | 24 | public static boolean isVersionGreaterOrEq(String versionStr, String otherStr) { 25 | int[] versionInt = splitVersionStringToIntArray(versionStr); 26 | int[] otherInt = splitVersionStringToIntArray(otherStr); 27 | if (Arrays.equals(versionInt, otherInt)) return true; // = 28 | for (int i = 0; i < otherInt.length; i++) { 29 | if (versionInt.length <= i) { 30 | return false;//< 31 | } 32 | if (versionInt[i] < otherInt[i]) return false;//> 33 | } 34 | return versionInt.length >= otherInt.length; 35 | } 36 | 37 | public synchronized static int[] splitVersionStringToIntArray(String version) { 38 | int[] result; 39 | if ((result = VersionCache.get(version)) != null) return result; 40 | result = splitVersionStringToIntegerList(version).stream().mapToInt(i -> i).toArray(); 41 | VersionCache.put(version, result); 42 | return result; 43 | } 44 | 45 | private static List splitVersionStringToIntegerList(String version) { 46 | String[] splitVersion = splitVersionString(version); 47 | List result = new ArrayList<>(); 48 | for (String s : splitVersion) { 49 | int versionint; 50 | try { 51 | versionint = Integer.parseInt(s.replaceAll("[^\\d]", "")); 52 | } catch (NumberFormatException ignored) { 53 | versionint = 0; 54 | } 55 | result.add(versionint); 56 | } 57 | while (result.lastIndexOf(0) == (result.size() - 1)) { 58 | result.remove(result.size() - 1); 59 | } 60 | return result; 61 | } 62 | 63 | public static String[] splitVersionString(String version) { 64 | version = version.replace('-', '.'); 65 | version = version.replace('_', '.'); 66 | version = version.replace('R', '.'); 67 | String[] splitVersion = version.split("\\."); 68 | List result = new ArrayList<>(); 69 | for (String s : splitVersion) { 70 | if (!s.equals("")) result.add(s); 71 | } 72 | return result.toArray(new String[0]); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main/resources/lang/en_US.yml: -------------------------------------------------------------------------------- 1 | internal: 2 | info: 3 | using_language: "Now using language: %s" 4 | command_complete: "Command Executed!" 5 | usage_prompt: "Usage: %s" 6 | error: 7 | bad_subcommand: "Bad subcommand handler: %s" 8 | not_player: "Only players can do this" 9 | no_item_hand: "No item in main hand" 10 | no_item_offhand: "No item in off hand" 11 | command_exception: "Internal server error. Unable to run command." 12 | no_required_permission: "You do not have the required permission: %s" 13 | invalid_command_arg: "Invalid command line arguments." 14 | 15 | bad_int: "Not a valid integer: %s" 16 | bad_double: "Not a valid number: %s" 17 | bad_enum: "Not valid for enum type: %s. Values: %s" 18 | bad_decimal_pattern: "Not a valid pattern: %s" 19 | 20 | no_more_int: "No more integers in argument" 21 | no_more_double: "No more numbers in argument" 22 | no_more_enum: "No more enum values in argument" 23 | no_more_bool: "No more booleans in argument" 24 | no_more_string: "No more strings in argument" 25 | no_more_player: "No more player name in argument" 26 | no_more_entity: "No more entity id in argument" 27 | 28 | assert_fail: "Command assertion fail, Expect: %s, Actual: %s" 29 | player_not_found: "Cannot find player \"%s\"" 30 | entity_not_found: "Cannot find entity \"%s\"" 31 | named_argument: 32 | missing_arg: "Missing argument: %s" 33 | not_int: "Argument \"%s\" is not an integer: %s" 34 | not_double: "Argument \"%s\" is not a double: %s" 35 | 36 | manual: 37 | no_description: "No description" 38 | no_usage: "No usage" 39 | -------------------------------------------------------------------------------- /src/main/resources/plugin.yml: -------------------------------------------------------------------------------- 1 | name: NyaaCore 2 | main: cat.nyaa.nyaacore.NyaaCoreLoader 3 | description: "Code infrastructure for all NyaaCat plugins." 4 | version: ${version} 5 | softdepend: [ Essentials ] 6 | load: STARTUP 7 | loadbefore: 8 | - aolib 9 | authors: [ RecursiveG,Librazy,cyilin ] 10 | website: "https://github.com/NyaaCat/NyaaCore" 11 | api-version: ${api_version} 12 | -------------------------------------------------------------------------------- /src/test/java/cat/nyaa/nyaacore/ArgumentsTest.java: -------------------------------------------------------------------------------- 1 | package cat.nyaa.nyaacore; 2 | 3 | import cat.nyaa.nyaacore.cmdreceiver.Arguments; 4 | import cat.nyaa.nyaacore.cmdreceiver.BadCommandException; 5 | import org.junit.jupiter.api.Test; 6 | 7 | import static org.junit.jupiter.api.Assertions.*; 8 | 9 | public class ArgumentsTest { 10 | @Test 11 | public void test1() throws Exception { 12 | String cmd = "`foo bar` far `bar \\`foo`"; 13 | Arguments arg = Arguments.parse(cmd.split(" ")); 14 | assertNotNull(arg); 15 | assertEquals("foo bar", arg.nextString()); 16 | assertEquals("far", arg.nextString()); 17 | assertEquals("bar `foo", arg.nextString()); 18 | } 19 | 20 | @Test 21 | public void test2() throws Exception { 22 | String cmd = "key:33 a key:66 b key:13 `key2:a b c` `key 3:d e f`"; 23 | Arguments arg = Arguments.parse(cmd.split(" "), null); 24 | assertNotNull(arg); 25 | assertEquals("key:33", arg.nextString()); 26 | assertEquals(66, arg.argInt("key")); 27 | assertEquals("a b c", arg.argString("key2")); 28 | assertEquals("d e f", arg.argString("key 3")); 29 | 30 | assertEquals("a", arg.nextString()); 31 | assertEquals("b", arg.nextString()); 32 | assertEquals("key:13", arg.nextString()); 33 | assertNull(arg.next()); 34 | } 35 | 36 | @Test 37 | public void test3() throws Exception { 38 | String cmd = "t w key:`3` key2:`/co l u:miu_bug` ke3y:`12`"; 39 | Arguments arg = Arguments.parse(cmd.split(" ")); 40 | assertNotNull(arg); 41 | assertEquals(3, arg.argInt("key")); 42 | assertEquals("/co l u:miu_bug", arg.argString("key2")); 43 | assertEquals("t", arg.next()); 44 | assertEquals("12", arg.argString("ke3y")); 45 | assertEquals("w", arg.nextString()); 46 | try { 47 | arg.nextString(); 48 | } catch (BadCommandException e) {//seems that no expectThrows available here 49 | assertEquals("internal.error.no_more_string", e.getMessage());//magic string, but no impact... 50 | } 51 | } 52 | 53 | @Test 54 | public void test4() throws Exception { 55 | String cmd = "key :`3`"; 56 | Arguments arg = Arguments.parse(cmd.split(" "), null); 57 | assertNull(arg); 58 | } 59 | 60 | @Test 61 | public void test5() throws Exception { 62 | String cmd = "key: `3`"; 63 | Arguments arg = Arguments.parse(cmd.split(" "), null); 64 | assertNotNull(arg); 65 | assertEquals("key:", arg.top()); 66 | assertEquals("", arg.argString("key")); 67 | assertEquals("3", arg.nextString()); 68 | assertNull(arg.next()); 69 | } 70 | 71 | @Test 72 | public void test6() throws Exception { 73 | String cmd = "\"a\"b"; 74 | Arguments arg = Arguments.parse(cmd.split(" "), null); 75 | assertNull(arg); 76 | 77 | cmd = "`a`b"; 78 | arg = Arguments.parse(cmd.split(" "), null); 79 | assertNull(arg); 80 | 81 | cmd = "\""; 82 | arg = Arguments.parse(cmd.split(" "), null); 83 | assertNull(arg); 84 | 85 | cmd = "`"; 86 | arg = Arguments.parse(cmd.split(" "), null); 87 | assertNull(arg); 88 | 89 | cmd = "\"\\"; 90 | arg = Arguments.parse(cmd.split(" "), null); 91 | assertNull(arg); 92 | 93 | cmd = "`\\"; 94 | arg = Arguments.parse(cmd.split(" "), null); 95 | assertNull(arg); 96 | 97 | cmd = "a\""; 98 | arg = Arguments.parse(cmd.split(" "), null); 99 | assertNull(arg); 100 | 101 | cmd = "a`"; 102 | arg = Arguments.parse(cmd.split(" "), null); 103 | assertNull(arg); 104 | 105 | cmd = "a\\"; 106 | arg = Arguments.parse(cmd.split(" "), null); 107 | assertNull(arg); 108 | 109 | cmd = "`\\\"`"; 110 | arg = Arguments.parse(cmd.split(" "), null); 111 | assertNull(arg); 112 | 113 | cmd = "\"\\`\""; 114 | arg = Arguments.parse(cmd.split(" "), null); 115 | assertNull(arg); 116 | } 117 | 118 | @Test 119 | public void test7() throws Exception { 120 | String cmd; 121 | Arguments arg; 122 | 123 | cmd = "key1:\"foo\\\"\" \"ab cd\" key2:"; 124 | arg = Arguments.parse(cmd.split(" "), null); 125 | assertNotNull(arg); 126 | assertEquals("foo\"", arg.argString("key1")); 127 | assertEquals("ab cd", arg.nextString()); 128 | assertEquals("", arg.argString("key2")); 129 | assertNull(arg.next()); 130 | 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/test/java/cat/nyaa/nyaacore/LanguageRepositoryTest.java: -------------------------------------------------------------------------------- 1 | package cat.nyaa.nyaacore; 2 | 3 | import be.seeseemelk.mockbukkit.MockBukkit; 4 | import be.seeseemelk.mockbukkit.ServerMock; 5 | import org.bukkit.plugin.Plugin; 6 | import org.junit.jupiter.api.AfterAll; 7 | import org.junit.jupiter.api.Assertions; 8 | import org.junit.jupiter.api.BeforeAll; 9 | import org.junit.jupiter.api.Test; 10 | import org.junit.jupiter.api.io.TempDir; 11 | import org.mockito.Mockito; 12 | 13 | import java.io.*; 14 | 15 | public class LanguageRepositoryTest { 16 | 17 | @TempDir 18 | public static File another_plugin_data_dir; 19 | private static ServerMock server; 20 | private static NyaaCoreLoader nyaacore; 21 | private static Plugin plugin; 22 | 23 | @BeforeAll 24 | public static void setUpMockServer() throws IOException { 25 | server = MockBukkit.mock(); 26 | nyaacore = MockBukkit.load(NyaaCoreLoader.class, true); 27 | plugin = Mockito.mock(Plugin.class); 28 | Mockito.when(plugin.getDataFolder()).thenReturn(another_plugin_data_dir); 29 | 30 | // Create file in NyaaCore's data folder 31 | FileWriter fw = new FileWriter(new File(nyaacore.getDataFolder(), "en_US.yml")); 32 | fw.write("internal:\n info:\n usage_prompt: usage_prompt_overwritten"); 33 | fw.close(); 34 | 35 | // Create file in plugin's data folder 36 | fw = new FileWriter(new File(plugin.getDataFolder(), "en_US.yml")); 37 | fw.write("key2: val2_overwritten"); 38 | fw.close(); 39 | } 40 | 41 | @AfterAll 42 | public static void tearDownMockServer() { 43 | MockBukkit.unmock(); 44 | } 45 | 46 | @Test 47 | public void test() { 48 | LanguageRepository repo = new LanguageRepository() { 49 | @Override 50 | protected Plugin getPlugin() { 51 | return plugin; 52 | } 53 | 54 | @Override 55 | protected String getLanguage() { 56 | return "en_US"; 57 | } 58 | }; 59 | 60 | String s = "key: val\n"; 61 | s += "key2: val2\n"; 62 | s += "internal:\n"; 63 | s += " info:\n"; 64 | s += " using_language: using_lang_overwritten"; 65 | InputStream bundled_lang_file = new ByteArrayInputStream(s.getBytes()); 66 | Mockito.when(plugin.getResource(Mockito.eq("lang/en_US.yml"))).thenReturn(bundled_lang_file); 67 | repo.load(); 68 | 69 | Assertions.assertEquals("Command Executed!", repo.getFormatted("internal.info.command_complete")); 70 | Assertions.assertEquals("usage_prompt_overwritten", repo.getFormatted("internal.info.usage_prompt")); 71 | Assertions.assertEquals("using_lang_overwritten", repo.getFormatted("internal.info.using_language")); 72 | Assertions.assertEquals("val", repo.getFormatted("key")); 73 | Assertions.assertEquals("val2_overwritten", repo.getFormatted("key2")); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/test/java/cat/nyaa/nyaacore/MessageTest.java: -------------------------------------------------------------------------------- 1 | package cat.nyaa.nyaacore; 2 | 3 | import be.seeseemelk.mockbukkit.MockBukkit; 4 | import be.seeseemelk.mockbukkit.ServerMock; 5 | import be.seeseemelk.mockbukkit.entity.PlayerMock; 6 | import org.junit.jupiter.api.AfterEach; 7 | import org.junit.jupiter.api.BeforeEach; 8 | import org.junit.jupiter.api.Test; 9 | 10 | public class MessageTest { 11 | 12 | public static final char COLOR_CHAR = '\u00A7'; 13 | private ServerMock server; 14 | private NyaaCoreLoader plugin; 15 | 16 | @BeforeEach 17 | public void setUp() { 18 | server = MockBukkit.mock(); 19 | plugin = MockBukkit.load(NyaaCoreLoader.class, true); 20 | } 21 | 22 | @AfterEach 23 | public void tearDown() { 24 | MockBukkit.unmock(); 25 | } 26 | 27 | @Test 28 | public void chatText() { 29 | PlayerMock pm = server.addPlayer(); 30 | new Message("foobar").send(pm); 31 | pm.assertSaid("foobar"); 32 | //pm.assertSaid("foobar"); 33 | pm.assertNoMoreSaid(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/test/java/cat/nyaa/nyaacore/TableCreationTest.java: -------------------------------------------------------------------------------- 1 | package cat.nyaa.nyaacore; 2 | 3 | import cat.nyaa.nyaacore.orm.annotations.Column; 4 | import cat.nyaa.nyaacore.orm.annotations.Table; 5 | import cat.nyaa.nyaacore.orm.backends.SQLiteDatabase; 6 | import org.junit.jupiter.api.Test; 7 | 8 | import java.lang.reflect.InvocationTargetException; 9 | import java.lang.reflect.Method; 10 | 11 | public class TableCreationTest { 12 | @Test 13 | public void autoIncrementTest() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { 14 | Method getTableCreationSql1 = SQLiteDatabase.class.getDeclaredMethod("getTableCreationSql", Class.class); 15 | getTableCreationSql1.setAccessible(true); 16 | Object invoke = getTableCreationSql1.invoke(null, TestTable.class); 17 | // assertEquals(invoke, "CREATE TABLE IF NOT EXISTS testTable(id BIGINT PRIMARY KEY NOT NULL AUTO_INCREMENT name LONGTEXT UNIQUE age INT)"); 18 | System.out.println(invoke); 19 | } 20 | 21 | @Table("testTable") 22 | static class TestTable { 23 | @Column(name = "id", primary = true, autoIncrement = true) 24 | Long autoIncrement; 25 | @Column(name = "name", unique = true) 26 | String unique; 27 | @Column(name = "age") 28 | int age; 29 | 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/cat/nyaa/nyaacore/cmdreceiver/dispatchtest/CmdRoot.java: -------------------------------------------------------------------------------- 1 | package cat.nyaa.nyaacore.cmdreceiver.dispatchtest; 2 | 3 | import cat.nyaa.nyaacore.cmdreceiver.Arguments; 4 | import cat.nyaa.nyaacore.cmdreceiver.CommandReceiver; 5 | import cat.nyaa.nyaacore.cmdreceiver.SubCommand; 6 | import org.bukkit.command.CommandSender; 7 | import org.bukkit.plugin.Plugin; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | public class CmdRoot extends CommandReceiver { 13 | @SubCommand("sub1") 14 | public CmdSub1 sub1; 15 | @SubCommand("sub2") 16 | public CmdSub2 sub2; 17 | 18 | public CmdRoot(Plugin plugin) { 19 | super(plugin, null); 20 | } 21 | 22 | @Override 23 | public String getHelpPrefix() { 24 | return ""; 25 | } 26 | 27 | // call with: nct sub3 {...} 28 | @SubCommand(value = "sub3", tabCompleter = "sub3tc") 29 | public void sub3Cmd(CommandSender sender, Arguments args) { 30 | DispatchTest.callback.onCommand("nct-sub3", sender, args); 31 | } 32 | 33 | public List sub3tc(CommandSender sender, Arguments args) { 34 | DispatchTest.callback.onTab("nct-sub3tc", sender, args); 35 | String s = args.next(); 36 | while (args.top() != null) s = args.next(); 37 | List ret = new ArrayList<>(); 38 | ret.add(s + "_s1"); 39 | return ret; 40 | } 41 | 42 | // call with: nct {[anything except sub123] ...} 43 | @SubCommand(isDefaultCommand = true, tabCompleter = "deftc") 44 | public void defCmd(CommandSender sender, Arguments args) { 45 | DispatchTest.callback.onCommand("nct-", sender, args); 46 | } 47 | 48 | public List deftc(CommandSender sender, Arguments args) { 49 | DispatchTest.callback.onTab("nct-deftc", sender, args); 50 | String s = args.next(); 51 | while (args.top() != null) s = args.next(); 52 | List ret = new ArrayList<>(); 53 | ret.add(s + "_s1"); 54 | return ret; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/test/java/cat/nyaa/nyaacore/cmdreceiver/dispatchtest/CmdSub1.java: -------------------------------------------------------------------------------- 1 | package cat.nyaa.nyaacore.cmdreceiver.dispatchtest; 2 | 3 | import cat.nyaa.nyaacore.ILocalizer; 4 | import cat.nyaa.nyaacore.cmdreceiver.Arguments; 5 | import cat.nyaa.nyaacore.cmdreceiver.CommandReceiver; 6 | import cat.nyaa.nyaacore.cmdreceiver.SubCommand; 7 | import org.bukkit.command.CommandSender; 8 | import org.bukkit.plugin.Plugin; 9 | 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | public class CmdSub1 extends CommandReceiver { 14 | public CmdSub1(Plugin plugin, ILocalizer i18n) { 15 | super(plugin, i18n); 16 | } 17 | 18 | @Override 19 | public String getHelpPrefix() { 20 | return "sub1"; 21 | } 22 | 23 | // call with: nct sub1 a {...} 24 | @SubCommand("a") 25 | public void sub3Cmd(CommandSender sender, Arguments args) { 26 | DispatchTest.callback.onCommand("nct-sub1-a", sender, args); 27 | } 28 | 29 | // call with: nct sub1 {[anything except "a"] ...} 30 | @SubCommand(isDefaultCommand = true, tabCompleter = "deftc") 31 | public void sub1Def(CommandSender sender, Arguments args) { 32 | DispatchTest.callback.onCommand("nct-sub1-", sender, args); 33 | } 34 | 35 | public List deftc(CommandSender sender, Arguments args) { 36 | DispatchTest.callback.onTab("nct-sub1tc", sender, args); 37 | String s = args.next(); 38 | while (args.top() != null) s = args.next(); 39 | List ret = new ArrayList<>(); 40 | ret.add(s + "_s1"); 41 | return ret; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/test/java/cat/nyaa/nyaacore/cmdreceiver/dispatchtest/CmdSub2.java: -------------------------------------------------------------------------------- 1 | package cat.nyaa.nyaacore.cmdreceiver.dispatchtest; 2 | 3 | import cat.nyaa.nyaacore.ILocalizer; 4 | import cat.nyaa.nyaacore.cmdreceiver.Arguments; 5 | import cat.nyaa.nyaacore.cmdreceiver.CommandReceiver; 6 | import cat.nyaa.nyaacore.cmdreceiver.SubCommand; 7 | import org.bukkit.command.CommandSender; 8 | import org.bukkit.plugin.Plugin; 9 | 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | public class CmdSub2 extends CommandReceiver { 14 | @SubCommand(isDefaultCommand = true) 15 | public CmdSub2A sub2a; 16 | 17 | public CmdSub2(Plugin plugin, ILocalizer i18n) { 18 | super(plugin, i18n); 19 | } 20 | 21 | @Override 22 | public String getHelpPrefix() { 23 | return "sub2"; 24 | } 25 | 26 | // call with: nct sub2 b {...} 27 | @SubCommand(value = "b", tabCompleter = "btc") 28 | public void sub3Cmd(CommandSender sender, Arguments args) { 29 | DispatchTest.callback.onCommand("nct-sub2-b", sender, args); 30 | } 31 | 32 | public List btc(CommandSender sender, Arguments args) { 33 | DispatchTest.callback.onTab("nct-sub2-btc", sender, args); 34 | String s = args.next(); 35 | while (args.top() != null) s = args.next(); 36 | List ret = new ArrayList<>(); 37 | ret.add(s + "_s1"); 38 | return ret; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/test/java/cat/nyaa/nyaacore/cmdreceiver/dispatchtest/CmdSub2A.java: -------------------------------------------------------------------------------- 1 | package cat.nyaa.nyaacore.cmdreceiver.dispatchtest; 2 | 3 | import cat.nyaa.nyaacore.ILocalizer; 4 | import cat.nyaa.nyaacore.cmdreceiver.Arguments; 5 | import cat.nyaa.nyaacore.cmdreceiver.CommandReceiver; 6 | import cat.nyaa.nyaacore.cmdreceiver.SubCommand; 7 | import org.bukkit.command.CommandSender; 8 | import org.bukkit.plugin.Plugin; 9 | import org.junit.jupiter.api.Assertions; 10 | 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | public class CmdSub2A extends CommandReceiver { 15 | public CmdSub2A(Plugin plugin, ILocalizer i18n) { 16 | super(plugin, i18n); 17 | } 18 | 19 | @Override 20 | public String getHelpPrefix() { 21 | return "sub2.def"; 22 | } 23 | 24 | // call with: nct sub2 {[anything except "b" and "c"] ...} 25 | @SubCommand(isDefaultCommand = true) 26 | public void sub2ADef(CommandSender sender, Arguments args) { 27 | DispatchTest.callback.onCommand("nct-sub2-a-", sender, args); 28 | } 29 | 30 | // call with: nct sub2 c {...} 31 | @SubCommand(value = "c", tabCompleter = "ctc") 32 | public void sub2AC(CommandSender sender, Arguments args) { 33 | DispatchTest.callback.onCommand("nct-sub2-a-c", sender, args); 34 | } 35 | 36 | // no way to call this sub command 37 | @SubCommand(value = "b") 38 | public void sub2AB(CommandSender sender, Arguments args) { 39 | Assertions.fail("should not be able to be called"); 40 | } 41 | 42 | public List ctc(CommandSender sender, Arguments args) { 43 | DispatchTest.callback.onTab("nct-sub2a-ctc", sender, args); 44 | String s = args.next(); 45 | while (args.top() != null) s = args.next(); 46 | List ret = new ArrayList<>(); 47 | ret.add(s + "_s1"); 48 | return ret; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/test/java/cat/nyaa/nyaacore/cmdreceiver/dispatchtest/DispatchTest.java: -------------------------------------------------------------------------------- 1 | package cat.nyaa.nyaacore.cmdreceiver.dispatchtest; 2 | 3 | import be.seeseemelk.mockbukkit.MockBukkit; 4 | import be.seeseemelk.mockbukkit.ServerMock; 5 | import cat.nyaa.nyaacore.NyaaCoreLoader; 6 | import cat.nyaa.nyaacore.cmdreceiver.Arguments; 7 | import org.bukkit.command.CommandSender; 8 | import org.bukkit.command.PluginCommand; 9 | import org.bukkit.command.PluginCommandUtils; 10 | import org.junit.jupiter.api.AfterAll; 11 | import org.junit.jupiter.api.BeforeAll; 12 | import org.junit.jupiter.api.BeforeEach; 13 | import org.junit.jupiter.api.Test; 14 | import org.junit.jupiter.api.extension.ExtendWith; 15 | import org.mockito.ArgumentCaptor; 16 | import org.mockito.Mockito; 17 | import org.mockito.junit.jupiter.MockitoExtension; 18 | 19 | import java.util.ArrayList; 20 | import java.util.Arrays; 21 | import java.util.Collections; 22 | import java.util.List; 23 | 24 | import static org.junit.jupiter.api.Assertions.*; 25 | import static org.mockito.ArgumentMatchers.eq; 26 | import static org.mockito.ArgumentMatchers.same; 27 | import static org.mockito.Mockito.verify; 28 | 29 | @ExtendWith(MockitoExtension.class) 30 | public class DispatchTest { 31 | public static ICallback callback; 32 | private static ServerMock server; 33 | private static NyaaCoreLoader plugin; 34 | private static CmdRoot cmdRoot; 35 | private static PluginCommand cmd; 36 | 37 | @BeforeAll 38 | public static void setUp() { 39 | server = MockBukkit.mock(); 40 | plugin = MockBukkit.load(NyaaCoreLoader.class, true); 41 | cmdRoot = new CmdRoot(plugin); 42 | 43 | cmd = PluginCommandUtils.createPluginCommand("nct", plugin); 44 | cmd.setExecutor(cmdRoot); 45 | server.getCommandMap().register("nyaacoretest", cmd); 46 | } 47 | 48 | @AfterAll 49 | public static void tearDown() { 50 | MockBukkit.unmock(); 51 | } 52 | 53 | public static boolean equalsIgnoreOrder(List a, List b) { 54 | if (a.size() != b.size()) return false; 55 | List a2 = new ArrayList<>(a); 56 | List b2 = new ArrayList<>(b); 57 | Collections.sort(a2); 58 | Collections.sort(b2); 59 | return a2.equals(b2); 60 | } 61 | 62 | @BeforeEach 63 | public void setUpCallbackStub() { 64 | callback = Mockito.mock(ICallback.class); 65 | } 66 | 67 | private Arguments invoke(String command, String mark) { 68 | CommandSender sender = server.getConsoleSender(); 69 | server.dispatchCommand(sender, command); 70 | ArgumentCaptor captor = ArgumentCaptor.forClass(Arguments.class); 71 | verify(callback).onCommand(eq(mark), same(sender), captor.capture()); 72 | return captor.getValue(); 73 | } 74 | 75 | private List tabCompletion(String command, String mark) { 76 | String[] split = command.split(" ", -1); 77 | String alias = split[0]; 78 | String[] args = Arrays.copyOfRange(split, 1, split.length); 79 | 80 | CommandSender sender = server.getConsoleSender(); 81 | List result = cmdRoot.onTabComplete(sender, cmd, alias, args); 82 | 83 | ArgumentCaptor captor = ArgumentCaptor.forClass(Arguments.class); 84 | verify(callback).onTab(eq(mark), same(sender), captor.capture()); 85 | assertNull(captor.getValue().next()); 86 | return result; 87 | } 88 | 89 | @Test 90 | public void testRootDef() { 91 | Arguments args = invoke("nct", "nct-"); 92 | assertNull(args.next()); 93 | } 94 | 95 | @Test 96 | public void testRootDefExtraArgs() { 97 | Arguments args = invoke("nct abcd", "nct-"); 98 | assertEquals("abcd", args.next()); 99 | assertNull(args.next()); 100 | } 101 | 102 | @Test 103 | public void testRootSub3() { 104 | Arguments args = invoke("nct sub3", "nct-sub3"); 105 | assertNull(args.next()); 106 | } 107 | 108 | @Test 109 | public void testRootSub1A() { 110 | Arguments args = invoke("nct sub1 a", "nct-sub1-a"); 111 | assertNull(args.next()); 112 | } 113 | 114 | @Test 115 | public void testRootSub1Def() { 116 | Arguments args = invoke("nct sub1", "nct-sub1-"); 117 | assertNull(args.next()); 118 | } 119 | 120 | @Test 121 | public void testRootSub1DefExtraArgs() { 122 | Arguments args = invoke("nct sub1 abcd", "nct-sub1-"); 123 | assertEquals("abcd", args.next()); 124 | assertNull(args.next()); 125 | } 126 | 127 | @Test 128 | public void testRootSub2B() { 129 | Arguments args = invoke("nct sub2 b", "nct-sub2-b"); 130 | assertNull(args.next()); 131 | } 132 | 133 | @Test 134 | public void testRootSub2ADef() { 135 | Arguments args = invoke("nct sub2", "nct-sub2-a-"); 136 | assertNull(args.next()); 137 | } 138 | 139 | @Test 140 | public void testRootSub2ADefExtraArgs() { 141 | Arguments args = invoke("nct sub2 abcd", "nct-sub2-a-"); 142 | assertEquals("abcd", args.next()); 143 | assertNull(args.next()); 144 | } 145 | 146 | @Test 147 | public void testRootSub2AC() { 148 | Arguments args = invoke("nct sub2 c", "nct-sub2-a-c"); 149 | assertNull(args.next()); 150 | } 151 | 152 | @Test 153 | public void testTabSub3() { 154 | List result = tabCompletion("nct sub3 a", "nct-sub3tc"); 155 | assertTrue(equalsIgnoreOrder(result, List.of("a_s1"))); 156 | } 157 | 158 | @Test 159 | public void testTabDef() { 160 | List result = tabCompletion("nct sub", "nct-deftc"); 161 | assertTrue(equalsIgnoreOrder(result, List.of("sub1", "sub2", "sub3", "sub_s1"))); 162 | } 163 | 164 | @Test 165 | public void testTabSub1() { 166 | List result = tabCompletion("nct sub1 ", "nct-sub1tc"); 167 | assertTrue(equalsIgnoreOrder(result, List.of("_s1", "a", "help"))); 168 | } 169 | 170 | @Test 171 | public void testTabSub2B() { 172 | List result = tabCompletion("nct sub2 b abcd", "nct-sub2-btc"); 173 | assertTrue(equalsIgnoreOrder(result, List.of("abcd_s1"))); 174 | } 175 | 176 | @Test 177 | public void testTabSub2AC() { 178 | List result = tabCompletion("nct sub2 c abcd", "nct-sub2a-ctc"); 179 | assertTrue(equalsIgnoreOrder(result, List.of("abcd_s1"))); 180 | } 181 | 182 | public interface ICallback { 183 | void onCommand(String mark, CommandSender sender, Arguments args); 184 | 185 | void onTab(String mark, CommandSender sender, Arguments args); 186 | } 187 | } 188 | 189 | -------------------------------------------------------------------------------- /src/test/java/cat/nyaa/nyaacore/utils/VersionUtilsTest.java: -------------------------------------------------------------------------------- 1 | package cat.nyaa.nyaacore.utils; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.util.Arrays; 6 | 7 | public class VersionUtilsTest { 8 | @Test 9 | public void test1() throws Exception { 10 | String[] res1 = VersionUtils.splitVersionString("1.17.1R1"); 11 | assert (Arrays.equals(res1, new String[]{"1", "17", "1", "1"})); 12 | String[] res2 = VersionUtils.splitVersionString("1.17.1R1_2"); 13 | assert (Arrays.equals(res2, new String[]{"1", "17", "1", "1", "2"})); 14 | String[] res3 = VersionUtils.splitVersionString("1.17.1R1_2A"); 15 | assert (Arrays.equals(res3, new String[]{"1", "17", "1", "1", "2A"})); 16 | } 17 | 18 | @Test 19 | public void test2() throws Exception { 20 | int[] res1 = VersionUtils.splitVersionStringToIntArray("1.17.1R1"); 21 | assert (Arrays.equals(res1, new int[]{1, 17, 1, 1})); 22 | int[] res2 = VersionUtils.splitVersionStringToIntArray("1.17.1R1_2"); 23 | assert (Arrays.equals(res2, new int[]{1, 17, 1, 1, 2})); 24 | int[] res3 = VersionUtils.splitVersionStringToIntArray("1.17.1R1_2A"); 25 | assert (Arrays.equals(res3, new int[]{1, 17, 1, 1, 2})); 26 | int[] res4 = VersionUtils.splitVersionStringToIntArray("1.17.1R1_2A.0.1.0000.00.0a"); 27 | assert (Arrays.equals(res4, new int[]{1, 17, 1, 1, 2, 0, 1})); 28 | } 29 | 30 | @Test 31 | public void test3() throws Exception { 32 | boolean res1 = VersionUtils.isVersionGreaterOrEq("1.17.1R1", "1.17.1R2"); 33 | assert (!res1); 34 | boolean res2 = VersionUtils.isVersionGreaterOrEq("1.17.1R1", "1.17.1R1"); 35 | assert (res2); 36 | boolean res3 = VersionUtils.isVersionGreaterOrEq("1.17.1", "1.17.1R0"); 37 | assert (res3); 38 | boolean res4 = VersionUtils.isVersionGreaterOrEq("1.16.3", "1.17.1R0"); 39 | assert (!res4); 40 | boolean res5 = VersionUtils.isVersionGreaterOrEq("1.18.1R3", "1.17.1R0"); 41 | assert (res5); 42 | } 43 | } 44 | --------------------------------------------------------------------------------